# The Swap Flow

A programmatic swap is four steps: **discover → quote → pay → track**. This page covers each in
depth, including every EVM payment variant (native, ERC-20 approve, ERC-20 permit), multisend,
and where the Solana / TON payment differs.

All HTTP calls hit the base URL `https://drip.tools/api`. Full endpoint references live under
[API reference](https://docs.drip.tools/api/discovery.md); the EVM contract is documented in
[Depository contract](https://docs.drip.tools/swap/contract.md) and the Solana / TON transfer in
[Paying from Solana & TON](https://docs.drip.tools/swap/memo.md).

## Step 1: Discover chains & tokens

Call [`GET /api/chains`](https://docs.drip.tools/api/discovery.md#get-api-chains) to learn what you can swap. Pass
`?unwrap=true` to get a single flat array instead of `{ testnet, mainnet }`.

**cURL**
```bash
curl 'https://drip.tools/api/chains?unwrap=true'
```

Each chain object tells you everything you need:

**Response**
```json
{
  "chainId": "42161",
  "name": "arbitrum",
  "displayName": "Arbitrum",
  "explorer": "https://arbiscan.io",
  "isTestnet": false,                         // false = FROM (mainnet) side
  "isEnabled": true,
  "rpc": ["https://arbitrum.drpc.org", "https://arbitrum-one-rpc.publicnode.com", "…"],
  "tokens": [
    { "symbol": "ETH",  "address": "0x0000000000000000000000000000000000000000", "decimals": 18 },
    { "symbol": "USDC", "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "decimals": 6 }
  ],
  "contracts": {
    "depository": "0x52361c94…"               // ← the pay target (FROM chains only)
  }
}
```

- **FROM chains** have `isTestnet: false` and expose `contracts.depository` — where you pay.
  On an EVM chain it is the depository contract; on Solana and TON (`family: "solana"` /
  `"ton"`) it is the **treasury account** you transfer to. If `depository` is missing, that chain
  isn't enabled for programmatic swaps.
- **TO chains** have `isTestnet: true`. Skip the ones with `isEnabled: false` — they are listed,
  but `POST /api/quote` answers `BAD_TO_CHAIN`. There is nothing to call on a TO chain — our
  relayers send your recipients their testnet tokens; the payout tx hash comes back on the order.
- `rpc` is an **ordered list**: `rpc[0]` is the primary endpoint, the rest are fallbacks.
- Token `address` `0x0000…0000` is the chain's **native** coin (also on Solana and TON). An SPL
  token on Solana is identified by its base58 mint address. `decimals` is the base-unit
  exponent — you'll need it for every amount.

To see the testnet token's USD rate and whether the relayer currently has liquidity, call
[`GET /api/quotes`](https://docs.drip.tools/api/discovery.md#get-api-quotes-rates-liquidity):

**cURL**
```bash
curl 'https://drip.tools/api/quotes?chainId=421614&symbol=ETH'
# { "symbol":"ETH", "price":0.25, "balance":"12513664307…", "available":true, … }
```

`available: false` (or a `balance` smaller than your order) means that TO token can't be dispensed
right now — the quote step will reject it with `NO_TO_PRICE` / `NO_LIQUIDITY`.

## Step 2: Request a signed quote

Post the recipients and the **exact TO amounts** you want each to receive to
[`POST /api/quote`](https://docs.drip.tools/api/quote.md). Amounts are **base-unit integer strings** of the TO token.

**cURL**
```bash
curl -X POST 'https://drip.tools/api/quote' \
  -H 'Content-Type: application/json' \
  -d '{
    "chainId": 8453,
    "fromToken": "0x0000000000000000000000000000000000000000",
    "sender": "0x9246ef7F2b7fEcE8AfD8a72271A76Cc2A8d8A3E5",
    "toChainId": 421614,
    "toToken": "0x0000000000000000000000000000000000000000",
    "recipients": ["0x2F4edA5e7EEa6eb634C7FBb0acAFA04b8938F63A"],
    "toAmounts": ["5000000000000000000"]
  }'
```

Response:

```json
{
  "quoteId":   "0x1f5215a4577ef4548b550313d772594473e237c67514943751634818fdff9615",
  "signer":    "0x5C0917e114e49F3Cf266D6467ac129FEe42d77c5",
  "vault":     "0x52361c9412dcec6a83980fb065d0276ec001face",
  "issuedAt":  1784120925,
  "expiry":    1784121105,
  "deadline":  1784121105,
  "fromToken": "0x0000000000000000000000000000000000000000",
  "fromTotal": "517554316856097",
  "fromAmounts": ["517554316856097"],
  "toAmounts":   ["5000000000000000000"],
  "usdTotal":  "1.25",
  "signature": "0x23fbc0…1b"
}
```

What matters:

| Field | Use |
| --- | --- |
| `quoteId` | The only reference carried on-chain. Pass it to the `pay` call, or put it in the Solana memo / TON comment. |
| `vault` | The pay target — equal to the FROM chain's `contracts.depository` (contract on EVM, treasury on Solana / TON); lowercased on EVM, so compare case-insensitively. |
| `fromTotal` | The exact FROM amount to pay (base units). `msg.value` for native, `amount` for ERC-20, lamports / nanotons on Solana / TON. |
| `deadline` | Pass to the `pay` call. The tx reverts (`Expired`) after this. Equal to `expiry`. |
| `fromAmounts` | Per-recipient FROM breakdown (bookkeeping; the backend expands the batch by these). |
| `signature` | EIP-712 signature over the quote. Optional to verify client-side — see below. |

### You pick TO, the API prices FROM

You never specify how much mainnet token to spend. You say "recipient gets `5` testnet ETH", and
the API multiplies by the live FROM price (Chainlink) and the TO rate to return `fromTotal`. The
quote **locks** those TO amounts — the backend delivers exactly `toAmounts`, regardless of price
movement between quoting and payment.

### Minimums & expiry

- **$1** minimum for a single recipient, **$5** for a multisend (>1 recipient). Below that:
  `BELOW_MIN_TOTAL`.
- TO amounts sit on a **4-decimal grid** (whole sats on Bitcoin): more precision →
  `TO_TOO_PRECISE`, less than one grid step → `TO_TOO_SMALL`. On a Solana TO chain each
  recipient needs at least **0.001 SOL** (`BELOW_SOL_RENT_MIN`).
- A quote is valid for **180 seconds**. Submit your on-chain tx before
  `deadline`; otherwise request a fresh quote.
- Requesting a quote is free and non-binding — nothing happens until you pay. Quote right before
  you are ready to send.

### Pay exactly what was quoted

The payment is matched against the quote **off-chain**: same payer as `sender`, same token as
`fromToken`, an amount **equal** to `fromTotal`, before `deadline`. A payment that differs does
not become an order — see [Payment rules](https://docs.drip.tools/reference/lifecycle.md#payment-rules). Never round
`fromTotal`, and never pay from a different wallet than the one you quoted for.

### Verify the signature (optional)

The quote carries an EIP-712 signature from `signer`. Checking it before you pay is optional; a
worked example is in
[Verifying the quote signature](https://docs.drip.tools/swap/contract.md#verifying-the-quote-signature-optional).

## Step 3: Pay on-chain

Send **one** transaction on the FROM chain. The recipient list and amounts are **not** in it —
only the `quoteId` (and, on EVM, the `deadline`). Pick the variant that matches your FROM chain
and token.

### Solana and TON

There is no contract call: transfer exactly `fromTotal` to the treasury (`quote.vault`) with the
`quoteId` as the memo (Solana) or the text comment (TON). The transaction shape is a protocol —
read [Paying from Solana & TON](https://docs.drip.tools/swap/memo.md) before sending anything. The rest of this step is
EVM only.

### Native token (ETH, BNB, POL, AVAX, …)

`msg.value` must equal `fromTotal`.

**JavaScript · viem**
```js
import { parseAbi } from 'viem'

const abi = parseAbi(['function pay(bytes32 quoteId, uint256 deadline) payable'])

const hash = await wallet.writeContract({
  address: quote.vault,
  abi,
  functionName: 'pay',
  args: [quote.quoteId, BigInt(quote.deadline)],
  value: BigInt(quote.fromTotal),
})
```

### ERC-20 token — approve + payWithToken

First approve the pay target (`quote.vault`) for at least `fromTotal`, then call `payWithToken`. `amount` must
equal `fromTotal`.

**JavaScript · viem**
```js
const erc20 = parseAbi(['function approve(address spender, uint256 amount) returns (bool)'])
const abi   = parseAbi([
  'function payWithToken(address token, uint256 amount, bytes32 quoteId, uint256 deadline)',
])

// 1. approve (skip if the current allowance already covers fromTotal)
const approveHash = await wallet.writeContract({
  address: quote.fromToken,
  abi: erc20,
  functionName: 'approve',
  args: [quote.vault, BigInt(quote.fromTotal)],
})
await publicClient.waitForTransactionReceipt({ hash: approveHash })

// 2. pay
const hash = await wallet.writeContract({
  address: quote.vault,
  abi,
  functionName: 'payWithToken',
  args: [quote.fromToken, BigInt(quote.fromTotal), quote.quoteId, BigInt(quote.deadline)],
})
```

> **Warning: Ethereum-mainnet USDT**
>
> USDT on Ethereum requires resetting the allowance to `0` before setting a new non-zero value. If a
> prior non-zero allowance exists, send `approve(spender, 0)` first, then `approve(spender, fromTotal)`.

### ERC-20 token — gasless approve with EIP-2612 permit

If the FROM token supports [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) (`permit`), you can
skip the approve transaction entirely: sign a permit off-chain and pass it to `payWithTokenPermit`
in a single transaction.

**JavaScript · viem**
```js
const abi = parseAbi([
  'function payWithTokenPermit(address token, uint256 amount, bytes32 quoteId, uint256 deadline, uint256 permitDeadline, uint8 v, bytes32 r, bytes32 s)',
])

// Sign an EIP-2612 permit for spender = depository, value = fromTotal.
// (Build domain/types from the token's name/version/nonces — see Examples.)
const { v, r, s, permitDeadline } = await signErc2612Permit(/* … */)

const hash = await wallet.writeContract({
  address: quote.vault,
  abi,
  functionName: 'payWithTokenPermit',
  args: [
    quote.fromToken, BigInt(quote.fromTotal), quote.quoteId, BigInt(quote.deadline),
    BigInt(permitDeadline), v, r, s,
  ],
})
```

A full permit example (building the EIP-2612 signature) is in
[Examples → ERC-20 with permit](https://docs.drip.tools/swap/examples.md#erc-20-with-permit). Tokens with a Uniswap
**Permit2** allowance can use `payWithPermit2(...)` instead — see the
[contract reference](https://docs.drip.tools/swap/contract.md#functions).

### Multisend is the same call

A batch is not a different function. You requested many recipients in Step 2, so the quote's
`fromTotal` already covers all of them. You still make **one** `pay` / `payWithToken` call (or one
Solana / TON transfer) with `msg.value` / `amount = fromTotal`. The backend reads the recipients
from the signed quote and delivers to each. This is the whole point of the off-chain model.

## Step 4: Track your order

After the payment confirms, poll by your FROM tx hash. One payment expands into **one order per
recipient**.

**cURL**
```bash
curl 'https://drip.tools/api/order?from_tx_hash=0x<yourFromTx>'
```

**Response**
```json
{
  "data": [
    {
      "status": "completed",
      "id": "abc123…",
      "from": { "chainId": "8453", "symbol": "ETH", "amount": "0.00051755", "txHash": "0x…" },
      "to":   { "chainId": "421614", "symbol": "ETH", "amount": "5.00000000",
                "address": "0x2f4e…f63a", "txHash": "0x<payoutTx>", "ts": 1784120990 }
    }
  ],
  "refund": null
}
```

- Each order's `status` starts as `processing` and flips to `completed` (or `failed`).
- `to.txHash` is the payout transaction on the testnet — proof the recipient was funded.
- If some recipients failed, their FROM slice is refunded and surfaces in `refund` — see
  [Order lifecycle & refunds](https://docs.drip.tools/reference/lifecycle.md).
- **What to pass as `from_tx_hash`:** the EVM transaction hash, the Solana transaction
  signature, or — on TON, where your wallet never learns the treasury-side hash — the
  **`quoteId`** itself. See [Orders](https://docs.drip.tools/api/orders.md#get-api-order).

Other tracking options:

- **By order id** — [`GET /api/order?id=<order_id>`](https://docs.drip.tools/api/orders.md#get-api-order).
- **Large batches** — [`GET /api/order?from_tx_hash=…&paginate=1`](https://docs.drip.tools/api/orders.md#paginated-recipients-paginate-1)
  returns an aggregate summary plus a page of recipients instead of the whole array.
- **Bulk** — [`POST /api/orders/status`](https://docs.drip.tools/api/orders.md#post-api-orders-status) with `{ ids: [...] }`.
- **History** — [`GET /api/orders?address=<payer>&view=batches`](https://docs.drip.tools/api/orders.md#get-api-orders).
- **Wait estimate** — [`GET /api/orders/load?ref=<txHashOrId>`](https://docs.drip.tools/api/orders.md#get-api-orders-load)
  returns your position in the dispense queue.
- **Push instead of poll** — [`GET /api/orders/stream`](https://docs.drip.tools/api/orders.md#get-api-orders-stream) is a
  Server-Sent Events feed that fires whenever one of your orders changes.

Ready-to-run scripts for every variant are in [Examples](https://docs.drip.tools/swap/examples.md).
