Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 39 additions & 123 deletions src/pages/guide/bridge-layerzero.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,7 @@ await walletClient.writeContract({

## Bridge from Tempo

To bridge from Tempo back to another chain, call `sendToken` on the Stargate OFT contract on Tempo. The process is similar to bridging in - quote, approve, send - but includes additional steps to prepare the messaging fee.

Because Tempo has no native gas token, LayerZero messaging fees are paid in a TIP-20 stablecoin via [LZEndpointDollar](#endpointdollar). Before sending a bridge transaction, you must wrap your USDC.e into an LZD (LayerZero Dollar) token that the endpoint can consume as a fee. This involves approving USDC.e to the LZD wrapper contract, wrapping it, and then approving the resulting LZD to the Stargate pool.
Use [`TempoOFTWrapper`](https://explore.tempo.xyz/address/0xbb95daF376cd63F258d7c37a4eFe57c10055E8E0) (`0xbb95daF376cd63F258d7c37a4eFe57c10055E8E0`). It pulls your stablecoin, wraps the fee portion into LZD, and calls `send()` on the OFT in a single transaction. Pay fees in `USDC.e`, `USDT0`, or `pathUSD`.

#### Using cast (Foundry)

Expand All @@ -280,181 +278,99 @@ cast call 0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \
--rpc-url https://rpc.tempo.xyz
```

Take the first returned number as `<NATIVE_FEE>` (in stablecoin units, not ETH).

### Approve USDC.e to the LZD wrapper
The first returned number is `<NATIVE_FEE>` in stablecoin units.

Approve the `LZEndpointDollar` wrapper contract to spend `<NATIVE_FEE>` of your USDC.e. This is the amount needed to cover the LayerZero messaging fee.
### Approve USDC.e to the wrapper

```bash
cast send 0x20C000000000000000000000b9537d11c60E8b50 \
"approve(address,uint256)" \
0x0cEb237E109eE22374a567c6b09F373C73FA4cBb \
<NATIVE_FEE> \
--rpc-url https://rpc.tempo.xyz \
--private-key $PRIVATE_KEY
```

### Wrap USDC.e into LZD

Wrap your USDC.e into the LZD token so it can be used as a messaging fee by the LayerZero endpoint.

```bash
cast send 0x0cEb237E109eE22374a567c6b09F373C73FA4cBb \
"wrap(address,address,uint256)" \
0x20C000000000000000000000b9537d11c60E8b50 \
<WALLET_ADDRESS> \
<NATIVE_FEE> \
'approve(address,uint256)' \
0xbb95daF376cd63F258d7c37a4eFe57c10055E8E0 \
$((<AMOUNT> + <NATIVE_FEE>)) \
--rpc-url https://rpc.tempo.xyz \
--private-key $PRIVATE_KEY
```

### Approve LZD to Stargate
One approval covers both the bridge amount and the fee since they share the same token. Use a max approval if you bridge frequently.

Approve the Stargate OFT contract to spend your LZD so it can pay the messaging fee when sending.
### Send

```bash
cast send 0x0cEb237E109eE22374a567c6b09F373C73FA4cBb \
"approve(address,uint256)" \
cast send 0xbb95daF376cd63F258d7c37a4eFe57c10055E8E0 \
'sendOFT(address,address,(uint32,bytes32,uint256,uint256,bytes,bytes,bytes),uint256)' \
0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \
0x20C000000000000000000000b9537d11c60E8b50 \
"(30184,$(cast abi-encode 'f(address)' <DESTINATION_ADDRESS>),<AMOUNT>,<MIN_AMOUNT>,0x,0x,0x)" \
<NATIVE_FEE> \
--rpc-url https://rpc.tempo.xyz \
--private-key $PRIVATE_KEY
```

### Approve token on Tempo

```bash
cast send 0x20C000000000000000000000b9537d11c60E8b50 \
'approve(address,uint256)' \
0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392 \
<AMOUNT> \
--rpc-url https://rpc.tempo.xyz \
--private-key $PRIVATE_KEY
```

### 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)' <DESTINATION_ADDRESS>),<AMOUNT>,<MIN_AMOUNT>,0x,0x,0x)" \
"(<NATIVE_FEE>,0)" \
<TEMPO_ADDRESS> \
--rpc-url https://rpc.tempo.xyz \
--private-key $PRIVATE_KEY
```
`<NATIVE_FEE>` here is `maxNativeFee` — the call reverts if the fee at execution exceeds this.

### Verify transaction status

Track delivery to the destination chain via the LayerZero scan API:

```text
https://scan.layerzero-api.com/v1/messages/tx/<SOURCE_TX_HASH>
https://scan.layerzero-api.com/v1/messages/tx/<TX_HASH>
```

:::::

#### Using TypeScript (viem)

```typescript
import { createWalletClient, createPublicClient, http, parseUnits, pad } from 'viem'
import { createWalletClient, createPublicClient, http, parseUnits, pad, parseAbi } from 'viem'
import { tempo } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({ account, chain: tempo, transport: http() })
const publicClient = createPublicClient({ chain: tempo, transport: http() })

const walletClient = createWalletClient({
account,
chain: tempo,
transport: http(),
})

// Stargate OFT for USDC.e on Tempo
const stargateOFT = '0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392' as const
// USDC.e on Tempo
const oft = '0x8c76e2F6C5ceDA9AA7772e7efF30280226c44392' as const // Stargate USDC.e
const usdce = '0x20C000000000000000000000b9537d11c60E8b50' as const
// LZEndpointDollar wrapper
const lzd = '0x0cEb237E109eE22374a567c6b09F373C73FA4cBb' as const

const amount = parseUnits('1', 6) // 1 USDC.e
const minAmount = parseUnits('0.99', 6) // 1% slippage tolerance
const wrapper = '0xbb95daF376cd63F258d7c37a4eFe57c10055E8E0' as const

const amount = parseUnits('1', 6)
const minAmount = parseUnits('0.99', 6)
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)
oftCmd: '0x' as const,
}

const wrapAbi = [
{
name: 'wrap',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'token', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [],
},
] as const

// 1. Quote the fee
const publicClient = createPublicClient({ chain: tempo, transport: http() })
const wrapperAbi = parseAbi([
'function sendOFT(address oft, address feeToken, (uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, uint256 maxNativeFee)',
])

// 1. Quote
const msgFee = await publicClient.readContract({
address: stargateOFT,
abi: stargateAbi, // same ABI as above
functionName: 'quoteSend',
args: [sendParam, false],
address: oft, abi: stargateAbi, functionName: 'quoteSend', args: [sendParam, false],
})

// 2. Approve USDC.e to LZD wrapper (for the messaging fee)
// 2. Approve USDC.e to wrapper (bridge amount + fee)
await walletClient.writeContract({
address: usdce,
abi: erc20Abi,
functionName: 'approve',
args: [lzd, msgFee.nativeFee],
})

// 3. Wrap USDC.e into LZD
await walletClient.writeContract({
address: lzd,
abi: wrapAbi,
functionName: 'wrap',
args: [usdce, account.address, msgFee.nativeFee],
})

// 4. Approve LZD to Stargate (for the messaging fee)
await walletClient.writeContract({
address: lzd,
abi: erc20Abi,
functionName: 'approve',
args: [stargateOFT, msgFee.nativeFee],
})

// 5. Approve USDC.e to Stargate (for the bridge amount)
await walletClient.writeContract({
address: usdce,
abi: erc20Abi,
functionName: 'approve',
args: [stargateOFT, amount],
address: usdce, abi: erc20Abi, functionName: 'approve',
args: [wrapper, amount + msgFee.nativeFee],
})

// 6. Send the bridge transaction (no value - fee handled via EndpointDollar)
// 3. Send (wrap + approve + send happen atomically inside)
await walletClient.writeContract({
address: stargateOFT,
abi: stargateAbi,
functionName: 'sendToken',
args: [sendParam, msgFee, account.address],
address: wrapper, abi: wrapperAbi, functionName: 'sendOFT',
args: [oft, usdce, sendParam, msgFee.nativeFee],
})
```

:::warning
**Do not use the wrapper for compose messages that refund `msg.sender`.** Refunds go to the wrapper, not your wallet, and are lost. For that case call the OFT directly — see [LayerZero's direct flow](https://docs.layerzero.network/v2/developers/tempo/how-to/support-ofts-and-oapps#direct-flow-1-view-call-5-transactions).
:::

### Bus vs. Taxi mode

Stargate offers two delivery modes:
Expand Down
Loading