@coinbase/agentkit 0.0.0-nightly-20250831210403 → 0.0.0-nightly-20250902210403

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 CHANGED
@@ -524,6 +524,15 @@ it will return payment details that can be used on retry.</td>
524
524
  <tr>
525
525
  <td width="200"><code>make_http_request_with_x402</code></td>
526
526
  <td width="768">Combines make_http_request and retry_http_request_with_x402 into a single step.</td>
527
+ <summary><strong>ZeroX</strong></summary>
528
+ <table width="100%">
529
+ <tr>
530
+ <td width="200"><code>get_swap_price_quote_from_0x</code></td>
531
+ <td width="768">Fetches a price quote for swapping between two tokens using the 0x API.</td>
532
+ </tr>
533
+ <tr>
534
+ <td width="200"><code>execute_swap_on_0x</code></td>
535
+ <td width="768">Executes a token swap between two tokens using the 0x API.</td>
527
536
  </tr>
528
537
  </table>
529
538
  </details>
@@ -31,4 +31,5 @@ export * from "./vaultsfyi";
31
31
  export * from "./x402";
32
32
  export * from "./zerion";
33
33
  export * from "./zerodev";
34
+ export * from "./zeroX";
34
35
  export * from "./zora";
@@ -47,4 +47,5 @@ __exportStar(require("./vaultsfyi"), exports);
47
47
  __exportStar(require("./x402"), exports);
48
48
  __exportStar(require("./zerion"), exports);
49
49
  __exportStar(require("./zerodev"), exports);
50
+ __exportStar(require("./zeroX"), exports);
50
51
  __exportStar(require("./zora"), exports);
@@ -0,0 +1 @@
1
+ export * from "./zeroXActionProvider";
@@ -0,0 +1,17 @@
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("./zeroXActionProvider"), exports);
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Input schema for getting a swap price.
4
+ */
5
+ export declare const GetSwapPriceSchema: z.ZodObject<{
6
+ sellToken: z.ZodString;
7
+ buyToken: z.ZodString;
8
+ sellAmount: z.ZodString;
9
+ slippageBps: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
10
+ swapFeeRecipient: z.ZodOptional<z.ZodString>;
11
+ swapFeeBps: z.ZodDefault<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ slippageBps: number;
14
+ buyToken: string;
15
+ sellToken: string;
16
+ sellAmount: string;
17
+ swapFeeBps: number;
18
+ swapFeeRecipient?: string | undefined;
19
+ }, {
20
+ buyToken: string;
21
+ sellToken: string;
22
+ sellAmount: string;
23
+ slippageBps?: number | undefined;
24
+ swapFeeRecipient?: string | undefined;
25
+ swapFeeBps?: number | undefined;
26
+ }>;
27
+ /**
28
+ * Input schema for executing a swap.
29
+ */
30
+ export declare const ExecuteSwapSchema: z.ZodObject<{
31
+ sellToken: z.ZodString;
32
+ buyToken: z.ZodString;
33
+ sellAmount: z.ZodString;
34
+ slippageBps: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
35
+ swapFeeRecipient: z.ZodOptional<z.ZodString>;
36
+ swapFeeBps: z.ZodDefault<z.ZodNumber>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ slippageBps: number;
39
+ buyToken: string;
40
+ sellToken: string;
41
+ sellAmount: string;
42
+ swapFeeBps: number;
43
+ swapFeeRecipient?: string | undefined;
44
+ }, {
45
+ buyToken: string;
46
+ sellToken: string;
47
+ sellAmount: string;
48
+ slippageBps?: number | undefined;
49
+ swapFeeRecipient?: string | undefined;
50
+ swapFeeBps?: number | undefined;
51
+ }>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExecuteSwapSchema = exports.GetSwapPriceSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Input schema for getting a swap price.
7
+ */
8
+ exports.GetSwapPriceSchema = zod_1.z
9
+ .object({
10
+ sellToken: zod_1.z
11
+ .string()
12
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
13
+ .describe("The token contract address to sell"),
14
+ buyToken: zod_1.z
15
+ .string()
16
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
17
+ .describe("The token contract address to buy"),
18
+ sellAmount: zod_1.z
19
+ .string()
20
+ .describe("The amount of sellToken to sell in whole units (e.g., 1.5 WETH, 10 USDC)"),
21
+ slippageBps: zod_1.z
22
+ .number()
23
+ .int()
24
+ .min(0)
25
+ .max(10000)
26
+ .optional()
27
+ .default(100)
28
+ .describe("The maximum acceptable slippage in basis points (0-10000, default: 100)"),
29
+ swapFeeRecipient: zod_1.z
30
+ .string()
31
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
32
+ .optional()
33
+ .describe("The wallet address to receive an affiliate fee on the trade"),
34
+ swapFeeBps: zod_1.z
35
+ .number()
36
+ .int()
37
+ .min(0)
38
+ .max(1000)
39
+ .default(100)
40
+ .describe("The amount in basis points (0-1000) of the sellToken to charge as trading fee (defaults to 100 = 1%), only used if swapFeeRecipient is provided"),
41
+ })
42
+ .strip()
43
+ .describe("Get a price quote for swapping one token for another");
44
+ /**
45
+ * Input schema for executing a swap.
46
+ */
47
+ exports.ExecuteSwapSchema = zod_1.z
48
+ .object({
49
+ sellToken: zod_1.z
50
+ .string()
51
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
52
+ .describe("The token contract address to sell"),
53
+ buyToken: zod_1.z
54
+ .string()
55
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
56
+ .describe("The token contract address to buy"),
57
+ sellAmount: zod_1.z
58
+ .string()
59
+ .describe("The amount of sellToken to sell in whole units (e.g., 1.5 WETH, 10 USDC)"),
60
+ slippageBps: zod_1.z
61
+ .number()
62
+ .int()
63
+ .min(0)
64
+ .max(10000)
65
+ .optional()
66
+ .default(100)
67
+ .describe("The maximum acceptable slippage in basis points (0-10000, default: 100)"),
68
+ swapFeeRecipient: zod_1.z
69
+ .string()
70
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
71
+ .optional()
72
+ .describe("The wallet address to receive an affiliate fee on the trade"),
73
+ swapFeeBps: zod_1.z
74
+ .number()
75
+ .int()
76
+ .min(0)
77
+ .max(1000)
78
+ .default(100)
79
+ .describe("The amount in basis points (0-1000) of the sellToken to charge as trading fee (defaults to 100 = 1%), only used if swapFeeRecipient is provided"),
80
+ })
81
+ .strip()
82
+ .describe("Execute a swap between two tokens");
@@ -0,0 +1,23 @@
1
+ import { EvmWalletProvider } from "../../wallet-providers";
2
+ export declare const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
3
+ /**
4
+ * Checks if a token is native ETH.
5
+ *
6
+ * @param token - The token address to check.
7
+ * @returns True if the token is native ETH, false otherwise.
8
+ */
9
+ export declare function isNativeEth(token: string): boolean;
10
+ /**
11
+ * Gets the details (decimals and name) for both fromToken and toToken
12
+ *
13
+ * @param walletProvider - The EVM wallet provider to read contracts
14
+ * @param fromToken - The contract address of the from token
15
+ * @param toToken - The contract address of the to token
16
+ * @returns Promise<{fromTokenDecimals: number, toTokenDecimals: number, fromTokenName: string, toTokenName: string}>
17
+ */
18
+ export declare function getTokenDetails(walletProvider: EvmWalletProvider, fromToken: string, toToken: string): Promise<{
19
+ fromTokenDecimals: number;
20
+ toTokenDecimals: number;
21
+ fromTokenName: string;
22
+ toTokenName: string;
23
+ }>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PERMIT2_ADDRESS = void 0;
4
+ exports.isNativeEth = isNativeEth;
5
+ exports.getTokenDetails = getTokenDetails;
6
+ const viem_1 = require("viem");
7
+ // Permit2 contract address is the same across all networks
8
+ exports.PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
9
+ /**
10
+ * Checks if a token is native ETH.
11
+ *
12
+ * @param token - The token address to check.
13
+ * @returns True if the token is native ETH, false otherwise.
14
+ */
15
+ function isNativeEth(token) {
16
+ return token.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
17
+ }
18
+ /**
19
+ * Gets the details (decimals and name) for both fromToken and toToken
20
+ *
21
+ * @param walletProvider - The EVM wallet provider to read contracts
22
+ * @param fromToken - The contract address of the from token
23
+ * @param toToken - The contract address of the to token
24
+ * @returns Promise<{fromTokenDecimals: number, toTokenDecimals: number, fromTokenName: string, toTokenName: string}>
25
+ */
26
+ async function getTokenDetails(walletProvider, fromToken, toToken) {
27
+ // Initialize default values for native ETH
28
+ let fromTokenDecimals = 18;
29
+ let fromTokenName = "ETH";
30
+ let toTokenDecimals = 18;
31
+ let toTokenName = "ETH";
32
+ // Prepare multicall contracts array
33
+ const contracts = [];
34
+ const contractIndexMap = {
35
+ fromDecimals: -1,
36
+ fromName: -1,
37
+ toDecimals: -1,
38
+ toName: -1,
39
+ };
40
+ // Add from token contracts if not native ETH
41
+ if (!isNativeEth(fromToken)) {
42
+ contractIndexMap.fromDecimals = contracts.length;
43
+ contracts.push({
44
+ address: fromToken,
45
+ abi: viem_1.erc20Abi,
46
+ functionName: "decimals",
47
+ });
48
+ contractIndexMap.fromName = contracts.length;
49
+ contracts.push({
50
+ address: fromToken,
51
+ abi: viem_1.erc20Abi,
52
+ functionName: "name",
53
+ });
54
+ }
55
+ // Add to token contracts if not native ETH
56
+ if (!isNativeEth(toToken)) {
57
+ contractIndexMap.toDecimals = contracts.length;
58
+ contracts.push({
59
+ address: toToken,
60
+ abi: viem_1.erc20Abi,
61
+ functionName: "decimals",
62
+ });
63
+ contractIndexMap.toName = contracts.length;
64
+ contracts.push({
65
+ address: toToken,
66
+ abi: viem_1.erc20Abi,
67
+ functionName: "name",
68
+ });
69
+ }
70
+ // Execute multicall if there are contracts to call
71
+ if (contracts.length > 0) {
72
+ try {
73
+ const results = await walletProvider.getPublicClient().multicall({
74
+ contracts,
75
+ });
76
+ // Extract from token details
77
+ if (contractIndexMap.fromDecimals !== -1) {
78
+ const decimalsResult = results[contractIndexMap.fromDecimals];
79
+ const nameResult = results[contractIndexMap.fromName];
80
+ if (decimalsResult.status === "success" && nameResult.status === "success") {
81
+ fromTokenDecimals = decimalsResult.result;
82
+ fromTokenName = nameResult.result;
83
+ }
84
+ else {
85
+ throw new Error(`Failed to read details for fromToken ${fromToken}. This address may not be a valid ERC20 contract.`);
86
+ }
87
+ }
88
+ // Extract to token details
89
+ if (contractIndexMap.toDecimals !== -1) {
90
+ const decimalsResult = results[contractIndexMap.toDecimals];
91
+ const nameResult = results[contractIndexMap.toName];
92
+ if (decimalsResult.status === "success" && nameResult.status === "success") {
93
+ toTokenDecimals = decimalsResult.result;
94
+ toTokenName = nameResult.result;
95
+ }
96
+ else {
97
+ throw new Error(`Failed to read details for toToken ${toToken}. This address may not be a valid ERC20 contract.`);
98
+ }
99
+ }
100
+ }
101
+ catch (error) {
102
+ throw new Error(`Failed to read token details via multicall. Error: ${error}`);
103
+ }
104
+ }
105
+ return { fromTokenDecimals, toTokenDecimals, fromTokenName, toTokenName };
106
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import { ActionProvider } from "../actionProvider";
3
+ import { Network } from "../../network";
4
+ import { GetSwapPriceSchema, ExecuteSwapSchema } from "./schemas";
5
+ import { EvmWalletProvider } from "../../wallet-providers";
6
+ /**
7
+ * Configuration for the ZeroXActionProvider.
8
+ */
9
+ export interface ZeroXActionProviderConfig {
10
+ /**
11
+ * The API key to use for 0x API requests.
12
+ */
13
+ apiKey?: string;
14
+ }
15
+ /**
16
+ * 0x API Action Provider for token swaps.
17
+ * Requires a 0x API key.
18
+ */
19
+ export declare class ZeroXActionProvider extends ActionProvider<EvmWalletProvider> {
20
+ #private;
21
+ /**
22
+ * Constructor for the ZeroXActionProvider.
23
+ *
24
+ * @param config - Configuration for the provider.
25
+ */
26
+ constructor(config: ZeroXActionProviderConfig);
27
+ /**
28
+ * Gets a price quote for swapping one token for another.
29
+ *
30
+ * @param walletProvider - The wallet provider to get information from.
31
+ * @param args - The input arguments for the action.
32
+ * @returns A message containing the price quote.
33
+ */
34
+ getSwapPrice(walletProvider: EvmWalletProvider, args: z.infer<typeof GetSwapPriceSchema>): Promise<string>;
35
+ /**
36
+ * Executes a token swap using the 0x API.
37
+ *
38
+ * @param walletProvider - The wallet provider to use for the swap.
39
+ * @param args - The input arguments for the action.
40
+ * @returns A message containing the result of the swap.
41
+ */
42
+ executeSwap(walletProvider: EvmWalletProvider, args: z.infer<typeof ExecuteSwapSchema>): Promise<string>;
43
+ /**
44
+ * Checks if the ZeroX action provider supports the given network.
45
+ *
46
+ * @param network - The network to check.
47
+ * @returns True if the ZeroX action provider supports the network, false otherwise.
48
+ */
49
+ supportsNetwork: (network: Network) => boolean;
50
+ }
51
+ /**
52
+ * Creates a new ZeroXActionProvider with the provided configuration.
53
+ *
54
+ * @param config - Optional configuration for the provider.
55
+ * @returns A new ZeroXActionProvider.
56
+ */
57
+ export declare const zeroXActionProvider: (config: ZeroXActionProviderConfig) => ZeroXActionProvider;
@@ -0,0 +1,405 @@
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
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
12
+ if (kind === "m") throw new TypeError("Private method is not writable");
13
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
14
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
15
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
16
+ };
17
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
18
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
19
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
20
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
21
+ };
22
+ var _ZeroXActionProvider_apiKey;
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.zeroXActionProvider = exports.ZeroXActionProvider = void 0;
25
+ const zod_1 = require("zod");
26
+ const actionProvider_1 = require("../actionProvider");
27
+ const actionDecorator_1 = require("../actionDecorator");
28
+ const schemas_1 = require("./schemas");
29
+ const wallet_providers_1 = require("../../wallet-providers");
30
+ const viem_1 = require("viem");
31
+ const utils_1 = require("./utils");
32
+ /**
33
+ * 0x API Action Provider for token swaps.
34
+ * Requires a 0x API key.
35
+ */
36
+ class ZeroXActionProvider extends actionProvider_1.ActionProvider {
37
+ /**
38
+ * Constructor for the ZeroXActionProvider.
39
+ *
40
+ * @param config - Configuration for the provider.
41
+ */
42
+ constructor(config) {
43
+ super("zerox", []);
44
+ _ZeroXActionProvider_apiKey.set(this, void 0);
45
+ /**
46
+ * Checks if the ZeroX action provider supports the given network.
47
+ *
48
+ * @param network - The network to check.
49
+ * @returns True if the ZeroX action provider supports the network, false otherwise.
50
+ */
51
+ this.supportsNetwork = (network) => network.protocolFamily === "evm";
52
+ const apiKey = config.apiKey || process.env.ZEROX_API_KEY;
53
+ if (!apiKey) {
54
+ throw new Error("0x API key not provided.");
55
+ }
56
+ __classPrivateFieldSet(this, _ZeroXActionProvider_apiKey, apiKey, "f");
57
+ }
58
+ /**
59
+ * Gets a price quote for swapping one token for another.
60
+ *
61
+ * @param walletProvider - The wallet provider to get information from.
62
+ * @param args - The input arguments for the action.
63
+ * @returns A message containing the price quote.
64
+ */
65
+ async getSwapPrice(walletProvider, args) {
66
+ const network = walletProvider.getNetwork();
67
+ const chainId = network.chainId;
68
+ if (!chainId)
69
+ throw new Error("Chain ID not available from wallet provider");
70
+ try {
71
+ // Get token details
72
+ const { fromTokenDecimals: sellTokenDecimals, toTokenDecimals: buyTokenDecimals, fromTokenName: sellTokenName, toTokenName: buyTokenName, } = await (0, utils_1.getTokenDetails)(walletProvider, args.sellToken, args.buyToken);
73
+ // Convert sell amount to base units
74
+ const sellAmount = (0, viem_1.parseUnits)(args.sellAmount, sellTokenDecimals).toString();
75
+ // Create URL for the price API request
76
+ const url = new URL("https://api.0x.org/swap/permit2/price");
77
+ url.searchParams.append("chainId", chainId.toString());
78
+ url.searchParams.append("sellToken", args.sellToken);
79
+ url.searchParams.append("buyToken", args.buyToken);
80
+ url.searchParams.append("sellAmount", sellAmount);
81
+ url.searchParams.append("taker", walletProvider.getAddress());
82
+ url.searchParams.append("slippageBps", args.slippageBps.toString());
83
+ if (args.swapFeeRecipient) {
84
+ url.searchParams.append("swapFeeRecipient", args.swapFeeRecipient);
85
+ url.searchParams.append("swapFeeBps", args.swapFeeBps.toString());
86
+ url.searchParams.append("swapFeeToken", args.sellToken);
87
+ }
88
+ // Make the request
89
+ const response = await fetch(url.toString(), {
90
+ method: "GET",
91
+ headers: {
92
+ "Content-Type": "application/json",
93
+ "0x-api-key": __classPrivateFieldGet(this, _ZeroXActionProvider_apiKey, "f"),
94
+ "0x-version": "v2",
95
+ },
96
+ });
97
+ if (!response.ok) {
98
+ const errorText = await response.text();
99
+ return JSON.stringify({
100
+ success: false,
101
+ error: `Error fetching swap price: ${response.status} ${response.statusText} - ${errorText}`,
102
+ });
103
+ }
104
+ const data = await response.json();
105
+ // Format the response
106
+ const formattedResponse = {
107
+ success: true,
108
+ sellAmount: (0, viem_1.formatUnits)(BigInt(sellAmount), sellTokenDecimals),
109
+ sellTokenName: sellTokenName,
110
+ sellToken: args.sellToken,
111
+ buyAmount: (0, viem_1.formatUnits)(data.buyAmount, buyTokenDecimals),
112
+ minBuyAmount: data.minBuyAmount ? (0, viem_1.formatUnits)(data.minBuyAmount, buyTokenDecimals) : null,
113
+ buyTokenName: buyTokenName,
114
+ buyToken: args.buyToken,
115
+ slippageBps: args.slippageBps,
116
+ liquidityAvailable: data.liquidityAvailable,
117
+ balanceEnough: data.issues?.balance === null,
118
+ priceOfBuyTokenInSellToken: (Number((0, viem_1.formatUnits)(BigInt(sellAmount), sellTokenDecimals)) /
119
+ Number((0, viem_1.formatUnits)(data.buyAmount, buyTokenDecimals))).toString(),
120
+ priceOfSellTokenInBuyToken: (Number((0, viem_1.formatUnits)(data.buyAmount, buyTokenDecimals)) /
121
+ Number((0, viem_1.formatUnits)(BigInt(sellAmount), sellTokenDecimals))).toString(),
122
+ };
123
+ return JSON.stringify(formattedResponse);
124
+ }
125
+ catch (error) {
126
+ return JSON.stringify({
127
+ success: false,
128
+ error: `Error fetching swap price: ${error}`,
129
+ });
130
+ }
131
+ }
132
+ /**
133
+ * Executes a token swap using the 0x API.
134
+ *
135
+ * @param walletProvider - The wallet provider to use for the swap.
136
+ * @param args - The input arguments for the action.
137
+ * @returns A message containing the result of the swap.
138
+ */
139
+ async executeSwap(walletProvider, args) {
140
+ // Sanity checks
141
+ const network = walletProvider.getNetwork();
142
+ const chainId = network.chainId;
143
+ if (!chainId)
144
+ throw new Error("Chain ID not available from wallet provider");
145
+ try {
146
+ // Get token details
147
+ const { fromTokenDecimals: sellTokenDecimals, toTokenDecimals: buyTokenDecimals, fromTokenName: sellTokenName, toTokenName: buyTokenName, } = await (0, utils_1.getTokenDetails)(walletProvider, args.sellToken, args.buyToken);
148
+ // Convert sell amount to base units
149
+ const sellAmount = (0, viem_1.parseUnits)(args.sellAmount, sellTokenDecimals).toString();
150
+ // Get the wallet address
151
+ const walletAddress = walletProvider.getAddress();
152
+ // Fetch price quote first
153
+ const priceUrl = new URL("https://api.0x.org/swap/permit2/price");
154
+ priceUrl.searchParams.append("chainId", chainId.toString());
155
+ priceUrl.searchParams.append("sellToken", args.sellToken);
156
+ priceUrl.searchParams.append("buyToken", args.buyToken);
157
+ priceUrl.searchParams.append("sellAmount", sellAmount);
158
+ priceUrl.searchParams.append("taker", walletAddress);
159
+ priceUrl.searchParams.append("slippageBps", args.slippageBps.toString());
160
+ if (args.swapFeeRecipient) {
161
+ priceUrl.searchParams.append("swapFeeRecipient", args.swapFeeRecipient);
162
+ priceUrl.searchParams.append("swapFeeBps", args.swapFeeBps.toString());
163
+ priceUrl.searchParams.append("swapFeeToken", args.sellToken);
164
+ }
165
+ const priceResponse = await fetch(priceUrl.toString(), {
166
+ method: "GET",
167
+ headers: {
168
+ "Content-Type": "application/json",
169
+ "0x-api-key": __classPrivateFieldGet(this, _ZeroXActionProvider_apiKey, "f"),
170
+ "0x-version": "v2",
171
+ },
172
+ });
173
+ if (!priceResponse.ok) {
174
+ const errorText = await priceResponse.text();
175
+ return JSON.stringify({
176
+ success: false,
177
+ error: `Error fetching swap price: ${priceResponse.status} ${priceResponse.statusText} - ${errorText}`,
178
+ });
179
+ }
180
+ const priceData = await priceResponse.json();
181
+ // Check if liquidity is available
182
+ if (priceData.liquidityAvailable === false) {
183
+ return JSON.stringify({
184
+ success: false,
185
+ error: "No liquidity available for this swap.",
186
+ });
187
+ }
188
+ // Check if balance of sell token is enough
189
+ if (priceData.balance != null) {
190
+ return JSON.stringify({
191
+ success: false,
192
+ error: `Insufficient balance of sell token ${priceData.balance.token}. Requested to swap ${priceData.balance.expected}, but balance is only ${priceData.balance.actual}.`,
193
+ });
194
+ }
195
+ // Check if permit2 approval is needed for ERC20 tokens
196
+ // Only needed once per token per address
197
+ let approvalTxHash = null;
198
+ if (priceData.issues?.allowance) {
199
+ try {
200
+ approvalTxHash = await walletProvider.sendTransaction({
201
+ to: args.sellToken,
202
+ data: (0, viem_1.encodeFunctionData)({
203
+ abi: viem_1.erc20Abi,
204
+ functionName: "approve",
205
+ args: [utils_1.PERMIT2_ADDRESS, viem_1.maxUint256],
206
+ }),
207
+ });
208
+ await walletProvider.waitForTransactionReceipt(approvalTxHash);
209
+ }
210
+ catch (error) {
211
+ return JSON.stringify({
212
+ success: false,
213
+ error: `Error approving token: ${error}`,
214
+ });
215
+ }
216
+ }
217
+ // Fetch the swap quote
218
+ const quoteUrl = new URL("https://api.0x.org/swap/permit2/quote");
219
+ quoteUrl.searchParams.append("chainId", chainId.toString());
220
+ quoteUrl.searchParams.append("sellToken", args.sellToken);
221
+ quoteUrl.searchParams.append("buyToken", args.buyToken);
222
+ quoteUrl.searchParams.append("sellAmount", sellAmount);
223
+ quoteUrl.searchParams.append("taker", walletAddress);
224
+ quoteUrl.searchParams.append("slippageBps", args.slippageBps.toString());
225
+ if (args.swapFeeRecipient) {
226
+ quoteUrl.searchParams.append("swapFeeRecipient", args.swapFeeRecipient);
227
+ quoteUrl.searchParams.append("swapFeeBps", args.swapFeeBps.toString());
228
+ quoteUrl.searchParams.append("swapFeeToken", args.sellToken);
229
+ }
230
+ const quoteResponse = await fetch(quoteUrl.toString(), {
231
+ method: "GET",
232
+ headers: {
233
+ "Content-Type": "application/json",
234
+ "0x-api-key": __classPrivateFieldGet(this, _ZeroXActionProvider_apiKey, "f"),
235
+ "0x-version": "v2",
236
+ },
237
+ });
238
+ if (!quoteResponse.ok) {
239
+ const errorText = await quoteResponse.text();
240
+ return JSON.stringify({
241
+ success: false,
242
+ error: `Error fetching swap quote: ${quoteResponse.status} ${quoteResponse.statusText} - ${errorText}`,
243
+ });
244
+ }
245
+ const quoteData = await quoteResponse.json();
246
+ // Sign Permit2.eip712 returned from quote
247
+ let signature;
248
+ if (quoteData.permit2?.eip712) {
249
+ try {
250
+ // For LegacyCdpWalletProvider, remove EIP712Domain to avoid ambiguous primary types
251
+ const types = walletProvider instanceof wallet_providers_1.LegacyCdpWalletProvider
252
+ ? (() => {
253
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
254
+ const { EIP712Domain, ...rest } = quoteData.permit2.eip712.types;
255
+ return rest;
256
+ })()
257
+ : quoteData.permit2.eip712.types;
258
+ const typedData = {
259
+ domain: quoteData.permit2.eip712.domain,
260
+ types,
261
+ primaryType: quoteData.permit2.eip712.primaryType,
262
+ message: quoteData.permit2.eip712.message,
263
+ };
264
+ signature = await walletProvider.signTypedData(typedData);
265
+ // Append sig length and sig data to transaction.data
266
+ if (signature && quoteData.transaction?.data) {
267
+ const signatureLengthInHex = (0, viem_1.numberToHex)((0, viem_1.size)(signature), {
268
+ signed: false,
269
+ size: 32,
270
+ });
271
+ const transactionData = quoteData.transaction.data;
272
+ const sigLengthHex = signatureLengthInHex;
273
+ const sig = signature;
274
+ quoteData.transaction.data = (0, viem_1.concat)([transactionData, sigLengthHex, sig]);
275
+ }
276
+ }
277
+ catch (error) {
278
+ return JSON.stringify({
279
+ success: false,
280
+ error: `Error signing permit2 message: ${error}`,
281
+ });
282
+ }
283
+ }
284
+ // Execute swap
285
+ try {
286
+ // Prepare transaction parameters
287
+ const txParams = {
288
+ to: quoteData.transaction.to,
289
+ data: quoteData.transaction.data,
290
+ ...(quoteData?.transaction.gas ? { gas: BigInt(quoteData.transaction.gas) } : {}),
291
+ ...(quoteData.transaction.value ? { value: BigInt(quoteData.transaction.value) } : {}),
292
+ };
293
+ // Send transaction
294
+ const txHash = await walletProvider.sendTransaction(txParams);
295
+ const receipt = await walletProvider.waitForTransactionReceipt(txHash);
296
+ if (receipt.status !== "complete" && receipt.status !== "success") {
297
+ return JSON.stringify({
298
+ success: false,
299
+ ...(approvalTxHash ? { approvalTxHash } : {}),
300
+ transactionHash: receipt.transactionHash,
301
+ error: `Swap transaction failed`,
302
+ });
303
+ }
304
+ // Format the response
305
+ const formattedResponse = {
306
+ success: true,
307
+ ...(approvalTxHash ? { approvalTxHash } : {}),
308
+ transactionHash: receipt.transactionHash,
309
+ sellAmount: (0, viem_1.formatUnits)(BigInt(sellAmount), sellTokenDecimals),
310
+ sellTokenName: sellTokenName,
311
+ sellToken: args.sellToken,
312
+ buyAmount: (0, viem_1.formatUnits)(quoteData.buyAmount, buyTokenDecimals),
313
+ minBuyAmount: quoteData.minBuyAmount
314
+ ? (0, viem_1.formatUnits)(quoteData.minBuyAmount, buyTokenDecimals)
315
+ : null,
316
+ buyTokenName: buyTokenName,
317
+ buyToken: args.buyToken,
318
+ slippageBps: args.slippageBps,
319
+ network: network.networkId,
320
+ };
321
+ return JSON.stringify(formattedResponse);
322
+ }
323
+ catch (error) {
324
+ return JSON.stringify({
325
+ success: false,
326
+ error: `Error sending swap transaction: ${error}`,
327
+ ...(approvalTxHash ? { approvalTxHash } : {}),
328
+ });
329
+ }
330
+ }
331
+ catch (error) {
332
+ return JSON.stringify({
333
+ success: false,
334
+ error: `Error executing swap: ${error}`,
335
+ });
336
+ }
337
+ }
338
+ }
339
+ exports.ZeroXActionProvider = ZeroXActionProvider;
340
+ _ZeroXActionProvider_apiKey = new WeakMap();
341
+ __decorate([
342
+ (0, actionDecorator_1.CreateAction)({
343
+ name: "get_swap_price_quote_from_0x",
344
+ description: `
345
+ This tool fetches a price quote for swapping between two tokens using the 0x API.
346
+
347
+ It takes the following inputs:
348
+ - sellToken: The contract address of the token to sell
349
+ - buyToken: The contract address of the token to buy
350
+ - sellAmount: The amount of sellToken to swap in whole units (e.g. 1 ETH or 10 USDC)
351
+ - slippageBps: (Optional) Maximum allowed slippage in basis points (100 = 1%)
352
+ - swapFeeRecipient: (Optional) The wallet address to receive affiliate trading fees
353
+ - swapFeeBps: The amount in basis points (0-1000) to charge as affiliate fees (defaults to 100 = 1%), only used if swapFeeRecipient is provided
354
+
355
+ Important notes:
356
+ - The contract address for native ETH is "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
357
+ - This only fetches a price quote and does not execute a swap
358
+ - Supported on all EVM networks compatible with 0x API
359
+ - Use sellToken units exactly as provided, do not convert to wei or any other units
360
+ `,
361
+ schema: schemas_1.GetSwapPriceSchema,
362
+ }),
363
+ __metadata("design:type", Function),
364
+ __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
365
+ __metadata("design:returntype", Promise)
366
+ ], ZeroXActionProvider.prototype, "getSwapPrice", null);
367
+ __decorate([
368
+ (0, actionDecorator_1.CreateAction)({
369
+ name: "execute_swap_on_0x",
370
+ description: `
371
+ This tool executes a token swap between two tokens using the 0x API.
372
+
373
+ It takes the following inputs:
374
+ - sellToken: The contract address of the token to sell
375
+ - buyToken: The contract address of the token to buy
376
+ - sellAmount: The amount of sellToken to swap in whole units (e.g. 1 ETH or 10 USDC)
377
+ - slippageBps: (Optional) Maximum allowed slippage in basis points (100 = 1%)
378
+ - swapFeeRecipient: (Optional) The wallet address to receive affiliate trading fees
379
+ - swapFeeBps: The amount in basis points (0-1000) to charge as affiliate fees (defaults to 100 = 1%), only used if swapFeeRecipient is provided
380
+
381
+ Important notes:
382
+ - The contract address for native ETH is "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
383
+ - This will execute an actual swap transaction that sends tokens from your wallet
384
+ - If needed, it will automatically approve the permit2 contract to spend the sell token
385
+ - The approval transaction is only needed once per token
386
+ - Ensure you have sufficient balance of the sell token before executing
387
+ - The trade size might influence the excecution price depending on available liquidity
388
+ - First fetch a price quote and only execute swap if you are happy with the indicated price
389
+ - Supported on all EVM networks compatible with 0x API
390
+ - Use sellToken units exactly as provided, do not convert to wei or any other units
391
+ `,
392
+ schema: schemas_1.ExecuteSwapSchema,
393
+ }),
394
+ __metadata("design:type", Function),
395
+ __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
396
+ __metadata("design:returntype", Promise)
397
+ ], ZeroXActionProvider.prototype, "executeSwap", null);
398
+ /**
399
+ * Creates a new ZeroXActionProvider with the provided configuration.
400
+ *
401
+ * @param config - Optional configuration for the provider.
402
+ * @returns A new ZeroXActionProvider.
403
+ */
404
+ const zeroXActionProvider = (config) => new ZeroXActionProvider(config);
405
+ exports.zeroXActionProvider = zeroXActionProvider;
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zeroXActionProvider_1 = require("./zeroXActionProvider");
4
+ const schemas_1 = require("./schemas");
5
+ // Mock the fetch function
6
+ global.fetch = jest.fn();
7
+ describe("ZeroX Schema Validation", () => {
8
+ it("should validate GetSwapPrice schema with valid input", () => {
9
+ const validInput = {
10
+ sellToken: "0x1234567890123456789012345678901234567890",
11
+ buyToken: "0x0987654321098765432109876543210987654321",
12
+ sellAmount: "1.5",
13
+ slippageBps: 50,
14
+ };
15
+ const result = schemas_1.GetSwapPriceSchema.safeParse(validInput);
16
+ expect(result.success).toBe(true);
17
+ });
18
+ it("should fail validation with invalid address format", () => {
19
+ const invalidInput = {
20
+ sellToken: "invalid-address",
21
+ buyToken: "0x0987654321098765432109876543210987654321",
22
+ sellAmount: "1.5",
23
+ slippageBps: 50,
24
+ };
25
+ const result = schemas_1.GetSwapPriceSchema.safeParse(invalidInput);
26
+ expect(result.success).toBe(false);
27
+ });
28
+ it("should use default slippageBps when not provided", () => {
29
+ const inputWithoutSlippage = {
30
+ sellToken: "0x1234567890123456789012345678901234567890",
31
+ buyToken: "0x0987654321098765432109876543210987654321",
32
+ sellAmount: "1.5",
33
+ };
34
+ const result = schemas_1.GetSwapPriceSchema.safeParse(inputWithoutSlippage);
35
+ expect(result.success).toBe(true);
36
+ if (result.success) {
37
+ expect(result.data.slippageBps).toBe(100); // Default value from schema
38
+ }
39
+ });
40
+ it("should validate swap fee parameters when both provided", () => {
41
+ const inputWithSwapFees = {
42
+ sellToken: "0x1234567890123456789012345678901234567890",
43
+ buyToken: "0x0987654321098765432109876543210987654321",
44
+ sellAmount: "1.5",
45
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
46
+ swapFeeBps: 50,
47
+ };
48
+ const result = schemas_1.GetSwapPriceSchema.safeParse(inputWithSwapFees);
49
+ expect(result.success).toBe(true);
50
+ });
51
+ it("should validate when only swapFeeRecipient provided (swapFeeBps defaults to 100)", () => {
52
+ const inputWithOnlyRecipient = {
53
+ sellToken: "0x1234567890123456789012345678901234567890",
54
+ buyToken: "0x0987654321098765432109876543210987654321",
55
+ sellAmount: "1.5",
56
+ swapFeeBps: 100,
57
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
58
+ };
59
+ const result = schemas_1.GetSwapPriceSchema.safeParse(inputWithOnlyRecipient);
60
+ expect(result.success).toBe(true);
61
+ if (result.success) {
62
+ expect(result.data.swapFeeBps).toBe(100); // Default value
63
+ }
64
+ });
65
+ it("should fail validation when swapFeeBps exceeds maximum", () => {
66
+ const inputWithInvalidSwapFeeBps = {
67
+ sellToken: "0x1234567890123456789012345678901234567890",
68
+ buyToken: "0x0987654321098765432109876543210987654321",
69
+ sellAmount: "1.5",
70
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
71
+ swapFeeBps: 1500, // Exceeds maximum of 1000
72
+ };
73
+ const result = schemas_1.GetSwapPriceSchema.safeParse(inputWithInvalidSwapFeeBps);
74
+ expect(result.success).toBe(false);
75
+ });
76
+ it("should validate ExecuteSwap schema with valid input", () => {
77
+ const validInput = {
78
+ sellToken: "0x1234567890123456789012345678901234567890",
79
+ buyToken: "0x0987654321098765432109876543210987654321",
80
+ sellAmount: "1.5",
81
+ slippageBps: 50,
82
+ };
83
+ const result = schemas_1.ExecuteSwapSchema.safeParse(validInput);
84
+ expect(result.success).toBe(true);
85
+ });
86
+ });
87
+ describe("ZeroX Action Provider", () => {
88
+ let provider;
89
+ let mockWalletProvider;
90
+ const MOCK_SELL_TOKEN = "0x1234567890123456789012345678901234567890";
91
+ const MOCK_BUY_TOKEN = "0x0987654321098765432109876543210987654321";
92
+ const MOCK_SELL_AMOUNT = "1.5";
93
+ const MOCK_CHAIN_ID = 1;
94
+ const MOCK_ADDRESS = "0xabcdef1234567890abcdef1234567890abcdef12";
95
+ beforeEach(() => {
96
+ provider = (0, zeroXActionProvider_1.zeroXActionProvider)({ apiKey: "test-api-key" });
97
+ mockWalletProvider = {
98
+ getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS),
99
+ getNetwork: jest.fn().mockReturnValue({
100
+ chainId: MOCK_CHAIN_ID,
101
+ protocolFamily: "evm",
102
+ networkId: "ethereum-mainnet",
103
+ }),
104
+ readContract: jest.fn(),
105
+ getPublicClient: jest.fn().mockReturnValue({
106
+ multicall: jest.fn(),
107
+ }),
108
+ sendTransaction: jest.fn(),
109
+ waitForTransactionReceipt: jest.fn(),
110
+ signTypedData: jest.fn(),
111
+ };
112
+ // Reset mocks
113
+ global.fetch.mockReset();
114
+ });
115
+ describe("getSwapPrice", () => {
116
+ beforeEach(() => {
117
+ // Mock multicall for token details (decimals and names)
118
+ const mockMulticallResults = [
119
+ { status: "success", result: 18 }, // sellToken decimals
120
+ { status: "success", result: "TEST" }, // sellToken name
121
+ { status: "success", result: 6 }, // buyToken decimals
122
+ { status: "success", result: "USDC" }, // buyToken name
123
+ ];
124
+ mockWalletProvider.getPublicClient().multicall.mockResolvedValue(mockMulticallResults);
125
+ // Mock fetch for price API
126
+ const mockPriceResponse = {
127
+ buyAmount: "1000000", // 1 USDC with 6 decimals
128
+ minBuyAmount: "990000", // 0.99 USDC with 6 decimals
129
+ totalNetworkFee: "100000000000000", // 0.0001 ETH
130
+ issues: { balance: null },
131
+ liquidityAvailable: true,
132
+ };
133
+ global.fetch.mockResolvedValueOnce({
134
+ ok: true,
135
+ json: jest.fn().mockResolvedValueOnce(mockPriceResponse),
136
+ });
137
+ });
138
+ it("should get swap price successfully", async () => {
139
+ const args = {
140
+ sellToken: MOCK_SELL_TOKEN,
141
+ buyToken: MOCK_BUY_TOKEN,
142
+ sellAmount: MOCK_SELL_AMOUNT,
143
+ slippageBps: 50,
144
+ swapFeeBps: 100,
145
+ };
146
+ const response = await provider.getSwapPrice(mockWalletProvider, args);
147
+ const parsedResponse = JSON.parse(response);
148
+ // Verify fetch was called with correct URL params
149
+ expect(global.fetch).toHaveBeenCalledTimes(1);
150
+ expect(global.fetch.mock.calls[0][0]).toContain("api.0x.org/swap/permit2/price");
151
+ expect(global.fetch.mock.calls[0][0]).toContain(`chainId=${MOCK_CHAIN_ID}`);
152
+ expect(global.fetch.mock.calls[0][0]).toContain(`sellToken=${MOCK_SELL_TOKEN}`);
153
+ expect(global.fetch.mock.calls[0][0]).toContain(`buyToken=${MOCK_BUY_TOKEN}`);
154
+ // Verify response formatting
155
+ expect(parsedResponse.success).toBe(true);
156
+ expect(parsedResponse.sellToken).toBe(MOCK_SELL_TOKEN);
157
+ expect(parsedResponse.sellTokenName).toBe("TEST");
158
+ expect(parsedResponse.buyToken).toBe(MOCK_BUY_TOKEN);
159
+ expect(parsedResponse.buyTokenName).toBe("USDC");
160
+ expect(parsedResponse.liquidityAvailable).toBe(true);
161
+ expect(parsedResponse.balanceEnough).toBe(true);
162
+ expect(parsedResponse.slippageBps).toBe(50);
163
+ expect(parsedResponse.buyAmount).toBeDefined();
164
+ expect(parsedResponse.minBuyAmount).toBeDefined();
165
+ });
166
+ it("should handle API errors", async () => {
167
+ global.fetch.mockReset();
168
+ global.fetch.mockResolvedValueOnce({
169
+ ok: false,
170
+ status: 400,
171
+ statusText: "Bad Request",
172
+ text: jest.fn().mockResolvedValueOnce("Invalid request parameters"),
173
+ });
174
+ const args = {
175
+ sellToken: MOCK_SELL_TOKEN,
176
+ buyToken: MOCK_BUY_TOKEN,
177
+ sellAmount: MOCK_SELL_AMOUNT,
178
+ slippageBps: 50,
179
+ swapFeeBps: 100,
180
+ };
181
+ const response = await provider.getSwapPrice(mockWalletProvider, args);
182
+ const parsedResponse = JSON.parse(response);
183
+ expect(parsedResponse.success).toBe(false);
184
+ expect(parsedResponse.error).toContain("Error fetching swap price");
185
+ });
186
+ it("should handle fetch errors", async () => {
187
+ global.fetch.mockReset();
188
+ global.fetch.mockRejectedValueOnce(new Error("Network error"));
189
+ const args = {
190
+ sellToken: MOCK_SELL_TOKEN,
191
+ buyToken: MOCK_BUY_TOKEN,
192
+ sellAmount: MOCK_SELL_AMOUNT,
193
+ slippageBps: 50,
194
+ swapFeeBps: 100,
195
+ };
196
+ const response = await provider.getSwapPrice(mockWalletProvider, args);
197
+ const parsedResponse = JSON.parse(response);
198
+ expect(parsedResponse.success).toBe(false);
199
+ expect(parsedResponse.error).toContain("Error fetching swap price");
200
+ });
201
+ it("should include swap fee parameters in API call when provided", async () => {
202
+ const args = {
203
+ sellToken: MOCK_SELL_TOKEN,
204
+ buyToken: MOCK_BUY_TOKEN,
205
+ sellAmount: MOCK_SELL_AMOUNT,
206
+ slippageBps: 50,
207
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
208
+ swapFeeBps: 100,
209
+ };
210
+ await provider.getSwapPrice(mockWalletProvider, args);
211
+ // Verify fetch was called with swap fee parameters
212
+ expect(global.fetch).toHaveBeenCalledTimes(1);
213
+ const fetchUrl = global.fetch.mock.calls[0][0];
214
+ expect(fetchUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
215
+ expect(fetchUrl).toContain("swapFeeBps=100");
216
+ expect(fetchUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
217
+ });
218
+ it("should not include swap fee parameters when not provided", async () => {
219
+ const args = {
220
+ sellToken: MOCK_SELL_TOKEN,
221
+ buyToken: MOCK_BUY_TOKEN,
222
+ sellAmount: MOCK_SELL_AMOUNT,
223
+ slippageBps: 50,
224
+ swapFeeBps: 100,
225
+ };
226
+ await provider.getSwapPrice(mockWalletProvider, args);
227
+ // Verify fetch was called without swap fee parameters
228
+ expect(global.fetch).toHaveBeenCalledTimes(1);
229
+ const fetchUrl = global.fetch.mock.calls[0][0];
230
+ expect(fetchUrl).not.toContain("swapFeeRecipient");
231
+ expect(fetchUrl).not.toContain("swapFeeBps");
232
+ expect(fetchUrl).not.toContain("swapFeeToken");
233
+ });
234
+ it("should include swap fee parameters with default swapFeeBps when only recipient provided", async () => {
235
+ const args = {
236
+ sellToken: MOCK_SELL_TOKEN,
237
+ buyToken: MOCK_BUY_TOKEN,
238
+ sellAmount: MOCK_SELL_AMOUNT,
239
+ slippageBps: 50,
240
+ swapFeeBps: 100,
241
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
242
+ };
243
+ await provider.getSwapPrice(mockWalletProvider, args);
244
+ // Verify fetch was called with swap fee parameters including default swapFeeBps
245
+ expect(global.fetch).toHaveBeenCalledTimes(1);
246
+ const fetchUrl = global.fetch.mock.calls[0][0];
247
+ expect(fetchUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
248
+ expect(fetchUrl).toContain("swapFeeBps=100"); // Default value
249
+ expect(fetchUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
250
+ });
251
+ });
252
+ describe("executeSwap", () => {
253
+ const MOCK_TX_HASH = "0xtxhash123456";
254
+ beforeEach(() => {
255
+ // Mock multicall for token details (decimals and names)
256
+ const mockMulticallResults = [
257
+ { status: "success", result: 18 }, // sellToken decimals
258
+ { status: "success", result: "TEST" }, // sellToken name
259
+ { status: "success", result: 6 }, // buyToken decimals
260
+ { status: "success", result: "USDC" }, // buyToken name
261
+ ];
262
+ mockWalletProvider.getPublicClient().multicall.mockResolvedValue(mockMulticallResults);
263
+ // Mock API responses
264
+ const mockPriceResponse = {
265
+ buyAmount: "1000000", // 1 USDC with 6 decimals
266
+ minBuyAmount: "990000", // 0.99 USDC with 6 decimals
267
+ totalNetworkFee: "100000000000000", // 0.0001 ETH
268
+ issues: null,
269
+ liquidityAvailable: true,
270
+ };
271
+ const mockQuoteResponse = {
272
+ buyAmount: "1000000",
273
+ minBuyAmount: "990000",
274
+ totalNetworkFee: "100000000000000",
275
+ transaction: {
276
+ to: "0x0000000000000000000000000000000000000001",
277
+ data: "0x12345678",
278
+ value: "1500000000000000000", // 1.5 ETH
279
+ gas: "300000",
280
+ gasPrice: "20000000000",
281
+ },
282
+ };
283
+ // First fetch for price
284
+ global.fetch.mockResolvedValueOnce({
285
+ ok: true,
286
+ json: jest.fn().mockResolvedValueOnce(mockPriceResponse),
287
+ });
288
+ // Second fetch for quote
289
+ global.fetch.mockResolvedValueOnce({
290
+ ok: true,
291
+ json: jest.fn().mockResolvedValueOnce(mockQuoteResponse),
292
+ });
293
+ // Mock transaction functions
294
+ mockWalletProvider.sendTransaction.mockResolvedValueOnce(MOCK_TX_HASH);
295
+ mockWalletProvider.waitForTransactionReceipt.mockResolvedValueOnce({
296
+ transactionHash: MOCK_TX_HASH,
297
+ status: "success",
298
+ });
299
+ });
300
+ it("should execute swap successfully", async () => {
301
+ const args = {
302
+ sellToken: MOCK_SELL_TOKEN,
303
+ buyToken: MOCK_BUY_TOKEN,
304
+ sellAmount: MOCK_SELL_AMOUNT,
305
+ slippageBps: 50,
306
+ swapFeeBps: 100,
307
+ };
308
+ const response = await provider.executeSwap(mockWalletProvider, args);
309
+ const parsedResponse = JSON.parse(response);
310
+ // Verify API calls
311
+ expect(global.fetch).toHaveBeenCalledTimes(2);
312
+ expect(global.fetch.mock.calls[0][0]).toContain("api.0x.org/swap/permit2/price");
313
+ expect(global.fetch.mock.calls[1][0]).toContain("api.0x.org/swap/permit2/quote");
314
+ // Verify transaction was sent
315
+ expect(mockWalletProvider.sendTransaction).toHaveBeenCalledTimes(1);
316
+ expect(mockWalletProvider.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
317
+ // Verify response formatting
318
+ expect(parsedResponse.success).toBe(true);
319
+ expect(parsedResponse.sellToken).toBe(MOCK_SELL_TOKEN);
320
+ expect(parsedResponse.sellTokenName).toBe("TEST");
321
+ expect(parsedResponse.buyToken).toBe(MOCK_BUY_TOKEN);
322
+ expect(parsedResponse.buyTokenName).toBe("USDC");
323
+ expect(parsedResponse.transactionHash).toBe(MOCK_TX_HASH);
324
+ expect(parsedResponse.slippageBps).toBe(50);
325
+ expect(parsedResponse.network).toBe("ethereum-mainnet");
326
+ });
327
+ it("should handle price API errors", async () => {
328
+ global.fetch.mockReset();
329
+ global.fetch.mockResolvedValueOnce({
330
+ ok: false,
331
+ status: 400,
332
+ statusText: "Bad Request",
333
+ text: jest.fn().mockResolvedValueOnce("Invalid request parameters"),
334
+ });
335
+ const args = {
336
+ sellToken: MOCK_SELL_TOKEN,
337
+ buyToken: MOCK_BUY_TOKEN,
338
+ sellAmount: MOCK_SELL_AMOUNT,
339
+ slippageBps: 50,
340
+ swapFeeBps: 100,
341
+ };
342
+ const response = await provider.executeSwap(mockWalletProvider, args);
343
+ const parsedResponse = JSON.parse(response);
344
+ expect(parsedResponse.success).toBe(false);
345
+ expect(parsedResponse.error).toContain("Error fetching swap price");
346
+ });
347
+ it("should handle no liquidity available", async () => {
348
+ global.fetch.mockReset();
349
+ const mockPriceResponse = {
350
+ liquidityAvailable: false,
351
+ };
352
+ global.fetch.mockResolvedValueOnce({
353
+ ok: true,
354
+ json: jest.fn().mockResolvedValueOnce(mockPriceResponse),
355
+ });
356
+ const args = {
357
+ sellToken: MOCK_SELL_TOKEN,
358
+ buyToken: MOCK_BUY_TOKEN,
359
+ sellAmount: MOCK_SELL_AMOUNT,
360
+ slippageBps: 50,
361
+ swapFeeBps: 100,
362
+ };
363
+ const response = await provider.executeSwap(mockWalletProvider, args);
364
+ const parsedResponse = JSON.parse(response);
365
+ expect(parsedResponse.success).toBe(false);
366
+ expect(parsedResponse.error).toContain("No liquidity available");
367
+ });
368
+ it("should include swap fee parameters in both API calls when provided", async () => {
369
+ const args = {
370
+ sellToken: MOCK_SELL_TOKEN,
371
+ buyToken: MOCK_BUY_TOKEN,
372
+ sellAmount: MOCK_SELL_AMOUNT,
373
+ slippageBps: 50,
374
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
375
+ swapFeeBps: 100,
376
+ };
377
+ await provider.executeSwap(mockWalletProvider, args);
378
+ // Verify both API calls include swap fee parameters
379
+ expect(global.fetch).toHaveBeenCalledTimes(2);
380
+ // Check price API call
381
+ const priceUrl = global.fetch.mock.calls[0][0];
382
+ expect(priceUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
383
+ expect(priceUrl).toContain("swapFeeBps=100");
384
+ expect(priceUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
385
+ // Check quote API call
386
+ const quoteUrl = global.fetch.mock.calls[1][0];
387
+ expect(quoteUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
388
+ expect(quoteUrl).toContain("swapFeeBps=100");
389
+ expect(quoteUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
390
+ });
391
+ it("should not include swap fee parameters when not provided", async () => {
392
+ const args = {
393
+ sellToken: MOCK_SELL_TOKEN,
394
+ buyToken: MOCK_BUY_TOKEN,
395
+ sellAmount: MOCK_SELL_AMOUNT,
396
+ slippageBps: 50,
397
+ swapFeeBps: 100,
398
+ };
399
+ await provider.executeSwap(mockWalletProvider, args);
400
+ // Verify both API calls exclude swap fee parameters
401
+ expect(global.fetch).toHaveBeenCalledTimes(2);
402
+ // Check price API call
403
+ const priceUrl = global.fetch.mock.calls[0][0];
404
+ expect(priceUrl).not.toContain("swapFeeRecipient");
405
+ expect(priceUrl).not.toContain("swapFeeBps");
406
+ expect(priceUrl).not.toContain("swapFeeToken");
407
+ // Check quote API call
408
+ const quoteUrl = global.fetch.mock.calls[1][0];
409
+ expect(quoteUrl).not.toContain("swapFeeRecipient");
410
+ expect(quoteUrl).not.toContain("swapFeeBps");
411
+ expect(quoteUrl).not.toContain("swapFeeToken");
412
+ });
413
+ it("should include swap fee parameters with default swapFeeBps when only recipient provided", async () => {
414
+ const args = {
415
+ sellToken: MOCK_SELL_TOKEN,
416
+ buyToken: MOCK_BUY_TOKEN,
417
+ sellAmount: MOCK_SELL_AMOUNT,
418
+ slippageBps: 50,
419
+ swapFeeBps: 100,
420
+ swapFeeRecipient: "0xabcdef1234567890abcdef1234567890abcdef12",
421
+ };
422
+ await provider.executeSwap(mockWalletProvider, args);
423
+ // Verify both API calls include swap fee parameters with default swapFeeBps
424
+ expect(global.fetch).toHaveBeenCalledTimes(2);
425
+ // Check price API call
426
+ const priceUrl = global.fetch.mock.calls[0][0];
427
+ expect(priceUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
428
+ expect(priceUrl).toContain("swapFeeBps=100"); // Default value
429
+ expect(priceUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
430
+ // Check quote API call
431
+ const quoteUrl = global.fetch.mock.calls[1][0];
432
+ expect(quoteUrl).toContain("swapFeeRecipient=0xabcdef1234567890abcdef1234567890abcdef12");
433
+ expect(quoteUrl).toContain("swapFeeBps=100"); // Default value
434
+ expect(quoteUrl).toContain(`swapFeeToken=${MOCK_SELL_TOKEN}`);
435
+ });
436
+ });
437
+ describe("supportsNetwork", () => {
438
+ it("should return true for evm networks", () => {
439
+ expect(provider.supportsNetwork({ protocolFamily: "evm" })).toBe(true);
440
+ });
441
+ it("should return false for non-evm networks", () => {
442
+ expect(provider.supportsNetwork({ protocolFamily: "solana" })).toBe(false);
443
+ });
444
+ });
445
+ });
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-20250831210403",
5
+ "version": "0.0.0-nightly-20250902210403",
6
6
  "author": "Coinbase Inc.",
7
7
  "license": "Apache-2.0",
8
8
  "main": "dist/index.js",