Migrate from /suggested-fees to the Swap API
Move from the legacy /suggested-fees + depositV3 flow to the Swap API, one call that returns executable calldata, approvals, and fees.
/suggested-fees returns a fee quote only. You then build and send the SpokePool depositV3
transaction yourself (encode the calldata, handle the ERC-20 approval, and append your integrator ID
to the calldata). The Swap API (/swap/approval) collapses all of that into one call: it returns
ready-to-execute calldata, any required approval transactions, and the full fee breakdown.
Why migrate
- Approval transactions come pre-built (
approvalTxns[]); no manual ERC-20approve. - No hand-encoded
depositV3calldata; you send the returnedswapTxas-is. - Your Integrator ID is a clean query param (
integratorId=0x…); no calldata delimiter. - Pre-flight
checks(allowance + balance) surface problems before you sign. - Swap any token, not just bridge the same token; origin/destination swaps are handled for you.
- Settlement path is selected automatically per route.
- Bigger transfer sizes, up to $10M depending on the token/chain.
- Easier to maintain.
- You can still track deposits via
GET /deposit/status— that part is unchanged. - swap/chains and swap/tokens endpoints are available to dynamically discover supported chains and tokens.
Migrate in four steps
Get an API key + Integrator ID
Production requests need a Bearer API key, and an Integrator ID attributes your volume. Request both via the integrator form.
Swap the endpoint call
Replace the /suggested-fees request with /swap/approval. Add tradeType=exactInput and
depositor, and pass integratorId as a query parameter.
Delete the manual deposit code
Drop the depositV3 encoding, the calldata delimiter, and your hand-rolled approval. Execute the
returned approvalTxns[] (if any), then send swapTx as-is.
Track the deposit
Unchanged. Poll GET /deposit/status until terminal.
Gotchas
- Production needs an API key (
Authorization: Bearer …). - Once migrated, stop appending
1dc0de+ your ID to calldata; it becomes theintegratorIdquery param. - Don't cache quotes; they expire. Respect
quoteExpiryTimestampand re-quote when stale.
Integration changes
One script, showing exactly what to remove (red) and what to add (green). The /suggested-fees
call, the hand-encoded depositV3, the calldata delimiter, and the manual approval all go; the
/swap/approval call and executing the returned transactions replace them.
// 1. Quote
const params = new URLSearchParams({
originChainId, destinationChainId, inputToken, outputToken, amount, recipient,
tradeType: "exactInput",
depositor: account.address,
integratorId: "0xdead", // your ID as a query param, no calldata delimiter
});
const quote = await fetch(
`https://app.across.to/api/suggested-fees?${params}`,
`https://app.across.to/api/swap/approval?${params}`,
{ headers: { Authorization: "Bearer YOUR_API_KEY" } },
).then((r) => r.json());
// 2. Deposit
const calldata = encodeFunctionData({
abi: spokePoolAbi,
functionName: "depositV3",
args: [depositor, recipient, inputToken, outputToken, inputAmount, outputAmount,
destinationChainId, quote.exclusiveRelayer, quote.timestamp,
quote.fillDeadline, quote.exclusivityDeadline, "0x"],
});
const data = `${calldata}1dc0de${INTEGRATOR_ID}`; // append integrator ID to raw calldata
await approveErc20(inputToken, quote.spokePoolAddress, inputAmount);
await walletClient.sendTransaction({ to: quote.spokePoolAddress, data });
for (const tx of quote.approvalTxns ?? []) {
await walletClient.sendTransaction({ to: tx.to, data: tx.data });
}
await walletClient.sendTransaction({
to: quote.swapTx.to,
data: quote.swapTx.data,
value: quote.swapTx.value ? BigInt(quote.swapTx.value) : 0n,
}); Parameter mapping
| Artifact | /suggested-fees | /swap/approval |
|---|---|---|
| Origin chain | originChainId | originChainId |
| Destination chain | destinationChainId | destinationChainId |
| Input token | inputToken | inputToken |
| Output token | outputToken | outputToken |
| Amount | amount | amount + tradeType: exactInput |
| Recipient | recipient | recipient |
| Destination call | message | embedded actions (POST /swap/approval) |
| Depositor | (implicit signer) | depositor (required) |
| Integrator ID | calldata delimiter (1dc0de + ID) | integratorId (query param) |
| Quote timestamp | timestamp | (handled internally) |
| Relayer override | relayer | (none) |
| Unmatched decimals | allowUnmatchedDecimals | (handled) |
Response mapping
| Artifact | /suggested-fees | /swap/approval |
|---|---|---|
| Total relay fee | totalRelayFee / relayFeeTotal | fees.total |
| Fee components | relayerCapitalFee / relayerGasFee / lpFee | fees.total.details |
| Max fee | (none) | fees.totalMax |
| Output amount | outputAmount | expectedOutputAmount / minOutputAmount |
| Fill-time estimate | estimatedFillTimeSec | expectedFillTime |
| Limits / too-low check | limits / isAmountTooLow | checks + error codes |
| Deposit settings | fillDeadline, exclusiveRelayer, exclusivityDeadline, quoteBlock, spokePoolAddress | baked into swapTx |
| Executable transaction | (you build depositV3) | swapTx |
| Token approvals | (you call approve) | approvalTxns |
| Route type | (none) | crossSwapType |
| Quote identifier | id | id |
Not ready to fully migrate? At least attribute your volume
If you're staying on the direct depositV3 flow for now, you should still send an Integrator ID so
your volume is attributed. On the legacy path the only way to do that is to append it to the raw
transaction calldata using a fixed delimiter.
Append the delimiter 1dc0de followed by your integrator ID to the end of the encoded
depositV3 calldata:
| Component | Value | Description |
|---|---|---|
| Delimiter | 1dc0de | Fixed 3-byte hex string that marks the start of the identifier |
| Integrator ID | Your assigned ID (e.g. f001) | Unique identifier provided by the Across team |
import { encodeFunctionData, type Hex } from "viem";
const DELIMITER = "1dc0de";
const INTEGRATOR_ID = "f001"; // Replace with your assigned ID
// Encode depositV3 calldata normally...
const calldata = encodeFunctionData({
abi: spokePoolAbi,
functionName: "depositV3",
args: [depositor, recipient, inputToken, outputToken, inputAmount,
outputAmount, destinationChainId, exclusiveRelayer,
quoteTimestamp, fillDeadline, exclusivityDeadline, message],
});
// ...then append the delimiter + integrator ID to the RAW calldata.
const calldataWithId = `${calldata}${DELIMITER}${INTEGRATOR_ID}` as Hex;
await walletClient.sendTransaction({ to: spokePoolAddress, data: calldataWithId });Do NOT pass the delimiter + ID to any depositV3 parameter, including message. It goes only on
the end of the raw transaction calldata.
Do NOT use f001; it's an example. Request your unique ID from the Across team via your shared
channel (Telegram, Slack), the integrator form.
After you migrate to /swap/approval, all of this goes away: the Integrator ID is just the
integratorId=0x… query parameter on the request. The delimiter is the interim attribution path;
the query param is the destination.