-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1482 from Archethect/feature/squid-bridge
feat: Add cross chain swaps through Squid Router
- Loading branch information
Showing
15 changed files
with
852 additions
and
3 deletions.
There are no files selected for viewing
This file contains 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 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 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 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,26 @@ | ||
# @elizaos/squid-router | ||
|
||
This plugin adds Squid Router functionality to Eliza agents. It allows cross chain swaps between blockchains. | ||
For now, only swaps beteen EVM chains are supported, but the plan is to add swaps from/to Solana and the Cosomos ecosystem. | ||
For supported chains and tokens, please refer to the [Squid Router documentation](https://docs.squidrouter.com/). | ||
|
||
## Configuration | ||
|
||
The plugin requires the following configuration: | ||
``` | ||
# Squid Router | ||
SQUID_SDK_URL=https://apiplus.squidrouter.com # Default: https://apiplus.squidrouter.com | ||
SQUID_INTEGRATOR_ID= # get integrator id through https://docs.squidrouter.com/ | ||
SQUID_EVM_ADDRESS= | ||
SQUID_EVM_PRIVATE_KEY= | ||
``` | ||
|
||
## Actions | ||
|
||
### Cross Chain Swap | ||
|
||
name: `X_CHAIN_SWAP` | ||
|
||
Perform cross chain swaps for both native and ERC20 tokens supported by Squid Router. | ||
|
||
Message sample: `Bridge 1 ETH from Ethereum to Base` |
This file contains 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,3 @@ | ||
import eslintGlobalConfig from "../../eslint.config.mjs"; | ||
|
||
export default [...eslintGlobalConfig]; |
This file contains 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,25 @@ | ||
{ | ||
"name": "@elizaos/plugin-squid-router", | ||
"version": "0.1.7-alpha.1", | ||
"main": "dist/index.js", | ||
"type": "module", | ||
"types": "dist/index.d.ts", | ||
"dependencies": { | ||
"@0xsquid/sdk": "2.8.29", | ||
"@0xsquid/squid-types": "0.1.122", | ||
"@elizaos/core": "workspace:*", | ||
"ethers": "6.8.1", | ||
"optional": "0.1.4", | ||
"sharp": "0.33.5", | ||
"tsup": "8.3.5" | ||
}, | ||
"scripts": { | ||
"build": "tsup --format esm --dts", | ||
"dev": "tsup --format esm --dts --watch", | ||
"test": "vitest run", | ||
"lint": "eslint --fix --cache ." | ||
}, | ||
"peerDependencies": { | ||
"whatwg-url": "7.1.0" | ||
} | ||
} |
This file contains 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,262 @@ | ||
import { | ||
composeContext, | ||
elizaLogger, | ||
generateObjectDeprecated, | ||
HandlerCallback, | ||
IAgentRuntime, | ||
Memory, | ||
ModelClass, | ||
State | ||
} from "@elizaos/core"; | ||
import {xChainSwapTemplate} from "../templates"; | ||
import {convertToWei, isXChainSwapContent, validateSquidRouterConfig} from "../helpers/utils.ts"; | ||
import {ethers} from "ethers"; | ||
import {initSquidRouterProvider} from "../providers/squidRouter.ts"; | ||
|
||
export { xChainSwapTemplate }; | ||
|
||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); | ||
|
||
const approveSpending = async (transactionRequestTarget: string, fromToken: string, fromAmount: string, signer: ethers.Signer) => { | ||
const erc20Abi = [ | ||
"function approve(address spender, uint256 amount) public returns (bool)" | ||
]; | ||
const tokenContract = new ethers.Contract(fromToken, erc20Abi, signer); | ||
try { | ||
const tx = await tokenContract.approve(transactionRequestTarget, fromAmount); | ||
await tx.wait(); | ||
console.log(`Approved ${fromAmount} tokens for ${transactionRequestTarget}`); | ||
} catch (error) { | ||
console.error('Approval failed:', error); | ||
throw error; | ||
} | ||
}; | ||
|
||
export const xChainSwapAction = { | ||
name: "X_CHAIN_SWAP", | ||
description: "Swaps tokens across chains from the agent's wallet to a recipient wallet. \n"+ | ||
"By default the senders configured wallets will be used to send the assets to on the destination chains, unless clearly defined otherwise by providing a recipient address.\n" + | ||
"The system supports bridging, cross chain swaps and normal swaps.", | ||
handler: async ( | ||
runtime: IAgentRuntime, | ||
message: Memory, | ||
state: State, | ||
_options: { [key: string]: unknown }, | ||
callback?: HandlerCallback | ||
): Promise<boolean> => { | ||
elizaLogger.log("Starting X_CHAIN_SWAP handler..."); | ||
|
||
// Initialize or update state | ||
if (!state) { | ||
state = (await runtime.composeState(message)) as State; | ||
} else { | ||
state = await runtime.updateRecentMessageState(state); | ||
} | ||
|
||
// Compose X chain swap context | ||
const xChainSwapContext = composeContext({ | ||
state, | ||
template: xChainSwapTemplate, | ||
}); | ||
|
||
// Generate X chain swap content | ||
const content = await generateObjectDeprecated({ | ||
runtime, | ||
context: xChainSwapContext, | ||
modelClass: ModelClass.SMALL, | ||
}); | ||
|
||
if(content.toAddress === null) { | ||
content.toAddress = runtime.getSetting("SQUID_EVM_ADDRESS"); | ||
} | ||
|
||
elizaLogger.log("swap content: ",JSON.stringify(content)); | ||
|
||
// Validate transfer content | ||
if (!isXChainSwapContent(content)) { | ||
console.error("Invalid content for X_CHAIN_SWAP action."); | ||
if (callback) { | ||
callback({ | ||
text: "Unable to process cross-chain swap request. Invalid content provided.", | ||
content: { error: "Invalid cross-chain swap content" }, | ||
}); | ||
} | ||
return false; | ||
} | ||
|
||
try { | ||
|
||
const squidRouter = initSquidRouterProvider(runtime); | ||
await squidRouter.initialize(); | ||
console.log("Initialized Squid SDK"); | ||
|
||
const fromChainObject = squidRouter.getChain(content.fromChain); | ||
if(!fromChainObject) { | ||
throw new Error( | ||
"Chain to swap from is not supported." | ||
); | ||
} | ||
|
||
const toChainObject = squidRouter.getChain(content.toChain); | ||
if(!toChainObject) { | ||
throw new Error( | ||
"Chain to swap to is not supported." | ||
); | ||
} | ||
|
||
const fromTokenObject = squidRouter.getToken(fromChainObject, content.fromToken); | ||
if(!fromTokenObject?.enabled) { | ||
throw new Error( | ||
"Token to swap from is not supported." | ||
); | ||
} | ||
|
||
const toTokenObject = squidRouter.getToken(toChainObject, content.toToken); | ||
if(!fromTokenObject?.enabled) { | ||
throw new Error( | ||
"Token to swap into is not supported." | ||
); | ||
} | ||
|
||
const signer = await squidRouter.getEVMSignerForChain(fromChainObject, runtime); | ||
|
||
const params = { | ||
fromAddress: await signer.getAddress(), | ||
fromChain: fromChainObject.chainId, | ||
fromToken: fromTokenObject.address, | ||
fromAmount: convertToWei(content.amount, fromTokenObject), | ||
toChain: toChainObject.chainId, | ||
toToken: toTokenObject.address, | ||
toAddress: content.toAddress, | ||
quoteOnly: false | ||
}; | ||
|
||
console.log("Parameters:", params); // Printing the parameters for QA | ||
|
||
const throttleInterval = runtime.getSetting("SQUID_API_THROTTLE_INTERVAL") ? Number(runtime.getSetting("SQUID_API_THROTTLE_INTERVAL")) : 0 | ||
|
||
await delay(throttleInterval); | ||
|
||
// Get the swap route using Squid SDK | ||
const {route} = await squidRouter.getRoute(params); | ||
console.log("Calculated route:", route.estimate.toAmount); | ||
|
||
const transactionRequest = route.transactionRequest; | ||
|
||
// Approve the transactionRequest.target to spend fromAmount of fromToken | ||
if ("target" in transactionRequest) { | ||
if(!fromTokenObject.isNative) { | ||
await approveSpending(transactionRequest.target, params.fromToken, params.fromAmount, signer); | ||
} | ||
} else { | ||
throw new Error( | ||
"Non-expected transaction request" | ||
); | ||
} | ||
|
||
await delay(throttleInterval); | ||
|
||
// Execute the swap transaction | ||
const tx = (await squidRouter.executeRoute({ | ||
signer, | ||
route, | ||
})) as unknown as ethers.TransactionResponse; | ||
const txReceipt = await tx.wait(); | ||
|
||
// Show the transaction receipt with Axelarscan link | ||
const axelarScanLink = "https://axelarscan.io/gmp/" + txReceipt.hash; | ||
elizaLogger.log(`Finished! Check Axelarscan for details: ${axelarScanLink}`); | ||
|
||
if (callback) { | ||
callback({ | ||
text: | ||
"Swap completed successfully! Check Axelarscan for details:\n " + axelarScanLink, | ||
content: {}, | ||
}); | ||
} | ||
|
||
|
||
|
||
} catch (error) { | ||
elizaLogger.error("Error during cross-chain swap:", error); | ||
if (callback) { | ||
callback({ | ||
text: `Error during cross-chain swap: ${error.message}`, | ||
content: { error: error.message }, | ||
}); | ||
} | ||
return false; | ||
} | ||
}, | ||
template: xChainSwapTemplate, | ||
validate: async (runtime: IAgentRuntime) => { | ||
await validateSquidRouterConfig(runtime); | ||
return true; | ||
}, | ||
examples: [ | ||
[ | ||
{ | ||
user: "{{user1}}", | ||
content: { | ||
text: "Bridge 1 ETH from Ethereum to Base", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Sure, I'll send 1 ETH from Ethereum to Base", | ||
action: "X_CHAIN_SWAP", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Successfully sent 1 ETH to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62 on Base\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec", | ||
}, | ||
}, | ||
], | ||
[ | ||
{ | ||
user: "{{user1}}", | ||
content: { | ||
text: "Please swap 1 SOL into USDC from Solana to Base on address 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Sure, I'll swap 1 SOL into USDC from Solana to Base on address 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8", | ||
action: "X_CHAIN_SWAP", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Successfully Swapped 1 SOL into USDC and sent to 0xF43042865f4D3B32A19ECBD1C7d4d924613c41E8 on Base\nTransaction: 2sj3ifA5iPdRDfnkyK5LZ4KoyN57AH2QoHFSzuefom11F1rgdiUriYf2CodBbq9LBi77Q5bLHz4CShveisTu954B", | ||
}, | ||
}, | ||
], | ||
[ | ||
{ | ||
user: "{{user1}}", | ||
content: { | ||
text: "Send 100 UNI from Arbitrum to Ethereum", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Sure, I'll send 100 UNI to Ethereum right away.", | ||
action: "X_CHAIN_SWAP", | ||
}, | ||
}, | ||
{ | ||
user: "{{agent}}", | ||
content: { | ||
text: "Successfully sent 100 UNI to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62 on Ethereum\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec", | ||
}, | ||
}, | ||
] | ||
], | ||
similes: ["CROSS_CHAIN_SWAP", "CROSS_CHAIN_BRIDGE", "MOVE_CROSS_CHAIN", "SWAP","BRIDGE"], | ||
}; // TODO: add more examples / similies |
Oops, something went wrong.