# Programmatic Gateway

## Sonic Bridge: Programmatic Usage Guide

### Contract Addresses

```javascript
// Ethereum (L1)
const ETH_CONTRACTS = {
    TOKEN_DEPOSIT: "0xa1E2481a9CD0Cb0447EeB1cbc26F1b3fff3bec20",
    TOKEN_PAIRS: "0xf2b1510c2709072C88C5b14db90Ec3b6297193e4",
    STATE_ORACLE: "0xB7e8CC3F5FeA12443136f0cc13D81F109B2dEd7f"
};

// Sonic (L2)
const SONIC_CONTRACTS = {
    BRIDGE: "0x9Ef7629F9B930168b76283AdD7120777b3c895b3",
    TOKEN_PAIRS: "0x134E4c207aD5A13549DE1eBF8D43c1f49b00ba94",
    STATE_ORACLE: "0x836664B0c0CB29B7877bCcF94159CC996528F2C3"
};
```

### Setup

```javascript
// Network RPC endpoints
const ETHEREUM_RPC = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY";
const SONIC_RPC = "https://rpc.soniclabs.com";

// Initialize providers
const ethProvider = new ethers.providers.JsonRpcProvider(ETHEREUM_RPC);
const sonicProvider = new ethers.providers.JsonRpcProvider(SONIC_RPC);

// Initialize signer with your private key
const PRIVATE_KEY = "your-private-key";
const ethSigner = new ethers.Wallet(PRIVATE_KEY, ethProvider);
const sonicSigner = new ethers.Wallet(PRIVATE_KEY, sonicProvider);
```

### Bridge Operations

#### 1. Ethereum to Sonic Transfer

```javascript
async function bridgeToSonic(tokenAddress, amount) {
    // 1. Check if token is supported
    const tokenPairs = new ethers.Contract(ETH_CONTRACTS.TOKEN_PAIRS, TOKEN_PAIRS_ABI, ethProvider);
    const mintedToken = await tokenPairs.originalToMinted(tokenAddress);
    if (mintedToken === ethers.constants.AddressZero) {
        throw new Error("Token not supported");
    }

    // 2. Approve token spending
    const token = new ethers.Contract(tokenAddress, ERC20_ABI, ethSigner);
    const approveTx = await token.approve(ETH_CONTRACTS.TOKEN_DEPOSIT, amount);
    await approveTx.wait();

    // 3. Deposit tokens
    const deposit = new ethers.Contract(ETH_CONTRACTS.TOKEN_DEPOSIT, TOKEN_DEPOSIT_ABI, ethSigner);
    const tx = await deposit.deposit(Date.now(), tokenAddress, amount);
    const receipt = await tx.wait();

    return {
        transactionHash: receipt.transactionHash,
        mintedToken,
        blockNumber: receipt.blockNumber,
        depositId: receipt.events.find(e => e.event === 'Deposit').args.id
    };
}
```

#### 2. Claim Tokens on Sonic

```javascript
async function waitForStateUpdate(depositBlockNumber) {
    const stateOracle = new ethers.Contract(SONIC_CONTRACTS.STATE_ORACLE, STATE_ORACLE_ABI, sonicProvider);
    
    while (true) {
        const currentBlockNum = await stateOracle.lastBlockNum();
        if (currentBlockNum >= depositBlockNumber) {
            return currentBlockNum;
        }
        await new Promise(resolve => setTimeout(resolve, 30000)); // Check every 30 seconds
    }
}

async function generateProof(depositId, blockNum) {
    // Generate storage slot for deposit
    const storageSlot = ethers.utils.keccak256(
        ethers.utils.defaultAbiCoder.encode(['uint256', 'uint8'], [depositId, 7])
    );
    
    // Get proof from Ethereum node
    const proof = await ethProvider.send("eth_getProof", [
        ETH_CONTRACTS.TOKEN_DEPOSIT,
        [storageSlot],
        ethers.utils.hexValue(blockNum) // important to use current stateOracle.lastBlockNum
    ]);
    
    // Encode proof in required format
    return ethers.utils.RLP.encode([
        ethers.utils.RLP.encode(proof.accountProof),
        ethers.utils.RLP.encode(proof.storageProof[0].proof)
    ]);
}

async function claimOnSonic(depositBlockNumber, depositId, tokenAddress, amount) {
    // 1. Wait for state oracle update
    console.log("Waiting for state oracle update to block ", depositBlockNumber);
    const blockNum = await waitForStateUpdate(depositBlockNumber);
    
    // 2. Generate proof
    console.log("Generating proof...");
    const proof = await generateProof(depositId, blockNum);
    
    // 3. Claim tokens with proof
    console.log("Claiming tokens...");
    const bridge = new ethers.Contract(SONIC_CONTRACTS.BRIDGE, BRIDGE_ABI, sonicSigner);
    const tx = await bridge.claim(depositId, tokenAddress, amount, proof);
    const receipt = await tx.wait();

    return receipt.transactionHash;
}
```

#### 3. Sonic to Ethereum Transfer

```javascript
async function bridgeToEthereum(tokenAddress, amount) {
    // 1. Check if token is supported
    const tokenPairs = new ethers.Contract(SONIC_CONTRACTS.TOKEN_PAIRS, TOKEN_PAIRS_ABI, sonicProvider);
    const originalToken = await tokenPairs.mintedToOriginal(tokenAddress);
    if (originalToken === ethers.constants.AddressZero) {
        throw new Error("Token not supported");
    }
    
    // 2. Approve token spending
    const token = new ethers.Contract(tokenAddress, ERC20_ABI, sonicSigner);
    const approveTx = await token.approve(SONIC_CONTRACTS.BRIDGE, amount);
    console.log("waiting... ", approveTx.hash);
    await approveTx.wait();

    // 3. Initiate withdrawal
    const bridge = new ethers.Contract(SONIC_CONTRACTS.BRIDGE, BRIDGE_ABI, sonicSigner);
    const tx = await bridge.withdraw(Date.now(), originalToken, amount);
    const receipt = await tx.wait();

    return {
        transactionHash: receipt.transactionHash,
        originalToken,
        blockNumber: receipt.blockNumber,
        withdrawalId: receipt.events.find(e => e.event === 'Withdrawal').args.id
    };
}
```

#### 4. Claim Tokens on Ethereum

```javascript
async function waitForEthStateUpdate(withdrawalBlockNumber) {
    const stateOracle = new ethers.Contract(ETH_CONTRACTS.STATE_ORACLE, STATE_ORACLE_ABI, ethProvider);
    
    while (true) {
        const currentBlockNum = await stateOracle.lastBlockNum();
        if (currentBlockNum >= withdrawalBlockNumber) {
            return currentBlockNum;
        }
        await new Promise(resolve => setTimeout(resolve, 30000)); // Check every 30 seconds
    }
}

async function generateWithdrawalProof(withdrawalId, blockNum) {
    // Generate storage slot for withdrawal
    const storageSlot = ethers.utils.keccak256(
        ethers.utils.defaultAbiCoder.encode(['uint256', 'uint8'], [withdrawalId, 1])
    );
    
    // Get proof from Sonic node
    const proof = await sonicProvider.send("eth_getProof", [
        SONIC_CONTRACTS.BRIDGE,
        [storageSlot],
        ethers.utils.hexValue(blockNum) // important to use current stateOracle.lastBlockNum
    ]);
    
    // Encode proof in required format
    return ethers.utils.RLP.encode([
        ethers.utils.RLP.encode(proof.accountProof),
        ethers.utils.RLP.encode(proof.storageProof[0].proof)
    ]);
}

async function claimOnEthereum(withdrawalBlockNumber, withdrawalId, tokenAddress, amount) {
    // 1. Wait for state oracle update
    console.log("Waiting for state oracle update to block ", withdrawalBlockNumber);
    const blockNum = await waitForEthStateUpdate(withdrawalBlockNumber);
    
    // 2. Generate proof
    console.log("Generating proof...");
    const proof = await generateWithdrawalProof(withdrawalId, blockNum);
    
    // 3. Claim tokens with proof
    console.log("Claim tokens with proof...");
    const deposit = new ethers.Contract(ETH_CONTRACTS.TOKEN_DEPOSIT, TOKEN_DEPOSIT_ABI, ethSigner);
    const tx = await deposit.claim(withdrawalId, tokenAddress, amount, proof);
    const receipt = await tx.wait();

    return receipt.transactionHash;
}
```

### Complete Example

```javascript
async function bridgeUSDC() {
    try {
        // USDC details
        const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
        const amount = ethers.utils.parseUnits("100", 6); // USDC has 6 decimals

        // 1. Bridge USDC to Sonic
        console.log("Initiating bridge to Sonic...");
        const deposit = await bridgeToSonic(USDC_ADDRESS, amount);
        console.log(`Deposit successful: ${deposit.transactionHash}`);

        // 2. Claim USDC on Sonic
        console.log("Waiting for state update and claiming on Sonic...");
        const claimTx = await claimOnSonic(deposit.blockNumber, deposit.depositId, USDC_ADDRESS, amount);
        console.log(`Claim successful: ${claimTx}`);

        // Later: Bridge back to Ethereum
        console.log("Initiating bridge back to Ethereum...");
        const withdrawal = await bridgeToEthereum(deposit.mintedToken, amount);
        console.log(`Withdrawal initiated: ${withdrawal.transactionHash}`);

        // Claim on Ethereum
        console.log("Waiting for state update and claiming on Ethereum...");
        const finalClaim = await claimOnEthereum(
            withdrawal.blockNumber,
            withdrawal.withdrawalId,
            withdrawal.originalToken,
            amount
        );
        console.log(`Final claim successful: ${finalClaim}`);
    } catch (error) {
        console.error("Bridge operation failed:", error.message);
        throw error;
    }
}
```

### Required ABIs

```javascript
const STATE_ORACLE_ABI = [
    "function lastBlockNum() external view returns (uint256)",
    "function lastState() external view returns (bytes32)"
];

const ERC20_ABI = [
    "function approve(address spender, uint256 amount) external returns (bool)",
    "function allowance(address owner, address spender) external view returns (uint256)"
];

const TOKEN_PAIRS_ABI = [
    "function originalToMinted(address) external view returns (address)",
    "function mintedToOriginal(address) external view returns (address)"
];

const TOKEN_DEPOSIT_ABI = [
    "function deposit(uint96 uid, address token, uint256 amount) external",
    "function claim(uint256 id, address token, uint256 amount, bytes calldata proof) external",
    "event Deposit(uint256 indexed id, address indexed owner, address token, uint256 amount)"
];

const BRIDGE_ABI = [
    "function withdraw(uint96 uid, address token, uint256 amount) external",
    "function claim(uint256 id, address token, uint256 amount, bytes calldata proof) external",
    "event Withdrawal(uint256 indexed id, address indexed owner, address token, uint256 amount)"
];
```

### Important Notes

1. **State Updates**
   * Ethereum → Sonic: Monitor StateOracle.lastBlockNum until it's >= deposit block
   * Sonic → Ethereum: Monitor StateOracle.lastBlockNum until it's >= withdrawal block
2. **Proofs**
   * Required for all claim operations
   * Generated using eth\_getProof RPC call with correct storage slots
   * Must be RLP encoded in format: `RLP.encode([RLP.encode(accountProof), RLP.encode(storageProof)])`
   * Storage slots are calculated using:
     * Deposits: `keccak256(abi.encode(depositId, uint8(7)))`
     * Withdrawals: `keccak256(abi.encode(withdrawalId, uint8(1)))`
3. **Gas Fees**
   * Keep enough ETH/S for gas on both networks
   * Claim operations typically cost more gas due to proof verification
4. **Security**
   * Never share private keys
   * Always verify contract addresses
   * Test with small amounts first
   * Use the same private key for both networks
5. **Monitoring**
   * Monitor transaction status on both networks
   * Keep transaction hashes for reference
   * Verify successful claims before proceeding
   * Monitor StateOracle updates for claim timing


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.soniclabs.com/sonic/build-on-sonic/programmatic-gateway.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
