Refunds
How refunds work when a crosschain transfer expires without being filled.
Refunds occur when no relayer fills a deposit before its fillDeadline expires. This is rare on mainnet — the competitive relayer network fills most deposits in ~2 seconds — but it's important to handle in production integrations.
When Refunds Happen
A deposit becomes eligible for a refund when:
- The
fillDeadlinepasses without any relayer filling the intent - No partial fills were executed
Common causes include extremely large transfers, unusual token pairs, or temporary relayer downtime.
Refund Flow
Deposit Expires
The fillDeadline passes and the deposit status changes to expired in the /deposit/status API. The user's funds are still escrowed in the origin SpokePool.
Bundle Processing
The Across Dataworker includes the expired deposit in the next settlement bundle. Bundles are proposed to the HubPool every ~1.5 hours and must pass a challenge period via UMA's Optimistic Oracle.
Settlement
After the challenge period, the bundle is finalized and the refund root is transmitted to the appropriate chain via canonical bridges.
Refund Execution
The refund is executed on-chain, returning the escrowed funds to the refundAddress. The deposit status changes to refunded in the API.
Refunds are not instant. After a deposit expires, the refund goes through the bundle settlement process. With ~1.5-hour bundle intervals plus the challenge period and canonical bridge delays, refunds can take several hours to arrive. Do not tell users to expect immediate refunds.
Refund Parameters
Configure refund behavior when calling the Swap API.
| Parameter | Type | Default | Description |
|---|---|---|---|
refundAddress | string | depositor | Address that receives the refund |
refundOnOrigin | boolean | true | If true, refund on origin chain. If false, refund on destination chain |
const params = new URLSearchParams({
tradeType: "minOutput",
originChainId: "42161",
destinationChainId: "8453",
inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
outputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
amount: "1000000000",
depositor: "0xYourWalletAddress",
// Refund configuration
refundAddress: "0xYourWalletAddress", // Where to send the refund
refundOnOrigin: "true", // Refund on Arbitrum (origin)
});
const response = await fetch(
`https://app.across.to/api/swap/approval?${params}`
);Checking Refund Status
Use the /deposit/status endpoint to monitor whether an expired deposit has been refunded.
import { createPublicClient, http } from "viem";
import { arbitrum } from "viem/chains";
const BASE_URL = "https://app.across.to/api";
async function checkRefundStatus(originChainId: number, depositId: number) {
const params = new URLSearchParams({
originChainId: originChainId.toString(),
depositId: depositId.toString(),
});
const res = await fetch(`${BASE_URL}/deposit/status?${params}`);
const data = await res.json();
switch (data.status) {
case "pending":
console.log("Deposit still pending — waiting for fill");
break;
case "filled":
console.log("Deposit filled successfully — no refund needed");
break;
case "expired":
console.log("Deposit expired — refund is being processed");
console.log("Refund will arrive after bundle settlement (~hours)");
break;
case "refunded":
console.log("Refund complete — funds returned to refund address");
break;
}
return data.status;
}
// Poll until refunded
async function waitForRefund(originChainId: number, depositId: number) {
const intervalMs = 60_000; // Check every minute for refunds
const maxAttempts = 360; // Up to 6 hours
for (let i = 0; i < maxAttempts; i++) {
const status = await checkRefundStatus(originChainId, depositId);
if (status === "refunded") {
return "refunded";
}
if (status === "filled") {
return "filled"; // Was filled after all — no refund needed
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error("Refund polling timed out");
}For refund polling, use a 60-second interval instead of the 10-second interval used for fill tracking. Refunds take hours, not seconds, so frequent polling is unnecessary.