Bridging
On this page
Gully Chain uses the canonical Arbitrum bridge: the rollup's own Inbox and Outbox for ETH, and the standard Arbitrum token bridge (gateway router plus gateways) for ERC-20 tokens. No third-party bridge or extra trust is involved.
The easiest way to bridge is the Gully Bridge UI. This page explains what happens underneath and how to do it from code. All addresses are on Contract addresses.
#Deposits: Arbitrum Sepolia to Gully Chain
#ETH
An ETH deposit is a call to depositEth() on the rollup's Inbox on Arbitrum Sepolia, sending the ETH as value. The same address receives the ETH on Gully Chain once the chain reads the message from the parent chain, which takes about a minute.
import { createWalletClient, custom, parseEther } from "viem";
import { arbitrumSepolia } from "viem/chains";
const INBOX = "0x7B8499f1C058c9bdE2Bc9BdFEBE40364232C52B5"; // on Arbitrum Sepolia
const wallet = createWalletClient({ chain: arbitrumSepolia, transport: custom(window.ethereum) });
const [account] = await wallet.requestAddresses();
const hash = await wallet.writeContract({
account,
address: INBOX,
abi: [{ type: "function", name: "depositEth", stateMutability: "payable", inputs: [], outputs: [{ type: "uint256" }] }],
functionName: "depositEth",
value: parseEther("0.01"),
});The deposit goes to the address that sent it. If the sender is a contract, its address is aliased on Gully Chain (the standard Arbitrum address alias), so deposit from an EOA unless you know you need otherwise.
#ERC-20 tokens
ERC-20 deposits go through the gateway router on Arbitrum Sepolia. The router picks the gateway for the token (the standard gateway for ordinary tokens), the gateway locks your tokens, and a matching token is minted to you on Gully Chain. The first deposit of a token also deploys its Gully Chain version. Deposits arrive in about a minute.
The Arbitrum SDK handles the approval, gas estimation and retryable ticket for you. Register Gully Chain once, then use Erc20Bridger:
// @arbitrum/sdk v4 (uses ethers v5 signers and providers)
import { registerCustomArbitrumNetwork, Erc20Bridger, EthBridger } from "@arbitrum/sdk";
const gully = registerCustomArbitrumNetwork({
name: "Gully Chain Testnet",
chainId: 485590,
parentChainId: 421614,
confirmPeriodBlocks: 150,
isCustom: true,
isTestnet: true,
ethBridge: {
bridge: "0x2073106fd3fc844Bbde31f5Bf0BB91cf1e2d6C8A",
inbox: "0x7B8499f1C058c9bdE2Bc9BdFEBE40364232C52B5",
sequencerInbox: "0x483f77f692635d4D8a8147D0f732A016614Ce685",
outbox: "0x976C651fe5E63E45C8C28449507F51548922f68c",
rollup: "0x85D80C06Fa5334E8Cd1C51F3F39D477E5dc35E0D",
},
tokenBridge: {
parentGatewayRouter: "0xfbd7503009AB4ee5A1f980a2fCDEE4C7BEB6b2B9",
parentErc20Gateway: "0x8E35E523bFd709d577A77F749c831cB045bCabd0",
parentCustomGateway: "0xA90752f7057dc1BFe2677f1791c5084535A4E42a",
parentWethGateway: "0xEB602d8Fc5F5d87DA629201905bE51885165EC4f",
parentWeth: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73",
parentMultiCall: "0xce1CAd780c529e66e3aa6D952a1ED9A6447791c1",
childGatewayRouter: "0x32C680cA4c27A3566fE5Dfd4643fE420eE1039B7",
childErc20Gateway: "0x5CbA5BFD52beA26Fb65c26Fa452f290cB8610fE2",
childCustomGateway: "0x27530E706c67F210753081508302d69ba1693A41",
childWethGateway: "0xB8BAAd59F553707534F546983912486562FA0Eb3",
childWeth: "0x3292dE74305DF64dbe082c6834fa841353eB2415",
childMultiCall: "0xc0Dee8709648264495af14c65fEA2Ae7a728E01a",
},
});
const erc20Bridger = new Erc20Bridger(gully);
// 1. approve the gateway once per token
await (await erc20Bridger.approveToken({ erc20ParentAddress: TOKEN, parentSigner })).wait();
// 2. deposit, then wait for the tokens on Gully Chain
const tx = await erc20Bridger.deposit({
amount: 1_000_000n,
erc20ParentAddress: TOKEN,
parentSigner, // signer on Arbitrum Sepolia
childProvider, // provider for https://testnet-rpc.gullychain.xyz
});
const receipt = await tx.wait();
await receipt.waitForChildTransactionReceipt(childProvider);EthBridger from the same SDK does ETH deposits and withdrawals with the same network object.
#Withdrawals: Gully Chain to Arbitrum Sepolia
Heads up
Withdrawals are rolling out. You can start a withdrawal today, but it can only be finished on Arbitrum Sepolia after the chain's validator has confirmed it, and the validator is still being staked. Until it is, treat bridged ETH and tokens as one-way. The Status page will say when withdrawals are live.
A withdrawal has three stages:
- Start it on Gully Chain. For ETH, call
withdrawEth(destination)on the ArbSys precompile at0x…64with the ETH as value. For tokens, use the gateway router on Gully Chain (orErc20Bridger.withdraw). This burns the funds on Gully Chain and creates an outgoing message. - Wait for confirmation. The sequencer posts the batch to Arbitrum Sepolia, the validator posts an assertion that includes it, and the assertion must sit unchallenged for the rollup's confirmation period of 150 blocks. The rollup counts blocks with
block.numberon Arbitrum Sepolia, which follows Ethereum Sepolia's block number, so 150 blocks is roughly 30 minutes once the assertion is posted. - Claim it on Arbitrum Sepolia. Execute the message on the Outbox. The SDK does this with
getChildToParentMessages,waitUntilReadyToExecuteandexecute.
// start an ETH withdrawal on Gully Chain with viem
await wallet.writeContract({
address: "0x0000000000000000000000000000000000000064", // ArbSys
abi: [{ type: "function", name: "withdrawEth", stateMutability: "payable",
inputs: [{ name: "destination", type: "address" }], outputs: [{ type: "uint256" }] }],
functionName: "withdrawEth",
args: [account],
value: parseEther("0.001"),
});The 150-block period is the testnet's setting. Mainnet's period will be published before launch.
#Retryable tickets
Anything sent from Arbitrum Sepolia to a contract on Gully Chain, including every ERC-20 deposit, travels as a retryable ticket:
- You pay on Arbitrum Sepolia for a submission fee plus the L3 gas the call will need. Unused value is refunded to a refund address on Gully Chain.
- Gully Chain tries to run the call automatically as soon as the message arrives (the auto-redeem).
- If the auto-redeem fails, for example because the gas price rose or too little gas was given, the ticket is not lost. Anyone can retry it on Gully Chain through the
ArbRetryableTxprecompile (redeem(ticketId)at0x…6E) for up to seven days, and the lifetime can be extended.
The Arbitrum SDK estimates the fees and waits for the redeem for you. Plain ETH deposits through depositEth() are not retryables; they credit the ETH directly.
