diff --git a/antd-go/client.go b/antd-go/client.go index e5a8d80..fc57cfb 100644 --- a/antd-go/client.go +++ b/antd-go/client.go @@ -455,7 +455,7 @@ func parsePrepareResponse(j map[string]any) *PrepareUploadResult { return result } -// PrepareUpload prepares a file upload for external signing. +// PrepareUpload prepares a private file upload for external signing. // Returns payment details that an external signer must process before calling // FinalizeUpload (wave_batch) or FinalizeMerkleUpload (merkle). func (c *Client) PrepareUpload(ctx context.Context, path string) (*PrepareUploadResult, error) { @@ -468,10 +468,33 @@ func (c *Client) PrepareUpload(ctx context.Context, path string) (*PrepareUpload return parsePrepareResponse(j), nil } -// PrepareDataUpload prepares a data upload for external signing. +// PrepareUploadPublic prepares a public file upload for external signing. +// In addition to the data chunks, the daemon bundles the serialized DataMap +// chunk into the same payment batch — so the external signer signs ONE EVM +// transaction covering chunks + DataMap. After FinalizeUpload, the result's +// DataMapAddress is the shareable retrieval handle. +// +// Requires antd >= 0.6.1. +func (c *Client) PrepareUploadPublic(ctx context.Context, path string) (*PrepareUploadResult, error) { + j, _, err := c.doJSON(ctx, http.MethodPost, "/v1/upload/prepare", map[string]any{ + "path": path, + "visibility": "public", + }) + if err != nil { + return nil, err + } + return parsePrepareResponse(j), nil +} + +// PrepareDataUpload prepares a private data upload for external signing. // Takes raw bytes, base64-encodes them, and POSTs to /v1/data/prepare. // Returns payment details that an external signer must process before calling // FinalizeUpload (wave_batch) or FinalizeMerkleUpload (merkle). +// +// The public variant of this endpoint is not yet available — the daemon +// returns 501 for visibility:"public" until upstream ant-core exposes +// data_prepare_upload_with_visibility. Use PrepareUploadPublic with a file +// path instead. func (c *Client) PrepareDataUpload(ctx context.Context, data []byte) (*PrepareUploadResult, error) { j, _, err := c.doJSON(ctx, http.MethodPost, "/v1/data/prepare", map[string]any{ "data": b64Encode(data), @@ -495,9 +518,10 @@ func (c *Client) FinalizeUpload(ctx context.Context, uploadID string, txHashes m return nil, err } return &FinalizeUploadResult{ - DataMap: str(j, "data_map"), - Address: str(j, "address"), - ChunksStored: num64(j, "chunks_stored"), + DataMap: str(j, "data_map"), + Address: str(j, "address"), + DataMapAddress: str(j, "data_map_address"), + ChunksStored: num64(j, "chunks_stored"), }, nil } @@ -514,8 +538,9 @@ func (c *Client) FinalizeMerkleUpload(ctx context.Context, uploadID string, winn return nil, err } return &FinalizeUploadResult{ - DataMap: str(j, "data_map"), - Address: str(j, "address"), - ChunksStored: num64(j, "chunks_stored"), + DataMap: str(j, "data_map"), + Address: str(j, "address"), + DataMapAddress: str(j, "data_map_address"), + ChunksStored: num64(j, "chunks_stored"), }, nil } diff --git a/antd-go/client_test.go b/antd-go/client_test.go index f1b4090..54482ee 100644 --- a/antd-go/client_test.go +++ b/antd-go/client_test.go @@ -602,3 +602,102 @@ func TestPrepareUploadBackwardCompat(t *testing.T) { t.Fatalf("expected 1 payment, got %d", len(res.Payments)) } } + +func TestPrepareUploadPublicSendsVisibility(t *testing.T) { + var capturedBody map[string]any + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && r.URL.Path == "/v1/upload/prepare" { + _ = json.NewDecoder(r.Body).Decode(&capturedBody) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "upload_id": "up-pub-1", + "payment_type": "wave_batch", + "payments": []any{map[string]any{"quote_hash": "qh1", "rewards_address": "ra1", "amount": "100"}}, + "total_amount": "100", + "payment_vault_address": "dp1", + "payment_token_address": "pt1", + "rpc_url": "http://localhost:8545", + }) + return + } + w.WriteHeader(404) + })) + defer srv.Close() + + c := NewClient(srv.URL) + res, err := c.PrepareUploadPublic(context.Background(), "/tmp/test.txt") + if err != nil { + t.Fatal(err) + } + if got, want := capturedBody["visibility"], "public"; got != want { + t.Fatalf("expected visibility=%q in request body, got %v", want, got) + } + if got, want := capturedBody["path"], "/tmp/test.txt"; got != want { + t.Fatalf("expected path=%q in request body, got %v", want, got) + } + if res.UploadID != "up-pub-1" { + t.Fatalf("unexpected upload_id: %s", res.UploadID) + } +} + +func TestFinalizeUploadSurfacesDataMapAddress(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && r.URL.Path == "/v1/upload/finalize" { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "data_map": "deadbeef", + "data_map_address": "cafebabe", + "chunks_stored": float64(4), + }) + return + } + w.WriteHeader(404) + })) + defer srv.Close() + + c := NewClient(srv.URL) + res, err := c.FinalizeUpload(context.Background(), "up1", map[string]string{"qh1": "tx1"}, false) + if err != nil { + t.Fatal(err) + } + if res.DataMapAddress != "cafebabe" { + t.Fatalf("expected DataMapAddress=cafebabe, got %q", res.DataMapAddress) + } + if res.Address != "" { + t.Fatalf("expected empty legacy Address, got %q", res.Address) + } + if res.DataMap != "deadbeef" { + t.Fatalf("expected DataMap=deadbeef, got %q", res.DataMap) + } + if res.ChunksStored != 4 { + t.Fatalf("expected ChunksStored=4, got %d", res.ChunksStored) + } +} + +func TestFinalizeUploadOmitsDataMapAddressForPrivate(t *testing.T) { + // Old daemons (pre-0.6.1) don't return data_map_address; field defaults to "". + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && r.URL.Path == "/v1/upload/finalize" { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "data_map": "deadbeef", + "chunks_stored": float64(2), + }) + return + } + w.WriteHeader(404) + })) + defer srv.Close() + + c := NewClient(srv.URL) + res, err := c.FinalizeUpload(context.Background(), "up1", map[string]string{"qh1": "tx1"}, false) + if err != nil { + t.Fatal(err) + } + if res.DataMapAddress != "" { + t.Fatalf("expected empty DataMapAddress for old daemon, got %q", res.DataMapAddress) + } + if res.DataMap != "deadbeef" { + t.Fatalf("expected DataMap=deadbeef, got %q", res.DataMap) + } +} diff --git a/antd-go/models.go b/antd-go/models.go index deeddc6..fc60a30 100644 --- a/antd-go/models.go +++ b/antd-go/models.go @@ -82,9 +82,10 @@ type CandidateNodeEntry struct { // FinalizeUploadResult is the result of finalizing an externally-signed upload. type FinalizeUploadResult struct { - DataMap string `json:"data_map"` // hex-encoded serialized DataMap (always returned) - Address string `json:"address,omitempty"` // network address (only when store_data_map=true) - ChunksStored int64 `json:"chunks_stored"` // number of chunks stored + DataMap string `json:"data_map"` // hex-encoded serialized DataMap (always returned) + Address string `json:"address,omitempty"` // legacy: set when store_data_map=true was passed (paid by daemon wallet) + DataMapAddress string `json:"data_map_address,omitempty"` // set when prepare was called with visibility="public" (paid in same external-signer batch) + ChunksStored int64 `json:"chunks_stored"` // number of chunks stored } // UploadCostEstimate is the result of an estimate (EstimateDataCost / EstimateFileCost).