Migration Guides

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-20 approve.
  • No hand-encoded depositV3 calldata; you send the returned swapTx as-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 the integratorId query param.
  • Don't cache quotes; they expire. Respect quoteExpiryTimestamp and 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.

migrate.ts
// 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 chainoriginChainIdoriginChainId
Destination chaindestinationChainIddestinationChainId
Input tokeninputTokeninputToken
Output tokenoutputTokenoutputToken
Amountamountamount + tradeType: exactInput
Recipientrecipientrecipient
Destination callmessageembedded actions (POST /swap/approval)
Depositor(implicit signer)depositor (required)
Integrator IDcalldata delimiter (1dc0de + ID)integratorId (query param)
Quote timestamptimestamp(handled internally)
Relayer overriderelayer(none)
Unmatched decimalsallowUnmatchedDecimals(handled)

Response mapping

Artifact/suggested-fees/swap/approval
Total relay feetotalRelayFee / relayFeeTotalfees.total
Fee componentsrelayerCapitalFee / relayerGasFee / lpFeefees.total.details
Max fee(none)fees.totalMax
Output amountoutputAmountexpectedOutputAmount / minOutputAmount
Fill-time estimateestimatedFillTimeSecexpectedFillTime
Limits / too-low checklimits / isAmountTooLowchecks + error codes
Deposit settingsfillDeadline, exclusiveRelayer, exclusivityDeadline, quoteBlock, spokePoolAddressbaked into swapTx
Executable transaction(you build depositV3)swapTx
Token approvals(you call approve)approvalTxns
Route type(none)crossSwapType
Quote identifieridid

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:

ComponentValueDescription
Delimiter1dc0deFixed 3-byte hex string that marks the start of the identifier
Integrator IDYour assigned ID (e.g. f001)Unique identifier provided by the Across team
append-integrator-id.ts
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.

Next steps

On this page