How to execute a payout quote on EVM, Solana, and Stellar: approve, delegate, or sign

Funding a payout from your own wallet needs one on-chain authorization. What EVM approve, Solana delegation, and Stellar signing look like, step by step.

To execute a payout quote from your own wallet, authorize the exact quoted amount on-chain, then create the payout before the quote expires. On EVM chains you call approve on the token contract, on Solana you sign a token delegation, and on Stellar you sign a payment transaction. A BlindPay managed wallet skips the authorization.

Same quote, three chain-native ways to say yes. This guide puts them side by side, with the endpoints, the fields, and the timing traps inside a 5-minute quote window.

Key takeaways

  • Every payout quote expires 5 minutes after creation. The authorization has to confirm inside that window.
  • EVM: call approve with the contract payload from the quote, then POST /payouts/evm.
  • Solana: POST /prepare-delegate-solana, sign and submit, then POST /payouts/evm.
  • Stellar: POST /payouts/stellar/authorize, sign and submit, then POST /payouts/stellar with the signed transaction.
  • Quotes and authorizations are single-use. Authorize again for every payout.

Why does a payout from your own wallet need an authorization?

Because BlindPay never holds keys to your wallet. For payout flows from customer-controlled wallets, BlindPay operates as a non-custodial payment processor: you authorize the exact amount and BlindPay executes that single transaction. If a payout can't be delivered after the stablecoins are collected, BlindPay refunds them to the quote's refund wallet, or to the sending wallet when none is set. On Pix and other non-USD local rails that refund is automatic, while USD payouts and payouts whose review times out are refunded after an operator review.

The authorization is how you grant "this amount, this once." Each chain has its own primitive for it. What happens on-chain in a stablecoin payment shows where that step sits in the full flow.

If the funds sit in a BlindPay managed wallet instead, there's nothing to sign. You create the quote and call POST /payouts/evm with the managed wallet's address, for every supported chain, Solana included (payout with managed wallet). Managed wallets are in beta and custodial.

How do the three chains compare?

StepEVM (Ethereum, Base, Polygon, Arbitrum)SolanaStellar
PrimitiveERC-20 allowance (approve)Token delegationSigned payment transaction (XDR)
What BlindPay returnscontract object inside the quoteSerialized transaction from prepare-delegate-solanaUnsigned transaction from payouts/stellar/authorize
Who submits on-chainYouYouYou
Payout endpointPOST /payouts/evmPOST /payouts/evmPOST /payouts/stellar
Extra field on the payout callNoneNonesigned_transaction
Reusable?No, single-useNo, one delegation per quoteNo, single-use

The quote request is the same everywhere: bank_account_id, currency_type, cover_fees, request_amount in minor units, plus the network and token you send from (payout quotes). The chain only changes what happens between the quote and the payout. If you're still choosing the chain, which network to off-ramp from compares finality and fees.

How do you execute a quote on an EVM chain?

Approve, wait, pay. The quote response for an EVM network carries a contract object with everything the approval needs (payout with EVM):

contract fieldWhat it is
addressThe ERC-20 token contract for the chosen token and network
abiThe ERC-20 ABI, ready for your Ethereum library
functionNameAlways approve
blindpayContractAddressThe spender to approve: BlindPay's receiving address on that network
amountThe decimal-adjusted amount to approve, as a string
network{ name, chainId } for the quoted network

With ethers.js, the call looks like this:

JavaScript
const contract = new ethers.Contract(quote.contract.address, quote.contract.abi, wallet)
await contract.approve(quote.contract.blindpayContractAddress, quote.contract.amount)

The ERC-20 standard defines approve as allowing a spender to withdraw from your account up to the approved value (EIP-20). Once the approval confirms, create the payout with quote_id and sender_wallet_address. You pay gas on the approval, which who pays gas fees covers in detail.

How do you execute a quote on Solana?

Delegate, sign, pay. Solana payouts need a token delegation before the payout executes (payout with Solana):

  1. POST /v1/instances/{instance_id}/prepare-delegate-solana with owner_address, token_address, and amount. It returns { "success": true, "transaction": "..." }, a base64 serialized transaction.
  2. Deserialize it as a VersionedTransaction, sign it with the sender's wallet (a browser wallet or a server-side keypair), send it, and wait for confirmation.
  3. POST /v1/instances/{instance_id}/payouts/evm with quote_id and the Solana sender_wallet_address. Yes, the EVM path. The name is historical.

Solana's token program says account owners can delegate authority over some or all of their balance with the Approve instruction, and delegates may transfer or burn up to the amount delegated (Solana Program Library). Each BlindPay delegation covers only the amount tied to that quote, so repeat all three steps for every payout.

How do you execute a quote on Stellar?

Authorize, sign, pay with the signature. Stellar has no allowance mechanism, so you sign a real payment to BlindPay's treasury address (payout with Stellar):

  1. POST /v1/instances/{instance_id}/payouts/stellar/authorize with quote_id and sender_wallet_address. It returns an unsigned transaction in transaction_hash. This call does not consume the quote or create any record.
  2. Rebuild it with TransactionBuilder.fromXDR, sign with the sender's key, and submit it to the network.
  3. POST /v1/instances/{instance_id}/payouts/stellar with quote_id, signed_transaction, and sender_wallet_address. BlindPay re-validates the signed transaction against the quote, destination and amount, before dispatching the payout.

Stellar encodes transactions in XDR, a compact binary format that SDKs convert to and from base64 (Stellar docs). BlindPay's Stellar mainnet treasury address is GCOSSQDM2SWMHRP7CDBOLL2V45NHCRLUWUCEHPPBA2ABCOOLPOLZKIHE, useful when you reconcile on an explorer.

Illustrative example: does the authorization fit in a 5-minute quote?

A treasury script pays 1,250.00 USDC from an external wallet. request_amount is 125000 in minor units. The approval amount is decimal-adjusted on-chain: BlindPay's own Solana example delegates "50000000" for a 50-dollar quote, which is 50 x 1,000,000, so a token with six decimals needs 1,250 x 1,000,000 = 1,250,000,000 base units here. Read contract.amount from the quote instead of computing it.

Now the clock. The step timings below are illustrative.

StepIllustrative timeClock used
Create quote1 s1 s
Build and sign the authorization3 s4 s
Wait for on-chain confirmation30 s34 s
Create the payout1 s35 s
Quote window300 s265 s to spare

Plenty of room when it's a script. The real risk is a human: a wallet pop-up left open while someone gets coffee. If 300 seconds pass before the payout call, the quote is dead and the old authorization was sized for it. Re-quote and authorize again. Handling expired quotes in TypeScript and Python has the retry loop, and stablecoin API idempotency keys covers safe retries on the payout call. For reading the quote's two rates, see how to read an FX quote.

What to do next

Pick the chain your treasury already uses, open the matching tutorial in the BlindPay docs, and run one 50-dollar payout end to end on a development instance with USDB. Start with payout with EVM if you're unsure.

FAQ