Skip to main content
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.
Test this flow in the Sandbox at https://crypto-sandbox.yativo.com/api/v1/. Sandbox transactions are simulated — no real funds move.
1

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.
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"
  }'
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"
}
*/
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"
Response:
{
  "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

PrioritySpeedUse case
slow1–3 minNon-urgent payouts, batch processing
medium15–30 secDefault for most use cases
fast5–15 secTime-sensitive transactions
2

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.
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"
  }'
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...
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...
Response:
{
  "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
3

Poll for transaction status

You can poll for the current transaction status using POST /transactions/get-transactions, filtering by transaction_id.
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"
  }'
// 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...
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)
Response (completed):
{
  "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

StatusDescription
pendingSubmitted, waiting to be broadcast
completedIncluded in a block with sufficient confirmations
failedTransaction failed (see error_message)
4

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 (webhook handler)
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;
}

Common Errors

ErrorCauseFix
Assets requiredassets body field missingInclude the asset ObjectId in the request body
Receiving address requiredreceiving_address missingInclude the destination address
Valid amount requiredamount missing, zero, or negativeProvide a positive numeric amount
Insufficient balanceAsset balance too lowCheck balance before sending
Withdrawal amount must be greater than total feesAmount too small to cover platform fee + gas markupIncrease amount or check fee estimates first
Self funding is only available for native token transactionsuse_self_funding: true on a token assetRemove use_self_funding for token withdrawals
Transaction blocked due to high-risk receiving addressChainalysis/Circle screening blocked the addressDo not send to flagged addresses
DUPLICATE_REQUEST_IN_PROGRESSSame Idempotency-Key submitted while first request is still processingWait for the first request to complete before retrying

Next Steps

  • See Webhook Integration for the full transaction.withdrawal.completed and transaction.failed event payloads.
  • Use Swap Tokens to exchange assets before sending.