---
url: /docs/stablecoin/quickstart.md
description: >-
  Send your first stablecoin off-ramp payout on Base Sepolia, from minting test
  USDB to executing the payout.
---

This quickstart walks through an off-ramp payout: pulling USDB from a blockchain wallet on Base Sepolia and delivering USD to a bank account. You will accept the terms of service, create a customer, add a bank account, create a payout quote, approve the token transfer, and execute the payout.

## Before you begin

You need:

* A BlindPay account and API key for a development instance
* A wallet on Base Sepolia with ETH for gas and USDB to spend

Get ETH from the [Base Sepolia faucet](https://www.alchemy.com/faucets/base-sepolia). Mint USDB (the development test token) at `https://app.blindpay.com/instances/YOUR_INSTANCE_ID/utilities/mint`.

## Steps

### Accept terms of service

For testing you can accept the terms of service yourself. In production, your customer should be the one accepting them.

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "idempotency_key": "<your_uuid>"
  }'
```

Open the returned URL in your browser, accept the terms, and copy the `tos_id` shown on the confirmation screen. You will need it to create the customer.

### Create a customer

Every payout requires a customer that has completed KYC. On development instances, customers are auto-approved.

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/customers \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "tos_id": "to_000000000000",
    "type": "individual",
    "kyc_type": "standard",
    "email": "email@example.com",
    "tax_id": "12345678",
    "address_line_1": "8 The Green",
    "address_line_2": "#12345",
    "city": "Dover",
    "state_province_region": "DE",
    "country": "US",
    "postal_code": "02050",
    "ip_address": "127.0.0.1",
    "phone_number": "+1234567890",
    "proof_of_address_doc_type": "UTILITY_BILL",
    "proof_of_address_doc_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/v4-460px-Get-Proof-of-Address-Step-3-Version-2.jpg.jpeg",
    "first_name": "John",
    "last_name": "Doe",
    "date_of_birth": "1998-01-01T00:00:00Z",
    "id_doc_country": "US",
    "id_doc_type": "PASSPORT",
    "id_doc_front_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/1000_F_365165797_VwQbNaD4yjWwQ6y1ENKh1xS0TXauOQvj.jpg",
    "selfie_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/selfie.png"
  }'
```

This is the full standard KYC payload (`kyc_type: "standard"`): identity document, selfie, and proof of address. Save the customer ID (`re_...`) from the response.

### Add a bank account

This is the fiat destination for the payout, the bank account that receives the USD once the stablecoin is converted. This example adds a US ACH account. Use real, valid bank details, even on development.

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "ach",
    "name": "Display Name",
    "beneficiary_name": "<Replace this>",
    "routing_number": "<Replace this>",
    "account_number": "<Replace this>",
    "account_type": "checking",
    "account_class": "individual"
  }'
```

Save the bank account ID (`ba_...`).

### Create a payout quote

A quote locks the conversion rate and fees for 5 minutes. On development instances the token is always `USDB`, BlindPay's test stablecoin, minted on `base_sepolia` in this example.

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "bank_account_id": "ba_000000000000",
    "currency_type": "sender",
    "cover_fees": false,
    "request_amount": 5000,
    "network": "base_sepolia",
    "token": "USDB"
  }'
```

`request_amount` is an integer in minor units, `5000` is $50.00. `cover_fees: false` means the customer's stablecoin amount absorbs the fee, the common case. The response includes a `contract` object with the ERC-20 token address, ABI, BlindPay's contract address, and the amount to approve, everything the next step needs.

### Authorize and execute

Every stablecoin on BlindPay is an ERC-20 token, so on EVM chains you authorize the transfer by calling `approve` on the token contract before creating the payout. This example uses [express.js](https://expressjs.com/en/starter/hello-world.html) and [ethers.js](https://docs.ethers.org/v6/) to run all three steps (quote, approve, execute) in one request handler.

Make sure [Node.js](https://nodejs.org/en/download/) is installed, then create a project and install the dependencies:

```bash
npm init
npm install express ethers
```

Create `index.js`:

```js [index.js]
import express from 'express'
import { ethers } from 'ethers'

const app = express()

app.get('/', async (req, res) => {
  // Before start
  const rpcProviderUrl = '<Replace this>' // Get one from https://chainlist.org/?search=base&testnets=true
  const walletPrivateKey = '<Replace this>' // Must hold ETH and USDB on Base Sepolia
  const instanceId = '<Replace this>'
  const blindpayApiKey = '<Replace this>'
  const bankAccountId = '<Replace this>'

  // BlindPay API config
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${blindpayApiKey}`,
  }

  // 1. Create a quote
  const fiftyDollars = 5000
  const quoteBody = {
    bank_account_id: bankAccountId,
    currency_type: 'sender',
    cover_fees: false,
    request_amount: fiftyDollars,
    network: 'base_sepolia', // also: "sepolia", "arbitrum_sepolia", "polygon_amoy"
    token: 'USDB', // always "USDB" on development instances
  }
  const createQuote = await fetch(`https://api.blindpay.com/v1/instances/${instanceId}/quotes`, {
    headers,
    method: 'POST',
    body: JSON.stringify(quoteBody),
  })
  const quoteResponse = await createQuote.json()

  // 2. Approve tokens
  const provider = new ethers.JsonRpcProvider(rpcProviderUrl, quoteResponse.contract.network)
  const yourWallet = new ethers.Wallet(walletPrivateKey, provider)
  const contract = new ethers.Contract(quoteResponse.contract.address, quoteResponse.contract.abi, provider)
  const contractSigner = contract.connect(yourWallet)

  await contractSigner.approve(
    quoteResponse.contract.blindpayContractAddress,
    quoteResponse.contract.amount,
  )

  // 3. Execute the payout
  const senderWalletAddress = await yourWallet.getAddress()
  const payoutBody = {
    quote_id: quoteResponse.id,
    sender_wallet_address: senderWalletAddress,
  }
  const executePayout = await fetch(`https://api.blindpay.com/v1/instances/${instanceId}/payouts/evm`, {
    headers,
    method: 'POST',
    body: JSON.stringify(payoutBody),
  })
  const payoutResponse = await executePayout.json()

  res.send(payoutResponse)
})

/* istanbul ignore next */
app.listen(3000)
console.log('Express started on port 3000')
```

Run it and open `http://localhost:3000`:

```bash
node index.js
```

On a development instance, the payout completes automatically a few seconds after creation, no manual settlement step needed. You will receive a `payout.complete` webhook once it does.

## Next steps

This quickstart covered the EVM path on Base Sepolia. BlindPay also supports Stellar (authorize + signed XDR) and Solana (token delegation) for off-ramp payouts.

## Related

* [Off-ramp](/stablecoin/send): full reference, including Stellar and Solana authorization
* [On-ramp](/stablecoin/receive): accept fiat and deliver stablecoins to a wallet
* [Managed wallet](/stablecoin/store): hold stablecoins per customer without external wallet signing
* [Webhooks](/learn/webhooks): handle `payout.complete` and other real-time events
* [KYC](/kb/kyc): customer verification levels and required fields
