Skip to content

Commit

Permalink
add eth-weth example to readme (#53)
Browse files Browse the repository at this point in the history
* add eth-weth example to readme

* tidy up

* use eth/weth example only

* edit

* show progress state machine

* small fix

* fox spelling errors
  • Loading branch information
gsteenkamp89 authored Oct 30, 2024
1 parent 1b206f7 commit 8b3a8e9
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 92 deletions.
20 changes: 8 additions & 12 deletions apps/example/lib/stake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,18 @@ export const StakerContractABI = [
export const STAKE_CONTRACT = {
address: "0x733Debf51574c70CfCdb7918F032E16F686bd9f8",
chain: optimism,
token: stakeToken,
token: {
address: "0x4200000000000000000000000000000000000006", // notice that ETH & WETH both have the same address
symbol: "ETH",
name: "Ether",
decimals: 18,
logoUrl:
"https://raw.githubusercontent.com/across-protocol/frontend/master/src/assets/token-logos/eth.svg",
},
multicallHandler: "0x924a9f036260DdD5808007E1AA95f08eD08aA569",
abi: StakerContractABI,
} as const;

export function generateStakeAction(
userAddress: Address,
value: bigint,
): CrossChainAction {
return {
target: STAKE_CONTRACT.address,
callData: generateStakeCallData(userAddress),
value,
};
}

export function generateStakeCallData(userAddress: Address) {
return encodeFunctionData({
abi: STAKE_CONTRACT.abi,
Expand Down
182 changes: 102 additions & 80 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,20 @@ const client = createAcrossClient({
Now, you can retrieve a quote for a given route.

```ts
// USDC from Arbitrum -> Optimism
import { parseEther } from "viem";

// WETH from Arbitrum -> Optimism
const route = {
originChainId: arbitrum.id,
destinationChainId: optimism.id,
inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
outputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
inputToken: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH arb
outputToken: "0x4200000000000000000000000000000000000006", // WETH opt
};

const quote = await client.getQuote({
route,
inputAmount: parseUnits("1000", 6) // USDC decimals
})
inputAmount: parseEther("1"),
});
```

Note that we provide additional utilities for retrieving available routes, chain details, and token infos.
Expand All @@ -94,7 +97,19 @@ await client.executeQuote({
walletClient: wallet,
deposit: quote.deposit, // returned by `getQuote`
onProgress: (progress) => {
// handle progress
if (progress.step === "approve" && progress.status === "txSuccess") {
// if approving an ERC20, you have access to the approval receipt
const { txReceipt } = progress;
}
if (progress.step === "deposit" && progress.status === "txSuccess") {
// once deposit is successful you have access to depositId and the receipt
const { depositId, txReceipt } = progress;
}
if (progress.step === "fill" && progress.status === "txSuccess") {
// if the fill is successful, you have access the following data
const { fillTxTimestamp, txReceipt, actionSuccess } = progress;
// actionSuccess is a boolean flag, telling us if your cross chain messages were successful
}
},
});
```
Expand All @@ -112,86 +127,90 @@ Have a look at our [example app](../../apps/example) for a more detailed usage o

Across enables users to seamlessly interact with your dApp or chain using assets from other chains.

### 1. Craft a cross-chain message
### Example: Stake Native ETH on Destination Chain

To implement this feature, you first need to specify a `crossChainMessage`.
The example below shows a cross-chain message for staking USDC into a contract deployed
on Optimism by:
To stake native ETH in one step:

1. Approve USDC to be pulled into staking
2. Stake approved amount into contract
1. Bridge WETH to the destination chain, sending the output tokens (WETH) to Across's MulticallHandler contract (since contracts receive WETH).
2. Unwrap WETH to obtain native ETH.
3. Stake the ETH on your staking contract.

```ts
// Example staking contract on Optimism
const stakingContractAddress = "0x733Debf51574c70CfCdb7918F032E16F686bd9f8";
// USDC on Optimism
const usdcAddress = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85";
// Example user address
const userAddress = "0x924a9f036260DdD5808007E1AA95f08eD08aA569a";
const inputAmount = parseUnits("200", 6);

const crossChainMessage = {
// This address will receive the amount of bridged tokens on the destination chain if
// one of the cross-chain actions fail. Leftover tokens are here as well if all actions
// succeed.
fallbackRecipient: userAddress,
// List of actions that should be executed on the destination chain
actions: [
{
// Address of target contract on destination chain, i.e. USDC on Optimism
target: usdcAddress,
// Encoded function data, i.e. calldata for approving USDC to be pulled in by
// staking contract
callData: generateApproveCallData(
stakingContractAddress,
inputAmount
),
// Native msg.value, can be 0 in the context of USDC
value: 0n,
// Update call data callback - we need to provide a callback function to
// re-generate calldata because it depends on the `outputAmount`, i.e.
// `inputAmount` - `relayer fee`. This is the amount the user has available after a
// relayer filled the deposit on the destination chain.
updateCallData: (outputAmount) => generateApproveCallData(
stakingContractAddress,
outputAmount
)
},
{
// Address of target contract on destination chain, i.e. staking contract
// on Optimism
target: stakingContractAddress,
// Encoded function data, i.e. calldata for staking USDC on behalf of user
callData: generateStakeCallData(
userAddress,
inputAmount
)
// Native msg.value, can be 0 in the context of USDC
value: 0n,
// Same reasoning as above in the approve step.
updateCallData: (outputAmount) => generateStakeCallData(
stakingContractAddress,
outputAmount
)
}
]
}
**Note**: If your calldata depends on the output amount, you must use the `update()` function.

function generateApproveCallData(spender: Address, amount: bigint) {
const approveCallData = encodeFunctionData({
abi: [parseAbiItem("function approve(address spender, uint256 value)")],
args: [spender, amount],
});
### 1. Craft a cross-chain message

return approveCallData;
```ts
import { type Amount } from "@across-protocol/app-sdk";
import { encodeFunctionData } from "viem";
import { optimism } from "viem/chains";

// constants
const userAddress = "0xFoo";
const multicallHandlerOptimism = "0x924a9f036260DdD5808007E1AA95f08eD08aA569";

export const WETH_OPTIMISM = {
chain: optimism,
abi: WethAbi,
address: "0x4200000000000000000000000000000000000006",
decimals: 18,
logoUrl:
"https://raw.githubusercontent.com/across-protocol/frontend/master/src/assets/token-logos/weth.svg",
name: "Wrapped Ether",
symbol: "WETH",
} as const;

export const STAKE_CONTRACT = {
address: "0x733Debf51574c70CfCdb7918F032E16F686bd9f8",
chain: optimism,
abi: StakerContractABI,
} as const;

// WETH unwrap action
function generateUnwrapCallData(wethAmount: Amount) {
return encodeFunctionData({
abi: WETH_OPTIMISM.abi,
functionName: "withdraw",
args: [BigInt(wethAmount)],
});
}

function generateStakeCallData(userAddress: Address, amount: bigint) {
// STAKE action
function generateStakeCallData(userAddress: Address) {
return encodeFunctionData({
abi: [parseAbiItem("function stake(address stakerAddress, uint256 amount)")],
args: [userAddress, amount],
abi: STAKE_CONTRACT.abi,
functionName: "stake",
args: [userAddress],
});
}

const unwrapAndStakeMessage = {
actions: [
{
target: WETH_OPTIMISM.address,
callData: generateUnwrapCallData(inputAmount),
value: 0n,
// we only update the calldata since the unwrap call is non-payable, but we DO care about the output amount.
update: (updatedOutputAmount) => {
return {
callData: generateUnwrapCallData(updatedOutputAmount),
};
},
},
{
target: STAKE_CONTRACT.address,
callData: generateStakeCallData(userAddress),
// 🔔 the initial value may be set equal to the output amount. This MUST be updated via the `update()` function below oir this call will fail!
value: inputAmount,
// now we MUST update msg.value since this last call is calling a payable function.
update: (updatedOutputAmount) => {
return {
value: updatedOutputAmount,
};
},
},
],
fallbackRecipient: userAddress,
};
```

### 2. Retrieve a quote
Expand All @@ -202,13 +221,16 @@ After specifying a cross-chain message, you simply can fetch a quote the same wa
const route = {
originChainId: arbitrum.id,
destinationChainId: optimism.id,
inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
outputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
inputToken: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH arbitrum
outputToken: "0x4200000000000000000000000000000000000006", // WETH optimism
};

const quote = await client.getQuote({
route,
inputAmount,
crossChainMessage // created above
inputAmount: parseEther("1"),
// 🔔 Notice the recipient is not the staking contract itself or even the user, but the contract that will execute our cross chain messages
recipient: multicallHandlerOptimism,
crossChainMessage: unwrapAndStakeMessage,
});
```

Expand Down

0 comments on commit 8b3a8e9

Please sign in to comment.