> ## Documentation Index
> Fetch the complete documentation index at: https://www.bitnob.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Sign sponsored transfers on EVM chains

> Sign the EIP-7702 authorization and EIP-712 witness from an evm_7702 quote, and submit both signatures for a gasless USDC transfer.

On Ethereum, Base, Arbitrum, Optimism, Polygon, and BSC, a quote returns `"scheme": "evm_7702"`. The sender signs twice with the same key:

1. An **EIP-7702 authorization**, which lets their address run Bitnob's paymaster router code for the transfer.
2. An **EIP-712 witness**, which binds the router to exactly the transfer that was quoted.

The sender doesn't need ETH, POL, or BNB. Bitnob pays the gas.

## Request the quote

The request is the same as on every chain. Only `chain`, `asset`, and the address format change.

```bash theme={null}
curl https://api.platform.bitnob.com/v1/sponsored-transfers \
  -H "X-API-Key: $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: transfer-0002" \
  -d '{
    "chain": "base",
    "asset": "USDC",
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x2222222222222222222222222222222222222222",
    "amount": "25.00"
  }'
```

The `sign` block contains:

| Field | Contents |
| - | - |
| `scheme` | `evm_7702` |
| `authorization` | The EIP-7702 tuple to sign: `address` (the router contract), `chainId` (a number), and `nonce` |
| `witness` | The EIP-712 typed data to sign |
| `digests` | The 32-byte hashes of both, for signers that only sign hashes |

The response also includes `router_version`, the router contract version this quote targets. Leave `router_version` out of the request unless Bitnob support has asked you to pin one.

## Sign with a local key

This example uses [ethers](https://docs.ethers.org/v6/) v6.14 or later, which can sign EIP-7702 authorizations. It's suited to sandbox tests and to self-custody apps where your client code holds the user's key.

```javascript sign-evm.mjs theme={null}
import { Wallet } from "ethers";

export async function signEvmQuote(quote, privateKey) {
  const { scheme, authorization, witness } = quote.sign;
  if (scheme !== "evm_7702") throw new Error(`Unexpected scheme ${scheme}`);

  const wallet = new Wallet(privateKey);
  if (wallet.address.toLowerCase() !== quote.from.toLowerCase()) {
    throw new Error("This key is not the quote's sender");
  }

  // 1. EIP-7702 authorization for the router contract
  const auth = await wallet.authorize({
    address: authorization.address,
    chainId: BigInt(authorization.chainId),
    nonce: BigInt(authorization.nonce),
  });

  // 2. EIP-712 witness. ethers derives the domain type itself.
  const { EIP712Domain, ...types } = witness.types;
  const witnessSig = await wallet.signTypedData(witness.domain, types, witness.message);

  return {
    signatures: {
      authorization: auth.signature.serialized,
      witness: witnessSig,
    },
  };
}
```

Both values are 0x-prefixed 65-byte signatures (`r`, `s`, `v`), which is the format Submit expects.

`witness` is standard EIP-712 typed data. On Base it looks like this:

```json theme={null}
"witness": {
  "primaryType": "SponsoredTransfer",
  "domain": {
    "name": "BitnobSponsoredTransfer",
    "version": "2",
    "chainId": "8453",
    "verifyingContract": "0x1111111111111111111111111111111111111111"
  },
  "types": {
    "EIP712Domain": [ ... ],
    "SponsoredTransfer": [
      { "name": "token", "type": "address" },
      { "name": "recipient", "type": "address" },
      { "name": "recipientAmount", "type": "uint256" },
      { "name": "collector", "type": "address" },
      { "name": "feeAmount", "type": "uint256" },
      { "name": "partner", "type": "address" },
      { "name": "partnerAmount", "type": "uint256" },
      { "name": "nonce", "type": "uint256" },
      { "name": "deadline", "type": "uint256" }
    ]
  },
  "message": { "token": "0x8335...2913", "recipient": "0x2222...2222", "recipientAmount": "1000000", "feeAmount": "767192", ... }
}
```

`verifyingContract` is the sender's own address, because EIP-7702 delegates it to the router. Amounts in `message` are in base units, such as `1000000` for 1 USDC. Show the user the decimal amounts from the quote, not these.

## Sign with a hardware or custom signer

If your signer can't take structured EIP-7702 or EIP-712 input, sign the two hashes in `sign.digests` directly. The result is the same pair of signatures.

```javascript theme={null}
import { SigningKey } from "ethers";

const key = new SigningKey(privateKey);
const signatures = {
  authorization: key.sign(quote.sign.digests.authorization).serialized,
  witness: key.sign(quote.sign.digests.witness).serialized,
};
```

Sign a digest without hashing it again. `signMessage` and `personal_sign` add the EIP-191 prefix and produce a different signature, which Submit rejects with `422`.

## Signing in the user's browser wallet

Your user's wallet has to support signing EIP-7702 authorizations for an arbitrary contract, and many browser extension wallets don't yet allow it. Before you build on a wallet, test it in sandbox with a real quote. Embedded wallets and self-custody apps that hold the key in your own client code can use the local-key example above.

## Submit and track

Send both signatures in one call to [Submit signatures](https://www.bitnob.dev/api-reference/platform/sponsored-transfers/submit-signatures.md):

```bash theme={null}
curl https://api.platform.bitnob.com/v1/sponsored-transfers/$TRANSFER_ID/submit \
  -H "X-API-Key: $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: transfer-0002-submit" \
  -d '{
    "signatures": {
      "authorization": "0x8f1c...1b",
      "witness": "0x4a7e...1c"
    }
  }'
```

On EVM chains the status passes through `assembled`, when Bitnob has built the fully signed transaction, before `pending` and `completed`.

## Common problems

| Symptom | Cause |
| - | - |
| `422 signature_invalid` | A different key signed, the payload was changed, or a digest was hashed again. The quote is still valid, so sign again. |
| `failed` with `insufficient_token_balance` | The sender's balance dropped below `total_debit` before broadcast. |
| `failed` with `transfer_reverts` or `reverted` | The token transfer reverted, for example because the token blocks the sender or recipient. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.