@coinbase/agentkit 0.0.0-nightly-20251115210411 → 0.0.0-nightly-20251117210447
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.
- package/README.md +17 -0
- package/dist/action-providers/index.d.ts +1 -0
- package/dist/action-providers/index.js +1 -0
- package/dist/action-providers/sushi/constants.d.ts +35 -0
- package/dist/action-providers/sushi/constants.js +7 -0
- package/dist/action-providers/sushi/index.d.ts +4 -0
- package/dist/action-providers/sushi/index.js +20 -0
- package/dist/action-providers/sushi/sushiDataActionProvider.d.ts +32 -0
- package/dist/action-providers/sushi/sushiDataActionProvider.js +113 -0
- package/dist/action-providers/sushi/sushiDataSchemas.d.ts +11 -0
- package/dist/action-providers/sushi/sushiDataSchemas.js +16 -0
- package/dist/action-providers/sushi/sushiRouterActionProvider.d.ts +40 -0
- package/dist/action-providers/sushi/sushiRouterActionProvider.js +386 -0
- package/dist/action-providers/sushi/sushiRouterActionProvider.test.d.ts +1 -0
- package/dist/action-providers/sushi/sushiRouterActionProvider.test.js +392 -0
- package/dist/action-providers/sushi/sushiRouterSchemas.d.ts +36 -0
- package/dist/action-providers/sushi/sushiRouterSchemas.js +55 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -505,6 +505,23 @@ const agent = createReactAgent({
|
|
|
505
505
|
</table>
|
|
506
506
|
</details>
|
|
507
507
|
<details>
|
|
508
|
+
<summary><strong>Sushi</strong></summary>
|
|
509
|
+
<table width="100%">
|
|
510
|
+
<tr>
|
|
511
|
+
<td width="200"><code>find-token</code></td>
|
|
512
|
+
<td width="768">Searches the Sushi Data API for up to 10 matching tokens by symbol or address.</td>
|
|
513
|
+
</tr>
|
|
514
|
+
<tr>
|
|
515
|
+
<td width="200"><code>quote</code></td>
|
|
516
|
+
<td width="768">Fetches an off-chain swap quote between ERC20 or native assets using the Sushi Swap API.</td>
|
|
517
|
+
</tr>
|
|
518
|
+
<tr>
|
|
519
|
+
<td width="200"><code>swap</code></td>
|
|
520
|
+
<td width="768">Executes a Sushi-routed swap after validating balances and approvals, returning the transaction hash.</td>
|
|
521
|
+
</tr>
|
|
522
|
+
</table>
|
|
523
|
+
</details>
|
|
524
|
+
<details>
|
|
508
525
|
<summary><strong>Twitter</strong></summary>
|
|
509
526
|
<table width="100%">
|
|
510
527
|
<tr>
|
|
@@ -38,6 +38,7 @@ __exportStar(require("./morpho"), exports);
|
|
|
38
38
|
__exportStar(require("./opensea"), exports);
|
|
39
39
|
__exportStar(require("./spl"), exports);
|
|
40
40
|
__exportStar(require("./superfluid"), exports);
|
|
41
|
+
__exportStar(require("./sushi"), exports);
|
|
41
42
|
__exportStar(require("./truemarkets"), exports);
|
|
42
43
|
__exportStar(require("./twitter"), exports);
|
|
43
44
|
__exportStar(require("./wallet"), exports);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const routeProcessor9Abi_Route: readonly [{
|
|
2
|
+
readonly name: "Route";
|
|
3
|
+
readonly type: "event";
|
|
4
|
+
readonly inputs: readonly [{
|
|
5
|
+
readonly type: "address";
|
|
6
|
+
readonly name: "from";
|
|
7
|
+
readonly indexed: true;
|
|
8
|
+
}, {
|
|
9
|
+
readonly type: "address";
|
|
10
|
+
readonly name: "to";
|
|
11
|
+
}, {
|
|
12
|
+
readonly type: "address";
|
|
13
|
+
readonly name: "tokenIn";
|
|
14
|
+
readonly indexed: true;
|
|
15
|
+
}, {
|
|
16
|
+
readonly type: "address";
|
|
17
|
+
readonly name: "tokenOut";
|
|
18
|
+
}, {
|
|
19
|
+
readonly type: "uint256";
|
|
20
|
+
readonly name: "amountIn";
|
|
21
|
+
}, {
|
|
22
|
+
readonly type: "uint256";
|
|
23
|
+
readonly name: "amountOut";
|
|
24
|
+
}, {
|
|
25
|
+
readonly type: "int256";
|
|
26
|
+
readonly name: "slippage";
|
|
27
|
+
}, {
|
|
28
|
+
readonly type: "uint32";
|
|
29
|
+
readonly name: "referralCode";
|
|
30
|
+
readonly indexed: true;
|
|
31
|
+
}, {
|
|
32
|
+
readonly type: "bytes32";
|
|
33
|
+
readonly name: "diagnosticsFirst32";
|
|
34
|
+
}];
|
|
35
|
+
}];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.routeProcessor9Abi_Route = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
exports.routeProcessor9Abi_Route = (0, viem_1.parseAbi)([
|
|
6
|
+
"event Route(address indexed from, address to, address indexed tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, int256 slippage, uint32 indexed referralCode, bytes32 diagnosticsFirst32)",
|
|
7
|
+
]);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./sushiDataSchemas"), exports);
|
|
18
|
+
__exportStar(require("./sushiDataActionProvider"), exports);
|
|
19
|
+
__exportStar(require("./sushiRouterSchemas"), exports);
|
|
20
|
+
__exportStar(require("./sushiRouterActionProvider"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EvmWalletProvider } from "../../wallet-providers";
|
|
3
|
+
import { ActionProvider } from "../actionProvider";
|
|
4
|
+
import { Network } from "../../network";
|
|
5
|
+
import { FindTokenSchema } from "./sushiDataSchemas";
|
|
6
|
+
/**
|
|
7
|
+
* SushiDataActionProvider is an action provider for Sushi.
|
|
8
|
+
*
|
|
9
|
+
* This provider is used for any action that uses the Sushi Data API.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SushiDataActionProvider extends ActionProvider<EvmWalletProvider> {
|
|
12
|
+
/**
|
|
13
|
+
* Constructor for the SushiDataActionProvider class.
|
|
14
|
+
*/
|
|
15
|
+
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* Swaps a specified amount of a from token to a to token for the wallet.
|
|
18
|
+
*
|
|
19
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
20
|
+
* @param args - The input arguments for the action.
|
|
21
|
+
* @returns A message containing the swap details.
|
|
22
|
+
*/
|
|
23
|
+
findToken(walletProvider: EvmWalletProvider, args: z.infer<typeof FindTokenSchema>): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Custom action providers are supported on all networks
|
|
26
|
+
*
|
|
27
|
+
* @param network - The network to checkpointSaver
|
|
28
|
+
* @returns True if the network is supported, false otherwise
|
|
29
|
+
*/
|
|
30
|
+
supportsNetwork(network: Network): boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const sushiDataActionProvider: () => SushiDataActionProvider;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sushiDataActionProvider = exports.SushiDataActionProvider = void 0;
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
|
+
const wallet_providers_1 = require("../../wallet-providers");
|
|
15
|
+
const actionDecorator_1 = require("../actionDecorator");
|
|
16
|
+
const actionProvider_1 = require("../actionProvider");
|
|
17
|
+
const sushiDataSchemas_1 = require("./sushiDataSchemas");
|
|
18
|
+
const evm_1 = require("sushi/evm");
|
|
19
|
+
const viem_1 = require("viem");
|
|
20
|
+
/**
|
|
21
|
+
* SushiDataActionProvider is an action provider for Sushi.
|
|
22
|
+
*
|
|
23
|
+
* This provider is used for any action that uses the Sushi Data API.
|
|
24
|
+
*/
|
|
25
|
+
class SushiDataActionProvider extends actionProvider_1.ActionProvider {
|
|
26
|
+
/**
|
|
27
|
+
* Constructor for the SushiDataActionProvider class.
|
|
28
|
+
*/
|
|
29
|
+
constructor() {
|
|
30
|
+
super("sushi-data", []);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Swaps a specified amount of a from token to a to token for the wallet.
|
|
34
|
+
*
|
|
35
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
36
|
+
* @param args - The input arguments for the action.
|
|
37
|
+
* @returns A message containing the swap details.
|
|
38
|
+
*/
|
|
39
|
+
async findToken(walletProvider, args) {
|
|
40
|
+
try {
|
|
41
|
+
const chainId = Number((await walletProvider.getNetwork()).chainId);
|
|
42
|
+
if (!(0, evm_1.isEvmChainId)(chainId)) {
|
|
43
|
+
return `Unsupported chainId: ${chainId}`;
|
|
44
|
+
}
|
|
45
|
+
const request = await fetch(`${evm_1.SUSHI_DATA_API_HOST}/graphql`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Accept: "application/json",
|
|
50
|
+
},
|
|
51
|
+
body: `{"query":"query { tokenList(chainId: ${chainId}, first: 10, search: \\"${args.search}\\") { address symbol name decimals } }"}`,
|
|
52
|
+
});
|
|
53
|
+
const response = await request.json();
|
|
54
|
+
const schema = zod_1.z.object({
|
|
55
|
+
data: zod_1.z.object({
|
|
56
|
+
tokenList: zod_1.z.array(zod_1.z.object({
|
|
57
|
+
address: zod_1.z.string().refine(val => (0, viem_1.isAddress)(val, { strict: false })),
|
|
58
|
+
symbol: zod_1.z.string(),
|
|
59
|
+
name: zod_1.z.string(),
|
|
60
|
+
decimals: zod_1.z.number(),
|
|
61
|
+
})),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const result = schema.safeParse(response);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
return `Error parsing response: ${result.error.message}`;
|
|
67
|
+
}
|
|
68
|
+
const tokens = result.data.data.tokenList;
|
|
69
|
+
const chain = (0, evm_1.getEvmChainById)(chainId);
|
|
70
|
+
let message = `Found ${tokens.length} tokens on ${chain.shortName}:`;
|
|
71
|
+
tokens.forEach(token => {
|
|
72
|
+
message += `\n- ${token.symbol} (${token.name}) - ${token.address}`;
|
|
73
|
+
});
|
|
74
|
+
return message;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return `Error finding tokens: ${error}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Custom action providers are supported on all networks
|
|
82
|
+
*
|
|
83
|
+
* @param network - The network to checkpointSaver
|
|
84
|
+
* @returns True if the network is supported, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
supportsNetwork(network) {
|
|
87
|
+
if (network.protocolFamily !== "evm" || !network.chainId) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return (0, evm_1.isEvmChainId)(Number(network.chainId));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.SushiDataActionProvider = SushiDataActionProvider;
|
|
94
|
+
__decorate([
|
|
95
|
+
(0, actionDecorator_1.CreateAction)({
|
|
96
|
+
name: "find-token",
|
|
97
|
+
description: `This tool finds tokens (erc20) using the Sushi Data API
|
|
98
|
+
It takes the following inputs:
|
|
99
|
+
- Search: Either the token symbol (either full or partial) or token address to search for
|
|
100
|
+
|
|
101
|
+
Important notes:
|
|
102
|
+
- Only returns the first 10 tokens found
|
|
103
|
+
- Always use the full token symbol for better results
|
|
104
|
+
- Always use this tool to verify the token address if you are not sure about the address
|
|
105
|
+
`,
|
|
106
|
+
schema: sushiDataSchemas_1.FindTokenSchema,
|
|
107
|
+
}),
|
|
108
|
+
__metadata("design:type", Function),
|
|
109
|
+
__metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
|
|
110
|
+
__metadata("design:returntype", Promise)
|
|
111
|
+
], SushiDataActionProvider.prototype, "findToken", null);
|
|
112
|
+
const sushiDataActionProvider = () => new SushiDataActionProvider();
|
|
113
|
+
exports.sushiDataActionProvider = sushiDataActionProvider;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FindTokenSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Input schema for find token action
|
|
7
|
+
*/
|
|
8
|
+
exports.FindTokenSchema = zod_1.z
|
|
9
|
+
.object({
|
|
10
|
+
search: zod_1.z
|
|
11
|
+
.string()
|
|
12
|
+
.min(2)
|
|
13
|
+
.describe("Either the (partial or complete) symbol OR the address of the token to find"),
|
|
14
|
+
})
|
|
15
|
+
.strip()
|
|
16
|
+
.describe("Instructions for finding a token");
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EvmWalletProvider } from "../../wallet-providers";
|
|
3
|
+
import { ActionProvider } from "../actionProvider";
|
|
4
|
+
import { SushiQuoteSchema, SushiSwapSchema } from "./sushiRouterSchemas";
|
|
5
|
+
import { Network } from "../../network";
|
|
6
|
+
/**
|
|
7
|
+
* SushiRouterActionProvider is an action provider for Sushi.
|
|
8
|
+
*
|
|
9
|
+
* This provider is used for any action that uses the Sushi Router API.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SushiRouterActionProvider extends ActionProvider<EvmWalletProvider> {
|
|
12
|
+
/**
|
|
13
|
+
* Constructor for the SushiRouterActionProvider class.
|
|
14
|
+
*/
|
|
15
|
+
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* Swaps a specified amount of a from token to a to token for the wallet.
|
|
18
|
+
*
|
|
19
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
20
|
+
* @param args - The input arguments for the action.
|
|
21
|
+
* @returns A message containing the swap details.
|
|
22
|
+
*/
|
|
23
|
+
swap(walletProvider: EvmWalletProvider, args: z.infer<typeof SushiSwapSchema>): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Gets a quote for a specified amount of a from token to a to token
|
|
26
|
+
*
|
|
27
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
28
|
+
* @param args - The input arguments for the action.
|
|
29
|
+
* @returns A message containing the quote details.
|
|
30
|
+
*/
|
|
31
|
+
quote(walletProvider: EvmWalletProvider, args: z.infer<typeof SushiQuoteSchema>): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Custom action providers are supported on all networks
|
|
34
|
+
*
|
|
35
|
+
* @param network - The network to checkpointSaver
|
|
36
|
+
* @returns True if the network is supported, false otherwise
|
|
37
|
+
*/
|
|
38
|
+
supportsNetwork(network: Network): boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare const sushiRouterActionProvider: () => SushiRouterActionProvider;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sushiRouterActionProvider = exports.SushiRouterActionProvider = void 0;
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
|
+
const wallet_providers_1 = require("../../wallet-providers");
|
|
15
|
+
const actionDecorator_1 = require("../actionDecorator");
|
|
16
|
+
const actionProvider_1 = require("../actionProvider");
|
|
17
|
+
const sushiRouterSchemas_1 = require("./sushiRouterSchemas");
|
|
18
|
+
const evm_1 = require("sushi/evm");
|
|
19
|
+
const viem_1 = require("viem");
|
|
20
|
+
const constants_1 = require("./constants");
|
|
21
|
+
/**
|
|
22
|
+
* SushiRouterActionProvider is an action provider for Sushi.
|
|
23
|
+
*
|
|
24
|
+
* This provider is used for any action that uses the Sushi Router API.
|
|
25
|
+
*/
|
|
26
|
+
class SushiRouterActionProvider extends actionProvider_1.ActionProvider {
|
|
27
|
+
/**
|
|
28
|
+
* Constructor for the SushiRouterActionProvider class.
|
|
29
|
+
*/
|
|
30
|
+
constructor() {
|
|
31
|
+
super("sushi-router", []);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Swaps a specified amount of a from token to a to token for the wallet.
|
|
35
|
+
*
|
|
36
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
37
|
+
* @param args - The input arguments for the action.
|
|
38
|
+
* @returns A message containing the swap details.
|
|
39
|
+
*/
|
|
40
|
+
async swap(walletProvider, args) {
|
|
41
|
+
try {
|
|
42
|
+
const chainId = Number((await walletProvider.getNetwork()).chainId);
|
|
43
|
+
// Compatible chainId is expected since it should be pre-checked in supportsNetwork
|
|
44
|
+
if (!(0, evm_1.isSwapApiSupportedChainId)(chainId)) {
|
|
45
|
+
return `Unsupported chainId: ${chainId}`;
|
|
46
|
+
}
|
|
47
|
+
const chain = (0, evm_1.getEvmChainById)(chainId);
|
|
48
|
+
const decimalsIn = await fetchDecimals({ walletProvider, token: args.fromAssetAddress });
|
|
49
|
+
if (!decimalsIn.success) {
|
|
50
|
+
return decimalsIn.message;
|
|
51
|
+
}
|
|
52
|
+
const amountIn = (0, viem_1.parseUnits)(args.amount, decimalsIn.decimals);
|
|
53
|
+
// First fetch to see if the swap is even possible
|
|
54
|
+
const firstSwap = await handleGetSwap({
|
|
55
|
+
amount: amountIn,
|
|
56
|
+
chainId,
|
|
57
|
+
tokenIn: args.fromAssetAddress,
|
|
58
|
+
tokenOut: args.toAssetAddress,
|
|
59
|
+
maxSlippage: args.maxSlippage,
|
|
60
|
+
recipient: walletProvider.getAddress(),
|
|
61
|
+
});
|
|
62
|
+
if (firstSwap.swap.status !== evm_1.RouteStatus.Success) {
|
|
63
|
+
return firstSwap.message;
|
|
64
|
+
}
|
|
65
|
+
// Check if the wallet has enough balance to perform the swap
|
|
66
|
+
const balance = await handleBalance({
|
|
67
|
+
walletProvider,
|
|
68
|
+
token: firstSwap.swap.tokenFrom,
|
|
69
|
+
minAmount: amountIn,
|
|
70
|
+
});
|
|
71
|
+
if (!balance.success) {
|
|
72
|
+
return balance.message;
|
|
73
|
+
}
|
|
74
|
+
const approval = await handleApproval({
|
|
75
|
+
walletProvider,
|
|
76
|
+
token: args.fromAssetAddress,
|
|
77
|
+
to: firstSwap.swap.tx.to,
|
|
78
|
+
amount: amountIn,
|
|
79
|
+
});
|
|
80
|
+
if (!approval.success) {
|
|
81
|
+
return approval.message;
|
|
82
|
+
}
|
|
83
|
+
// Refetch in case the route changed during approval
|
|
84
|
+
const secondSwap = await handleGetSwap({
|
|
85
|
+
amount: amountIn,
|
|
86
|
+
chainId,
|
|
87
|
+
tokenIn: args.fromAssetAddress,
|
|
88
|
+
tokenOut: args.toAssetAddress,
|
|
89
|
+
maxSlippage: args.maxSlippage,
|
|
90
|
+
recipient: walletProvider.getAddress(),
|
|
91
|
+
});
|
|
92
|
+
if (secondSwap.swap.status !== evm_1.RouteStatus.Success) {
|
|
93
|
+
return secondSwap.message;
|
|
94
|
+
}
|
|
95
|
+
const swapHash = await walletProvider.sendTransaction({
|
|
96
|
+
from: secondSwap.swap.tx.from,
|
|
97
|
+
to: secondSwap.swap.tx.to,
|
|
98
|
+
data: secondSwap.swap.tx.data,
|
|
99
|
+
value: BigInt(secondSwap.swap.tx.value || 0),
|
|
100
|
+
});
|
|
101
|
+
const swapReceipt = await walletProvider.waitForTransactionReceipt(swapHash);
|
|
102
|
+
if (swapReceipt.status === "reverted" || swapReceipt.status === "failed") {
|
|
103
|
+
return `Swap failed: Transaction Reverted.\n - Transaction hash: ${swapHash}\n - Transaction link: ${chain.getTransactionUrl(swapHash)}`;
|
|
104
|
+
}
|
|
105
|
+
// Find the Route event log, which includes the actual amountOut
|
|
106
|
+
const [routeLog] = swapReceipt.logs
|
|
107
|
+
.filter(log => (0, viem_1.encodeEventTopics)({
|
|
108
|
+
abi: constants_1.routeProcessor9Abi_Route,
|
|
109
|
+
eventName: "Route",
|
|
110
|
+
})[0] === log.topics[0])
|
|
111
|
+
.map(log => (0, viem_1.decodeEventLog)({
|
|
112
|
+
abi: constants_1.routeProcessor9Abi_Route,
|
|
113
|
+
data: log.data,
|
|
114
|
+
topics: log.topics,
|
|
115
|
+
}));
|
|
116
|
+
return `Swapped ${(0, viem_1.formatUnits)(routeLog.args.amountIn, secondSwap.swap.tokenFrom.decimals)} of ${secondSwap.swap.tokenFrom.symbol} (${args.fromAssetAddress}) for ${(0, viem_1.formatUnits)(routeLog.args.amountOut, secondSwap.swap.tokenTo.decimals)} of ${secondSwap.swap.tokenTo.symbol} (${args.toAssetAddress}) on ${chain.shortName}
|
|
117
|
+
- Transaction hash: ${swapHash}
|
|
118
|
+
- Transaction link: ${chain.getTransactionUrl(swapHash)}`;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return `Error swapping tokens: ${error}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Gets a quote for a specified amount of a from token to a to token
|
|
126
|
+
*
|
|
127
|
+
* @param walletProvider - The wallet provider to swap the tokens from.
|
|
128
|
+
* @param args - The input arguments for the action.
|
|
129
|
+
* @returns A message containing the quote details.
|
|
130
|
+
*/
|
|
131
|
+
async quote(walletProvider, args) {
|
|
132
|
+
try {
|
|
133
|
+
const chainId = Number((await walletProvider.getNetwork()).chainId);
|
|
134
|
+
// Compatible chainId is expected since it should be pre-checked in supportsNetwork
|
|
135
|
+
if (!(0, evm_1.isSwapApiSupportedChainId)(chainId)) {
|
|
136
|
+
return `Unsupported chainId: ${chainId}`;
|
|
137
|
+
}
|
|
138
|
+
const decimalsIn = await fetchDecimals({ walletProvider, token: args.fromAssetAddress });
|
|
139
|
+
if (!decimalsIn.success) {
|
|
140
|
+
return decimalsIn.message;
|
|
141
|
+
}
|
|
142
|
+
const amountIn = (0, viem_1.parseUnits)(args.amount, decimalsIn.decimals);
|
|
143
|
+
const swap = await handleGetSwap({
|
|
144
|
+
amount: amountIn,
|
|
145
|
+
chainId,
|
|
146
|
+
tokenIn: args.fromAssetAddress,
|
|
147
|
+
tokenOut: args.toAssetAddress,
|
|
148
|
+
maxSlippage: 0.0005, // 0.05%
|
|
149
|
+
recipient: walletProvider.getAddress(),
|
|
150
|
+
});
|
|
151
|
+
return swap.message;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return `Error quoting for tokens: ${error}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Custom action providers are supported on all networks
|
|
159
|
+
*
|
|
160
|
+
* @param network - The network to checkpointSaver
|
|
161
|
+
* @returns True if the network is supported, false otherwise
|
|
162
|
+
*/
|
|
163
|
+
supportsNetwork(network) {
|
|
164
|
+
if (network.protocolFamily !== "evm" || !network.chainId) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return (0, evm_1.isSwapApiSupportedChainId)(Number(network.chainId));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.SushiRouterActionProvider = SushiRouterActionProvider;
|
|
171
|
+
__decorate([
|
|
172
|
+
(0, actionDecorator_1.CreateAction)({
|
|
173
|
+
name: "swap",
|
|
174
|
+
description: `This tool will swap a specified amount of a 'from token' (erc20) to a 'to token' (erc20) for the wallet.
|
|
175
|
+
It takes the following inputs:
|
|
176
|
+
- The human-readable amount of the 'from token' to swap
|
|
177
|
+
- The from token address to trade
|
|
178
|
+
- The token address to receive from the swap
|
|
179
|
+
- The maximum slippage allowed for the swap, where 0 is 0% and 1 is 100%, the default is 0.005 (0.05%)
|
|
180
|
+
|
|
181
|
+
Important notes:
|
|
182
|
+
- The native asset (ie 'eth' on 'ethereum-mainnet') is represented by ${evm_1.nativeAddress} (not the wrapped native asset!)
|
|
183
|
+
- Fetch a quote first before the swap action. Stop, ask the user if they want to proceed. If the user answers affirmatively, then swap
|
|
184
|
+
- If you are not absolutely sure about token addresses, either use an action to fetch the token address or ask the user
|
|
185
|
+
`,
|
|
186
|
+
schema: sushiRouterSchemas_1.SushiSwapSchema,
|
|
187
|
+
}),
|
|
188
|
+
__metadata("design:type", Function),
|
|
189
|
+
__metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
|
|
190
|
+
__metadata("design:returntype", Promise)
|
|
191
|
+
], SushiRouterActionProvider.prototype, "swap", null);
|
|
192
|
+
__decorate([
|
|
193
|
+
(0, actionDecorator_1.CreateAction)({
|
|
194
|
+
name: "quote",
|
|
195
|
+
description: `This tool will fetch a quote for a specified amount of a 'from token' (erc20 or native ETH) to a 'to token' (erc20 or native ETH).
|
|
196
|
+
It takes the following inputs:
|
|
197
|
+
- The human-readable amount of the 'from token' to fetch a quote for
|
|
198
|
+
- The from token address to fetch a quote for
|
|
199
|
+
- The token address to receive from the quoted swap
|
|
200
|
+
|
|
201
|
+
Important notes:
|
|
202
|
+
- The native asset (ie 'eth' on 'ethereum-mainnet') is represented by ${evm_1.nativeAddress} (not the wrapped native asset!)
|
|
203
|
+
- This action does not require any on-chain transactions or gas
|
|
204
|
+
- If you are not 100% certain about token addresses, use an action to fetch the token address first or ask the user
|
|
205
|
+
- NEVER assume that tokens have the same address on across networks (ie the address of 'usdc' on 'ethereum-mainnet' is different from 'usdc' on 'base-mainnet')
|
|
206
|
+
`,
|
|
207
|
+
schema: sushiRouterSchemas_1.SushiQuoteSchema,
|
|
208
|
+
}),
|
|
209
|
+
__metadata("design:type", Function),
|
|
210
|
+
__metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
|
|
211
|
+
__metadata("design:returntype", Promise)
|
|
212
|
+
], SushiRouterActionProvider.prototype, "quote", null);
|
|
213
|
+
/**
|
|
214
|
+
* Fetches the number of decimals for the token
|
|
215
|
+
*
|
|
216
|
+
* @param root0 - The input arguments for the action
|
|
217
|
+
* @param root0.walletProvider - The wallet provider to fetch the decimals from
|
|
218
|
+
* @param root0.token - The token address to fetch the decimals for
|
|
219
|
+
*
|
|
220
|
+
* @returns The number of decimals for the token
|
|
221
|
+
*/
|
|
222
|
+
async function fetchDecimals({ walletProvider, token, }) {
|
|
223
|
+
const chainId = Number((await walletProvider.getNetwork()).chainId);
|
|
224
|
+
if (!(0, evm_1.isSwapApiSupportedChainId)(chainId)) {
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
message: `Unsupported chainId: ${chainId}`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (token === evm_1.nativeAddress) {
|
|
231
|
+
return { success: true, decimals: evm_1.EvmNative.fromChainId(chainId).decimals };
|
|
232
|
+
}
|
|
233
|
+
const decimals = (await walletProvider.readContract({
|
|
234
|
+
address: token,
|
|
235
|
+
abi: viem_1.erc20Abi,
|
|
236
|
+
functionName: "decimals",
|
|
237
|
+
}));
|
|
238
|
+
return { success: true, decimals };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Checks if the wallet has enough balance to perform the swap
|
|
242
|
+
*
|
|
243
|
+
* @param root0 - The input arguments for the action
|
|
244
|
+
* @param root0.walletProvider - The wallet provider to fetch the balance from
|
|
245
|
+
* @param root0.token - The token address to fetch the balance for
|
|
246
|
+
* @param root0.token.address - The token address to fetch the balance for
|
|
247
|
+
* @param root0.token.symbol - The token symbol to fetch the balance for
|
|
248
|
+
* @param root0.token.decimals - The token decimals to fetch the balance for
|
|
249
|
+
* @param root0.minAmount - The minimum amount to check for
|
|
250
|
+
*
|
|
251
|
+
* @returns The balance of the wallet
|
|
252
|
+
*/
|
|
253
|
+
async function handleBalance({ walletProvider, token, minAmount, }) {
|
|
254
|
+
let balance;
|
|
255
|
+
if (token.address.toLowerCase() === evm_1.nativeAddress) {
|
|
256
|
+
balance = await walletProvider.getBalance();
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
balance = (await walletProvider.readContract({
|
|
260
|
+
address: token.address,
|
|
261
|
+
abi: viem_1.erc20Abi,
|
|
262
|
+
functionName: "balanceOf",
|
|
263
|
+
args: [walletProvider.getAddress()],
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
if (balance < minAmount) {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
message: `Swap failed: Insufficient balance for ${token.symbol} (${token.address})
|
|
270
|
+
- Balance: ${(0, viem_1.formatUnits)(balance, token.decimals)}
|
|
271
|
+
- Required: ${(0, viem_1.formatUnits)(minAmount, token.decimals)}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
success: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
*
|
|
280
|
+
* Wraps the getSwap function, providing messages for possible states
|
|
281
|
+
*
|
|
282
|
+
* @param root0 - The input arguments for the action
|
|
283
|
+
* @param root0.amount - The amount to swap
|
|
284
|
+
* @param root0.chainId - The chainId to swap on
|
|
285
|
+
* @param root0.tokenIn - The input token address
|
|
286
|
+
* @param root0.tokenOut - The output token address
|
|
287
|
+
* @param root0.maxSlippage - The maximum slippage allowed
|
|
288
|
+
* @param root0.recipient - The recipient of the swap
|
|
289
|
+
*
|
|
290
|
+
* @returns The result of the swap and a message
|
|
291
|
+
*/
|
|
292
|
+
async function handleGetSwap({ amount, chainId, tokenIn, tokenOut, maxSlippage, recipient, }) {
|
|
293
|
+
if (!(0, evm_1.isRedSnwapperChainId)(chainId)) {
|
|
294
|
+
return {
|
|
295
|
+
swap: { status: evm_1.RouteStatus.NoWay },
|
|
296
|
+
message: `Unsupported chainId: ${chainId}`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const swap = await (0, evm_1.getSwap)({
|
|
300
|
+
amount,
|
|
301
|
+
chainId,
|
|
302
|
+
tokenIn,
|
|
303
|
+
tokenOut,
|
|
304
|
+
maxSlippage,
|
|
305
|
+
sender: recipient,
|
|
306
|
+
recipient,
|
|
307
|
+
});
|
|
308
|
+
const chain = (0, evm_1.getEvmChainById)(chainId);
|
|
309
|
+
if (swap.status === evm_1.RouteStatus.NoWay) {
|
|
310
|
+
return {
|
|
311
|
+
swap,
|
|
312
|
+
message: `No route found to swap ${amount} of ${tokenIn} for ${tokenOut} on ${chain.shortName}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (swap.status === evm_1.RouteStatus.Partial) {
|
|
316
|
+
return {
|
|
317
|
+
swap,
|
|
318
|
+
message: `Found a partial quote for ${swap.tokenFrom.symbol} -> ${swap.tokenTo.symbol}. Swapping the full amount is not possible.
|
|
319
|
+
- AmountIn: ${(0, viem_1.formatUnits)(BigInt(swap.amountIn), swap.tokenFrom.decimals)}
|
|
320
|
+
- AmountOut: ${(0, viem_1.formatUnits)(BigInt(swap.assumedAmountOut), swap.tokenTo.decimals)}`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
swap,
|
|
325
|
+
message: `Found a quote for ${swap.tokenFrom.symbol} (${swap.tokenFrom.address}) -> ${swap.tokenTo.symbol} (${swap.tokenTo.address})
|
|
326
|
+
- AmountIn: ${(0, viem_1.formatUnits)(BigInt(swap.amountIn), swap.tokenFrom.decimals)} ${swap.tokenFrom.symbol}
|
|
327
|
+
- AmountOut: ${(0, viem_1.formatUnits)(BigInt(swap.assumedAmountOut), swap.tokenTo.decimals)} ${swap.tokenTo.symbol}`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
*
|
|
332
|
+
* Handles the approval for the token
|
|
333
|
+
*
|
|
334
|
+
* @param root0 - The input arguments for the action
|
|
335
|
+
* @param root0.walletProvider - The wallet provider to handle the approval with
|
|
336
|
+
* @param root0.token - The token address to approve
|
|
337
|
+
* @param root0.to - The address to approve the token to
|
|
338
|
+
* @param root0.amount - The amount to approve
|
|
339
|
+
*
|
|
340
|
+
* @returns Either success: true on success or success: false with a message containing the reason for failure
|
|
341
|
+
*/
|
|
342
|
+
async function handleApproval({ walletProvider, token, to, amount, }) {
|
|
343
|
+
// No need to approve if the token is the native token
|
|
344
|
+
if (token.toLowerCase() === evm_1.nativeAddress) {
|
|
345
|
+
return { success: true };
|
|
346
|
+
}
|
|
347
|
+
// Check if the wallet already has enough allowance
|
|
348
|
+
const allowance = (await walletProvider.readContract({
|
|
349
|
+
address: token,
|
|
350
|
+
abi: viem_1.erc20Abi,
|
|
351
|
+
functionName: "allowance",
|
|
352
|
+
args: [walletProvider.getAddress(), to],
|
|
353
|
+
}));
|
|
354
|
+
if (allowance >= amount) {
|
|
355
|
+
return { success: true };
|
|
356
|
+
}
|
|
357
|
+
// Exact approval
|
|
358
|
+
const approvalHash = await walletProvider.sendTransaction({
|
|
359
|
+
to: token,
|
|
360
|
+
data: (0, viem_1.encodeFunctionData)({
|
|
361
|
+
abi: viem_1.erc20Abi,
|
|
362
|
+
functionName: "approve",
|
|
363
|
+
args: [to, BigInt(amount)],
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
const approvalReceipt = await walletProvider.waitForTransactionReceipt(approvalHash);
|
|
367
|
+
const chainId = Number((await walletProvider.getNetwork()).chainId);
|
|
368
|
+
if (!(0, evm_1.isSwapApiSupportedChainId)(chainId)) {
|
|
369
|
+
return {
|
|
370
|
+
success: false,
|
|
371
|
+
message: `Unsupported chainId: ${chainId}`,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const chain = (0, evm_1.getEvmChainById)(chainId);
|
|
375
|
+
if (approvalReceipt.status === "reverted") {
|
|
376
|
+
return {
|
|
377
|
+
success: false,
|
|
378
|
+
message: `Swap failed: Approval Reverted.
|
|
379
|
+
- Transaction hash: ${approvalHash}
|
|
380
|
+
- Transaction link: ${chain.getTransactionUrl(approvalHash)}`,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return { success: true };
|
|
384
|
+
}
|
|
385
|
+
const sushiRouterActionProvider = () => new SushiRouterActionProvider();
|
|
386
|
+
exports.sushiRouterActionProvider = sushiRouterActionProvider;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const sushiRouterSchemas_1 = require("./sushiRouterSchemas");
|
|
4
|
+
const evm_1 = require("sushi/evm");
|
|
5
|
+
const sushiRouterActionProvider_1 = require("./sushiRouterActionProvider");
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const constants_1 = require("./constants");
|
|
8
|
+
// Mock the entire module
|
|
9
|
+
jest.mock("sushi/evm", () => {
|
|
10
|
+
const originalModule = jest.requireActual("sushi/evm");
|
|
11
|
+
return {
|
|
12
|
+
__esModule: true,
|
|
13
|
+
...originalModule,
|
|
14
|
+
getSwap: jest.fn(originalModule.getSwap),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const mockedGetSwap = evm_1.getSwap;
|
|
18
|
+
describe("Sushi Action Provider Input Schemas", () => {
|
|
19
|
+
describe("Swap Schema", () => {
|
|
20
|
+
it("should successfully parse valid input", () => {
|
|
21
|
+
const validInput = {
|
|
22
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
23
|
+
amount: "0.0001",
|
|
24
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
25
|
+
maxSlippage: 0.005,
|
|
26
|
+
};
|
|
27
|
+
const result = sushiRouterSchemas_1.SushiSwapSchema.safeParse(validInput);
|
|
28
|
+
expect(result.success).toBe(true);
|
|
29
|
+
expect(result.data).toEqual(validInput);
|
|
30
|
+
});
|
|
31
|
+
it("should fail parsing invalid fromAssetAddress", () => {
|
|
32
|
+
const invalidInput = {
|
|
33
|
+
fromAssetAddress: "invalid-address",
|
|
34
|
+
amount: "0.0001",
|
|
35
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
36
|
+
maxSlippage: 0.005,
|
|
37
|
+
};
|
|
38
|
+
const result = sushiRouterSchemas_1.SushiSwapSchema.safeParse(invalidInput);
|
|
39
|
+
expect(result.success).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
it("should fail parsing invalid toAssetAddress", () => {
|
|
42
|
+
const invalidInput = {
|
|
43
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
44
|
+
amount: "0.0001",
|
|
45
|
+
toAssetAddress: "invalid-address",
|
|
46
|
+
maxSlippage: 0.005,
|
|
47
|
+
};
|
|
48
|
+
const result = sushiRouterSchemas_1.SushiSwapSchema.safeParse(invalidInput);
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
it("should fail parsing invalid maxSlippage (>1)", () => {
|
|
52
|
+
const invalidInput = {
|
|
53
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
54
|
+
amount: "0.0001",
|
|
55
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
56
|
+
maxSlippage: 1.1,
|
|
57
|
+
};
|
|
58
|
+
const result = sushiRouterSchemas_1.SushiSwapSchema.safeParse(invalidInput);
|
|
59
|
+
expect(result.success).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it("should fail parsing invalid maxSlippage (<0)", () => {
|
|
62
|
+
const invalidInput = {
|
|
63
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
64
|
+
amount: "0.0001",
|
|
65
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
66
|
+
maxSlippage: -1.1,
|
|
67
|
+
};
|
|
68
|
+
const result = sushiRouterSchemas_1.SushiSwapSchema.safeParse(invalidInput);
|
|
69
|
+
expect(result.success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("Quote Schema", () => {
|
|
73
|
+
it("should successfully parse valid input", () => {
|
|
74
|
+
const validInput = {
|
|
75
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
76
|
+
amount: "0.0001",
|
|
77
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
78
|
+
};
|
|
79
|
+
const result = sushiRouterSchemas_1.SushiQuoteSchema.safeParse(validInput);
|
|
80
|
+
expect(result.success).toBe(true);
|
|
81
|
+
expect(result.data).toEqual(validInput);
|
|
82
|
+
});
|
|
83
|
+
it("should fail parsing invalid fromAssetAddress", () => {
|
|
84
|
+
const invalidInput = {
|
|
85
|
+
fromAssetAddress: "invalid-address",
|
|
86
|
+
amount: "0.0001",
|
|
87
|
+
toAssetAddress: "0x1234567890123456789012345678901234567890",
|
|
88
|
+
maxSlippage: 0.005,
|
|
89
|
+
};
|
|
90
|
+
const result = sushiRouterSchemas_1.SushiQuoteSchema.safeParse(invalidInput);
|
|
91
|
+
expect(result.success).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
it("should fail parsing invalid toAssetAddress", () => {
|
|
94
|
+
const invalidInput = {
|
|
95
|
+
fromAssetAddress: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83",
|
|
96
|
+
amount: "0.0001",
|
|
97
|
+
toAssetAddress: "invalid-address",
|
|
98
|
+
maxSlippage: 0.005,
|
|
99
|
+
};
|
|
100
|
+
const result = sushiRouterSchemas_1.SushiQuoteSchema.safeParse(invalidInput);
|
|
101
|
+
expect(result.success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("Sushi Action Provider", () => {
|
|
106
|
+
let actionProvider;
|
|
107
|
+
let mockWallet;
|
|
108
|
+
const amountIn = BigInt(1000000);
|
|
109
|
+
const amountOut = BigInt(500000);
|
|
110
|
+
const nativeToken = {
|
|
111
|
+
address: evm_1.nativeAddress,
|
|
112
|
+
symbol: "ETH",
|
|
113
|
+
name: "Ether",
|
|
114
|
+
decimals: 18,
|
|
115
|
+
};
|
|
116
|
+
const tokenIn = {
|
|
117
|
+
address: "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa",
|
|
118
|
+
symbol: "TIN",
|
|
119
|
+
name: "Token In",
|
|
120
|
+
decimals: 18,
|
|
121
|
+
};
|
|
122
|
+
const tokenOut = {
|
|
123
|
+
address: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
|
|
124
|
+
symbol: "TOU",
|
|
125
|
+
name: "Token Out",
|
|
126
|
+
decimals: 18,
|
|
127
|
+
};
|
|
128
|
+
const user = "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF";
|
|
129
|
+
const txHash = "0xhash";
|
|
130
|
+
const chainId = 1;
|
|
131
|
+
const getRouteLog = ({ tokenIn, tokenOut, amountIn, amountOut, }) => [
|
|
132
|
+
{
|
|
133
|
+
data: (0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)("address to, address tokenOut, uint256 amountIn, uint256 amountOut, int256 slippage, bytes32 diagnosticsFirst32"), [user, tokenOut.address, amountIn, amountOut, 0n, `0x${"00".repeat(32)}`]),
|
|
134
|
+
topics: (0, viem_1.encodeEventTopics)({
|
|
135
|
+
abi: constants_1.routeProcessor9Abi_Route,
|
|
136
|
+
eventName: "Route",
|
|
137
|
+
args: {
|
|
138
|
+
from: user,
|
|
139
|
+
tokenIn: tokenIn.address,
|
|
140
|
+
referralCode: 0,
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
const getSuccessfullSwapResponse = async ({ tokenIn, amountIn, tokenOut, amountOut, }) => ({
|
|
146
|
+
amountIn: String(amountIn),
|
|
147
|
+
assumedAmountOut: String(amountOut),
|
|
148
|
+
priceImpact: 0,
|
|
149
|
+
status: evm_1.RouteStatus.Success,
|
|
150
|
+
swapPrice: 1,
|
|
151
|
+
tokens: [tokenIn, tokenOut],
|
|
152
|
+
tokenFrom: tokenIn,
|
|
153
|
+
tokenTo: tokenOut,
|
|
154
|
+
tx: {
|
|
155
|
+
to: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
|
156
|
+
data: "0x",
|
|
157
|
+
from: user,
|
|
158
|
+
value: BigInt(0),
|
|
159
|
+
gas: "1000000",
|
|
160
|
+
gasPrice: 1000000000,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
// Reset all mocks before each test
|
|
165
|
+
jest.clearAllMocks();
|
|
166
|
+
actionProvider = new sushiRouterActionProvider_1.SushiRouterActionProvider();
|
|
167
|
+
mockWallet = {
|
|
168
|
+
readContract: jest.fn(),
|
|
169
|
+
sendTransaction: jest.fn(),
|
|
170
|
+
waitForTransactionReceipt: jest.fn(),
|
|
171
|
+
getBalance: jest.fn(),
|
|
172
|
+
getNetwork: jest.fn().mockResolvedValue({
|
|
173
|
+
protocolFamily: "evm",
|
|
174
|
+
networkId: "ethereum-mainnet",
|
|
175
|
+
chainId: String(chainId),
|
|
176
|
+
}),
|
|
177
|
+
getAddress: jest.fn().mockReturnValue(user),
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
describe("swap", () => {
|
|
181
|
+
it("should successfully perform a swap (token -> token)", async () => {
|
|
182
|
+
const args = {
|
|
183
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
184
|
+
fromAssetAddress: tokenIn.address,
|
|
185
|
+
toAssetAddress: tokenOut.address,
|
|
186
|
+
maxSlippage: 0.005,
|
|
187
|
+
};
|
|
188
|
+
/*
|
|
189
|
+
* 1. Mock the readContract which checks the decimals of the fromAssetAddress token (18, default)
|
|
190
|
+
* 2. Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance)
|
|
191
|
+
* 3. Mock the readContract which checks for the approval (0, not approved)
|
|
192
|
+
*/
|
|
193
|
+
mockWallet.readContract
|
|
194
|
+
.mockResolvedValueOnce(tokenIn.decimals)
|
|
195
|
+
.mockResolvedValueOnce(amountIn)
|
|
196
|
+
.mockResolvedValueOnce(BigInt(0));
|
|
197
|
+
mockWallet.sendTransaction.mockResolvedValue(txHash);
|
|
198
|
+
/*
|
|
199
|
+
* 1. Mock the waitForTransactionReceipt to return success for the approval tx
|
|
200
|
+
* 2. Mock the waitForTransactionReceipt to return success for the swap tx, including the Route log
|
|
201
|
+
*/
|
|
202
|
+
mockWallet.waitForTransactionReceipt
|
|
203
|
+
.mockResolvedValueOnce({
|
|
204
|
+
status: "success",
|
|
205
|
+
})
|
|
206
|
+
.mockResolvedValueOnce({
|
|
207
|
+
status: "success",
|
|
208
|
+
logs: getRouteLog({
|
|
209
|
+
tokenIn,
|
|
210
|
+
tokenOut,
|
|
211
|
+
amountIn,
|
|
212
|
+
amountOut,
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
mockedGetSwap.mockReturnValue(getSuccessfullSwapResponse({
|
|
216
|
+
tokenIn,
|
|
217
|
+
amountIn,
|
|
218
|
+
tokenOut,
|
|
219
|
+
amountOut,
|
|
220
|
+
}));
|
|
221
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
222
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(3); // Decimals + Balance + Approval
|
|
223
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(2); // Approval + Swap
|
|
224
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(2);
|
|
225
|
+
expect(result).toContain(`Swapped ${(0, viem_1.formatUnits)(amountIn, tokenIn.decimals)} of ${tokenIn.symbol} (${tokenIn.address}) for ${(0, viem_1.formatUnits)(amountOut, tokenOut.decimals)} of ${tokenOut.symbol} (${tokenOut.address})`);
|
|
226
|
+
expect(result).toContain(`Transaction hash: ${txHash}`);
|
|
227
|
+
expect(result).toContain(`Transaction link: ${(0, evm_1.getEvmChainById)(chainId).getTransactionUrl(txHash)}`);
|
|
228
|
+
expect(result).toContain(`on ${(0, evm_1.getEvmChainById)(chainId).shortName}`);
|
|
229
|
+
});
|
|
230
|
+
it("should successfully perform a swap (native -> token)", async () => {
|
|
231
|
+
const args = {
|
|
232
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
233
|
+
fromAssetAddress: nativeToken.address,
|
|
234
|
+
toAssetAddress: tokenOut.address,
|
|
235
|
+
maxSlippage: 0.005,
|
|
236
|
+
};
|
|
237
|
+
// Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance)
|
|
238
|
+
mockWallet.getBalance.mockResolvedValue(amountIn);
|
|
239
|
+
mockWallet.sendTransaction.mockResolvedValue(txHash);
|
|
240
|
+
// Mock the waitForTransactionReceipt to return success for the swap tx, including the Route log
|
|
241
|
+
mockWallet.waitForTransactionReceipt.mockResolvedValueOnce({
|
|
242
|
+
status: "success",
|
|
243
|
+
logs: getRouteLog({
|
|
244
|
+
tokenIn: nativeToken,
|
|
245
|
+
tokenOut,
|
|
246
|
+
amountIn,
|
|
247
|
+
amountOut,
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
mockedGetSwap.mockReturnValue(getSuccessfullSwapResponse({
|
|
251
|
+
tokenIn: nativeToken,
|
|
252
|
+
amountIn,
|
|
253
|
+
tokenOut,
|
|
254
|
+
amountOut,
|
|
255
|
+
}));
|
|
256
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
257
|
+
expect(mockWallet.getBalance).toHaveBeenCalledTimes(1);
|
|
258
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(0); // No balance check nor approval
|
|
259
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(1); // Swap
|
|
260
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(2);
|
|
261
|
+
expect(result).toContain(`Swapped ${(0, viem_1.formatUnits)(amountIn, nativeToken.decimals)} of ${nativeToken.symbol} (${nativeToken.address}) for ${(0, viem_1.formatUnits)(amountOut, tokenOut.decimals)} of ${tokenOut.symbol} (${tokenOut.address})`);
|
|
262
|
+
expect(result).toContain(`Transaction hash: ${txHash}`);
|
|
263
|
+
expect(result).toContain(`Transaction link: ${(0, evm_1.getEvmChainById)(chainId).getTransactionUrl(txHash)}`);
|
|
264
|
+
expect(result).toContain(`on ${(0, evm_1.getEvmChainById)(chainId).shortName}`);
|
|
265
|
+
});
|
|
266
|
+
it("should fail if there isn't enough balance (native)", async () => {
|
|
267
|
+
const args = {
|
|
268
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
269
|
+
fromAssetAddress: nativeToken.address,
|
|
270
|
+
toAssetAddress: tokenOut.address,
|
|
271
|
+
maxSlippage: 0.005,
|
|
272
|
+
};
|
|
273
|
+
// Mock the readContract which checks for the balance of the fromAssetAddress token (99, not enough balance)
|
|
274
|
+
mockWallet.getBalance.mockResolvedValue(amountIn - BigInt(1));
|
|
275
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
276
|
+
expect(mockWallet.getBalance).toHaveBeenCalledTimes(1);
|
|
277
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(0);
|
|
278
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(0);
|
|
279
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(1);
|
|
280
|
+
expect(result).toContain(`Swap failed: Insufficient balance for ${nativeToken.symbol} (${nativeToken.address})`);
|
|
281
|
+
});
|
|
282
|
+
it("should fail if there isn't enough balance (token)", async () => {
|
|
283
|
+
const args = {
|
|
284
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
285
|
+
fromAssetAddress: tokenIn.address,
|
|
286
|
+
toAssetAddress: tokenOut.address,
|
|
287
|
+
maxSlippage: 0.005,
|
|
288
|
+
};
|
|
289
|
+
/*
|
|
290
|
+
* 1. Mock the readContract which checks the decimals of the fromAssetAddress token (18, default)
|
|
291
|
+
* 2. Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance)
|
|
292
|
+
*/
|
|
293
|
+
mockWallet.readContract.mockResolvedValueOnce(18).mockResolvedValue(amountIn - BigInt(1));
|
|
294
|
+
mockedGetSwap.mockReturnValue(getSuccessfullSwapResponse({
|
|
295
|
+
tokenIn,
|
|
296
|
+
amountIn,
|
|
297
|
+
tokenOut,
|
|
298
|
+
amountOut,
|
|
299
|
+
}));
|
|
300
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
301
|
+
expect(mockWallet.getBalance).toHaveBeenCalledTimes(0);
|
|
302
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(2);
|
|
303
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(0);
|
|
304
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(1);
|
|
305
|
+
expect(result).toContain(`Swap failed: Insufficient balance for ${tokenIn.symbol} (${tokenIn.address})`);
|
|
306
|
+
});
|
|
307
|
+
it("should not approve if already approved", async () => {
|
|
308
|
+
const args = {
|
|
309
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
310
|
+
fromAssetAddress: tokenIn.address,
|
|
311
|
+
toAssetAddress: tokenOut.address,
|
|
312
|
+
maxSlippage: 0.005,
|
|
313
|
+
};
|
|
314
|
+
/*
|
|
315
|
+
* 1. Mock the readContract which checks the decimals of the fromAssetAddress token (18, default)
|
|
316
|
+
* 2. Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance)
|
|
317
|
+
* 3. Mock the readContract which checks for the approval (1000000, approved)
|
|
318
|
+
*/
|
|
319
|
+
mockWallet.readContract
|
|
320
|
+
.mockResolvedValueOnce(tokenIn.decimals)
|
|
321
|
+
.mockResolvedValueOnce(amountIn)
|
|
322
|
+
.mockResolvedValueOnce(amountIn);
|
|
323
|
+
mockWallet.sendTransaction.mockResolvedValue(txHash);
|
|
324
|
+
// Mock the waitForTransactionReceipt to return success for the swap tx, including the Route log
|
|
325
|
+
mockWallet.waitForTransactionReceipt.mockResolvedValueOnce({
|
|
326
|
+
status: "success",
|
|
327
|
+
logs: getRouteLog({
|
|
328
|
+
tokenIn,
|
|
329
|
+
tokenOut,
|
|
330
|
+
amountIn,
|
|
331
|
+
amountOut,
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
334
|
+
mockedGetSwap.mockReturnValue(getSuccessfullSwapResponse({
|
|
335
|
+
tokenIn,
|
|
336
|
+
amountIn,
|
|
337
|
+
tokenOut,
|
|
338
|
+
amountOut,
|
|
339
|
+
}));
|
|
340
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
341
|
+
expect(mockWallet.getBalance).toHaveBeenCalledTimes(0);
|
|
342
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(3); // Decimals + Balance + Allowance
|
|
343
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(1); // Swap
|
|
344
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(2);
|
|
345
|
+
expect(result).toContain(`Swapped`);
|
|
346
|
+
});
|
|
347
|
+
it("should fail if there's no route", async () => {
|
|
348
|
+
const args = {
|
|
349
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
350
|
+
fromAssetAddress: tokenIn.address,
|
|
351
|
+
toAssetAddress: tokenOut.address,
|
|
352
|
+
maxSlippage: 0.005,
|
|
353
|
+
};
|
|
354
|
+
/*
|
|
355
|
+
* 1. Mock the readContract which checks for decimals of the fromAssetAddress token (18, default)
|
|
356
|
+
* 2. Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance)
|
|
357
|
+
*/
|
|
358
|
+
mockWallet.readContract
|
|
359
|
+
.mockResolvedValueOnce(tokenIn.decimals)
|
|
360
|
+
.mockResolvedValueOnce(amountIn);
|
|
361
|
+
mockedGetSwap.mockReturnValue(new Promise(r => r({
|
|
362
|
+
status: evm_1.RouteStatus.NoWay,
|
|
363
|
+
})));
|
|
364
|
+
const result = await actionProvider.swap(mockWallet, args);
|
|
365
|
+
expect(mockWallet.getBalance).toHaveBeenCalledTimes(0);
|
|
366
|
+
expect(mockWallet.readContract).toHaveBeenCalledTimes(1); // Decimals
|
|
367
|
+
expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(0);
|
|
368
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(1);
|
|
369
|
+
expect(result).toContain(`No route found to swap ${amountIn} of ${tokenIn.address} for ${tokenOut.address}`);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
describe("quote", () => {
|
|
373
|
+
it("should successfully fetch a quote (token -> token)", async () => {
|
|
374
|
+
const args = {
|
|
375
|
+
amount: (0, viem_1.formatUnits)(amountIn, tokenIn.decimals),
|
|
376
|
+
fromAssetAddress: tokenIn.address,
|
|
377
|
+
toAssetAddress: tokenOut.address,
|
|
378
|
+
};
|
|
379
|
+
mockedGetSwap.mockReturnValue(getSuccessfullSwapResponse({
|
|
380
|
+
tokenIn,
|
|
381
|
+
amountIn,
|
|
382
|
+
tokenOut,
|
|
383
|
+
amountOut,
|
|
384
|
+
}));
|
|
385
|
+
const result = await actionProvider.quote(mockWallet, args);
|
|
386
|
+
expect(mockedGetSwap).toHaveBeenCalledTimes(1);
|
|
387
|
+
expect(result).toContain(`Found a quote for ${tokenIn.symbol} (${tokenIn.address}) -> ${tokenOut.symbol} (${tokenOut.address})`);
|
|
388
|
+
expect(result).toContain(`AmountIn: ${(0, viem_1.formatUnits)(amountIn, tokenIn.decimals)}`);
|
|
389
|
+
expect(result).toContain(`AmountOut: ${(0, viem_1.formatUnits)(amountOut, tokenOut.decimals)}`);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Input schema for asset swap action
|
|
4
|
+
*/
|
|
5
|
+
export declare const SushiSwapSchema: z.ZodObject<{
|
|
6
|
+
fromAssetAddress: z.ZodEffects<z.ZodString, `0x${string}`, string>;
|
|
7
|
+
amount: z.ZodString;
|
|
8
|
+
toAssetAddress: z.ZodEffects<z.ZodString, `0x${string}`, string>;
|
|
9
|
+
maxSlippage: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
amount: string;
|
|
12
|
+
fromAssetAddress: `0x${string}`;
|
|
13
|
+
toAssetAddress: `0x${string}`;
|
|
14
|
+
maxSlippage: number;
|
|
15
|
+
}, {
|
|
16
|
+
amount: string;
|
|
17
|
+
fromAssetAddress: string;
|
|
18
|
+
toAssetAddress: string;
|
|
19
|
+
maxSlippage?: number | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Input schema for quote action
|
|
23
|
+
*/
|
|
24
|
+
export declare const SushiQuoteSchema: z.ZodObject<{
|
|
25
|
+
fromAssetAddress: z.ZodEffects<z.ZodString, `0x${string}`, string>;
|
|
26
|
+
amount: z.ZodString;
|
|
27
|
+
toAssetAddress: z.ZodEffects<z.ZodString, `0x${string}`, string>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
amount: string;
|
|
30
|
+
fromAssetAddress: `0x${string}`;
|
|
31
|
+
toAssetAddress: `0x${string}`;
|
|
32
|
+
}, {
|
|
33
|
+
amount: string;
|
|
34
|
+
fromAssetAddress: string;
|
|
35
|
+
toAssetAddress: string;
|
|
36
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SushiQuoteSchema = exports.SushiSwapSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Input schema for asset swap action
|
|
7
|
+
*/
|
|
8
|
+
exports.SushiSwapSchema = zod_1.z
|
|
9
|
+
.object({
|
|
10
|
+
fromAssetAddress: zod_1.z
|
|
11
|
+
.string()
|
|
12
|
+
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
|
|
13
|
+
.transform(val => val)
|
|
14
|
+
.describe("The Ethereum address of the input asset"),
|
|
15
|
+
amount: zod_1.z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^\d+(\.\d+)?$/, "Invalid number format")
|
|
18
|
+
.describe("The amount of the input asset to swap, in the human readable format"),
|
|
19
|
+
toAssetAddress: zod_1.z
|
|
20
|
+
.string()
|
|
21
|
+
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
|
|
22
|
+
.transform(val => val)
|
|
23
|
+
.describe("The Ethereum address of the output asset"),
|
|
24
|
+
maxSlippage: zod_1.z
|
|
25
|
+
.number()
|
|
26
|
+
.min(0)
|
|
27
|
+
.max(1)
|
|
28
|
+
.optional()
|
|
29
|
+
.default(0.005) // 0.05%
|
|
30
|
+
.describe("The maximum slippage allowed for the swap, where 0 is 0% and 1 is 100%"),
|
|
31
|
+
})
|
|
32
|
+
.strip()
|
|
33
|
+
.describe("Instructions for trading assets");
|
|
34
|
+
/**
|
|
35
|
+
* Input schema for quote action
|
|
36
|
+
*/
|
|
37
|
+
exports.SushiQuoteSchema = zod_1.z
|
|
38
|
+
.object({
|
|
39
|
+
fromAssetAddress: zod_1.z
|
|
40
|
+
.string()
|
|
41
|
+
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
|
|
42
|
+
.transform(val => val)
|
|
43
|
+
.describe("The Ethereum address of the input asset"),
|
|
44
|
+
amount: zod_1.z
|
|
45
|
+
.string()
|
|
46
|
+
.regex(/^\d+(\.\d+)?$/, "Invalid number format")
|
|
47
|
+
.describe("The amount of the input asset to get a quote for"),
|
|
48
|
+
toAssetAddress: zod_1.z
|
|
49
|
+
.string()
|
|
50
|
+
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
|
|
51
|
+
.transform(val => val)
|
|
52
|
+
.describe("The Ethereum address of the output asset"),
|
|
53
|
+
})
|
|
54
|
+
.strip()
|
|
55
|
+
.describe("Instructions for fetching a quote for a trade");
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@coinbase/agentkit",
|
|
3
3
|
"description": "Coinbase AgentKit core primitives",
|
|
4
4
|
"repository": "https://github.com/coinbase/agentkit",
|
|
5
|
-
"version": "0.0.0-nightly-
|
|
5
|
+
"version": "0.0.0-nightly-20251117210447",
|
|
6
6
|
"author": "Coinbase Inc.",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"main": "dist/index.js",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"md5": "^2.3.0",
|
|
49
49
|
"opensea-js": "^7.1.18",
|
|
50
50
|
"reflect-metadata": "^0.2.2",
|
|
51
|
+
"sushi": "6.2.1",
|
|
51
52
|
"@ensofinance/sdk": "^2.0.6",
|
|
52
53
|
"twitter-api-v2": "^1.18.2",
|
|
53
54
|
"viem": "^2.22.16",
|