# Use External Signers for Upload Payments

Use the two-phase upload flow when your application needs a wallet outside `antd` to sign and submit upload payments.

This is the right approach when:

* your users pay for their own uploads
* your app should not custody a wallet key for them
* you need to integrate with an external signer or wallet stack

## Prerequisites

* `antd` running without `AUTONOMI_WALLET_KEY` (see [Start the Local Daemon](/developers/sdk/install/start-the-local-daemon.md))
* EVM configuration available to the daemon for the target network
* An external signer or wallet stack that can submit the required payment transactions

## Steps

### 1. Start antd without a wallet key

The daemon does not have an `--external-signer` flag. External-signer mode is the absence of `AUTONOMI_WALLET_KEY` plus the use of the prepare/finalize endpoints.

From the `ant-sdk/antd` build directory, run:

```bash
EVM_RPC_URL=https://your-rpc-endpoint \
EVM_PAYMENT_TOKEN_ADDRESS=0x... \
EVM_PAYMENT_VAULT_ADDRESS=0x... \
./target/release/antd
```

If `antd` is already on your `PATH`, replace `./target/release/antd` with `antd`.

Use `EVM_PAYMENT_VAULT_ADDRESS` for both wave-batch and Merkle uploads in the external-signer flow.

### 2. Prepare the upload

For a single chunk (up to 4 MiB of raw bytes), call `POST /v1/chunks/prepare`. This is the simplest external-signer flow — one chunk, one payment:

```bash
CHUNK_B64=$(printf 'Hello, Autonomi!' | base64)

curl -X POST http://localhost:8082/v1/chunks/prepare \
  -H "Content-Type: application/json" \
  -d "{\"data\":\"$CHUNK_B64\"}"
```

When the chunk is already stored on the network, the response returns `already_stored: true` with the existing address and no `upload_id`. There is nothing more to do: no payment to make and no finalize call to issue. Otherwise the response returns the wave-batch payment shape:

```json
{
  "address": "<64_hex_chunk_address>",
  "already_stored": false,
  "upload_id": "<hex_id>",
  "payment_type": "wave_batch",
  "payments": [
    { "quote_hash": "0x...", "rewards_address": "0x...", "amount": "<atto_tokens>" }
  ],
  "total_amount": "<atto_tokens>",
  "payment_vault_address": "0x...",
  "payment_token_address": "0x...",
  "rpc_url": "https://your-rpc-endpoint"
}
```

After the external signer calls `payForQuotes()` with the returned `payments`, finalize with `POST /v1/chunks/finalize`:

```bash
curl -X POST http://localhost:8082/v1/chunks/finalize \
  -H "Content-Type: application/json" \
  -d '{"upload_id":"<hex_id>","tx_hashes":{"0xquote_hash":"0xtx_hash"}}'
```

The finalize response returns the network address of the stored chunk. Requires antd 0.7.0 or later.

For multi-chunk uploads (arbitrary files or in-memory data larger than one chunk), use the data or file prepare/finalize endpoints described below.

For in-memory data, call `POST /v1/data/prepare`.

```bash
DATA_B64=$(printf 'Hello, Autonomi!' | base64)

curl -X POST http://localhost:8082/v1/data/prepare \
  -H "Content-Type: application/json" \
  -d "{\"data\":\"$DATA_B64\"}"
```

The in-memory data prepare endpoint supports only private uploads: `visibility` is accepted but `"public"` returns `501`. To prepare a public upload with an external signer, use `POST /v1/upload/prepare` with a file path and `"visibility":"public"` instead.

The prepare endpoints return a `payment_type` discriminator. Use that value to decide which on-chain call to make and which finalize payload to send back.

The daemon returns `wave_batch` for uploads under 64 chunks and `merkle` for uploads with 64 or more chunks.

Wave-batch prepare response:

```json
{
  "upload_id": "<hex_id>",
  "payment_type": "wave_batch",
  "payments": [
    {
      "quote_hash": "0x...",
      "rewards_address": "0x...",
      "amount": "<atto_token_amount>"
    }
  ],
  "total_amount": "<atto_token_amount>",
  "payment_vault_address": "0x...",
  "payment_token_address": "0x...",
  "rpc_url": "https://your-rpc-endpoint"
}
```

Merkle prepare response:

```json
{
  "upload_id": "<hex_id>",
  "payment_type": "merkle",
  "depth": 6,
  "pool_commitments": [
    {
      "pool_hash": "0x...",
      "candidates": [
        {
          "rewards_address": "0x...",
          "amount": "<atto_token_amount>"
        }
      ]
    }
  ],
  "merkle_payment_timestamp": 1744041600,
  "payment_vault_address": "0x...",
  "total_amount": "0",
  "payment_token_address": "0x...",
  "rpc_url": "https://your-rpc-endpoint"
}
```

Each `pool_commitments` entry contains exactly 16 candidate payments. The sample above shows one candidate for brevity.

For file uploads, the equivalent is `POST /v1/upload/prepare` with a local `path` field instead of `data`. To make the upload publicly retrievable by address, add `"visibility":"public"` to the prepare request — the daemon bundles the serialized DataMap chunk into the same payment batch, and the finalize response includes a `data_map_address` field with its network address.

### 3. Submit the payment externally

Use your signer stack to submit the EVM payment transaction described by the prepare response.

`antd` does not sign or broadcast those transactions in this flow.

* For `wave_batch`, call `payForQuotes()` with the returned `payments` and keep the resulting transaction hashes keyed by `quote_hash`.
* For `merkle`, call `payForMerkleTree()` with `depth`, `pool_commitments`, and `merkle_payment_timestamp`, then keep the `winner_pool_hash` from the `MerklePaymentMade` event.

Both calls use the `payment_vault_address` returned by the prepare step.

### 4. Finalize the upload

After the external payment is on-chain, call `POST /v1/upload/finalize` with the matching fields for the prepared upload.

Wave-batch finalize request:

```bash
curl -X POST http://localhost:8082/v1/upload/finalize \
  -H "Content-Type: application/json" \
  -d '{"upload_id":"<hex_id>","tx_hashes":{"0xquote":"0xtx"},"store_data_map":true}'
```

Merkle finalize request:

```bash
curl -X POST http://localhost:8082/v1/upload/finalize \
  -H "Content-Type: application/json" \
  -d '{"upload_id":"<hex_id>","winner_pool_hash":"0x...","store_data_map":true}'
```

Expected response shape:

```json
{
  "data_map": "<hex_encoded_datamap>",
  "address": "<64_hex_address>",
  "data_map_address": "<64_hex_address>",
  "chunks_stored": <chunk_count>
}
```

`address` is only present when `store_data_map` is `true`; that path uses the daemon's own wallet to store the DataMap. `data_map_address` is only present when the upload was prepared with `visibility:"public"` — it is the network address of the DataMap chunk whose payment was included in the same external-signer batch.

### 5. Use SDK helpers when available

The daemon SDKs follow the same prepare/finalize split. Merkle-capable bindings expose `payment_type` on prepare results and a `finalize_merkle_upload` helper for the Merkle path.

Use the REST API when you need the full finalize response surface, such as the raw `data_map` value or explicit `store_data_map` control on wave-batch finalize requests.

If you are building in Rust with ant-core instead of the daemon, the library exposes native external-payment helpers such as `data_prepare_upload`, `data_prepare_upload_with_visibility`, `file_prepare_upload`, `prepare_merkle_batch_external`, and `finalize_merkle_batch`. Use `data_prepare_upload_with_visibility(content, Visibility::Public)` to bundle the DataMap chunk into the payment batch for a public in-memory upload. Progress-aware variants such as `file_prepare_upload_with_progress`, `finalize_upload_with_progress`, and `finalize_upload_merkle_with_progress` are also available when you need UI feedback during long-running uploads.

## Verify it worked

Finalize succeeds when the daemon accepts the `upload_id` plus either the `tx_hashes` map or the `winner_pool_hash`, then returns upload metadata. If you requested `store_data_map: true`, you can retrieve the stored content again through the returned address.

## Common errors

**404 Not Found**: The `upload_id` is missing, expired, or already finalized.

**400 Bad Request**: Check whether the prepared upload expects `tx_hashes` or `winner_pool_hash`, and validate the hex formatting of those values.

**503 Service Unavailable**: You accidentally started the daemon in direct-wallet mode or without the required network configuration.

## Next steps

* [Prepare a Wallet for Uploads](/developers/guides/prepare-a-wallet-for-uploads.md)
* [Estimate Costs and Handle Upload Payments](/developers/guides/estimate-costs-and-handle-upload-payments.md)
* [REST API](/developers/sdk/install/reference/rest-api.md)
* [Use the Daemon as a Local Service](/developers/sdk/install/how-to-guides/use-the-daemon-as-a-local-service.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.autonomi.com/developers/sdk/install/how-to-guides/use-external-signers-for-upload-payments.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
