diff --git a/.gitignore b/.gitignore index dab22d2..9db348e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,9 @@ src/pages.gen.ts .env*.local src/pages/protocol/tips/tip-* +# Test scratch files +guides/tmp/ + # Playwright playwright-report/ test-results/ diff --git a/src/pages/guide/bridge-usdc-stargate.mdx b/src/pages/guide/bridge-usdc-stargate.mdx new file mode 100644 index 0000000..090a480 --- /dev/null +++ b/src/pages/guide/bridge-usdc-stargate.mdx @@ -0,0 +1,403 @@ +--- +title: Bridge USDC via Stargate +description: Bridge USDC to and from Tempo using Stargate and LayerZero. Includes source chain addresses, cast commands, and TypeScript examples with viem. +--- + +import { Tabs, Tab } from 'vocs' + +# Bridge USDC via Stargate + +[Stargate](https://stargate.finance/) uses [LayerZero](https://layerzero.network) to enable 1:1 USDC bridging between Tempo and other chains. Stargate locks USDC on the source chain and mints **USDC.e** (Bridged USDC) on Tempo, typically in under a minute. + +## Contracts on Tempo + +| Contract | Address | +|----------|---------| +| **USDC.e** (Bridged USDC) | [`0x20C000000000000000000000b9537d11c60E8b50`](https://explore.tempo.xyz/address/0x20C000000000000000000000b9537d11c60E8b50) | +| **StargateOFTUSDC** | [`0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392`](https://explore.tempo.xyz/address/0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392) | +| **EndpointV2** | [`0x20Bb7C2E2f4e5ca2B4c57060d1aE2615245dCc9C`](https://explore.tempo.xyz/address/0x20Bb7C2E2f4e5ca2B4c57060d1aE2615245dCc9C) | +| **LZEndpointDollar** | [`0x0cEb237E109eE22374a567c6b09F373C73FA4cBb`](https://explore.tempo.xyz/address/0x0cEb237E109eE22374a567c6b09F373C73FA4cBb) | + +Tempo's LayerZero Endpoint ID is **`30410`**. + +## Source chain contracts + +Use the Stargate pool contract on the source chain to bridge USDC to Tempo, and the StargateOFTUSDC contract on Tempo to bridge back. + +| Chain | LZ Endpoint ID | Stargate USDC Pool | +|-------|---------------:|--------------------| +| Ethereum | `30101` | [`0xc026395860Db2d07ee33e05fE50ed7bD583189C7`](https://etherscan.io/address/0xc026395860Db2d07ee33e05fE50ed7bD583189C7) | +| Arbitrum | `30110` | [`0xe8CDF27AcD73a434D661C84887215F7598e7d0d3`](https://arbiscan.io/address/0xe8CDF27AcD73a434D661C84887215F7598e7d0d3) | +| Base | `30184` | [`0x27a16dc786820B16E5c9028b75B99F6f604b5d26`](https://basescan.org/address/0x27a16dc786820B16E5c9028b75B99F6f604b5d26) | +| Optimism | `30111` | [`0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0`](https://optimistic.etherscan.io/address/0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0) | +| Polygon | `30109` | [`0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4`](https://polygonscan.com/address/0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4) | +| Avalanche | `30106` | [`0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47`](https://snowtrace.io/address/0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47) | + +## Bridge to Tempo + +### Using the Stargate app + +1. Go to [stargate.finance](https://stargate.finance/) +2. Select your source chain (e.g. Ethereum) and USDC +3. Set **Tempo** as the destination chain +4. Enter the amount, approve, and send + +### Using cast (Foundry) + +Bridge USDC from Base to Tempo using `cast`. This calls `sendToken` on the Stargate pool on the source chain. This example uses [taxi mode](#bus-vs-taxi-mode) for immediate delivery. + +#### Step 1 — Get a quote fee estimate from Stargate router + +Use the same parameters you will pass to `sendToken`. Returns `(nativeFee, lzTokenFee)`. +Take the first returned number as ``. + +```bash +cast call 0x27a16dc786820B16E5c9028b75B99F6f604b5d26 \ + 'quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)((uint256,uint256))' \ + "(30410,$(cast abi-encode 'f(address)' ),,,0x,0x,0x)" \ + false \ + --rpc-url https://mainnet.base.org +``` + +#### Step 2 — Approve USDC on Base + +Approve the Stargate router to spend USDC. + +```bash +cast send 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ + 'approve(address,uint256)' \ + 0x27a16dc786820B16E5c9028b75B99F6f604b5d26 \ + \ + --rpc-url https://mainnet.base.org \ + --private-key $PRIVATE_KEY +``` + +#### Step 3 — Send bridge transaction + +```bash +cast send 0x27a16dc786820B16E5c9028b75B99F6f604b5d26 \ + 'sendToken((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)' \ + "(30410,$(cast abi-encode 'f(address)' ),,,0x,0x,0x)" \ + "(,0)" \ + \ + --value \ + --rpc-url https://mainnet.base.org \ + --private-key $PRIVATE_KEY +``` + +#### Step 4 — Verify transaction status + +Track the bridge with the transaction hash returned by `sendToken`: + +```text +https://scan.layerzero-api.com/v1/messages/tx/ +``` + +Once delivered, view the destination transaction on Tempo: + +```text +https://explore.tempo.xyz/tx/ +``` + +### Using TypeScript (viem) + +```typescript +import { createWalletClient, createPublicClient, http, parseUnits, pad } from 'viem' +import { base } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('0x...') + +const walletClient = createWalletClient({ + account, + chain: base, + transport: http(), +}) + +// Stargate pool on Base +const stargatePool = '0x27a16dc786820B16E5c9028b75B99F6f604b5d26' as const +// USDC on Base +const usdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const + +const amount = parseUnits('1', 6) // 1 USDC +const minAmount = parseUnits('0.99', 6) // 1% slippage tolerance + +const sendParam = { + dstEid: 30410, // Tempo + to: pad(account.address), + amountLD: amount, + minAmountLD: minAmount, + extraOptions: '0x' as const, + composeMsg: '0x' as const, + oftCmd: '0x' as const, // taxi mode (immediate) +} + +const stargateAbi = [ + { + 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' }, + ], + }, + ], + }, + { + name: 'sendToken', + type: 'function', + stateMutability: 'payable', + 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: '_fee', + type: 'tuple', + components: [ + { name: 'nativeFee', type: 'uint256' }, + { name: 'lzTokenFee', type: 'uint256' }, + ], + }, + { name: '_refundAddress', type: 'address' }, + ], + outputs: [], + }, +] as const + +const erc20Abi = [ + { + name: 'approve', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { name: 'spender', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ type: 'bool' }], + }, +] as const + +// 1. Quote the fee +const publicClient = createPublicClient({ chain: base, transport: http() }) + +const msgFee = await publicClient.readContract({ + address: stargatePool, + abi: stargateAbi, + functionName: 'quoteSend', + args: [sendParam, false], +}) + +// 2. Approve USDC +await walletClient.writeContract({ + address: usdc, + abi: erc20Abi, + functionName: 'approve', + args: [stargatePool, amount], +}) + +// 3. Send the bridge transaction +await walletClient.writeContract({ + address: stargatePool, + abi: stargateAbi, + functionName: 'sendToken', + args: [sendParam, msgFee, account.address], + value: msgFee.nativeFee, +}) +``` + +## Bridge from Tempo + +To bridge USDC.e from Tempo back to another chain, call `sendToken` on the **StargateOFTUSDC** contract on Tempo. The process is the same — quote, approve, send — but the source contract and destination EID are swapped. + +### Using cast (Foundry) + +Bridge USDC.e from Tempo to Base using `cast`. This calls `sendToken` on the **StargateOFTUSDC** contract on Tempo. This example uses [taxi mode](#bus-vs-taxi-mode) for immediate delivery. + +#### Step 1 — Quote the fee + +Use the same parameters you will pass to `sendToken`. Returns `(nativeFee, lzTokenFee)`. +Take the first returned number as `` (in stablecoin units, not ETH). + +```bash +cast call 0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \ + 'quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)((uint256,uint256))' \ + "(30184,$(cast abi-encode 'f(address)' ),,,0x,0x,0x)" \ + false \ + --rpc-url https://rpc.tempo.xyz +``` + +#### Step 2 — Approve USDC.e on Tempo + +Approve the StargateOFTUSDC contract to spend USDC.e. + +```bash +cast send 0x20C000000000000000000000b9537d11c60E8b50 \ + 'approve(address,uint256)' \ + 0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \ + \ + --rpc-url https://rpc.tempo.xyz \ + --private-key $PRIVATE_KEY +``` + +#### Step 3 — Send bridge transaction + +No `--value` is needed on Tempo — the messaging fee is paid in a TIP-20 stablecoin via [EndpointDollar](#endpointdollar). + +```bash +cast send 0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \ + 'sendToken((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)' \ + "(30184,$(cast abi-encode 'f(address)' ),,,0x,0x,0x)" \ + "(,0)" \ + \ + --rpc-url https://rpc.tempo.xyz \ + --private-key $PRIVATE_KEY +``` + +#### Step 4 — Verify transaction status + +Track the bridge with the transaction hash returned by `sendToken`: + +```text +https://scan.layerzero-api.com/v1/messages/tx/ +``` + +Once delivered, view the destination transaction on the destination chain explorer. + +### Using TypeScript (viem) + +```typescript +import { createWalletClient, createPublicClient, http, parseUnits, pad } from 'viem' +import { tempo } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('0x...') + +const walletClient = createWalletClient({ + account, + chain: tempo, + transport: http(), +}) + +// StargateOFTUSDC on Tempo +const stargateOFT = '0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392' as const +// USDC.e on Tempo +const usdce = '0x20C000000000000000000000b9537d11c60E8b50' as const + +const amount = parseUnits('1', 6) // 1 USDC.e +const minAmount = parseUnits('0.99', 6) // 1% slippage tolerance + +const sendParam = { + dstEid: 30184, // Base + to: pad(account.address), + amountLD: amount, + minAmountLD: minAmount, + extraOptions: '0x' as const, + composeMsg: '0x' as const, + oftCmd: '0x' as const, // taxi mode (immediate) +} + +// 1. Quote the fee +const publicClient = createPublicClient({ chain: tempo, transport: http() }) + +const msgFee = await publicClient.readContract({ + address: stargateOFT, + abi: stargateAbi, // same ABI as above + functionName: 'quoteSend', + args: [sendParam, false], +}) + +// 2. Approve USDC.e +await walletClient.writeContract({ + address: usdce, + abi: erc20Abi, + functionName: 'approve', + args: [stargateOFT, amount], +}) + +// 3. Send the bridge transaction (no value — fee handled via EndpointDollar) +await walletClient.writeContract({ + address: stargateOFT, + abi: stargateAbi, + functionName: 'sendToken', + args: [sendParam, msgFee, account.address], +}) +``` + +## EndpointDollar + +Tempo has no native gas token, so there is no `msg.value`. Standard LayerZero endpoints require `msg.value` to pay messaging fees, which doesn't work on Tempo. + +**LZEndpointDollar** ([`0x0cEb237E109eE22374a567c6b09F373C73FA4cBb`](https://explore.tempo.xyz/address/0x0cEb237E109eE22374a567c6b09F373C73FA4cBb)) is an adapter contract that routes LayerZero messaging fees through a TIP-20 stablecoin instead of `msg.value`. It wraps the standard `EndpointV2` so that Stargate's OFT contracts can function on Tempo without modification. + +This is transparent for end users: + +- **Bridging to Tempo** — fees are paid in native gas on the source chain (ETH, MATIC, AVAX, etc.) as normal. +- **Bridging from Tempo** — `LZEndpointDollar` automatically deducts the messaging fee from a TIP-20 stablecoin. No `msg.value` is needed. +- **Developers** don't need to interact with `LZEndpointDollar` directly. The StargateOFTUSDC contract handles it internally. + +## Bus vs. Taxi mode + +Stargate offers two delivery modes: + +| Mode | `oftCmd` | Delivery | Cost | +|------|----------|----------|------| +| **Taxi** | `0x` (empty) | Immediate — message sent right away | Higher gas cost | +| **Bus** | `0x00` (1 byte) | Batched — waits for other passengers | Lower gas cost | + +All examples above use taxi mode for immediate delivery. To use bus mode, set `oftCmd` to `0x00`: + +```bash +# cast — bus mode +oftCmd=0x00 +``` + +```typescript +// viem — bus mode +const sendParam = { + // ... + oftCmd: '0x00' as const, // bus mode +} +``` + +:::warning +Bus mode is not available on all routes. If a bus route is not configured for your source/destination pair, the transaction will revert. Use taxi mode (`0x`) for guaranteed delivery. +::: + +## Further reading + +- [Stargate documentation](https://stargateprotocol.gitbook.io/stargate/v2-developer-docs) +- [LayerZero V2 documentation](https://docs.layerzero.network/v2) +- [Bridges & Exchanges on Tempo](/ecosystem/bridges) +- [Getting Funds on Tempo](/guide/getting-funds) diff --git a/src/pages/guide/getting-funds.mdx b/src/pages/guide/getting-funds.mdx index 28c3800..d847ad0 100644 --- a/src/pages/guide/getting-funds.mdx +++ b/src/pages/guide/getting-funds.mdx @@ -51,7 +51,7 @@ codex exec "Read https://tempo.xyz/SKILL.md and fund my Tempo Wallet" Use one of these supported bridges to move assets to Tempo: -- **[LayerZero](https://stargate.finance/)**: Bridge supported assets from other chains to Tempo. +- **[LayerZero (Stargate)](/guide/bridge-usdc-stargate)**: Bridge USDC to and from Tempo via Stargate. See the [full bridging guide](/guide/bridge-usdc-stargate) for contract addresses, code examples, and EndpointDollar details. - **[Squid](https://app.squidrouter.com/)**: Swap and bridge assets to Tempo in one flow. - **[Relay](https://relay.link/)**: Bridge assets to Tempo with low fees. - **[Across](https://app.across.to/)**: Bridge assets to Tempo quickly with competitive fees. diff --git a/vocs.config.ts b/vocs.config.ts index b7f857e..6423656 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -281,6 +281,10 @@ export default defineConfig({ text: 'Contract Verification', link: '/quickstart/verify-contracts', }, + { + text: 'Bridge USDC via Stargate', + link: '/guide/bridge-usdc-stargate', + }, { text: 'Ecosystem', collapsed: true,