Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions antd-go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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),
Expand All @@ -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
}

Expand All @@ -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
}
99 changes: 99 additions & 0 deletions antd-go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
7 changes: 4 additions & 3 deletions antd-go/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down