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

# Cross-Chain Swap with 0x

> Learn how to bridge and swap tokens across chains using the Enclave MPC API with 0x cross-chain integration.

<Warning>
  Cross-chain swap endpoints are experimental and require explicit enablement by both the Portal and 0x teams before they can be used. Contact your Portal account manager to request access.
</Warning>

Portal's Enclave MPC API provides cross-chain bridging and swapping through 0x. This guide covers getting quotes, executing swaps, and tracking transaction status across chains.

## Overview

Using 0x cross-chain, you can:

* **Get quotes** for cross-chain swaps across EVM chains with multiple bridge providers
* **Execute cross-chain swaps** by signing and submitting the returned transaction
* **Track transaction status** for cross-chain transfers
* **Browse transaction history** for a wallet address
* **List available sources** including bridge providers and DEX sources

## Prerequisites

Before using 0x cross-chain operations, ensure you have:

* A properly initialized Portal client (see [Create a client](./create-a-client))
* An active wallet with the required token(s) on the origin chain (see [Create a wallet](./create-a-wallet))
* 0x integration enabled in your Portal Dashboard with a cross-chain-enabled API key (see [0x Integration](../../../integrations/Trading/zerox))

## Getting Cross-Chain Quotes

Use the `POST /api/v3/clients/me/integrations/0x/cross-chain/quotes` endpoint to get quotes for a cross-chain swap.

```bash theme={null}
curl --request POST \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/quotes' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "originChain": "eip155:8453",
  "destinationChain": "eip155:42161",
  "sellToken": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  "buyToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
  "sellAmount": "1000000",
  "sortQuotesBy": "price"
}'
```

### Parameters

| Name                 | Type   | Required | Description                                                                                                                                                                                     |
| -------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `originChain`        | string | Yes      | Origin chain in CAIP-2 format (e.g. `eip155:8453`)                                                                                                                                              |
| `destinationChain`   | string | Yes      | Destination chain in CAIP-2 format (e.g. `eip155:42161`)                                                                                                                                        |
| `sellToken`          | string | Yes      | Token to sell on the origin chain. Accepts a contract address (e.g. `0x833589...`), a native token keyword (`NATIVE`, `ETH`, `AVAX`, `POL`, etc.), or a supported token symbol (`USDC`, `USDT`) |
| `buyToken`           | string | Yes      | Token to buy on the destination chain. Same formats as `sellToken`                                                                                                                              |
| `sellAmount`         | string | Yes      | Amount of `sellToken` to sell, in base units (the smallest indivisible unit of the token, e.g. `1000000` for 1 USDC since USDC has 6 decimals, or wei for native ETH)                           |
| `sortQuotesBy`       | string | Yes      | Sort quotes by `speed` or `price`                                                                                                                                                               |
| `originAddress`      | string | No       | Wallet address on origin chain. Defaults to the client's EIP-155 address                                                                                                                        |
| `destinationAddress` | string | No       | Wallet address to receive tokens. Defaults to `originAddress`                                                                                                                                   |
| `slippageBps`        | number | No       | Maximum slippage in basis points (100 = 1%). Defaults to 100                                                                                                                                    |
| `maxNumQuotes`       | number | No       | Maximum number of quotes to return (1-10). Defaults to 3                                                                                                                                        |
| `excludedBridges`    | string | No       | Comma-separated bridge providers to exclude                                                                                                                                                     |
| `includedBridges`    | string | No       | Comma-separated bridge providers to include                                                                                                                                                     |
| `feeBps`             | string | No       | Integrator fee in basis points                                                                                                                                                                  |
| `feeRecipient`       | string | No       | Address to receive integrator fees                                                                                                                                                              |

### Response

The response includes an array of `quotes`, each containing:

* `sellAmount` / `buyAmount` / `minBuyAmount` - Token amounts in base units
* `steps` - Sequential steps (swap, bridge, wrap/unwrap) required to complete the cross-chain swap
* `transaction` - A Portal-formatted transaction object (`from`, `to`, `data`, `gas`, `gasPrice`, `value`) ready to sign and submit
* `approvalTransaction` - A Portal-formatted approval transaction (included automatically when token allowance is needed on EVM origin chains). Sign and submit this before the main `transaction`
* `estimatedTimeSeconds` - Estimated time for the swap to complete
* `fees` - Breakdown of integrator fees, 0x fees, and bridge fees
* `issues` - Problems that would cause the swap to fail (allowance, balance, simulation, invalid sources). See [Inspecting `issues`](#inspecting-issues) below

<Note>
  When `issues.allowance` is present, the response automatically includes an `approvalTransaction` that approves the exact `sellAmount` for the swap. This uses the minimum required approval for security — no unlimited approvals.
</Note>

### Inspecting `issues`

Before submitting any transactions, inspect the quote's `issues` property. It surfaces problems that would otherwise cause the swap to fail on-chain:

* `allowance` - The wallet hasn't approved enough of `sellToken` for the 0x contract. Sign the included `approvalTransaction` (covered in Step 1 below).
* `balance` - The wallet doesn't hold enough `sellToken` to cover `sellAmount`. Top up the wallet before retrying.
* `simulationIncomplete` - 0x couldn't fully simulate the swap, so execution may revert. Re-fetch the quote, or proceed with caution if the route is otherwise sound.
* `invalidSourcesPassed` - One of the bridges or DEX sources you requested via `includedBridges` isn't valid for this route. Adjust `includedBridges` / `excludedBridges` and re-fetch.

Resolve any issues (top up the wallet, exclude a problematic source, etc.), then fetch a new quote and proceed with the steps below.

## Executing a Cross-Chain Swap

After selecting a quote, check if an `approvalTransaction` is present. If so, you must sign and submit it **before** the main `transaction`. This is a two-step signing flow:

1. **Sign `approvalTransaction`** (if present) — approves the 0x contract to spend your ERC20 tokens
2. **Re-fetch a fresh quote** — the original quote may have expired while the approval was confirming
3. **Sign `transaction`** — executes the cross-chain swap

<Warning>
  If `approvalTransaction` is present in the quote response, you **must** sign and submit it before the main `transaction`. Submitting the swap transaction without approval will fail with an "ERC20: transfer amount exceeds allowance" error.
</Warning>

### Step 1: Approve Token Allowance (if required)

If the quote includes `approvalTransaction`, sign and submit it first. This approves the 0x contract to spend the exact amount of your sell token needed for the swap.

```bash theme={null}
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "share": "[share]",
  "method": "eth_sendTransaction",
  "params": [approvalTransaction],
  "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/8453",
  "chainId": "eip155:8453"
}'
```

Pass the entire `approvalTransaction` object from the quote response as `params`. Wait for the approval transaction to confirm before proceeding to the next step.

<Note>
  Native token swaps (e.g. ETH, AVAX) do not require approval since they are not ERC20 tokens. The `approvalTransaction` field will only be present when swapping ERC20 tokens that require allowance.
</Note>

### Step 2: Re-fetch a Fresh Quote

After the approval confirms, fetch a new quote. The original quote contains calldata with embedded timestamps that may have expired during the approval confirmation.

```bash theme={null}
# Re-fetch quote with the same parameters as Step 1
curl --request POST \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/quotes' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "originChain": "eip155:8453",
  "destinationChain": "eip155:42161",
  "sellToken": "USDC",
  "buyToken": "USDC",
  "sellAmount": "1000000",
  "sortQuotesBy": "price"
}'
```

The new quote should no longer include `approvalTransaction` (since the allowance is now set) and the `transaction` calldata will have fresh timestamps.

### Step 3: Sign and Submit the Cross-Chain Transaction

Use the `transaction` from the fresh quote:

```bash theme={null}
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "share": "[share]",
  "method": "eth_sendTransaction",
  "params": [transaction],
  "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/8453",
  "chainId": "eip155:8453"
}'
```

Pass the entire `transaction` object from the quote response as `params`.

<Note>
  The `rpcUrl` and `chainId` should correspond to the **origin chain**, since the transaction is submitted on the origin chain. The bridge provider handles delivering tokens to the destination chain.
</Note>

### Step 4: Resolve the Transaction Hash (Account Abstraction clients only)

If your client uses Account Abstraction, the `eth_sendTransaction` response contains a [user operation hash](https://eips.ethereum.org/EIPS/eip-4337) rather than a transaction hash. The cross-chain status endpoint requires the actual on-chain transaction hash, so resolve the userOp hash first:

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/chains/eip155:8453/transactions/[userOpHash]' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

Use `evmUserOperation.receipt.hash` from the response as the `originTxHash` in Step 5. EOA clients can skip this step and use the hash returned from signing directly. See the [transaction lookup endpoint](https://docs.portalhq.io/api-reference/wallet/get-transaction-details-by-signature) for the full response shape.

### Step 5: Track Transaction Status

Use the `GET /api/v3/clients/me/integrations/0x/cross-chain/status` endpoint to track the cross-chain transfer:

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/status?originChain=eip155:8453&originTxHash=0xYourTransactionHash' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

### Status Values

| Status                | Description                                                |
| --------------------- | ---------------------------------------------------------- |
| `origin_tx_pending`   | Origin chain transaction is pending                        |
| `origin_tx_succeeded` | Origin chain transaction succeeded                         |
| `origin_tx_confirmed` | Origin chain transaction is confirmed                      |
| `origin_tx_reverted`  | Origin chain transaction reverted                          |
| `bridge_pending`      | Bridge transfer is in progress                             |
| `bridge_filled`       | Bridge transfer completed successfully                     |
| `bridge_failed`       | Bridge transfer failed (check `failure` field for details) |

<Warning>
  If the status is `bridge_failed`, check the `failure` field in the response. In some cases (e.g. Stargate V2), a `failure.recovery.manualTransaction` may be provided that you can submit to recover funds on the destination chain.
</Warning>

## Listing Available Sources

Use the `GET /api/v3/clients/me/integrations/0x/cross-chain/sources` endpoint to list available bridge providers and DEX sources:

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/sources' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

The response includes:

* `bridges` - Available bridge providers with supported origin/destination chain pairs
* `swapSources` - Available DEX sources with supported chain IDs

## Viewing Transaction History

Use the `GET /api/v3/clients/me/integrations/0x/cross-chain/tx-history` endpoint to view cross-chain transaction history for a wallet:

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/tx-history?user=0xYourWalletAddress&limit=20' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

## Example Flow

Here's a complete example of a cross-chain swap from Base USDC to Arbitrum USDC:

```bash theme={null}
# 1. Get cross-chain quotes
QUOTE_RESPONSE=$(curl --request POST \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/quotes' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "originChain": "eip155:8453",
  "destinationChain": "eip155:42161",
  "sellToken": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  "buyToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
  "sellAmount": "1000000",
  "sortQuotesBy": "price"
}')

# 2. Inspect quote.issues, then select a quote and pass its `transaction`
#    object straight through as `params` below.

# 3. Sign and submit the transaction on the origin chain
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "share": "[share]",
  "method": "eth_sendTransaction",
  "params": [transaction],
  "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/8453",
  "chainId": "eip155:8453"
}'

# 4. Track the cross-chain transfer status (AA clients: first resolve the
#    userOp hash to a tx hash via GET /clients/me/chains/{chain}/transactions/{userOpHash})
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/0x/cross-chain/status?originChain=eip155:8453&originTxHash=0xYourTransactionHash' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

## Failure Handling

Cross-chain operations are non-atomic. If a bridge transfer fails:

* **Automatic refunds**: For Across, Relay, and Squid Coral, the user will be refunded in the bridging token (which may differ from the original sell token) on the origin chain.
* **Manual recovery**: For Stargate V2, funds may be delivered to the destination chain but not to the wallet. The status API will return a `failure.recovery.manualTransaction` that can be submitted to re-trigger delivery.

Always monitor transaction status after submission and implement appropriate error handling for your users.

## Supported Networks

0x cross-chain supports bridging between the following EVM networks:

* Ethereum (`eip155:1`)
* Base (`eip155:8453`)
* Arbitrum (`eip155:42161`)
* Optimism (`eip155:10`)
* Polygon (`eip155:137`)
* BSC (`eip155:56`)
* Avalanche (`eip155:43114`)
* Linea (`eip155:59144`)
* Blast (`eip155:81457`)
* Scroll (`eip155:534352`)

Use the [sources endpoint](#listing-available-sources) to get the full list of supported bridge pairs.

## Next Steps

* Learn about [signing Ethereum transactions](./sign-ethereum-transactions)
* Explore same-chain [0x swaps](/integrations/Trading/zerox) using the SDKs
* Compare with [Li.Fi cross-chain bridging](./lifi) for alternative bridge options
