# 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
