> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stablemint.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge USDSM

> Bridge USDSM between chains programmatically.

This guide shows how to bridge USDSM between chains programmatically. The process requires two transactions: an ERC-20 approval and a bridge send.

## Prerequisites

* USDSM tokens on the source chain
* Native gas token (ETH/XTZ) on the source chain for transaction fees + LayerZero messaging fee
* The destination chain's [Endpoint ID](/bridge/overview#supported-chains)

## Quick Start (ethers.js v6)

### Step 1: Set up contracts

```javascript theme={null}
import { ethers } from 'ethers';

// Connect to source chain (example: Ethereum)
const provider = new ethers.JsonRpcProvider('YOUR_RPC_URL');
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);

// Contract addresses (Ethereum — see overview for other chains)
const USDSM_ADDRESS = '0x399B29975CBE313C56269cD5097F5AE097Fa2741';
const ADAPTER_ADDRESS = '0x0aFa4BE6fA5ebAf5fFEe3AB9F88EAA3e16c37aDE';
const MINTER_BURNER_ADDRESS = '0x6A02e153bCbF9e6BE45C0b49F7F0ABc82859ebD5';

// Minimal ABIs
const ERC20_ABI = ['function approve(address, uint256) returns (bool)'];
const ADAPTER_ABI = [
  'function quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes), bool) view returns ((uint256,uint256))',
  'function send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes), (uint256,uint256), address) payable returns ((bytes32,uint64,(uint256,uint256))'
];

const usdsm = new ethers.Contract(USDSM_ADDRESS, ERC20_ABI, signer);
const adapter = new ethers.Contract(ADAPTER_ADDRESS, ADAPTER_ABI, signer);
```

### Step 2: Build send parameters

```javascript theme={null}
// Destination: Arbitrum (EID 30110)
const DST_EID = 30110;
const amount = ethers.parseEther('100'); // 100 USDSM

// Encode recipient as bytes32 (pad address to 32 bytes)
const recipientBytes32 = ethers.zeroPadValue(signer.address, 32);

// Enforced options: 120k gas for lzReceive
const extraOptions = '0x0003010011010000000000000000000000000001d4c0';

const sendParam = {
  dstEid: DST_EID,
  to: recipientBytes32,
  amountLD: amount,
  minAmountLD: amount,       // Set slippage tolerance if needed
  extraOptions: extraOptions,
  composeMsg: '0x',          // No composed message
  oftCmd: '0x',              // No OFT command
};
```

### Step 3: Quote the messaging fee

```javascript theme={null}
const [nativeFee, lzTokenFee] = await adapter.quoteSend(
  [sendParam.dstEid, sendParam.to, sendParam.amountLD, sendParam.minAmountLD,
   sendParam.extraOptions, sendParam.composeMsg, sendParam.oftCmd],
  false // don't pay in LZ token
);

console.log(`LayerZero fee: ${ethers.formatEther(nativeFee)} ETH`);
```

### Step 4: Approve and send

```javascript theme={null}
// IMPORTANT: Approve the MinterBurner, NOT the adapter
const approveTx = await usdsm.approve(MINTER_BURNER_ADDRESS, amount);
await approveTx.wait();
console.log('Approved MinterBurner');

// Send the bridge transaction
const fee = { nativeFee, lzTokenFee: 0n };
const sendTx = await adapter.send(
  [sendParam.dstEid, sendParam.to, sendParam.amountLD, sendParam.minAmountLD,
   sendParam.extraOptions, sendParam.composeMsg, sendParam.oftCmd],
  fee,
  signer.address, // refund address for excess gas
  { value: nativeFee }
);
const receipt = await sendTx.wait();
console.log(`Bridge TX: ${receipt.hash}`);
```

### Step 5: Track delivery

Monitor your transaction on [LayerZero Scan](https://layerzeroscan.com):

```
https://layerzeroscan.com/tx/YOUR_TX_HASH
```

Delivery typically takes 1–5 minutes depending on the source chain's block confirmations.

## Using viem

```typescript theme={null}
import { createPublicClient, createWalletClient, http, parseEther, padHex } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http() });

const USDSM = '0x399B29975CBE313C56269cD5097F5AE097Fa2741';
const ADAPTER = '0x0aFa4BE6fA5ebAf5fFEe3AB9F88EAA3e16c37aDE';
const MINTER_BURNER = '0x6A02e153bCbF9e6BE45C0b49F7F0ABc82859ebD5';

const amount = parseEther('100');
const dstEid = 30110; // Arbitrum

// 1. Approve MinterBurner
await walletClient.writeContract({
  address: USDSM,
  abi: [{ name: 'approve', type: 'function', inputs: [{ type: 'address' }, { type: 'uint256' }], outputs: [{ type: 'bool' }] }],
  functionName: 'approve',
  args: [MINTER_BURNER, amount],
});

// 2. Quote fee
const adapterAbi = [
  {
    name: 'quoteSend',
    type: 'function',
    stateMutability: 'view',
    inputs: [
      { name: '_sendParam', type: 'tuple', components: [
        { name: 'dstEid', type: 'uint32' },
        { name: 'to', type: 'bytes32' },
        { name: 'amountLD', type: 'uint256' },
        { name: 'minAmountLD', type: 'uint256' },
        { name: 'extraOptions', type: 'bytes' },
        { name: 'composeMsg', type: 'bytes' },
        { name: 'oftCmd', type: 'bytes' },
      ]},
      { name: '_payInLzToken', type: 'bool' },
    ],
    outputs: [
      { name: 'msgFee', type: 'tuple', components: [
        { name: 'nativeFee', type: 'uint256' },
        { name: 'lzTokenFee', type: 'uint256' },
      ]},
    ],
  },
];

const sendParam = {
  dstEid,
  to: padHex(account.address, { size: 32 }),
  amountLD: amount,
  minAmountLD: amount,
  extraOptions: '0x0003010011010000000000000000000000000001d4c0',
  composeMsg: '0x',
  oftCmd: '0x',
};

const { nativeFee } = await publicClient.readContract({
  address: ADAPTER,
  abi: adapterAbi,
  functionName: 'quoteSend',
  args: [sendParam, false],
});

// 3. Send bridge tx
// ... call adapter.send() with the quoted fee
```

## Endpoint IDs Reference

| Chain     | Endpoint ID | Confirmations |
| --------- | ----------- | ------------- |
| Ethereum  | 30101       | 15 blocks     |
| Base      | 30184       | 10 blocks     |
| Arbitrum  | 30110       | 20 blocks     |
| Etherlink | 30292       | 5 blocks      |

Higher confirmation counts mean longer wait before the message is attested by DVNs, but greater finality assurance.

## Worked Example: Bridge 10 USDSM from Ethereum to Arbitrum

This is a real mainnet bridge transaction. Here's exactly what was called:

```javascript theme={null}
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY');
const signer = new ethers.Wallet('YOUR_KEY', provider);

// Ethereum mainnet addresses
const USDSM = '0x399B29975CBE313C56269cD5097F5AE097Fa2741';
const ADAPTER = '0x0aFa4BE6fA5ebAf5fFEe3AB9F88EAA3e16c37aDE';
const MINTER_BURNER = '0x6A02e153bCbF9e6BE45C0b49F7F0ABc82859ebD5';

const amount = ethers.parseEther('10'); // 10 USDSM
const DST_EID = 30110; // Arbitrum

const sendParam = {
  dstEid: DST_EID,
  to: ethers.zeroPadValue(signer.address, 32),
  amountLD: amount,
  minAmountLD: amount,
  extraOptions: '0x0003010011010000000000000000000000000001d4c0', // 120k gas
  composeMsg: '0x',
  oftCmd: '0x',
};

// 1. Quote the LZ fee
const adapter = new ethers.Contract(ADAPTER, ADAPTER_ABI, signer);
const { nativeFee } = await adapter.quoteSend(sendParam, false);
// Result: 0.000117 ETH

// 2. Approve MinterBurner (NOT the adapter)
const usdsm = new ethers.Contract(USDSM, ERC20_ABI, signer);
await (await usdsm.approve(MINTER_BURNER, amount)).wait();

// 3. Send
const tx = await adapter.send(
  sendParam,
  { nativeFee, lzTokenFee: 0n },
  signer.address,
  { value: nativeFee }
);
// TX: 0xc3752bf55496509c38ae94fc94139a3a2e6e1de76f0db02800701197aac06493
```

**Result:** 10 USDSM burned on Ethereum, 10 USDSM minted on Arbitrum. Delivered in \~3 minutes.

Track on LayerZero Scan: [View transaction](https://layerzeroscan.com/tx/0xc3752bf55496509c38ae94fc94139a3a2e6e1de76f0db02800701197aac06493)

## Partner Integration

If you're integrating USDSM bridging into a bridge aggregator or dApp:

1. **Approval target is MinterBurner** — not the OFT Adapter. Your router/facet must approve the MinterBurner contract on each chain. See the [overview](/bridge/overview#important-approval-target) for addresses.

2. **Standard OFT interface** — The `adapter.send()` call, parameters, fee quoting, and options encoding are all standard LayerZero V2 OFT. The only non-standard aspect is the approval target.

3. **Token registration data** — Contact the StableMint team for integration support and token registry configuration.
