rig_onchain_kit/cross_chain/
tools.rs1use anyhow::{anyhow, Result};
2use blockhash_cache::{inject_blockhash_into_encoded_tx, BLOCKHASH_CACHE};
3use rig_tool_macro::tool;
4
5use crate::common::wrap_unsafe;
6use crate::signer::SignerContext;
7
8use lifi::LiFi;
9
10#[tool(description = "
15Get a quote for a multichain swap (or bridge).
16
17This might be required in case the user wonders how much it would cost to
18perform a swap or bridge. It is also good in case you would like to validate the
19token addresses and other params with the user before executing
20
21The from_token_address and to_token_address can either be a solana public key, evm
22address or a symbol, try to prioritize the address over the symbol
23
24The amount has to be a string to avoid precision loss. The amount is accounting
25for decimals, e.g. 1e6 for 1 USDC but 1e18 for 1 SOL.
26
27Note that sometimes the quote will return a transaction request, with an address that might require approval.
28In that case, you can use the approve_token tool to approve the token.
29
30Supported from_chains:
31- solana: 1151111081099710
32- arbitrum: 42161
33- base: 8453
34
35Supported to_chains:
36- sol: 1151111081099710
37- arb: 42161
38- base: 8453
39
40if a user hits you with a chain you cannot support, let them know
41")]
42pub async fn get_quote(
43 from_token_address: String,
44 to_token_address: String,
45 amount: String,
46 from_chain: String,
47 to_chain: String,
48) -> Result<serde_json::Value> {
49 let signer = SignerContext::current().await;
50 let lifi = LiFi::new(None);
51
52 let from_address = if from_chain == "1151111081099710"
53 || from_chain.to_lowercase() == "sol"
54 {
55 signer.pubkey()
56 } else {
57 signer.address()
58 };
59
60 let to_address = if to_chain == "1151111081099710"
61 || to_chain.to_lowercase() == "sol"
62 {
63 signer.pubkey()
64 } else {
65 signer.address()
66 };
67
68 let quote = lifi
69 .get_quote(
70 &from_chain,
71 &to_chain,
72 &from_token_address,
73 &to_token_address,
74 &from_address,
75 &to_address,
76 &amount,
77 )
78 .await
79 .map_err(|e| {
80 anyhow!(
81 "{:#?}",
82 e.to_string().chars().take(300).collect::<String>()
83 )
84 })?;
85
86 Ok(quote.summary())
87}
88
89#[tool(description = "
90Multichain swap (or bridge).
91
92This can be used for any swap, solana to solana, evm to evm, solana to evm,
93evm to solana, etc.
94
95Use this in case of the user trying to swap any tokens that exist on two remote
96chains, or would like to bridge the tokens
97
98Don't use this in case you are not certain about all of the params, use the
99get_multichain_quote tool instead to validate the params in that case.
100
101The from_token_address and to_token_address can either be a solana public key, evm
102address or a symbol, try to prioritize the address over the symbol
103
104The amount has to be a string to avoid precision loss. The amount is accounting
105for decimals, e.g. 1e6 for 1 USDC but 1e18 for 1 SOL.
106
107Supported from_chains:
108- solana: 1151111081099710
109- arbitrum: 42161
110- base: 8453
111
112Supported to_chains:
113- sol: 1151111081099710
114- arb: 42161
115- base: 8453
116
117if a user hits you with a chain you cannot support, let them know
118")]
119pub async fn swap(
120 from_token_address: String,
121 to_token_address: String,
122 amount: String,
123 from_chain: String,
124 to_chain: String,
125) -> Result<String> {
126 let signer = SignerContext::current().await;
127 let lifi = LiFi::new(None);
128
129 let from_address = if from_chain == "1151111081099710"
130 || from_chain.to_lowercase() == "sol"
131 {
132 signer.pubkey()
133 } else {
134 signer.address()
135 };
136
137 let to_address = if to_chain == "1151111081099710"
138 || to_chain.to_lowercase() == "sol"
139 {
140 signer.pubkey()
141 } else {
142 signer.address()
143 };
144
145 let quote = lifi
146 .get_quote(
147 &from_chain,
148 &to_chain,
149 &from_token_address,
150 &to_token_address,
151 &from_address,
152 &to_address,
153 &amount,
154 )
155 .await
156 .map_err(|e| {
157 anyhow!(
158 "{:#?}",
159 e.to_string().chars().take(300).collect::<String>()
160 )
161 })?;
162
163 match quote.transaction_request {
164 Some(transaction_request) => {
165 wrap_unsafe(move || async move {
166 if transaction_request.is_solana() {
167 let latest_blockhash =
168 BLOCKHASH_CACHE.get_blockhash().await?.to_string();
169 let encoded_tx = inject_blockhash_into_encoded_tx(
170 &transaction_request.data,
171 &latest_blockhash,
172 )?;
173 signer
174 .sign_and_send_encoded_solana_transaction(encoded_tx)
175 .await
176 } else {
177 signer
178 .sign_and_send_json_evm_transaction(
179 transaction_request.to_json_rpc()?,
180 )
181 .await
182 }
183 })
184 .await
185 }
186 None => Err(anyhow!("No transaction request")),
187 }
188}
189
190#[tool(description = "
191Check if a token has enough approval for a spender.
192
193token_address is the ERC20 token contract address
194spender_address is the address that needs approval
195amount is the amount to check approval for (in token decimals)
196
197Returns 'true' if approved, 'false' if not approved
198")]
199pub async fn check_approval(
200 token_address: String,
201 spender_address: String,
202 amount: String,
203 from_chain_caip2: String,
204) -> Result<String> {
205 let signer = SignerContext::current().await;
206 let owner_address = signer.address();
207
208 let allowance = evm_approvals::get_allowance(
209 &token_address,
210 &owner_address,
211 &spender_address,
212 evm_approvals::caip2_to_chain_id(&from_chain_caip2)?,
213 )
214 .await?;
215 let amount = amount
216 .parse::<u128>()
217 .map_err(|_| anyhow!("Invalid amount"))?;
218
219 Ok((allowance >= amount).to_string())
220}
221
222#[tool(description = "
223Approve a token for a spender.
224
225token_address is the ERC20 token contract address
226spender_address is the address that needs approval
227amount is the amount to approve (in token decimals)
228")]
229pub async fn approve_token(
230 token_address: String,
231 spender_address: String,
232 from_chain_caip2: String,
233) -> Result<String> {
234 let signer = SignerContext::current().await;
235 let owner_address = signer.address();
236
237 let transaction = evm_approvals::create_approval_transaction(
238 &token_address,
239 &spender_address,
240 &owner_address,
241 evm_approvals::caip2_to_chain_id(&from_chain_caip2)?,
242 )
243 .await?;
244
245 wrap_unsafe(move || async move {
246 signer
247 .sign_and_send_json_evm_transaction(transaction)
248 .await
249 .map_err(|e| anyhow!(e.to_string()))
250 })
251 .await?;
252
253 Ok("Approved".to_string())
254}