Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.astronpay.co/llms.txt

Use this file to discover all available pages before exploring further.

Esta é a forma recomendada de executar o passo de depósito on-chain de uma payout quando seu merchant utiliza wallet gerenciada pela Astron Pay (criada via API ou no dashboard). Você nunca toca em @solana/web3.js, não precisa de RPC Solana próprio, não precisa ter SOL para gas, e fica imune aos modos comuns de falha silenciosa (blockhash expirado, encoding ATA errado, falta de paymentReference).
Quando usar este fluxo. Use sempre que sua wallet de merchant for gerenciada pela Astron Pay. Se você operar self-custody (chave privada do seu lado) e quiser pagar o próprio gas, monte a tx localmente — a rota build-transfer-tx ainda ajuda: ela já devolve a tx pronta com paymentReference correto, basta você trocar o feePayer por sua própria wallet e assinar com sua chave.

Visão geral em 3 passos

  1. Criar a payout com POST /payout — recebe orderId, depositAddress, paymentReference.
  2. Pedir a tx pronta com POST /payout/build-transfer-tx — recebe a transação serializada em base64, com blockhash recente, ATA do destinatário correto, paymentReference anexado, e feePayer = wallet do receiver da order (default).
  3. Assinar e transmitir com POST /merchant/wallet/sign-and-send-transaction — a Astron Pay assina pela wallet do receiver, substitui o pagamento de gas pelo sponsor da plataforma, e transmite. A resposta só retorna depois que a rede confirmou — sem hash fantasma.

Você não precisa especificar a wallet de origem

A wallet que paga o USDC é deduzida automaticamente da cadeia receiverId → quoteId → orderId:
#ChamadaBodyCarrega receiverId?
1POST /quote/payout{ receiverId, amountCrypto, targetToken }✅ direto
2POST /payout{ quoteId, ... }✅ via quote
3POST /payout/build-transfer-tx{ orderId }✅ via order
4POST /merchant/wallet/sign-and-send-transaction{ transaction }(a tx já vem pronta)
Em nenhuma das chamadas você passa fromAddress, receiverWalletAddress ou similar. A Astron Pay resolve sempre para a wallet gerenciada do receiver da order — montada na criação do receiver com createPrivyWallet=true.
Importante: cada merchant tem múltiplos receivers. Toda chamada que mexe em saldo, payout, KYC ou wallet exige que você diga qual receiver está agindo (receiverId em /quote/payout e /payout, :id no path para endpoints /receivers/:id/...). Se você não passar isso, ou passar o merchantId no lugar do receiverId, a chamada erra ou aponta para a wallet errada.A wallet do merchant existe só como tenant identifier; ela nunca segura crypto de cliente final. A wallet do receiver é a que detém o USDC do payout.

1. Criar a payout

Já documentado em Fluxo Payout. O importante para esta guia é guardar o id da order retornada — usaremos no passo 2.
curl -X POST https://api.astronpay.co/api/v1/payout \
  -H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverId": "rcv_...",
    "quoteId": "qt_...",
    "amountCrypto": 200.00,
    "pixKey": "12345678901",
    "pixKeyType": "CPF",
    "recipientName": "João Silva",
    "recipientDocument": "12345678901"
  }'
Resposta (resumida):
{
  "id": "ord_01J...",
  "status": "AWAITING_DEPOSIT",
  "depositAddress": "...",
  "paymentReference": "..."
}

2. Pedir a tx pronta

curl -X POST https://api.astronpay.co/api/v1/payout/build-transfer-tx \
  -H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "orderId": "ord_01J..." }'
Resposta:
{
  "transaction": "AQAAAA...base64...",
  "blockhash": "EkSnNWid2cvwEVnVx9aBqawnmiCNiDgp3gUdkDPTKN1N",
  "lastValidBlockHeight": 295804123,
  "expiresAt": "2026-04-27T20:00:50.000Z",
  "fromAddress": "DhTfJ1x2VRvFtwUzafEX2nhwgo6F9VvHrfGhyaZfQER7",
  "depositAddress": "...",
  "paymentReference": "...",
  "amountCrypto": 200.00,
  "sourceToken": "USDC"
}
expiresAt é uma janela de segurança, não o limite duro do Solana. Em mainnet, blockhashes vivem ~60-90 s; nós devolvemos expiresAt ~50 s no futuro para você ter folga de assinar e transmitir. Se você esperar além desse tempo, chame build-transfer-tx de novo — é seguro repetir, o pedido continua em AWAITING_DEPOSIT.

Parâmetros

orderId
string
required
UUID da payout retornada por POST /payout. A order deve estar em status AWAITING_DEPOSIT e pertencer ao merchant autenticado. Esse é o único campo do body — a wallet de origem é deduzida do receiver da order.

Erros

CódigoStatusQuando aconteceO que fazer
NOT_FOUND404Order não existe ou pertence a outro merchantVerificar orderId. Por design, a Astron Pay não distingue “não existe” de “é de outro merchant” — para não vazar existência.
INVALID_ORDER_TYPE400Você passou um orderId de uma payinUse uma order de payout.
INVALID_ORDER_STATE409A order já avançou para DEPOSIT_RECEIVED/COMPLETED/FAILED/EXPIRED/REFUNDEDA order não aceita mais depósito. Crie uma nova se precisar.
RECEIVER_WALLET_MISSING409O receiver da order não tem wallet gerenciadaCrie o receiver com createPrivyWallet=true em POST /receivers.
RECEIVER_WALLET_NOT_FUNDED422A wallet do receiver existe mas não tem USDC (ATA não inicializada)Mande USDC para a wallet do receiver (NÃO a do merchant). A mensagem do erro inclui o endereço exato.
INSUFFICIENT_TOKEN_BALANCE422A wallet do receiver tem saldo, mas menor que o valor da orderTop up a wallet do receiver com a diferença e retente.
UNSUPPORTED_TOKEN400Token de origem da order fora da lista suportada (USDC, USDT, SOL)Refaça a payout especificando sourceToken: "USDC".
VALIDATION_ERROR400orderId não é UUID, fromAddress não é base58 válidoCorrija o body da requisição.
ORDER_INCONSISTENT500Defesa em profundidade: a row da order está corrompida (ex.: falta paymentReference)Contate o suporte.

O que está dentro do transaction

A tx serializada já tem tudo que a rede precisa:
  • Instrução SPL Transfer transferindo amountCrypto × 10^decimals (smallest unit) do ATA do fromAddress para o ATA do depositAddress no mint configurado (USDCEPjFWdd5...).
  • paymentReference anexado como conta extra non-signer, non-writable na instrução. É assim que a Astron Pay correlaciona o depósito on-chain com a sua order — se você montar a tx no seu lado e esquecer de incluir, o webhook order.deposit_received nunca dispara.
  • Caso a ATA da plataforma ainda não exista (caso muito raro), a tx começa com a instrução CreateAssociatedTokenAccount. Sem custo extra para você.
  • feePayer = fromAddress. Necessário para a serialização Solana. Quando a tx for transmitida via /merchant/wallet/sign-and-send-transaction, o sponsor da Astron Pay substitui o pagamento real do SOL.
  • recentBlockhash recente (finalized commitment) — o mesmo valor que vem no campo blockhash da resposta.
  • Sem assinaturas — tx pronta para ser assinada.

3. Assinar e transmitir

Pegue o transaction da resposta e mande direto para /merchant/wallet/sign-and-send-transaction:
curl -X POST https://api.astronpay.co/api/v1/merchant/wallet/sign-and-send-transaction \
  -H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "transaction": "AQAAAA...base64..." }'
Resposta:
{ "hash": "5sQAcj7...assinatura-on-chain..." }
Esta resposta só vem depois que a rede confirmou a transação. Internamente a Astron Pay verifica getSignatureStatuses antes de retornar 200. Se a transação não for confirmada em ~15 s, você recebe 502 TRANSACTION_NOT_BROADCASTED em vez de um falso sucesso. Trate esse 502 como retry-safe: chame build-transfer-tx de novo (blockhash novo) e reenvie.

Exemplo end-to-end (TypeScript)

Sem dependências Solana — só fetch.
async function executePayoutDeposit(opts: {
  orderId: string;
  apiKey: string;
  apiSecret: string;
  baseUrl?: string;
}): Promise<string> {
  const baseUrl = opts.baseUrl ?? "https://api.astronpay.co/api/v1";
  const headers = {
    "Content-Type": "application/json",
    "x-api-key": opts.apiKey,
    "x-api-secret": opts.apiSecret,
  };

  // 1. Pede a tx pronta para o orderId
  const buildRes = await fetch(`${baseUrl}/payout/build-transfer-tx`, {
    method: "POST",
    headers,
    body: JSON.stringify({ orderId: opts.orderId }),
  });
  if (!buildRes.ok) {
    const err = await buildRes.json();
    throw new Error(`build-transfer-tx failed: ${JSON.stringify(err)}`);
  }
  const { transaction } = (await buildRes.json()) as { transaction: string };

  // 2. Assina e transmite — a Astron Pay paga o gas
  const sendRes = await fetch(
    `${baseUrl}/merchant/wallet/sign-and-send-transaction`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({ transaction }),
    },
  );
  if (!sendRes.ok) {
    const err = await sendRes.json();
    if (err?.error?.code === "TRANSACTION_NOT_BROADCASTED") {
      // Safe to retry: a tx não foi confirmada na rede.
      throw new Error("Network did not confirm. Retry with a fresh build-transfer-tx call.");
    }
    throw new Error(`sign-and-send failed: ${JSON.stringify(err)}`);
  }

  const { hash } = (await sendRes.json()) as { hash: string };
  return hash;
}

// Uso:
const txHash = await executePayoutDeposit({
  orderId: "ord_01J...",
  apiKey: process.env.ASTRON_PAY_API_KEY!,
  apiSecret: process.env.ASTRON_PAY_API_SECRET!,
});
console.log(`https://solscan.io/tx/${txHash}`);

Estratégia de retry

Use a estratégia abaixo como referência. Não retente um 4xx — eles indicam input inválido. Retente 502 e 5xx, sempre regerando a tx.
async function executePayoutDepositWithRetry(opts: { orderId: string; apiKey: string; apiSecret: string }) {
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      return await executePayoutDeposit(opts);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      if (msg.includes("Network did not confirm") && attempt < 3) {
        // Backoff curto antes de tentar de novo. blockhash novo no próximo loop.
        await new Promise((r) => setTimeout(r, 2000 * attempt));
        continue;
      }
      throw err;
    }
  }
  throw new Error("Exhausted retries");
}

Checklist

  • Você cria a payout com POST /payout e guarda id, depositAddress, paymentReference.
  • Você chama POST /payout/build-transfer-tx logo antes de assinar (não cacheia a tx — blockhash expira em menos de 90 s).
  • Você manda o campo transaction retornado sem alteração para POST /merchant/wallet/sign-and-send-transaction.
  • Você trata 502 TRANSACTION_NOT_BROADCASTED como retry-safe (regerar a tx via build-transfer-tx).
  • Você assina o webhook order.deposit_received para saber quando a Astron Pay detectou o depósito on-chain (a correlação é feita pelo paymentReference que já está dentro da tx que enviamos a você).

Quando NÃO usar este fluxo

  • Self-custody: você tem chave privada própria e quer assinar localmente. Use /payout/build-transfer-tx para gerar a tx, troque o feePayer por sua wallet, assine com sua chave, e transmita pelo seu RPC. Você paga o próprio gas (a Astron Pay só patrocina gas para wallets gerenciadas).
  • Transferências fora do contexto de payout: a rota build-transfer-tx serve apenas para o passo de depósito de uma payout existente. Para qualquer outra movimentação on-chain, monte a tx no seu lado e use /merchant/wallet/sign-and-send-transaction diretamente.