> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yativo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Crypto

> Programmatically send crypto to any wallet address

This guide covers the full flow for sending crypto from a Yativo account to an external wallet: estimating gas fees, executing the transfer with an idempotency key, and tracking the transaction to completion.

<Note>
  Test this flow in the Sandbox at `https://crypto-sandbox.yativo.com/api/v1/`. Sandbox transactions are simulated — no real funds move.
</Note>

<Steps>
  <Step title="Get a gas fee estimate">
    Before sending, retrieve the current gas fee estimate for the target chain. This lets you show users an accurate fee breakdown and choose a priority level.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://crypto-api.yativo.com/api/v1/transactions/get-gas-price \
        -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
        -H "Content-Type: application/json" \
        -d '{
          "chainType": "ethereum",
          "priority": "medium",
          "token_symbol": "USDC"
        }'
      ```

      ```typescript TypeScript theme={null}
      import { YativoCrypto } from "@yativo/crypto-sdk";

      const client = new YativoCrypto({
        apiKey: process.env.YATIVO_API_KEY!,
        baseUrl: "https://crypto-api.yativo.com/api/v1/",
      });

      const gasEstimate = await client.transactions.getGasPrice({
        chain: "ethereum",
        asset: "USDC",
      });

      console.log(gasEstimate);
      /*
      {
        chain: "ethereum",
        slow:   { gasPrice: "18.2", estimatedFeeUsd: "0.82",  estimatedTimeSeconds: 120 },
        medium: { gasPrice: "22.5", estimatedFeeUsd: "1.02",  estimatedTimeSeconds: 30  },
        fast:   { gasPrice: "31.0", estimatedFeeUsd: "1.40",  estimatedTimeSeconds: 10  },
        nativeAsset: "ETH",
        updatedAt: "2026-03-26T11:00:00Z"
      }
      */
      ```

      ```python Python theme={null}
      from yativo_crypto import YativoCrypto

      client = YativoCrypto(
          api_key=os.environ["YATIVO_API_KEY"],
          base_url="https://crypto-api.yativo.com/api/v1/",
      )

      gas_estimate = client.transactions.get_gas_price(
          chain="ethereum",
          asset="USDC",
      )
      print(gas_estimate.medium.estimated_fee_usd)  # "1.02"
      ```
    </CodeGroup>

    **Response:**

    ```json theme={null}
    {
      "chain": "ethereum",
      "slow": {
        "gasPrice": "18.2",
        "estimatedFeeUsd": "0.82",
        "estimatedTimeSeconds": 120
      },
      "medium": {
        "gasPrice": "22.5",
        "estimatedFeeUsd": "1.02",
        "estimatedTimeSeconds": 30
      },
      "fast": {
        "gasPrice": "31.0",
        "estimatedFeeUsd": "1.40",
        "estimatedTimeSeconds": 10
      },
      "nativeAsset": "ETH",
      "updatedAt": "2026-03-26T11:00:00Z"
    }
    ```

    ### Priority levels

    | Priority | Speed     | Use case                             |
    | -------- | --------- | ------------------------------------ |
    | `slow`   | 1–3 min   | Non-urgent payouts, batch processing |
    | `medium` | 15–30 sec | Default for most use cases           |
    | `fast`   | 5–15 sec  | Time-sensitive transactions          |
  </Step>

  <Step title="Send funds with an idempotency key">
    Use `POST /transactions/send-funds` to initiate the transfer. Always include an `Idempotency-Key` header to safely retry without double-spending.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://crypto-api.yativo.com/api/v1/transactions/send-funds \
        -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: payout_01J2KG9MNPQ3R4S5T6UPAY001" \
        -d '{
          "account": "64b1f9e2a3c4d5e6f7a8b9c0",
          "assets": "64b1f9e2a3c4d5e6f7a8b9d1",
          "receiving_address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
          "amount": 100,
          "type": "USDC",
          "chain": "ethereum",
          "category": "payment",
          "priority": "medium",
          "description": "Payout for order #ORD-9821"
        }'
      ```

      ```typescript TypeScript theme={null}
      import { v4 as uuidv4 } from "uuid";

      // Generate a stable idempotency key tied to your payout record
      const idempotencyKey = `payout_${payoutRecord.id}`;

      const transaction = await client.transactions.sendFunds(
        {
          account: "64b1f9e2a3c4d5e6f7a8b9c0",
          assets: "64b1f9e2a3c4d5e6f7a8b9d1",
          receiving_address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
          amount: 100,
          type: "USDC",
          chain: "ethereum",
          category: "payment",
          priority: "medium",
          description: "Payout for order #ORD-9821",
        },
        { idempotencyKey }
      );

      console.log(transaction.data.transaction_id);  // txn_01HX9KZMB3F7VNQP8R2WDGT4E5
      console.log(transaction.data.transaction_hash); // 0x9f3e2d...
      ```

      ```python Python theme={null}
      transaction = client.transactions.send_funds(
          account="64b1f9e2a3c4d5e6f7a8b9c0",
          assets="64b1f9e2a3c4d5e6f7a8b9d1",
          receiving_address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
          amount=100,
          type="USDC",
          chain="ethereum",
          category="payment",
          priority="medium",
          description="Payout for order #ORD-9821",
          idempotency_key=f"payout_{payout_record.id}",
      )

      print(transaction.data.transaction_id)      # txn_01HX9KZMB3F7VNQP8R2WDGT4E5
      print(transaction.data.transaction_hash)    # 0x9f3e2d...
      ```
    </CodeGroup>

    **Response:**

    ```json theme={null}
    {
      "status": true,
      "message": "Transaction Created Successfully",
      "data": {
        "transaction_id": "txn_01HX9KZMB3F7VNQP8R2WDGT4E5",
        "transaction_hash": "0x9f3e2d1c4b7a5e8f2c9b3d6a1e4c7f2b5d8e1c4a",
        "gas_tx_hash": null,
        "platform_fee_tx_hash": null,
        "gas_amount": "0",
        "platform_fee": "0.50000000",
        "gas_funding_markup": "0.12000000",
        "total_fee": "0.62000000",
        "fee_breakdown": "N/A"
      }
    }
    ```

    ### Idempotency keys

    An idempotency key is a unique string you attach to a request. If the request fails or times out, you can safely retry with the same key — Yativo will return the original response instead of creating a duplicate transaction. If no key is supplied, no deduplication is applied.

    **Best practices:**

    * Derive the key from your internal record ID (e.g., `payout_${payoutId}`)
    * Keys must be unique per operation — reusing a key for a different transfer will return the original transaction
    * Keys expire after 24 hours
  </Step>

  <Step title="Poll for transaction status">
    You can poll for the current transaction status using `POST /transactions/get-transactions`, filtering by `transaction_id`.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://crypto-api.yativo.com/api/v1/transactions/get-transactions \
        -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
        -H "Content-Type: application/json" \
        -d '{
          "search": "txn_01J2KH3MNPQ7R4S5T6U7VSND99"
        }'
      ```

      ```typescript TypeScript theme={null}
      // Poll with exponential backoff
      async function waitForTransaction(transactionId: string): Promise<any> {
        const maxAttempts = 20;
        let delay = 2000; // start at 2 seconds

        for (let attempt = 0; attempt < maxAttempts; attempt++) {
          const result = await client.transactions.getTransactions({ search: transactionId });
          const txn = result.data?.[0];

          if (!txn) throw new Error('Transaction not found');
          if (txn.status === 'completed') return txn;
          if (txn.status === 'failed') throw new Error(`Transaction failed: ${txn.error_message}`);

          await new Promise((resolve) => setTimeout(resolve, delay));
          delay = Math.min(delay * 1.5, 30000); // cap at 30s
        }

        throw new Error('Transaction did not complete within timeout');
      }

      const completed = await waitForTransaction('txn_01J2KH3MNPQ7R4S5T6U7VSND99');
      console.log(completed.transaction_hash); // 0x9f3e2d1c4b7a5e8f2c9b3d6a1e4c7f2b5d8e1c4a...
      ```

      ```python Python theme={null}
      import time

      def wait_for_transaction(transaction_id: str) -> dict:
          max_attempts = 20
          delay = 2.0

          for attempt in range(max_attempts):
              result = client.transactions.get_transactions(search=transaction_id)
              txn = result.data[0] if result.data else None

              if not txn:
                  raise Exception('Transaction not found')
              if txn.status == 'completed':
                  return txn
              if txn.status == 'failed':
                  raise Exception(f'Transaction failed: {txn.error_message}')

              time.sleep(delay)
              delay = min(delay * 1.5, 30.0)

          raise TimeoutError('Transaction did not complete within timeout')

      completed = wait_for_transaction('txn_01J2KH3MNPQ7R4S5T6U7VSND99')
      print(completed.transaction_hash)
      ```
    </CodeGroup>

    **Response (completed):**

    ```json theme={null}
    {
      "status": true,
      "message": "Transactions fetched successfully",
      "data": [
        {
          "transaction_id": "txn_01J2KH3MNPQ7R4S5T6U7VSND99",
          "transaction_hash": "0x9f3e2d1c4b7a5e8f2c9b3d6a1e4c7f2b5d8e1c4a7f6e3d2b1a8c5f4e3d2c1b",
          "receiving_address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
          "amount": "100",
          "type": "USDC",
          "chain": "ethereum",
          "status": "completed",
          "blockchain_status": "confirmed",
          "block_number": 19847345,
          "confirmations": 12,
          "fee_paid": "0.62000000",
          "fee_currency": "USDC",
          "category": "payment",
          "description": "Payout for order #ORD-9821",
          "createdAt": "2026-03-26T11:05:42.000Z"
        }
      ]
    }
    ```

    ### Transaction statuses

    | Status      | Description                                       |
    | ----------- | ------------------------------------------------- |
    | `pending`   | Submitted, waiting to be broadcast                |
    | `completed` | Included in a block with sufficient confirmations |
    | `failed`    | Transaction failed (see `error_message`)          |
  </Step>

  <Step title="Handle completion via webhook (recommended)">
    Rather than polling, register a webhook for `transaction.{type}.completed` and `transaction.failed` events to receive push notifications when transactions settle.

    ```typescript TypeScript (webhook handler) theme={null}
    case "transaction.withdrawal.completed": {
      const { transactionId, toAddress, amount, asset, txHash } = event.data;

      await db.payouts.markCompleted(transactionId, {
        txHash,
        confirmedAt: event.created_at,
      });

      await notifyUserPayoutSent({ toAddress, amount, asset });
      break;
    }

    case "transaction.failed": {
      const { transactionId, failureReason } = event.data;

      await db.payouts.markFailed(transactionId, failureReason);
      // Refund or retry logic here
      break;
    }
    ```
  </Step>
</Steps>

## Common Errors

| Error                                                          | Cause                                                                    | Fix                                                    |
| -------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------ |
| `Assets required`                                              | `assets` body field missing                                              | Include the asset ObjectId in the request body         |
| `Receiving address required`                                   | `receiving_address` missing                                              | Include the destination address                        |
| `Valid amount required`                                        | `amount` missing, zero, or negative                                      | Provide a positive numeric amount                      |
| `Insufficient balance`                                         | Asset balance too low                                                    | Check balance before sending                           |
| `Withdrawal amount must be greater than total fees`            | Amount too small to cover platform fee + gas markup                      | Increase amount or check fee estimates first           |
| `Self funding is only available for native token transactions` | `use_self_funding: true` on a token asset                                | Remove `use_self_funding` for token withdrawals        |
| `Transaction blocked due to high-risk receiving address`       | Chainalysis/Circle screening blocked the address                         | Do not send to flagged addresses                       |
| `DUPLICATE_REQUEST_IN_PROGRESS`                                | Same `Idempotency-Key` submitted while first request is still processing | Wait for the first request to complete before retrying |

## Next Steps

* See **[Webhook Integration](/guides/webhook-integration)** for the full `transaction.withdrawal.completed` and `transaction.failed` event payloads.
* Use **[Swap Tokens](/guides/swap-tokens)** to exchange assets before sending.
