-
Notifications
You must be signed in to change notification settings - Fork 2
feat: socket cctp, across scripts #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sebastiantf
wants to merge
18
commits into
anxolin:main
Choose a base branch
from
SocketDotTech:feat/cow-socket-gateway-hook
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
7f46ecb
feat: socket cctp, across scripts
sebastiantf 51acacf
chore: logs
sebastiantf 9a1d85d
chore: add comments
sebastiantf e6acf55
chore: use token symbols in log
sebastiantf b16b3ff
feat: add verifier validation
sebastiantf 4aa6d66
fix: use sourceToken
sebastiantf f797030
chore: logs
sebastiantf 0518773
refactor: match both quotes
sebastiantf ac20b9b
chore: match both scripts
sebastiantf a9371ef
refactor: includeBridges → useBridge
sebastiantf 4ff5128
chore: log quotes amounts
sebastiantf 10eb739
feat: update outputAmount for across
sebastiantf 2aac8fb
fix: callStatic validation
sebastiantf 3436c8f
feat: add socketscan status api
sebastiantf 0456f3f
Merge branch 'main' into feat/cow-socket-gateway-hook
sebastiantf 76e2cb7
fix: merge changes from main
sebastiantf cc26cd1
chore: use USDC.e
sebastiantf 24a79b0
refactor: should apply any pct diff
sebastiantf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export const USDC_ADDRESS = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"; | ||
| export const USDT_ADDRESS = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"; | ||
| export const USDC_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; | ||
| export const USDT_ADDRESS = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'; | ||
| export const USDCe_ADDRESS = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| export const WETH_ADDRESS = "0x4200000000000000000000000000000000000006"; | ||
| export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| import { | ||
| createWeirollContract, | ||
| createWeirollDelegateCall, | ||
| EvmCall, | ||
| SupportedChainId, | ||
| WeirollCommandFlags, | ||
| } from '@cowprotocol/cow-sdk'; | ||
| import { Planner as WeirollPlanner } from '@weiroll/weiroll.js'; | ||
| import { ethers } from 'ethers'; | ||
| import { getWallet } from '../../utils'; | ||
| import { getCowShedAccount } from '../cowShed'; | ||
| import { getErc20Contract } from '../erc20'; | ||
| import { bungeeCowswapLibAbi, socketGatewayAbi, SocketRequest } from './types'; | ||
| import { | ||
| BungeeCowswapLibAddresses, | ||
| BungeeTxDataIndices, | ||
| decodeAmountsBungeeTxData, | ||
| decodeBungeeTxData, | ||
| getBungeeQuote, | ||
| getBungeeRouteTransactionData, | ||
| socketBridgeFunctionSignatures, | ||
| verifyBungeeTxData, | ||
| } from './utils'; | ||
|
|
||
| export interface BridgeWithBungeeParams { | ||
| owner: string; | ||
| sourceChain: SupportedChainId; | ||
| sourceToken: string; | ||
| sourceTokenAmount: bigint; | ||
| targetToken: string; | ||
| targetChain: number; | ||
| recipient: string; | ||
| useBridge: 'cctp' | 'across'; | ||
| } | ||
|
|
||
| export async function bridgeWithBungee( | ||
| params: BridgeWithBungeeParams | ||
| ): Promise<EvmCall> { | ||
| const { | ||
| owner, | ||
| sourceChain, | ||
| sourceToken, | ||
| sourceTokenAmount, | ||
| targetChain, | ||
| targetToken, | ||
| recipient, | ||
| useBridge, | ||
| } = params; | ||
|
|
||
| // Get cow-shed account | ||
| const cowShedAccount = getCowShedAccount(sourceChain, owner); | ||
|
|
||
| const planner = new WeirollPlanner(); | ||
|
|
||
| // Get bungee quote | ||
| const quote = await getBungeeQuote({ | ||
| fromChainId: sourceChain.toString(), | ||
| fromTokenAddress: sourceToken, | ||
| toChainId: targetChain.toString(), | ||
| toTokenAddress: targetToken, | ||
| fromAmount: sourceTokenAmount.toString(), | ||
| userAddress: cowShedAccount, // bridge input token will be in cowshed account | ||
| recipient: recipient, | ||
| sort: 'output', // optimize for output amount | ||
| singleTxOnly: true, // should be only single txn on src chain, no destination chain txn | ||
| isContractCall: true, // get quotes that are compatible with contracts | ||
| disableSwapping: true, // should not show routes that require swapping | ||
| includeBridges: [useBridge], | ||
| }); | ||
| if (!quote) { | ||
| throw new Error('No quote found'); | ||
| } | ||
| console.log('🔗 Socket quote:', quote.result.routes); | ||
| // check if routes are found | ||
| if (!quote.result.routes.length) { | ||
| throw new Error('No routes found'); | ||
| } | ||
| // check if only single user tx is present | ||
| if (quote.result.routes[0].userTxs.length > 1) { | ||
| throw new Error('Multiple user txs found'); | ||
| } | ||
| // check if the user tx is fund-movr | ||
| if (quote.result.routes[0].userTxs[0].userTxType !== 'fund-movr') { | ||
| throw new Error('User tx is not fund-movr'); | ||
| } | ||
|
|
||
| // use the first route to prepare the bridge tx | ||
| const route = quote.result.routes[0]; | ||
| const txData = await getBungeeRouteTransactionData(route); | ||
| const { routeId, encodedFunctionData } = decodeBungeeTxData( | ||
| txData.result.txData | ||
| ); | ||
| console.log('🔗 Socket txData:', txData.result.txData); | ||
| console.log('🔗 Socket routeId:', routeId); | ||
|
|
||
| // validate bungee tx data returned from socket API using SocketVerifier contract | ||
| const expectedSocketRequest: SocketRequest = { | ||
| amount: route.fromAmount, | ||
| recipient: route.recipient, | ||
| toChainId: targetChain.toString(), | ||
| token: sourceToken, | ||
| signature: socketBridgeFunctionSignatures[useBridge], | ||
| }; | ||
| await verifyBungeeTxData( | ||
| sourceChain, | ||
| txData.result.txData, | ||
| routeId, | ||
| expectedSocketRequest | ||
| ); | ||
|
|
||
| // Create bridged token contract | ||
| const bridgedTokenContract = createWeirollContract( | ||
| getErc20Contract(sourceToken), | ||
| WeirollCommandFlags.CALL | ||
| ); | ||
|
|
||
| // Get balance of CoW shed proxy | ||
| console.log( | ||
| `[socket] Get cow-shed balance for ERC20.balanceOf(${cowShedAccount}) for ${bridgedTokenContract.address}` | ||
| ); | ||
|
|
||
| // Check & set allowance for SocketGateway to transfer bridged tokens | ||
| // check if allowance is sufficient | ||
| let setAllowance = false; | ||
| const { | ||
| approvalData: { | ||
| approvalTokenAddress, | ||
| allowanceTarget, | ||
| minimumApprovalAmount, | ||
| }, | ||
| } = txData.result; | ||
| const intermediateTokenContract = getErc20Contract( | ||
| approvalTokenAddress, | ||
| await getWallet(sourceChain) | ||
| ); | ||
| const allowance = await intermediateTokenContract.allowance( | ||
| cowShedAccount, | ||
| allowanceTarget | ||
| ); | ||
| console.log('current cowshed allowance', allowance); | ||
| if (allowance < minimumApprovalAmount) { | ||
| setAllowance = true; | ||
| } | ||
|
|
||
| // set allowance | ||
| const approvalTokenContract = createWeirollContract( | ||
| getErc20Contract(approvalTokenAddress), | ||
| WeirollCommandFlags.CALL | ||
| ); | ||
|
|
||
| const allowanceToSet = ethers.utils.parseUnits( | ||
| '1000', | ||
| await intermediateTokenContract.decimals() | ||
| ); | ||
|
|
||
| const bridgeDepositCall = createWeirollDelegateCall((planner) => { | ||
| // Get bridged amount (balance of the intermediate token at swap time) | ||
| const sourceAmountIncludingSurplusBytes = planner.add( | ||
| bridgedTokenContract.balanceOf(cowShedAccount).rawValue() | ||
| ); | ||
|
|
||
| if (setAllowance) { | ||
| planner.add( | ||
| approvalTokenContract.approve(allowanceTarget, allowanceToSet) | ||
| ); | ||
| } | ||
|
|
||
| const BungeeCowswapLibContractAddress = | ||
| BungeeCowswapLibAddresses[sourceChain]; | ||
| if (!BungeeCowswapLibContractAddress) { | ||
| throw new Error('BungeeCowswapLib contract not found'); | ||
| } | ||
| const BungeeCowswapLibContract = createWeirollContract( | ||
| new ethers.Contract(BungeeCowswapLibContractAddress, bungeeCowswapLibAbi), | ||
| WeirollCommandFlags.CALL | ||
| ); | ||
|
|
||
| // weiroll: replace input amount with new input amount | ||
| const encodedFunctionDataWithNewInputAmount = planner.add( | ||
| BungeeCowswapLibContract.replaceBytes( | ||
| encodedFunctionData, | ||
| BungeeTxDataIndices[useBridge].inputAmountBytes_startIndex, | ||
| BungeeTxDataIndices[useBridge].inputAmountBytes_length, | ||
| sourceAmountIncludingSurplusBytes | ||
| ) | ||
| ); | ||
| let finalEncodedFunctionData = encodedFunctionDataWithNewInputAmount; | ||
|
|
||
| // if bridge is across, update the output amount based on pctDiff of the new balance | ||
| if (useBridge === 'across') { | ||
| // decode current input & output amounts | ||
| const { inputAmountBigNumber, outputAmountBigNumber } = | ||
| decodeAmountsBungeeTxData(encodedFunctionData, useBridge); | ||
| console.log('🔗 Socket input & output amounts:', { | ||
| inputAmountBigNumber: inputAmountBigNumber.toString(), | ||
| outputAmountBigNumber: outputAmountBigNumber.toString(), | ||
| }); | ||
|
|
||
| // new input amount | ||
| const newInputAmount = sourceAmountIncludingSurplusBytes; | ||
|
|
||
| // weiroll: increase output amount by pctDiff | ||
| const newOutputAmount = planner.add( | ||
| BungeeCowswapLibContract.applyPctDiff( | ||
| inputAmountBigNumber, // base | ||
| newInputAmount, // compare | ||
| outputAmountBigNumber // target | ||
| ).rawValue() | ||
| ); | ||
| // weiroll: replace output amount bytes with newOutputAmount | ||
| const encodedFunctionDataWithNewInputAndOutputAmount = planner.add( | ||
| BungeeCowswapLibContract.replaceBytes( | ||
| finalEncodedFunctionData, | ||
| BungeeTxDataIndices[useBridge].outputAmountBytes_startIndex!, | ||
| BungeeTxDataIndices[useBridge].outputAmountBytes_length!, | ||
| newOutputAmount | ||
| ) | ||
| ); | ||
| finalEncodedFunctionData = encodedFunctionDataWithNewInputAndOutputAmount; | ||
| } | ||
|
|
||
| const socketGatewayContract = createWeirollContract( | ||
| new ethers.Contract(txData.result.txTarget, socketGatewayAbi), | ||
| WeirollCommandFlags.CALL | ||
| ); | ||
| // Call executeRoute on SocketGateway | ||
| planner.add( | ||
| socketGatewayContract.executeRoute(routeId, finalEncodedFunctionData) | ||
| ); | ||
| }); | ||
|
|
||
| // Return the transaction | ||
| return bridgeDepositCall; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we be able to verify this data to guarantee we don't ask users to blind-sign on something returned from the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've added a calldata verification step in our PoC script
Basically we have a verifier contract which you could input the encoded calldata received from our backend and the expected values for key params like the input amount, recipient, destination chain id, token contract etc. and the verifier will validate if the encoded calldata does match the expected values.
You'll find it implemented in this commit: b16b3ff
These are the relevant verified contracts:
SocketVerifier: https://arbiscan.io/address/0x69D9f76e4cbE81044FE16C399387b12e4DBF27B1#code
AcrossV3Verification: https://arbiscan.io/address/0x2493Ac43A301d0217abAD6Ff320C5234aEe0931d#code
CCTPVerification: https://arbiscan.io/address/0xF58Db19f264359D6687b5693ee516bf316BE3Ba6#code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us know if this helps the verifiability of the bridge txn calldata
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great, thank you!
BTW, there's a typo in this contract function:
validateRotueIdBetter to fix it while it still in a single chain IMO :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually
SocketVerifieris already deployed on a few chains like arbitrum, optimism, polygon at the same address for a few other bridges and actively used for similar purposesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see! I guess I'll have to fight with my OCD and accept
rotuethen 😅