Deposit Addresses
Generate a counterfactual deposit address that users can send funds to in order to initiate a crosschain transfer.
Early Access.
Deposit addresses are available for approved integrators. Reach out on Telegram to get access.
Supported routes.
Deposit addresses currently only support USDC (Ethereum, Base and Arbitrum only) to USDH (HyperEVM, HyperCore). More routes will be added soon.
Overview
Deposit addresses let users initiate crosschain transfers by sending tokens to a generated address. Call /swap/counterfactual with your route parameters and receive a unique depositAddress. The user sends the input token to that address on the origin chain. Across detects the transfer, sweeps the funds, and completes the crosschain transfer to the recipient on the destination chain.
This is ideal for:
- Onramps: user withdraws from a CEX or fiat onramp directly to a deposit address
- Bots and automation: no wallet signing flow needed
- Simplified UX: reduce the integration to a single token transfer
How It Works
API key required.
This endpoint requires a valid API key in the Authorization header. Requests without one will return a 403 error. Request your API key and integrator ID
Generate a deposit address
Call /swap/counterfactual with the route parameters, recipient, and refund address.
const params = new URLSearchParams({
useDepositAddress: "true",
originChainId: "42161",
destinationChainId: "1337",
inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
outputToken: "0x2000000000000000000000000000000000000168", // USDH on HyperCore
amount: "1000000",
recipient: "0xRecipientAddress",
refundAddress: "0xUserOriginAddress",
});
const response = await fetch(
`https://app.across.to/api/swap/counterfactual?${params}`,
{
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
}
);
const data = await response.json();
console.log("Deposit address:", data.depositAddress);
console.log("Send", data.inputToken.symbol, "to this address on origin chain");Send tokens to the deposit address
Transfer the input token to the depositAddress on the origin chain. This is a standard ERC-20 transfer, no special calldata needed.
import { erc20Abi } from "viem";
const hash = await walletClient.writeContract({
address: data.inputToken.address,
abi: erc20Abi,
functionName: "transfer",
args: [data.depositAddress, BigInt(data.inputAmount)],
});
console.log("Transfer tx:", hash);Across handles the rest
After detecting the token transfer, Across:
- Fetches a fresh quote for the route
- Deploys the deposit address contract and sweeps funds into the SpokePool
- Completes the crosschain swap
The recipient receives the output token on the destination chain.
Track the deposit
Poll /deposit/status with the depositAddress to monitor progress:
const statusRes = await fetch(
`https://app.across.to/api/deposit/status?depositAddress=${data.depositAddress}&index=0`
);
const status = await statusRes.json();
console.log("Status:", status.status);
console.log("Deposit tx ref:", status.depositTxnRef);Once you have the depositTxnRef, query /deposit for full execution details including fees:
const depositRes = await fetch(
`https://app.across.to/api/deposit?depositTxnRef=${status.depositTxnRef}`
);
const deposit = await depositRes.json();
console.log("Fill tx:", deposit.fillTxnRef);API Parameters
Request
| Parameter | Type | Required | Description |
|---|---|---|---|
useDepositAddress | boolean | Yes | Must be true |
inputToken | string | Yes | Token address on origin chain |
outputToken | string | Yes | Token address on destination chain |
originChainId | number | Yes | Origin chain ID |
destinationChainId | number | Yes | Destination chain ID |
amount | string | Yes | Amount in smallest unit (wei) |
recipient | string | Yes | Address receiving tokens on destination |
refundAddress | string | Yes | Address for refunds on origin chain |
Response
| Field | Type | Description |
|---|---|---|
depositAddress | string | Address to send tokens to on origin chain |
id | string | Unique quote identifier |
crossSwapType | string | Type of crosschain swap (e.g. "bridgeableToBridgeable") |
amountType | string | Amount type used (e.g. "exactInput") |
inputToken | object | Input token details: address, symbol, name, decimals, chainId |
outputToken | object | Output token details: address, symbol, name, decimals, chainId |
refundToken | object | Token used for refunds: address, symbol, name, decimals, chainId |
inputAmount | string | Amount of input token (smallest unit) |
maxInputAmount | string | Maximum input amount |
expectedOutputAmount | string | Expected output amount after fees |
minOutputAmount | string | Minimum guaranteed output amount |
expectedFillTime | number | Expected fill time in seconds |
quoteExpiryTimestamp | number | Unix timestamp when this quote expires |
checks | object | Allowance and balance checks: allowance (token, spender, actual, expected) and balance (token, actual, expected) |
steps | object | Breakdown of the bridge step including inputAmount, outputAmount, tokenIn, tokenOut, fees, and provider |
fees | object | Fee breakdown: total (amount, amountUsd, pct, details), totalMax, and originGas |
swapTx | object | Transaction to execute: ecosystem, simulationSuccess, chainId, to, data, value, gas |
The fees.total.details object contains a full breakdown including bridge fees (relayer capital, destination gas, LP fee) and swapImpact. See the API playground for the complete nested structure.
Address Lifecycle
Each call to /swap/counterfactual returns a new, unique address. Deposit addresses are not intended to be reused.
| Behavior | Detail |
|---|---|
| Uniqueness | New address generated per quote |
| TTL | 24 hours: address expires if no token transfer is received |
| After first transfer | Across processes the transfer and stops monitoring the address |
| Reuse within TTL | If additional transfers are sent within the TTL, they will be processed, but this is not the intended flow |
Do not cache or persist deposit addresses. Generate a new one for each transfer.
Tracking Status
Track deposit status using the depositAddress and index parameters on /deposit/status:
GET /deposit/status?depositAddress=0x1234...abcd&index=0The index is 0-based. Use 0 for the first (and typically only) deposit to that address.
The response uses the same status values as standard deposits: pending > filled / expired > refunded.
See Tracking Deposits for the full polling guide.
Fees
There is currently no deployment fee charged for generating deposit addresses. If a fee is introduced in the future, it will be:
- A fixed amount (not a percentage)
- Deducted from the user's input amount
- Included in the quote response
Bridge fees (relayer capital, destination gas, LP) are included in the response under fees.total and steps.bridge.fees.
Refund Behavior
Always set refundAddress to an address the user controls on the origin chain. This address receives refunds if the transfer cannot be completed.
If there's ever an issue with your bridge transaction while using /swap/counterfactual, please know your funds are not lost. Deposit addresses have built-in safeguards to ensure funds can always be recovered.
Below is a breakdown of each scenario, what happens automatically, and what steps you need to take to resolve it.
| Scenario | What happens | What to do |
|---|---|---|
Deposit is showing pending status | The transfer is still being processed. This can take longer than usual during periods of high network congestion or relayer unprofitability. | Wait and monitor using /deposit/status. Most pending deposits resolve automatically. |
| Deposit filled but relayer can't complete the transfer | Refund is sent automatically to refundAddress on the origin chain. | No action needed — wait for the automatic refund. This typically completes within a few hours. |
| Correct token, wrong chain | Funds are stuck at the deposit address on the wrong chain. Manual recovery is required. | Reach out to our team (see below). |
| Wrong token, correct chain | The deposit address only processes the expected token. The wrong token will not be swept or filled. Manual recovery is required. | Reach out to our team (see below). |
| Wrong token, wrong chain | Funds are stuck at the deposit address on the wrong chain with the wrong token. Manual recovery is required. | Reach out to our team (see below). |
Need help recovering funds?
For any scenario that requires manual recovery, reach out to us with the following details:
depositAddress— the address returned by/swap/counterfactual- Token address — the contract address of the token you sent
- Chain ID — the chain where the funds were actually sent
Contact us via Telegram, Discord, or Slack if your team has a shared channel with us.
This refund behavior is specific to /swap/counterfactual. For general refund timing and behavior applicable on all other APIs, see Refunds.
Try it in the API playground: /swap/counterfactual