> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.0x.org/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.0x.org/_mcp/server.

# Cross Chain Swaps with Bitcoin

> Learn how to use the 0x Cross Chain API to quote, send, and track a cross chain swap that sells or buys native BTC.

## Steps to Cross Chain Swap Native BTC

This guide will walk you through using the `/quotes` and `/status` endpoints on 0x's Cross Chain API to:

* Buy BTC: sell a token on an EVM chain and receive native BTC at a Bitcoin address.
* Sell BTC: sell native BTC from a Bitcoin address and receive a token on another chain.

The examples use USDC on Base as the other side of the swap.

## Terms

| Term | Meaning                                                                                                                                                                                                           |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| UTXO | An unspent output of an earlier Bitcoin transaction. The sell amount and the miner fee are paid from the UTXOs at your address. See [Transaction data](https://developer.bitcoin.org/devguide/transactions.html). |
| PSBT | A Partially Signed Bitcoin Transaction. The sell quote returns one as hex. See [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).                                                         |
| WIF  | A text form of a Bitcoin private key. The sign step reads it from `BITCOIN_WIF`.                                                                                                                                  |

### 0. Prerequisites

Make sure you have:

* [0x API key](https://dashboard.0x.org/apps)
* A mainnet Bitcoin address. Use a P2WPKH address that starts with `bc1q` when you can.
* To buy BTC: a funded EVM wallet, set up as in [Get started](/cross-chain/cross-chain-api/guides/get-started).
* To sell BTC: BTC at your Bitcoin address in a confirmed UTXO. The amount must cover the sell amount and the miner fee.
* To sell BTC: a WIF in `BITCOIN_WIF`. The example uses [bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib), `ecpair`, and `tiny-secp256k1`.

## Bitcoin values

Use these values in every Bitcoin request:

| Item             | Value                                       |
| ---------------- | ------------------------------------------- |
| Chain name       | `bitcoin`                                   |
| Chain ID         | `999999999994`                              |
| Native BTC token | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` |
| Decimals         | `8`                                         |

Write BTC amounts as integers in base units, the same as EVM token amounts. The base unit of BTC is the satoshi. BTC has 8 decimals, so 1 BTC = 100,000,000 satoshis.

#### Supported Bitcoin address types

The API accepts mainnet addresses of these types:

| Type   | Starts with           |
| ------ | --------------------- |
| P2PKH  | `1`                   |
| P2SH   | `3`                   |
| P2WPKH | `bc1q`, 42 characters |
| P2WSH  | `bc1q`, 62 characters |
| P2TR   | `bc1p`                |

## Buy BTC with a token on an EVM chain

### 1. Fetch a quote

Set `destinationChain` to `bitcoin` and `buyToken` to the native BTC token.

```typescript
const quotesParams = new URLSearchParams({
    originChain: 'base', // Base mainnet
    destinationChain: 'bitcoin', // Bitcoin mainnet
    sellToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
    buyToken: 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8', // native BTC
    sellAmount: '20000000', // 20 USDC (6 decimals)
    originAddress: '$USER_EVM_ADDRESS', // Base address that sells the USDC
    destinationAddress: '$USER_BITCOIN_ADDRESS', // Bitcoin address that receives the BTC
    sortQuotesBy: 'price',
    maxNumQuotes: '3',
});

const headers = {
    '0x-api-key': '[api-key]', // Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps)
};

const response = await fetch('https://api.0x.org/cross-chain/quotes?' + quotesParams.toString(), { headers });
const quoteResponse = await response.json();
```

`destinationAddress` is required. Without it, the API returns HTTP 400 `INPUT_INVALID` with the reason `Bitcoin destinationAddress is required`.

### 2. Set an allowance and send the transaction

```typescript
import { createWalletClient, http, erc20Abi } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http('https://mainnet.base.org'),
});

const quote = quoteResponse.quotes[0];

if (quote.issues.allowance) {
    const approveTxHash = await walletClient.writeContract({
        address: quoteResponse.sellToken as `0x${string}`,
        abi: erc20Abi,
        functionName: 'approve',
        args: [quote.issues.allowance.spender as `0x${string}`, BigInt(quote.sellAmount)],
    });
    await walletClient.waitForTransactionReceipt({ hash: approveTxHash });
}

const txHash = await walletClient.sendTransaction({
    to: quote.transaction.details.to as `0x${string}`,
    data: quote.transaction.details.data as `0x${string}`,
    value: BigInt(quote.transaction.details.value),
    gas: quote.transaction.details.gas ? BigInt(quote.transaction.details.gas) : undefined,
});
```

### 3. Monitor the cross chain execution

Poll `/status` with the Base transaction hash and the `quoteId`.

```typescript
const statusParams = new URLSearchParams({
    originChain: 'base',
    originTxHash: txHash, // hash of the Base transaction from step 2
    quoteId: quoteResponse.quotes[0].quoteId,
});

const statusResponse = await fetch('https://api.0x.org/cross-chain/status?' + statusParams.toString(), { headers });

console.log(await statusResponse.json());
```

The BTC is at `destinationAddress` when `status` is `bridge_filled`.

## Sell BTC for a token on another chain

### 1. Fetch a quote

Set `originChain` to `bitcoin` and `sellToken` to the native BTC token.

```typescript
const quotesParams = new URLSearchParams({
    originChain: 'bitcoin', // Bitcoin mainnet
    destinationChain: 'base', // Base mainnet
    sellToken: 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8', // native BTC
    buyToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
    sellAmount: '1000000', // 0.01 BTC (8 decimals)
    originAddress: '$USER_BITCOIN_ADDRESS', // Bitcoin address that holds the BTC
    destinationAddress: '$USER_EVM_ADDRESS', // Base address that receives the USDC
    sortQuotesBy: 'price',
    maxNumQuotes: '3',
});

const headers = {
    '0x-api-key': '[api-key]', // Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps)
};

const response = await fetch('https://api.0x.org/cross-chain/quotes?' + quotesParams.toString(), { headers });
const quoteResponse = await response.json();
```

Check these points before you use the quote:

* Do not send `feeBps`, `feeRecipient`, or `feeToken`. The API returns HTTP 400 with the reason `Fee collection is not supported when origin chain is Bitcoin`.
* `allowanceTarget` is `null`. Bitcoin has no approval step and no wrap step.

#### Example response

```json
{
    "liquidityAvailable": true,
    "allowanceTarget": null,
    "originChainId": 999999999994,
    "originChain": "bitcoin",
    "destinationChainId": 8453,
    "destinationChain": "base",
    "sellToken": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8",
    "buyToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "issues": {
        "allowance": null,
        "balance": null
    },
    "quotes": [
        {
            "sellAmount": "1000000",
            "buyAmount": "<USDC base units>",
            "minBuyAmount": "<USDC base units>",
            "quoteId": "<quote id>",
            "gasCosts": {
                "chainType": "btc",
                "total": "<satoshis>"
            },
            "steps": [
                {
                    "type": "bridge",
                    "originChainId": 999999999994,
                    "destinationChainId": 8453,
                    "sellToken": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8",
                    "buyToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                    "sellAmount": "1000000",
                    "buyAmount": "<USDC base units>",
                    "minBuyAmount": "<USDC base units>",
                    "provider": "near_intents",
                    "estimatedTimeSeconds": "<seconds>"
                }
            ],
            "transaction": {
                "chainType": "btc",
                "details": {
                    "psbt": "70736274ff..."
                }
            },
            "estimatedTimeSeconds": "<seconds>"
        }
    ]
}
```

A Bitcoin confirmation takes about 10 minutes.

### 2. Sign the PSBT

```typescript
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from 'tiny-secp256k1';
import { ECPairFactory } from 'ecpair';

bitcoin.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);
const keyPair = ECPair.fromWIF(process.env.BITCOIN_WIF);

const quote = quoteResponse.quotes[0];
const psbt = bitcoin.Psbt.fromHex(quote.transaction.details.psbt.replace(/^0x/, ''));
psbt.signAllInputs(keyPair);
psbt.finalizeAllInputs();

const rawTx = psbt.extractTransaction().toHex();
```

### 3. Broadcast the transaction

[mempool.space](https://mempool.space/docs/api/rest) returns the txid.

```typescript
const broadcastResponse = await fetch('https://mempool.space/api/tx', {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' },
    body: rawTx,
});

const txid = (await broadcastResponse.text()).trim();
if (!broadcastResponse.ok) {
    throw new Error(txid);
}
```

### 4. Monitor the cross chain execution

Poll `/status` with `originChain` set to `bitcoin`, `originTxHash` set to the txid, and the `quoteId`.

```typescript
const statusParams = new URLSearchParams({
    originChain: 'bitcoin',
    originTxHash: txid, // 64 hex characters, with or without a 0x prefix
    quoteId: quoteResponse.quotes[0].quoteId,
});

const statusResponse = await fetch('https://api.0x.org/cross-chain/status?' + statusParams.toString(), { headers });

console.log(await statusResponse.json());
```

For the status lifecycle and failure handling, see [Tracking Transaction Status](/cross-chain/cross-chain-api/key-concepts/tracking-transaction-status).