@swapkit/toolboxes 0.0.0-nightly-20250304130539

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/chunk-fazw0jvt.js +3 -0
  2. package/dist/chunk-fazw0jvt.js.map +9 -0
  3. package/dist/chunk-tvrdndbw.js +4 -0
  4. package/dist/chunk-tvrdndbw.js.map +9 -0
  5. package/dist/cosmos/index.cjs +3 -0
  6. package/dist/cosmos/index.cjs.map +19 -0
  7. package/dist/cosmos/index.js +3 -0
  8. package/dist/cosmos/index.js.map +19 -0
  9. package/dist/evm/index.cjs +3 -0
  10. package/dist/evm/index.cjs.map +18 -0
  11. package/dist/evm/index.js +3 -0
  12. package/dist/evm/index.js.map +18 -0
  13. package/dist/index.cjs +3 -0
  14. package/dist/index.cjs.map +9 -0
  15. package/dist/index.js +3 -0
  16. package/dist/index.js.map +9 -0
  17. package/dist/radix/index.cjs +3 -0
  18. package/dist/radix/index.cjs.map +10 -0
  19. package/dist/radix/index.js +3 -0
  20. package/dist/radix/index.js.map +10 -0
  21. package/dist/solana/index.cjs +3 -0
  22. package/dist/solana/index.cjs.map +10 -0
  23. package/dist/solana/index.js +3 -0
  24. package/dist/solana/index.js.map +10 -0
  25. package/dist/substrate/index.cjs +3 -0
  26. package/dist/substrate/index.cjs.map +12 -0
  27. package/dist/substrate/index.js +3 -0
  28. package/dist/substrate/index.js.map +12 -0
  29. package/dist/utxo/index.cjs +3 -0
  30. package/dist/utxo/index.cjs.map +17 -0
  31. package/dist/utxo/index.js +3 -0
  32. package/dist/utxo/index.js.map +17 -0
  33. package/package.json +102 -0
  34. package/src/cosmos/index.ts +8 -0
  35. package/src/cosmos/thorchainUtils/addressFormat.ts +23 -0
  36. package/src/cosmos/thorchainUtils/index.ts +4 -0
  37. package/src/cosmos/thorchainUtils/messages.ts +244 -0
  38. package/src/cosmos/thorchainUtils/registry.ts +47 -0
  39. package/src/cosmos/thorchainUtils/types/client-types.ts +79 -0
  40. package/src/cosmos/thorchainUtils/types/index.ts +1 -0
  41. package/src/cosmos/thorchainUtils/types/proto/MsgCompiled.js +2806 -0
  42. package/src/cosmos/thorchainUtils/types/proto/MsgCompiled.ts +2802 -0
  43. package/src/cosmos/thorchainUtils/util.ts +46 -0
  44. package/src/cosmos/toolbox/BaseCosmosToolbox.ts +257 -0
  45. package/src/cosmos/toolbox/gaia.ts +39 -0
  46. package/src/cosmos/toolbox/getToolboxByChain.ts +29 -0
  47. package/src/cosmos/toolbox/kujira.ts +61 -0
  48. package/src/cosmos/toolbox/thorchain.ts +305 -0
  49. package/src/cosmos/types.ts +42 -0
  50. package/src/cosmos/util.ts +230 -0
  51. package/src/evm/__tests__/ethereum.test.ts +147 -0
  52. package/src/evm/api.ts +157 -0
  53. package/src/evm/contracts/eth/multicall.ts +165 -0
  54. package/src/evm/contracts/op/gasOracle.ts +151 -0
  55. package/src/evm/helpers.ts +213 -0
  56. package/src/evm/index.ts +15 -0
  57. package/src/evm/toolbox/baseEVMToolbox.ts +670 -0
  58. package/src/evm/toolbox/evm.ts +89 -0
  59. package/src/evm/toolbox/getToolboxByChain.ts +37 -0
  60. package/src/evm/toolbox/op.ts +152 -0
  61. package/src/evm/types.ts +110 -0
  62. package/src/index.ts +0 -0
  63. package/src/radix/index.ts +151 -0
  64. package/src/radix/toolbox.ts +693 -0
  65. package/src/solana/index.ts +49 -0
  66. package/src/solana/toolbox.ts +271 -0
  67. package/src/substrate/index.ts +3 -0
  68. package/src/substrate/toolbox/baseSubstrateToolbox.ts +288 -0
  69. package/src/substrate/toolbox/index.ts +40 -0
  70. package/src/substrate/types/index.ts +2 -0
  71. package/src/substrate/types/network.ts +42 -0
  72. package/src/substrate/types/wallet.ts +78 -0
  73. package/src/utxo/helpers/api.ts +431 -0
  74. package/src/utxo/helpers/bchaddrjs.ts +177 -0
  75. package/src/utxo/helpers/coinselect.ts +96 -0
  76. package/src/utxo/helpers/index.ts +5 -0
  77. package/src/utxo/helpers/txSize.ts +103 -0
  78. package/src/utxo/helpers/utils.ts +48 -0
  79. package/src/utxo/index.ts +7 -0
  80. package/src/utxo/toolbox/bitcoinCash.ts +268 -0
  81. package/src/utxo/toolbox/index.ts +41 -0
  82. package/src/utxo/toolbox/utxo.ts +372 -0
  83. package/src/utxo/types.ts +51 -0
@@ -0,0 +1,670 @@
1
+ import {
2
+ type Asset,
3
+ type AssetValue,
4
+ Chain,
5
+ ContractAddress,
6
+ type EVMChain,
7
+ FeeOption,
8
+ SwapKitError,
9
+ SwapKitNumber,
10
+ type WalletTxParams,
11
+ isGasAsset,
12
+ } from "@swapkit/helpers";
13
+ import { erc20ABI } from "@swapkit/helpers/contracts";
14
+ import {
15
+ BrowserProvider,
16
+ Contract,
17
+ type ContractTransaction,
18
+ type Fragment,
19
+ type HDNodeWallet,
20
+ Interface,
21
+ type JsonFragment,
22
+ type JsonRpcSigner,
23
+ type Provider,
24
+ type Signer,
25
+ getAddress,
26
+ } from "ethers";
27
+
28
+ import {
29
+ type ARBToolbox,
30
+ type AVAXToolbox,
31
+ type BASEToolbox,
32
+ type BSCToolbox,
33
+ type ETHToolbox,
34
+ type MATICToolbox,
35
+ type OPToolbox,
36
+ toHexString,
37
+ } from "../index";
38
+ import type {
39
+ ApproveParams,
40
+ CallParams,
41
+ EIP1559TxParams,
42
+ EVMTxParams,
43
+ EstimateCallParams,
44
+ IsApprovedParams,
45
+ LegacyEVMTxParams,
46
+ TransferParams,
47
+ } from "../types";
48
+
49
+ export const MAX_APPROVAL = BigInt(
50
+ "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
51
+ );
52
+
53
+ export function BaseEVMToolbox<
54
+ P extends Provider | BrowserProvider,
55
+ S extends Signer | JsonRpcSigner | HDNodeWallet | undefined,
56
+ >({
57
+ provider,
58
+ signer,
59
+ isEIP1559Compatible = true,
60
+ }: { signer: S; provider: P; isEIP1559Compatible?: boolean }) {
61
+ return {
62
+ call: getCall({ provider, signer, isEIP1559Compatible }),
63
+ estimateCall: getEstimateCall({ provider, signer }),
64
+ EIP1193SendTransaction: getEIP1193SendTransaction(provider),
65
+ approve: getApprove({ provider, signer, isEIP1559Compatible }),
66
+ approvedAmount: getApprovedAmount({ provider }),
67
+ broadcastTransaction: provider.broadcastTransaction,
68
+ createApprovalTx: getCreateApprovalTx(provider),
69
+ createContract: getCreateContract({ provider }),
70
+ createContractTxObject: getCreateContractTxObject(provider),
71
+ createTransferTx: getCreateTransferTx({ provider, signer }),
72
+ estimateGasLimit: getEstimateGasLimit({ provider, signer }),
73
+ estimateGasPrices: getEstimateGasPrices({ provider, isEIP1559Compatible }),
74
+ isApproved: getIsApproved({ provider }),
75
+ sendTransaction: getSendTransaction({ provider, signer, isEIP1559Compatible }),
76
+ signMessage: signer?.signMessage,
77
+ transfer: getTransfer({ provider, signer, isEIP1559Compatible }),
78
+ validateAddress: (address: string) => evmValidateAddress({ address }),
79
+ };
80
+ }
81
+
82
+ export function evmValidateAddress({ address }: { address: string }) {
83
+ try {
84
+ getAddress(address);
85
+ return true;
86
+ } catch (_error) {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ export type EVMWallet = ReturnType<typeof BaseEVMToolbox>;
92
+ export type EVMWalletType = {
93
+ [Chain.Arbitrum]: ReturnType<typeof ARBToolbox>;
94
+ [Chain.Avalanche]: ReturnType<typeof AVAXToolbox>;
95
+ [Chain.Base]: ReturnType<typeof BASEToolbox>;
96
+ [Chain.BinanceSmartChain]: ReturnType<typeof BSCToolbox>;
97
+ [Chain.Ethereum]: ReturnType<typeof ETHToolbox>;
98
+ [Chain.Optimism]: ReturnType<typeof OPToolbox>;
99
+ [Chain.Polygon]: ReturnType<typeof MATICToolbox>;
100
+ };
101
+
102
+ export type EVMWallets = {
103
+ [chain in EVMChain]: EVMWallet & EVMWalletType[chain];
104
+ };
105
+
106
+ const baseAssetAddress: Record<EVMChain, string> = {
107
+ [Chain.Arbitrum]: ContractAddress.ARB,
108
+ [Chain.Avalanche]: ContractAddress.AVAX,
109
+ [Chain.Base]: ContractAddress.BASE,
110
+ [Chain.BinanceSmartChain]: ContractAddress.BSC,
111
+ [Chain.Ethereum]: ContractAddress.ETH,
112
+ [Chain.Optimism]: ContractAddress.OP,
113
+ [Chain.Polygon]: ContractAddress.MATIC,
114
+ };
115
+
116
+ const stateMutable = ["payable", "nonpayable"];
117
+ // const nonStateMutable = ['view', 'pure'];
118
+
119
+ const isEIP1559Transaction = (tx: EVMTxParams) =>
120
+ (tx as EIP1559TxParams).type === 2 ||
121
+ !!(tx as EIP1559TxParams).maxFeePerGas ||
122
+ !!(tx as EIP1559TxParams).maxPriorityFeePerGas;
123
+
124
+ type ToolboxWrapParams<P = Provider | BrowserProvider, T = {}> = T & {
125
+ isEIP1559Compatible?: boolean;
126
+ provider: P;
127
+ signer?: Signer;
128
+ };
129
+
130
+ export function isBrowserProvider(provider: any) {
131
+ return provider instanceof BrowserProvider;
132
+ }
133
+
134
+ export function createContract(
135
+ address: string,
136
+ abi: readonly (JsonFragment | Fragment)[],
137
+ provider: Provider,
138
+ ) {
139
+ return new Contract(address, Interface.from(abi), provider);
140
+ }
141
+
142
+ export function getCreateContract({ provider }: ToolboxWrapParams) {
143
+ return function createContract(address: string, abi: readonly (JsonFragment | Fragment)[]) {
144
+ return new Contract(address, Interface.from(abi), provider);
145
+ };
146
+ }
147
+
148
+ export function isStateChangingCall({
149
+ abi,
150
+ funcName,
151
+ }: { abi: readonly JsonFragment[]; funcName: string }) {
152
+ const abiFragment = abi.find((fragment: any) => fragment.name === funcName) as any;
153
+ if (!abiFragment) throw new SwapKitError("toolbox_evm_no_abi_fragment", { funcName });
154
+ return abiFragment.stateMutability && stateMutable.includes(abiFragment.stateMutability);
155
+ }
156
+
157
+ function getCall({ provider, isEIP1559Compatible, signer }: ToolboxWrapParams) {
158
+ /**
159
+ * @info call contract function
160
+ * When using this method to make a non state changing call to the blockchain, like a isApproved call,
161
+ * the signer needs to be set to undefined
162
+ */
163
+ return async function call<T>({
164
+ callProvider,
165
+ contractAddress,
166
+ abi,
167
+ funcName,
168
+ funcParams = [],
169
+ txOverrides = {},
170
+ feeOption = FeeOption.Fast,
171
+ }: CallParams): Promise<T> {
172
+ const contractProvider = callProvider || provider;
173
+ if (!contractAddress) throw new Error("contractAddress must be provided");
174
+
175
+ const isStateChanging = isStateChangingCall({ abi, funcName });
176
+
177
+ if (isStateChanging && isBrowserProvider(contractProvider) && signer) {
178
+ const createTx = getCreateContractTxObject({ provider: contractProvider });
179
+ const from = txOverrides?.from || (await signer?.getAddress());
180
+ const txObject = await createTx({
181
+ contractAddress,
182
+ abi,
183
+ funcName,
184
+ funcParams,
185
+ txOverrides: { ...txOverrides, from },
186
+ });
187
+ const sendTx = getEIP1193SendTransaction(contractProvider);
188
+
189
+ return sendTx(txObject) as Promise<T>;
190
+ }
191
+ const contract = createContract(contractAddress, abi, contractProvider);
192
+
193
+ // only use signer if the contract function is state changing
194
+ if (isStateChanging) {
195
+ if (!signer) throw new SwapKitError("toolbox_evm_no_signer");
196
+
197
+ const from = txOverrides?.from || (await signer.getAddress());
198
+ if (!from) throw new SwapKitError("toolbox_evm_no_signer_address");
199
+
200
+ const connectedContract = contract.connect(signer);
201
+ const estimateGasPrices = getEstimateGasPrices({ provider, isEIP1559Compatible });
202
+ const { maxFeePerGas, maxPriorityFeePerGas, gasPrice } = (await estimateGasPrices())[
203
+ feeOption
204
+ ];
205
+
206
+ const gasLimit = await contract.getFunction(funcName).estimateGas(...funcParams, txOverrides);
207
+
208
+ // @ts-expect-error
209
+ const result = await connectedContract[funcName](...funcParams, {
210
+ ...txOverrides,
211
+ gasLimit,
212
+ maxFeePerGas,
213
+ maxPriorityFeePerGas,
214
+ gasPrice,
215
+ /**
216
+ * nonce must be set due to a possible bug with ethers.js,
217
+ * expecting a synchronous nonce while the JsonRpcProvider delivers Promise
218
+ */
219
+ nonce: txOverrides?.nonce || (await contractProvider.getTransactionCount(from)),
220
+ });
221
+
222
+ return typeof result?.hash === "string" ? result?.hash : result;
223
+ }
224
+
225
+ const result = await contract[funcName]?.(...funcParams);
226
+
227
+ return typeof result?.hash === "string" ? result?.hash : result;
228
+ };
229
+ }
230
+
231
+ export function getCreateContractTxObject({ provider }: ToolboxWrapParams) {
232
+ return async ({ contractAddress, abi, funcName, funcParams = [], txOverrides }: CallParams) =>
233
+ createContract(contractAddress, abi, provider)
234
+ .getFunction(funcName)
235
+ .populateTransaction(
236
+ ...funcParams.concat(txOverrides).filter((p) => typeof p !== "undefined"),
237
+ );
238
+ }
239
+
240
+ function getApprovedAmount({ provider }: ToolboxWrapParams) {
241
+ return function approveAmount({ assetAddress, spenderAddress, from }: IsApprovedParams) {
242
+ const call = getCall({ provider, isEIP1559Compatible: true });
243
+
244
+ return call<bigint>({
245
+ contractAddress: assetAddress,
246
+ abi: erc20ABI,
247
+ funcName: "allowance",
248
+ funcParams: [from, spenderAddress],
249
+ });
250
+ };
251
+ }
252
+
253
+ function getIsApproved({ provider }: ToolboxWrapParams) {
254
+ return async function isApproved({
255
+ assetAddress,
256
+ spenderAddress,
257
+ from,
258
+ amount = MAX_APPROVAL,
259
+ }: IsApprovedParams) {
260
+ const approvedAmount = await getApprovedAmount({ provider })({
261
+ assetAddress,
262
+ spenderAddress,
263
+ from,
264
+ });
265
+
266
+ return SwapKitNumber.fromBigInt(approvedAmount).gte(SwapKitNumber.fromBigInt(BigInt(amount)));
267
+ };
268
+ }
269
+
270
+ function getApprove({ signer, isEIP1559Compatible = true, provider }: ToolboxWrapParams) {
271
+ return async function approve({
272
+ assetAddress,
273
+ spenderAddress,
274
+ feeOptionKey = FeeOption.Fast,
275
+ amount,
276
+ gasLimitFallback,
277
+ from,
278
+ nonce,
279
+ }: ApproveParams) {
280
+ const funcParams = [spenderAddress, BigInt(amount || MAX_APPROVAL)];
281
+
282
+ const functionCallParams = {
283
+ contractAddress: assetAddress,
284
+ abi: erc20ABI,
285
+ funcName: "approve",
286
+ funcParams,
287
+ signer,
288
+ txOverrides: { from },
289
+ };
290
+
291
+ if (isBrowserProvider(provider)) {
292
+ const createTx = getCreateContractTxObject(provider);
293
+ const sendTx = getEIP1193SendTransaction(provider);
294
+ const txObject = await createTx(functionCallParams);
295
+
296
+ return sendTx(txObject);
297
+ }
298
+
299
+ const call = getCall({ provider, isEIP1559Compatible });
300
+
301
+ return call<string>({
302
+ ...functionCallParams,
303
+ funcParams,
304
+ txOverrides: {
305
+ from,
306
+ nonce,
307
+ gasLimit: gasLimitFallback ? BigInt(gasLimitFallback.toString()) : undefined,
308
+ },
309
+ feeOption: feeOptionKey,
310
+ });
311
+ };
312
+ }
313
+
314
+ function getTransfer({ signer, isEIP1559Compatible = true, provider }: ToolboxWrapParams) {
315
+ return async function transfer({
316
+ assetValue,
317
+ memo,
318
+ recipient,
319
+ feeOptionKey = FeeOption.Fast,
320
+ data,
321
+ from: fromOverride,
322
+ maxFeePerGas,
323
+ maxPriorityFeePerGas,
324
+ gasPrice,
325
+ ...tx
326
+ }: TransferParams) {
327
+ const { hexlify, toUtf8Bytes } = await import("ethers");
328
+ const txAmount = assetValue.getBaseValue("bigint");
329
+ const chain = assetValue.chain as EVMChain;
330
+ const from = fromOverride || (await signer?.getAddress());
331
+
332
+ if (!from) throw new SwapKitError("toolbox_evm_no_from_address");
333
+
334
+ if (assetValue.isGasAsset) {
335
+ const sendTx = getSendTransaction({ provider, signer, isEIP1559Compatible });
336
+ const txObject = {
337
+ ...tx,
338
+ from,
339
+ to: recipient,
340
+ value: txAmount,
341
+ data: data || hexlify(toUtf8Bytes(memo || "")),
342
+ feeOptionKey,
343
+ };
344
+
345
+ return sendTx(txObject);
346
+ }
347
+
348
+ const call = getCall({ signer, provider, isEIP1559Compatible });
349
+ const contractAddress = getTokenAddress(assetValue, chain);
350
+ if (!contractAddress) throw new SwapKitError("toolbox_evm_no_contract_address");
351
+
352
+ return call<string>({
353
+ contractAddress,
354
+ abi: erc20ABI,
355
+ funcName: "transfer",
356
+ funcParams: [recipient, txAmount],
357
+ txOverrides: { from, maxFeePerGas, maxPriorityFeePerGas, gasPrice },
358
+ feeOption: feeOptionKey,
359
+ });
360
+ };
361
+ }
362
+
363
+ export function getEstimateGasPrices({
364
+ provider,
365
+ isEIP1559Compatible = true,
366
+ }: { provider: Provider; isEIP1559Compatible?: boolean }) {
367
+ return async function estimateGasPrices() {
368
+ try {
369
+ const { maxFeePerGas, maxPriorityFeePerGas, gasPrice } = await provider.getFeeData();
370
+
371
+ if (isEIP1559Compatible) {
372
+ if (maxFeePerGas === null || maxPriorityFeePerGas === null)
373
+ throw new SwapKitError("toolbox_evm_no_fee_data");
374
+
375
+ return {
376
+ [FeeOption.Average]: { maxFeePerGas, maxPriorityFeePerGas },
377
+ [FeeOption.Fast]: {
378
+ maxFeePerGas: (maxFeePerGas * 15n) / 10n,
379
+ maxPriorityFeePerGas: (maxPriorityFeePerGas * 15n) / 10n,
380
+ },
381
+ [FeeOption.Fastest]: {
382
+ maxFeePerGas: maxFeePerGas * 2n,
383
+ maxPriorityFeePerGas: maxPriorityFeePerGas * 2n,
384
+ },
385
+ };
386
+ }
387
+ if (!gasPrice) throw new SwapKitError("toolbox_evm_no_gas_price");
388
+
389
+ return {
390
+ [FeeOption.Average]: { gasPrice },
391
+ [FeeOption.Fast]: { gasPrice: (gasPrice * 15n) / 10n },
392
+ [FeeOption.Fastest]: { gasPrice: gasPrice * 2n },
393
+ };
394
+ } catch (error) {
395
+ throw new Error(
396
+ `Failed to estimate gas price: ${(error as any).msg ?? (error as any).toString()}`,
397
+ );
398
+ }
399
+ };
400
+ }
401
+
402
+ function getEstimateCall({ provider, signer }: { signer?: Signer; provider: Provider }) {
403
+ return function estimateCall({
404
+ contractAddress,
405
+ abi,
406
+ funcName,
407
+ funcParams = [],
408
+ txOverrides,
409
+ }: EstimateCallParams) {
410
+ if (!contractAddress) throw new SwapKitError("toolbox_evm_no_contract_address");
411
+
412
+ const contract = createContract(contractAddress, abi, provider);
413
+ return signer
414
+ ? contract
415
+ .connect(signer)
416
+ .getFunction(funcName)
417
+ .estimateGas(...funcParams, txOverrides)
418
+ : contract.getFunction(funcName).estimateGas(...funcParams, txOverrides);
419
+ };
420
+ }
421
+
422
+ function getEstimateGasLimit({ provider, signer }: ToolboxWrapParams) {
423
+ return async function estimateGasLimit({
424
+ assetValue,
425
+ recipient,
426
+ memo,
427
+ from,
428
+ funcName,
429
+ funcParams,
430
+ txOverrides,
431
+ }: WalletTxParams & {
432
+ assetValue: AssetValue;
433
+ funcName?: string;
434
+ funcParams?: unknown[];
435
+ txOverrides?: EVMTxParams;
436
+ }) {
437
+ // const value = assetValue.getBaseValue("bigint");
438
+ const value = assetValue.bigIntValue;
439
+
440
+ const assetAddress = assetValue.isGasAsset
441
+ ? null
442
+ : getTokenAddress(assetValue, assetValue.chain as EVMChain);
443
+
444
+ if (assetAddress && funcName) {
445
+ const estimateCall = getEstimateCall({ provider, signer });
446
+ // ERC20 gas estimate
447
+ return estimateCall({
448
+ contractAddress: assetAddress,
449
+ abi: erc20ABI,
450
+ funcName,
451
+ funcParams,
452
+ txOverrides,
453
+ });
454
+ }
455
+
456
+ const { hexlify, toUtf8Bytes } = await import("ethers");
457
+
458
+ return provider.estimateGas({
459
+ from,
460
+ to: recipient,
461
+ value,
462
+ data: memo ? hexlify(toUtf8Bytes(memo)) : undefined,
463
+ });
464
+ };
465
+ }
466
+
467
+ function getSendTransaction({ provider, signer, isEIP1559Compatible = true }: ToolboxWrapParams) {
468
+ return async function sendTransaction({
469
+ feeOptionKey = FeeOption.Fast,
470
+ ...tx
471
+ }: EVMTxParams & { feeOptionKey?: FeeOption }) {
472
+ const { from, to, data, value, ...transaction } = tx;
473
+
474
+ if (!signer) throw new SwapKitError("toolbox_evm_no_signer");
475
+ if (!to) throw new SwapKitError("toolbox_evm_no_to_address");
476
+
477
+ const parsedTxObject = {
478
+ ...transaction,
479
+ data: data || "0x",
480
+ to,
481
+ from,
482
+ value: BigInt(value || 0),
483
+ };
484
+
485
+ // early return to skip gas estimation if provider is EIP-1193
486
+ if (isBrowserProvider(provider)) {
487
+ const sendTx = getEIP1193SendTransaction(provider);
488
+ return sendTx(parsedTxObject);
489
+ }
490
+
491
+ const address = from || (await signer.getAddress());
492
+ const nonce = tx.nonce || (await provider.getTransactionCount(address));
493
+ const chainId = (await provider.getNetwork()).chainId;
494
+
495
+ const isEIP1559 = isEIP1559Transaction(parsedTxObject) || isEIP1559Compatible;
496
+ const estimateGasPrices = getEstimateGasPrices({ provider, isEIP1559Compatible });
497
+
498
+ const feeData =
499
+ (isEIP1559 &&
500
+ !(
501
+ (parsedTxObject as EIP1559TxParams).maxFeePerGas &&
502
+ (parsedTxObject as EIP1559TxParams).maxPriorityFeePerGas
503
+ )) ||
504
+ !(parsedTxObject as LegacyEVMTxParams).gasPrice
505
+ ? Object.entries((await estimateGasPrices())[feeOptionKey]).reduce(
506
+ // biome-ignore lint/performance/noAccumulatingSpread: this is a small object
507
+ (acc, [k, v]) => ({ ...acc, [k]: toHexString(BigInt(v)) }),
508
+ {} as {
509
+ maxFeePerGas?: string;
510
+ maxPriorityFeePerGas?: string;
511
+ gasPrice?: string;
512
+ },
513
+ )
514
+ : {};
515
+ let gasLimit: string;
516
+ try {
517
+ gasLimit = toHexString(
518
+ parsedTxObject.gasLimit || ((await provider.estimateGas(parsedTxObject)) * 11n) / 10n,
519
+ );
520
+ } catch (error) {
521
+ throw new SwapKitError("toolbox_evm_error_estimating_gas_limit", { error });
522
+ }
523
+
524
+ try {
525
+ const txObject = {
526
+ ...parsedTxObject,
527
+ chainId,
528
+ type: isEIP1559 ? 2 : 0,
529
+ gasLimit,
530
+ nonce,
531
+ ...feeData,
532
+ };
533
+
534
+ try {
535
+ const response = await signer.sendTransaction(txObject);
536
+ return response.hash;
537
+ } catch (_error) {
538
+ const txHex = await signer.signTransaction({
539
+ ...txObject,
540
+ from: address,
541
+ });
542
+ const response = await provider.broadcastTransaction(txHex);
543
+ return response.hash;
544
+ }
545
+ } catch (error) {
546
+ throw new SwapKitError("toolbox_evm_error_sending_transaction", { error });
547
+ }
548
+ };
549
+ }
550
+
551
+ /**
552
+ * Exported helper functions
553
+ */
554
+ export function toChecksumAddress(address: string) {
555
+ return getAddress(address);
556
+ }
557
+
558
+ export function getEIP1193SendTransaction(provider: Provider | BrowserProvider) {
559
+ return function EIP1193SendTransaction({
560
+ value,
561
+ ...params
562
+ }: EVMTxParams | ContractTransaction): Promise<string> {
563
+ if (!isBrowserProvider(provider)) {
564
+ throw new SwapKitError("toolbox_evm_provider_not_eip1193_compatible");
565
+ }
566
+
567
+ return (provider as BrowserProvider).send("eth_sendTransaction", [
568
+ { value: toHexString(BigInt(value || 0)), ...params } as any,
569
+ ]);
570
+ };
571
+ }
572
+
573
+ export function getChecksumAddressFromAsset(asset: Asset, chain: EVMChain) {
574
+ const assetAddress = getTokenAddress(asset, chain);
575
+
576
+ if (assetAddress) {
577
+ return getAddress(assetAddress.toLowerCase());
578
+ }
579
+
580
+ throw new SwapKitError("toolbox_evm_invalid_gas_asset_address");
581
+ }
582
+
583
+ export function getTokenAddress({ chain, symbol, ticker }: Asset, baseAssetChain: EVMChain) {
584
+ try {
585
+ const isBSCBNB = chain === Chain.BinanceSmartChain && symbol === "BNB" && ticker === "BNB";
586
+ const isBaseAsset =
587
+ chain === baseAssetChain && symbol === baseAssetChain && ticker === baseAssetChain;
588
+ const isEVMAsset =
589
+ [Chain.Arbitrum, Chain.Base].includes(chain) && symbol === "ETH" && ticker === "ETH";
590
+
591
+ if (isBaseAsset || isBSCBNB || isEVMAsset) {
592
+ return baseAssetAddress[baseAssetChain];
593
+ }
594
+
595
+ // strip 0X only - 0x is still valid
596
+ return getAddress(symbol.slice(ticker.length + 1).replace(/^0X/, ""));
597
+ } catch (_error) {
598
+ return null;
599
+ }
600
+ }
601
+
602
+ function getCreateTransferTx({
603
+ provider,
604
+ signer,
605
+ }: { provider: Provider | BrowserProvider; signer?: Signer }) {
606
+ return async function createTransferTx({
607
+ assetValue,
608
+ memo,
609
+ recipient,
610
+ feeOptionKey = FeeOption.Fast,
611
+ data,
612
+ from: fromOverride,
613
+ maxFeePerGas,
614
+ maxPriorityFeePerGas,
615
+ gasPrice,
616
+ ...tx
617
+ }: TransferParams) {
618
+ const txAmount = assetValue.getBaseValue("bigint");
619
+ const chain = assetValue.chain as EVMChain;
620
+ const from = fromOverride || (await signer?.getAddress());
621
+
622
+ if (!from) throw new SwapKitError("toolbox_evm_no_from_address");
623
+
624
+ if (isGasAsset(assetValue)) {
625
+ const { hexlify, toUtf8Bytes } = await import("ethers");
626
+
627
+ return {
628
+ ...tx,
629
+ from,
630
+ to: recipient,
631
+ value: txAmount,
632
+ data: data || hexlify(toUtf8Bytes(memo || "")),
633
+ };
634
+ }
635
+
636
+ const contractAddress = getTokenAddress(assetValue, chain);
637
+ if (!contractAddress) throw new SwapKitError("toolbox_evm_no_contract_address");
638
+ const createTx = getCreateContractTxObject(provider);
639
+
640
+ return createTx({
641
+ contractAddress,
642
+ abi: erc20ABI,
643
+ funcName: "transfer",
644
+ funcParams: [recipient, txAmount],
645
+ txOverrides: { from, maxFeePerGas, maxPriorityFeePerGas, gasPrice },
646
+ });
647
+ };
648
+ }
649
+
650
+ function getCreateApprovalTx(provider: Provider) {
651
+ return async function createApprovalTx({
652
+ assetAddress,
653
+ spenderAddress,
654
+ amount,
655
+ from,
656
+ }: ApproveParams) {
657
+ const createTx = getCreateContractTxObject(provider);
658
+ const funcParams = [spenderAddress, BigInt(amount || MAX_APPROVAL)];
659
+
660
+ const txObject = await createTx({
661
+ contractAddress: assetAddress,
662
+ abi: erc20ABI,
663
+ funcName: "approve",
664
+ funcParams,
665
+ txOverrides: { from },
666
+ });
667
+
668
+ return txObject;
669
+ };
670
+ }