@coinbase/agentkit 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +137 -40
  2. package/dist/action-providers/across/schemas.d.ts +1 -1
  3. package/dist/action-providers/baseAccount/baseAccountActionProvider.d.ts +46 -0
  4. package/dist/action-providers/baseAccount/baseAccountActionProvider.js +404 -0
  5. package/dist/action-providers/baseAccount/baseAccountActionProvider.test.d.ts +1 -0
  6. package/dist/action-providers/baseAccount/baseAccountActionProvider.test.js +325 -0
  7. package/dist/action-providers/baseAccount/index.d.ts +2 -0
  8. package/dist/action-providers/baseAccount/index.js +18 -0
  9. package/dist/action-providers/baseAccount/schemas.d.ts +43 -0
  10. package/dist/action-providers/baseAccount/schemas.js +62 -0
  11. package/dist/action-providers/baseAccount/types.d.ts +17 -0
  12. package/dist/action-providers/baseAccount/types.js +2 -0
  13. package/dist/action-providers/baseAccount/utils.d.ts +14 -0
  14. package/dist/action-providers/baseAccount/utils.js +57 -0
  15. package/dist/action-providers/cdp/cdpEvmWalletActionProvider.js +2 -1
  16. package/dist/action-providers/cdp/cdpEvmWalletActionProvider.test.js +3 -1
  17. package/dist/action-providers/cdp/cdpSmartWalletActionProvider.js +2 -1
  18. package/dist/action-providers/cdp/cdpSmartWalletActionProvider.test.js +3 -1
  19. package/dist/action-providers/cdp/swapUtils.d.ts +0 -9
  20. package/dist/action-providers/cdp/swapUtils.js +0 -36
  21. package/dist/action-providers/clanker/schemas.d.ts +2 -2
  22. package/dist/action-providers/flaunch/client_utils.d.ts +25 -0
  23. package/dist/action-providers/flaunch/client_utils.js +62 -0
  24. package/dist/action-providers/flaunch/constants.d.ts +41 -20
  25. package/dist/action-providers/flaunch/constants.js +111 -36
  26. package/dist/action-providers/flaunch/flaunchActionProvider.d.ts +4 -43
  27. package/dist/action-providers/flaunch/flaunchActionProvider.js +132 -200
  28. package/dist/action-providers/flaunch/flaunchActionProvider.test.js +108 -13
  29. package/dist/action-providers/flaunch/metadata_utils.d.ts +12 -0
  30. package/dist/action-providers/flaunch/metadata_utils.js +216 -0
  31. package/dist/action-providers/flaunch/schemas.d.ts +39 -3
  32. package/dist/action-providers/flaunch/schemas.js +62 -10
  33. package/dist/action-providers/flaunch/{utils.d.ts → swap_utils.d.ts} +17 -19
  34. package/dist/action-providers/flaunch/{utils.js → swap_utils.js} +137 -172
  35. package/dist/action-providers/index.d.ts +1 -0
  36. package/dist/action-providers/index.js +1 -0
  37. package/dist/action-providers/pyth/pythActionProvider.d.ts +2 -2
  38. package/dist/action-providers/pyth/pythActionProvider.js +83 -31
  39. package/dist/action-providers/pyth/pythActionProvider.test.js +178 -26
  40. package/dist/action-providers/pyth/schemas.d.ts +6 -0
  41. package/dist/action-providers/pyth/schemas.js +9 -1
  42. package/dist/action-providers/superfluid/superfluidSuperTokenCreatorActionProvider.js +5 -4
  43. package/dist/action-providers/superfluid/utils/parseLogs.d.ts +2 -1
  44. package/dist/action-providers/superfluid/utils/parseLogs.js +6 -3
  45. package/dist/action-providers/wallet/walletActionProvider.js +3 -0
  46. package/dist/action-providers/x402/schemas.d.ts +7 -0
  47. package/dist/action-providers/x402/schemas.js +11 -1
  48. package/dist/action-providers/x402/utils.d.ts +55 -0
  49. package/dist/action-providers/x402/utils.js +160 -0
  50. package/dist/action-providers/x402/x402ActionProvider.d.ts +9 -9
  51. package/dist/action-providers/x402/x402ActionProvider.js +158 -39
  52. package/dist/action-providers/x402/x402ActionProvider.test.js +116 -10
  53. package/dist/utils.d.ts +10 -0
  54. package/dist/utils.js +43 -13
  55. package/dist/wallet-providers/cdpEvmWalletProvider.d.ts +7 -0
  56. package/dist/wallet-providers/cdpEvmWalletProvider.js +14 -21
  57. package/dist/wallet-providers/cdpEvmWalletProvider.test.js +7 -0
  58. package/dist/wallet-providers/cdpSmartWalletProvider.d.ts +7 -0
  59. package/dist/wallet-providers/cdpSmartWalletProvider.js +23 -6
  60. package/dist/wallet-providers/cdpSmartWalletProvider.test.js +6 -10
  61. package/dist/wallet-providers/evmWalletProvider.d.ts +9 -2
  62. package/dist/wallet-providers/evmWalletProvider.js +4 -0
  63. package/dist/wallet-providers/legacyCdpSmartWalletProvider.d.ts +9 -0
  64. package/dist/wallet-providers/legacyCdpSmartWalletProvider.js +11 -0
  65. package/dist/wallet-providers/legacyCdpWalletProvider.d.ts +7 -0
  66. package/dist/wallet-providers/legacyCdpWalletProvider.js +16 -0
  67. package/dist/wallet-providers/legacyCdpWalletProvider.test.js +6 -0
  68. package/dist/wallet-providers/privyEvmDelegatedEmbeddedWalletProvider.d.ts +7 -0
  69. package/dist/wallet-providers/privyEvmDelegatedEmbeddedWalletProvider.js +27 -0
  70. package/dist/wallet-providers/privyEvmWalletProvider.test.js +11 -0
  71. package/dist/wallet-providers/viemWalletProvider.d.ts +8 -1
  72. package/dist/wallet-providers/viemWalletProvider.js +21 -1
  73. package/dist/wallet-providers/viemWalletProvider.test.js +21 -1
  74. package/dist/wallet-providers/zeroDevWalletProvider.d.ts +7 -0
  75. package/dist/wallet-providers/zeroDevWalletProvider.js +12 -0
  76. package/dist/wallet-providers/zeroDevWalletProvider.test.js +10 -0
  77. package/package.json +6 -4
@@ -20,7 +20,10 @@ const schemas_1 = require("./schemas");
20
20
  const wallet_providers_1 = require("../../wallet-providers");
21
21
  const axios_1 = __importDefault(require("axios"));
22
22
  const x402_axios_1 = require("x402-axios");
23
- const SUPPORTED_NETWORKS = ["base-mainnet", "base-sepolia"];
23
+ const verify_1 = require("x402/verify");
24
+ const x402_1 = require("@coinbase/x402");
25
+ const utils_1 = require("./utils");
26
+ const SUPPORTED_NETWORKS = ["base-mainnet", "base-sepolia", "solana-mainnet", "solana-devnet"];
24
27
  /**
25
28
  * X402ActionProvider provides actions for making HTTP requests, with optional x402 payment handling.
26
29
  */
@@ -39,6 +42,116 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
39
42
  */
40
43
  this.supportsNetwork = (network) => network.protocolFamily === "evm" && SUPPORTED_NETWORKS.includes(network.networkId);
41
44
  }
45
+ /**
46
+ * Discovers available x402 services with optional filtering.
47
+ *
48
+ * @param walletProvider - The wallet provider to use for network filtering
49
+ * @param args - Optional filters: maxUsdcPrice
50
+ * @returns JSON string with the list of services (filtered by network and description)
51
+ */
52
+ async discoverX402Services(walletProvider, args) {
53
+ try {
54
+ const { list } = (0, verify_1.useFacilitator)(x402_1.facilitator);
55
+ const services = await list();
56
+ if (!services || !services.items) {
57
+ return JSON.stringify({
58
+ error: true,
59
+ message: "No services found",
60
+ });
61
+ }
62
+ // Get the current wallet network
63
+ const walletNetwork = (0, utils_1.getX402Network)(walletProvider.getNetwork());
64
+ // Filter services by network, description, and optional USDC price
65
+ const hasValidMaxUsdcPrice = typeof args.maxUsdcPrice === "number" &&
66
+ Number.isFinite(args.maxUsdcPrice) &&
67
+ args.maxUsdcPrice > 0;
68
+ const filteredServices = services.items.filter(item => {
69
+ // Filter by network - only include services that accept the current wallet network
70
+ const accepts = Array.isArray(item.accepts) ? item.accepts : [];
71
+ const hasMatchingNetwork = accepts.some(req => req.network === walletNetwork);
72
+ // Filter out services with empty or default descriptions
73
+ const hasDescription = accepts.some(req => req.description &&
74
+ req.description.trim() !== "" &&
75
+ req.description.trim() !== "Access to protected content");
76
+ return hasMatchingNetwork && hasDescription;
77
+ });
78
+ // Apply USDC price filtering if maxUsdcPrice is provided (only consider USDC assets)
79
+ let priceFilteredServices = filteredServices;
80
+ if (hasValidMaxUsdcPrice) {
81
+ priceFilteredServices = [];
82
+ for (const item of filteredServices) {
83
+ const accepts = Array.isArray(item.accepts) ? item.accepts : [];
84
+ let shouldInclude = false;
85
+ for (const req of accepts) {
86
+ if (req.network === walletNetwork && req.asset && req.maxAmountRequired) {
87
+ // Only consider USDC assets when maxUsdcPrice filter is applied
88
+ if ((0, utils_1.isUsdcAsset)(req.asset, walletProvider)) {
89
+ try {
90
+ const maxUsdcPriceAtomic = await (0, utils_1.convertWholeUnitsToAtomic)(args.maxUsdcPrice, req.asset, walletProvider);
91
+ if (maxUsdcPriceAtomic) {
92
+ const requirement = BigInt(req.maxAmountRequired);
93
+ const maxUsdcPriceAtomicBigInt = BigInt(maxUsdcPriceAtomic);
94
+ if (requirement <= maxUsdcPriceAtomicBigInt) {
95
+ shouldInclude = true;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ catch {
101
+ // If conversion fails, skip this requirement
102
+ continue;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (shouldInclude) {
108
+ priceFilteredServices.push(item);
109
+ }
110
+ }
111
+ }
112
+ // Format the filtered services
113
+ const filtered = await Promise.all(priceFilteredServices.map(async (item) => {
114
+ const accepts = Array.isArray(item.accepts) ? item.accepts : [];
115
+ const matchingAccept = accepts.find(req => req.network === walletNetwork);
116
+ // Format amount if available
117
+ let formattedMaxAmount = matchingAccept?.maxAmountRequired;
118
+ if (matchingAccept?.maxAmountRequired && matchingAccept?.asset) {
119
+ formattedMaxAmount = await (0, utils_1.formatPaymentOption)({
120
+ asset: matchingAccept.asset,
121
+ maxAmountRequired: matchingAccept.maxAmountRequired,
122
+ network: matchingAccept.network,
123
+ }, walletProvider);
124
+ }
125
+ return {
126
+ resource: item.resource,
127
+ description: matchingAccept?.description || "",
128
+ cost: formattedMaxAmount,
129
+ ...(matchingAccept?.outputSchema?.input && {
130
+ input: matchingAccept.outputSchema.input,
131
+ }),
132
+ ...(matchingAccept?.outputSchema?.output && {
133
+ output: matchingAccept.outputSchema.output,
134
+ }),
135
+ ...(item.metadata && item.metadata.length > 0 && { metadata: item.metadata }),
136
+ };
137
+ }));
138
+ return JSON.stringify({
139
+ success: true,
140
+ walletNetwork,
141
+ total: services.items.length,
142
+ returned: filtered.length,
143
+ items: filtered,
144
+ }, null, 2);
145
+ }
146
+ catch (error) {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ return JSON.stringify({
149
+ error: true,
150
+ message: "Failed to list x402 services",
151
+ details: message,
152
+ }, null, 2);
153
+ }
154
+ }
42
155
  /**
43
156
  * Makes a basic HTTP request to an API endpoint.
44
157
  *
@@ -64,19 +177,32 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
64
177
  data: response.data,
65
178
  }, null, 2);
66
179
  }
180
+ // Check if wallet network matches any available payment options
181
+ const walletNetwork = (0, utils_1.getX402Network)(walletProvider.getNetwork());
182
+ const availableNetworks = response.data.accepts.map(option => option.network);
183
+ const hasMatchingNetwork = availableNetworks.includes(walletNetwork);
184
+ let paymentOptionsText = `The wallet network ${walletNetwork} does not match any available payment options (${availableNetworks.join(", ")}).`;
185
+ // Format payment options for matching networks
186
+ if (hasMatchingNetwork) {
187
+ const matchingOptions = response.data.accepts.filter(option => option.network === walletNetwork);
188
+ const formattedOptions = await Promise.all(matchingOptions.map(option => (0, utils_1.formatPaymentOption)(option, walletProvider)));
189
+ paymentOptionsText = `The payment options are: ${formattedOptions.join(", ")}`;
190
+ }
67
191
  return JSON.stringify({
68
192
  status: "error_402_payment_required",
69
193
  acceptablePaymentOptions: response.data.accepts,
70
194
  nextSteps: [
71
195
  "Inform the user that the requested server replied with a 402 Payment Required response.",
72
- `The payment options are: ${response.data.accepts.map(option => `${option.asset} ${option.maxAmountRequired} ${option.network}`).join(", ")}`,
73
- "Ask the user if they want to retry the request with payment.",
74
- `Use retry_http_request_with_x402 to retry the request with payment.`,
196
+ paymentOptionsText,
197
+ hasMatchingNetwork ? "Ask the user if they want to retry the request with payment." : "",
198
+ hasMatchingNetwork
199
+ ? `Use retry_http_request_with_x402 to retry the request with payment.`
200
+ : "",
75
201
  ],
76
202
  });
77
203
  }
78
204
  catch (error) {
79
- return this.handleHttpError(error, args.url);
205
+ return (0, utils_1.handleHttpError)(error, args.url);
80
206
  }
81
207
  }
82
208
  /**
@@ -88,6 +214,16 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
88
214
  */
89
215
  async retryWithX402(walletProvider, args) {
90
216
  try {
217
+ // Check network compatibility before attempting payment
218
+ const walletNetwork = (0, utils_1.getX402Network)(walletProvider.getNetwork());
219
+ const selectedNetwork = args.selectedPaymentOption.network;
220
+ if (walletNetwork !== selectedNetwork) {
221
+ return JSON.stringify({
222
+ error: true,
223
+ message: "Network mismatch",
224
+ details: `Wallet is on ${walletNetwork} but payment requires ${selectedNetwork}`,
225
+ }, null, 2);
226
+ }
91
227
  // Make the request with payment handling
92
228
  const account = walletProvider.toSigner();
93
229
  const paymentSelector = (accepts) => {
@@ -108,7 +244,9 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
108
244
  }
109
245
  return accepts[0];
110
246
  };
111
- const api = (0, x402_axios_1.withPaymentInterceptor)(axios_1.default.create({}), account, paymentSelector);
247
+ const api = (0, x402_axios_1.withPaymentInterceptor)(axios_1.default.create({}),
248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
+ account, paymentSelector);
112
250
  const response = await api.request({
113
251
  url: args.url,
114
252
  method: args.method ?? "GET",
@@ -142,7 +280,7 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
142
280
  });
143
281
  }
144
282
  catch (error) {
145
- return this.handleHttpError(error, args.url);
283
+ return (0, utils_1.handleHttpError)(error, args.url);
146
284
  }
147
285
  }
148
286
  /**
@@ -155,6 +293,7 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
155
293
  async makeHttpRequestWithX402(walletProvider, args) {
156
294
  try {
157
295
  const account = walletProvider.toSigner();
296
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
297
  const api = (0, x402_axios_1.withPaymentInterceptor)(axios_1.default.create({}), account);
159
298
  const response = await api.request({
160
299
  url: args.url,
@@ -183,42 +322,21 @@ class X402ActionProvider extends actionProvider_1.ActionProvider {
183
322
  }, null, 2);
184
323
  }
185
324
  catch (error) {
186
- return this.handleHttpError(error, args.url);
187
- }
188
- }
189
- /**
190
- * Helper method to handle HTTP errors consistently.
191
- *
192
- * @param error - The axios error to handle
193
- * @param url - The URL that was being accessed when the error occurred
194
- * @returns A JSON string containing formatted error details
195
- */
196
- handleHttpError(error, url) {
197
- if (error.response) {
198
- return JSON.stringify({
199
- error: true,
200
- message: `HTTP ${error.response.status} error when accessing ${url}`,
201
- details: error.response.data?.error || error.response.statusText,
202
- suggestion: "Check if the URL is correct and the API is available.",
203
- }, null, 2);
204
- }
205
- if (error.request) {
206
- return JSON.stringify({
207
- error: true,
208
- message: `Network error when accessing ${url}`,
209
- details: error.message,
210
- suggestion: "Check your internet connection and verify the API endpoint is accessible.",
211
- }, null, 2);
325
+ return (0, utils_1.handleHttpError)(error, args.url);
212
326
  }
213
- return JSON.stringify({
214
- error: true,
215
- message: `Error making request to ${url}`,
216
- details: error.message,
217
- suggestion: "Please check the request parameters and try again.",
218
- }, null, 2);
219
327
  }
220
328
  }
221
329
  exports.X402ActionProvider = X402ActionProvider;
330
+ __decorate([
331
+ (0, actionDecorator_1.CreateAction)({
332
+ name: "discover_x402_services",
333
+ description: "Discover available x402 services. Optionally filter by a maximum price in whole units of USDC (only USDC payment options will be considered when filter is applied).",
334
+ schema: schemas_1.ListX402ServicesSchema,
335
+ }),
336
+ __metadata("design:type", Function),
337
+ __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]),
338
+ __metadata("design:returntype", Promise)
339
+ ], X402ActionProvider.prototype, "discoverX402Services", null);
222
340
  __decorate([
223
341
  (0, actionDecorator_1.CreateAction)({
224
342
  name: "make_http_request",
@@ -272,6 +390,7 @@ This action combines both steps into one, which means:
272
390
  - No chance to review payment details before paying
273
391
  - No confirmation step
274
392
  - Automatic payment processing
393
+ - Assumes payment option is compatible with wallet network
275
394
 
276
395
  EXAMPLES:
277
396
  - Production: make_http_request_with_x402("https://api.example.com/data")
@@ -39,9 +39,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const x402ActionProvider_1 = require("./x402ActionProvider");
40
40
  const axios_1 = __importDefault(require("axios"));
41
41
  const x402axios = __importStar(require("x402-axios"));
42
+ const x402Verify = __importStar(require("x402/verify"));
43
+ // Mock external facilitator dependency
44
+ jest.mock("@coinbase/x402", () => ({
45
+ facilitator: {},
46
+ }));
42
47
  // Mock modules
43
48
  jest.mock("axios");
44
49
  jest.mock("x402-axios");
50
+ jest.mock("x402/verify");
45
51
  // Create mock functions
46
52
  const mockRequest = jest.fn();
47
53
  // Create a complete mock axios instance
@@ -83,6 +89,7 @@ const mockAxios = {
83
89
  };
84
90
  const mockWithPaymentInterceptor = jest.fn().mockReturnValue(mockAxiosInstance);
85
91
  const mockDecodeXPaymentResponse = jest.fn();
92
+ const mockUseFacilitator = jest.fn();
86
93
  // Override the mocked modules
87
94
  axios_1.default.create = mockAxios.create;
88
95
  axios_1.default.request = mockRequest;
@@ -90,10 +97,12 @@ axios_1.default.isAxiosError = mockAxios.isAxiosError;
90
97
  // Mock x402-axios functions
91
98
  jest.mocked(x402axios.withPaymentInterceptor).mockImplementation(mockWithPaymentInterceptor);
92
99
  jest.mocked(x402axios.decodeXPaymentResponse).mockImplementation(mockDecodeXPaymentResponse);
100
+ jest.mocked(x402Verify.useFacilitator).mockImplementation(mockUseFacilitator);
93
101
  // Mock wallet provider
94
- const mockWalletProvider = {
102
+ const makeMockWalletProvider = (networkId) => ({
95
103
  toSigner: jest.fn().mockReturnValue("mock-signer"),
96
- };
104
+ getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", networkId }),
105
+ });
97
106
  // Sample responses based on real examples
98
107
  const MOCK_PAYMENT_INFO_RESPONSE = {
99
108
  paymentRequired: true,
@@ -171,7 +180,7 @@ describe("X402ActionProvider", () => {
171
180
  headers: {},
172
181
  config: {},
173
182
  });
174
- const result = await provider.makeHttpRequest(mockWalletProvider, {
183
+ const result = await provider.makeHttpRequest(makeMockWalletProvider("base-sepolia"), {
175
184
  url: "https://api.example.com/free",
176
185
  method: "GET",
177
186
  });
@@ -187,7 +196,7 @@ describe("X402ActionProvider", () => {
187
196
  headers: {},
188
197
  config: {},
189
198
  });
190
- const result = await provider.makeHttpRequest(mockWalletProvider, {
199
+ const result = await provider.makeHttpRequest(makeMockWalletProvider("base-sepolia"), {
191
200
  url: "https://www.x402.org/protected",
192
201
  method: "GET",
193
202
  });
@@ -201,7 +210,7 @@ describe("X402ActionProvider", () => {
201
210
  error.isAxiosError = true;
202
211
  error.request = {};
203
212
  mockRequest.mockRejectedValue(error);
204
- const result = await provider.makeHttpRequest(mockWalletProvider, {
213
+ const result = await provider.makeHttpRequest(makeMockWalletProvider("base-sepolia"), {
205
214
  url: "https://api.example.com/endpoint",
206
215
  method: "GET",
207
216
  });
@@ -210,6 +219,103 @@ describe("X402ActionProvider", () => {
210
219
  expect(parsedResult.message).toContain("Network error");
211
220
  });
212
221
  });
222
+ describe("listX402Services", () => {
223
+ it("should list services without filters", async () => {
224
+ const mockList = jest.fn().mockResolvedValue({
225
+ items: [
226
+ {
227
+ resource: "https://example.com/service1",
228
+ metadata: { category: "test" },
229
+ accepts: [
230
+ {
231
+ asset: "0xUSDC",
232
+ maxAmountRequired: "90000",
233
+ network: "base-sepolia",
234
+ scheme: "exact",
235
+ description: "Test service 1",
236
+ outputSchema: { type: "object" },
237
+ extra: { name: "USDC" },
238
+ },
239
+ ],
240
+ },
241
+ ],
242
+ });
243
+ mockUseFacilitator.mockReturnValue({ list: mockList });
244
+ const result = await provider.discoverX402Services(makeMockWalletProvider("base-sepolia"), {});
245
+ const parsed = JSON.parse(result);
246
+ expect(parsed.success).toBe(true);
247
+ expect(parsed.total).toBe(1);
248
+ expect(parsed.returned).toBe(1);
249
+ expect(parsed.items.length).toBe(1);
250
+ });
251
+ it("should filter services by asset and maxPrice", async () => {
252
+ const mockList = jest.fn().mockResolvedValue({
253
+ items: [
254
+ {
255
+ resource: "https://example.com/service1",
256
+ metadata: { category: "test" },
257
+ accepts: [
258
+ {
259
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // Real USDC address for base-sepolia
260
+ maxAmountRequired: "90000", // 0.09 USDC (should pass 0.1 filter)
261
+ network: "base-sepolia",
262
+ scheme: "exact",
263
+ description: "Test service 1",
264
+ outputSchema: { type: "object" },
265
+ extra: { name: "USDC" },
266
+ },
267
+ ],
268
+ },
269
+ {
270
+ resource: "https://example.com/service2",
271
+ metadata: { category: "test" },
272
+ accepts: [
273
+ {
274
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // Real USDC address for base-sepolia
275
+ maxAmountRequired: "150000", // 0.15 USDC (should fail 0.1 filter)
276
+ network: "base-sepolia",
277
+ scheme: "exact",
278
+ description: "Test service 2",
279
+ outputSchema: { type: "object" },
280
+ extra: { name: "USDC" },
281
+ },
282
+ ],
283
+ },
284
+ {
285
+ resource: "https://example.com/service3",
286
+ metadata: { category: "test" },
287
+ accepts: [
288
+ {
289
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // Real USDC address for base-sepolia
290
+ maxAmountRequired: "50000", // 0.05 USDC (should pass 0.1 filter)
291
+ network: "base-sepolia",
292
+ scheme: "exact",
293
+ description: "Test service 3",
294
+ outputSchema: { type: "object" },
295
+ extra: { name: "USDC" },
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ });
301
+ mockUseFacilitator.mockReturnValue({ list: mockList });
302
+ const result = await provider.discoverX402Services(makeMockWalletProvider("base-sepolia"), {
303
+ maxUsdcPrice: 0.1,
304
+ });
305
+ const parsed = JSON.parse(result);
306
+ expect(parsed.success).toBe(true);
307
+ expect(parsed.returned).toBe(2);
308
+ expect(parsed.items.map(item => item.resource)).toEqual(expect.arrayContaining(["https://example.com/service1", "https://example.com/service3"]));
309
+ });
310
+ it("should handle errors from facilitator", async () => {
311
+ const mockList = jest.fn().mockRejectedValue(new Error("boom"));
312
+ mockUseFacilitator.mockReturnValue({ list: mockList });
313
+ const result = await provider.discoverX402Services(makeMockWalletProvider("base-sepolia"), {});
314
+ const parsed = JSON.parse(result);
315
+ expect(parsed.error).toBe(true);
316
+ expect(parsed.message).toContain("Failed to list x402 services");
317
+ });
318
+ });
213
319
  describe("retryHttpRequestWithX402", () => {
214
320
  it("should successfully retry with payment", async () => {
215
321
  mockDecodeXPaymentResponse.mockReturnValue(MOCK_PAYMENT_RESPONSE);
@@ -222,7 +328,7 @@ describe("X402ActionProvider", () => {
222
328
  },
223
329
  config: {},
224
330
  });
225
- const result = await provider.retryWithX402(mockWalletProvider, {
331
+ const result = await provider.retryWithX402(makeMockWalletProvider("base-sepolia"), {
226
332
  url: "https://www.x402.org/protected",
227
333
  method: "GET",
228
334
  selectedPaymentOption: {
@@ -247,7 +353,7 @@ describe("X402ActionProvider", () => {
247
353
  error.isAxiosError = true;
248
354
  error.request = {};
249
355
  mockRequest.mockRejectedValue(error);
250
- const result = await provider.retryWithX402(mockWalletProvider, {
356
+ const result = await provider.retryWithX402(makeMockWalletProvider("base-sepolia"), {
251
357
  url: "https://www.x402.org/protected",
252
358
  method: "GET",
253
359
  selectedPaymentOption: {
@@ -274,7 +380,7 @@ describe("X402ActionProvider", () => {
274
380
  },
275
381
  config: {},
276
382
  });
277
- const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
383
+ const result = await provider.makeHttpRequestWithX402(makeMockWalletProvider("base-sepolia"), {
278
384
  url: "https://www.x402.org/protected",
279
385
  method: "GET",
280
386
  });
@@ -296,7 +402,7 @@ describe("X402ActionProvider", () => {
296
402
  headers: {},
297
403
  config: {},
298
404
  });
299
- const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
405
+ const result = await provider.makeHttpRequestWithX402(makeMockWalletProvider("base-sepolia"), {
300
406
  url: "https://api.example.com/free",
301
407
  method: "GET",
302
408
  });
@@ -310,7 +416,7 @@ describe("X402ActionProvider", () => {
310
416
  error.isAxiosError = true;
311
417
  error.request = {};
312
418
  mockRequest.mockRejectedValue(error);
313
- const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
419
+ const result = await provider.makeHttpRequestWithX402(makeMockWalletProvider("base-sepolia"), {
314
420
  url: "https://api.example.com/endpoint",
315
421
  method: "GET",
316
422
  });
package/dist/utils.d.ts CHANGED
@@ -20,3 +20,13 @@ export declare function approve(wallet: EvmWalletProvider, tokenAddress: string,
20
20
  * @returns The adjusted gas estimate as a bigint.
21
21
  */
22
22
  export declare function applyGasMultiplier(gas: bigint, multiplier: number): bigint;
23
+ /**
24
+ * Retry function with exponential backoff
25
+ *
26
+ * @param fn - The function to retry
27
+ * @param maxRetries - Maximum number of retries (default: 3)
28
+ * @param baseDelay - Base delay in milliseconds for retries (default: 1000)
29
+ * @param initialDelay - Initial delay before the first attempt in milliseconds (default: 0)
30
+ * @returns Promise that resolves with the function result or rejects with the last error
31
+ */
32
+ export declare function retryWithExponentialBackoff<T>(fn: () => Promise<T>, maxRetries?: number, baseDelay?: number, initialDelay?: number): Promise<T>;
package/dist/utils.js CHANGED
@@ -2,19 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.approve = approve;
4
4
  exports.applyGasMultiplier = applyGasMultiplier;
5
+ exports.retryWithExponentialBackoff = retryWithExponentialBackoff;
5
6
  const viem_1 = require("viem");
6
- const ERC20_ABI = [
7
- {
8
- inputs: [
9
- { name: "spender", type: "address" },
10
- { name: "amount", type: "uint256" },
11
- ],
12
- name: "approve",
13
- outputs: [{ name: "", type: "bool" }],
14
- stateMutability: "nonpayable",
15
- type: "function",
16
- },
17
- ];
7
+ const viem_2 = require("viem");
18
8
  /**
19
9
  * Approves a spender to spend tokens on behalf of the owner
20
10
  *
@@ -27,7 +17,7 @@ const ERC20_ABI = [
27
17
  async function approve(wallet, tokenAddress, spenderAddress, amount) {
28
18
  try {
29
19
  const data = (0, viem_1.encodeFunctionData)({
30
- abi: ERC20_ABI,
20
+ abi: viem_2.erc20Abi,
31
21
  functionName: "approve",
32
22
  args: [spenderAddress, amount],
33
23
  });
@@ -55,3 +45,43 @@ async function approve(wallet, tokenAddress, spenderAddress, amount) {
55
45
  function applyGasMultiplier(gas, multiplier) {
56
46
  return BigInt(Math.round(Number(gas) * multiplier));
57
47
  }
48
+ /**
49
+ * Utility function to sleep for a given number of milliseconds
50
+ *
51
+ * @param ms - Number of milliseconds to sleep
52
+ * @returns Promise that resolves after the specified delay
53
+ */
54
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
55
+ /**
56
+ * Retry function with exponential backoff
57
+ *
58
+ * @param fn - The function to retry
59
+ * @param maxRetries - Maximum number of retries (default: 3)
60
+ * @param baseDelay - Base delay in milliseconds for retries (default: 1000)
61
+ * @param initialDelay - Initial delay before the first attempt in milliseconds (default: 0)
62
+ * @returns Promise that resolves with the function result or rejects with the last error
63
+ */
64
+ async function retryWithExponentialBackoff(fn, maxRetries = 3, baseDelay = 1000, initialDelay = 0) {
65
+ let lastError;
66
+ // Wait before the first attempt if initialDelay is specified
67
+ if (initialDelay > 0) {
68
+ await sleep(initialDelay);
69
+ }
70
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
71
+ try {
72
+ return await fn();
73
+ }
74
+ catch (error) {
75
+ lastError = error;
76
+ // If this was the last attempt, throw the error
77
+ if (attempt === maxRetries) {
78
+ throw lastError;
79
+ }
80
+ // Wait after failed attempt with exponential backoff
81
+ // Calculate delay with exponential backoff: baseDelay * 2^attempt
82
+ const delay = baseDelay * Math.pow(2, attempt);
83
+ await sleep(delay);
84
+ }
85
+ }
86
+ throw lastError;
87
+ }
@@ -35,6 +35,13 @@ export declare class CdpEvmWalletProvider extends EvmWalletProvider implements W
35
35
  name: string | undefined;
36
36
  address: `0x${string}`;
37
37
  }>;
38
+ /**
39
+ * Signs a raw hash.
40
+ *
41
+ * @param hash - The hash to sign.
42
+ * @returns The signed hash.
43
+ */
44
+ sign(hash: `0x${string}`): Promise<Hex>;
38
45
  /**
39
46
  * Signs a message.
40
47
  *
@@ -89,6 +89,15 @@ class CdpEvmWalletProvider extends evmWalletProvider_1.EvmWalletProvider {
89
89
  address: __classPrivateFieldGet(this, _CdpEvmWalletProvider_serverAccount, "f").address,
90
90
  };
91
91
  }
92
+ /**
93
+ * Signs a raw hash.
94
+ *
95
+ * @param hash - The hash to sign.
96
+ * @returns The signed hash.
97
+ */
98
+ async sign(hash) {
99
+ return __classPrivateFieldGet(this, _CdpEvmWalletProvider_serverAccount, "f").sign({ hash });
100
+ }
92
101
  /**
93
102
  * Signs a message.
94
103
  *
@@ -129,29 +138,13 @@ class CdpEvmWalletProvider extends evmWalletProvider_1.EvmWalletProvider {
129
138
  * @returns The hash of the transaction.
130
139
  */
131
140
  async sendTransaction(transaction) {
132
- const txWithGasParams = {
133
- ...transaction,
134
- chainId: __classPrivateFieldGet(this, _CdpEvmWalletProvider_network, "f").chainId,
135
- };
136
- if (!txWithGasParams.maxFeePerGas && !txWithGasParams.gasPrice) {
137
- const feeData = await __classPrivateFieldGet(this, _CdpEvmWalletProvider_publicClient, "f").estimateFeesPerGas();
138
- txWithGasParams.maxFeePerGas = feeData.maxFeePerGas;
139
- txWithGasParams.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
140
- }
141
- if (!txWithGasParams.gas) {
142
- try {
143
- txWithGasParams.gas = await __classPrivateFieldGet(this, _CdpEvmWalletProvider_publicClient, "f").estimateGas({
144
- account: __classPrivateFieldGet(this, _CdpEvmWalletProvider_serverAccount, "f").address,
145
- ...txWithGasParams,
146
- });
147
- }
148
- catch (error) {
149
- console.warn("Failed to estimate gas, continuing without gas estimation", error);
150
- }
151
- }
152
141
  const result = await __classPrivateFieldGet(this, _CdpEvmWalletProvider_cdp, "f").evm.sendTransaction({
153
142
  address: __classPrivateFieldGet(this, _CdpEvmWalletProvider_serverAccount, "f").address,
154
- transaction: (0, viem_1.serializeTransaction)(txWithGasParams),
143
+ transaction: {
144
+ to: transaction.to,
145
+ value: transaction.value ? BigInt(transaction.value.toString()) : 0n,
146
+ data: transaction.data || "0x",
147
+ },
155
148
  network: this.getCdpSdkNetwork(),
156
149
  });
157
150
  return result.transactionHash;
@@ -114,6 +114,7 @@ describe("CdpEvmWalletProvider", () => {
114
114
  });
115
115
  mockServerAccount = {
116
116
  address: MOCK_ADDRESS,
117
+ sign: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
117
118
  signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
118
119
  signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
119
120
  };
@@ -214,6 +215,12 @@ describe("CdpEvmWalletProvider", () => {
214
215
  // signing operation tests
215
216
  // =========================================================
216
217
  describe("signing operations", () => {
218
+ it("should sign a hash", async () => {
219
+ const testHash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
220
+ const signature = await provider.sign(testHash);
221
+ expect(mockServerAccount.sign).toHaveBeenCalledWith({ hash: testHash });
222
+ expect(signature).toBe(MOCK_SIGNATURE);
223
+ });
217
224
  it("should sign messages", async () => {
218
225
  const signature = await provider.signMessage("Hello, world!");
219
226
  expect(mockServerAccount.signMessage).toHaveBeenCalledWith({ message: "Hello, world!" });
@@ -38,6 +38,13 @@ export declare class CdpSmartWalletProvider extends EvmWalletProvider implements
38
38
  address: Address;
39
39
  ownerAddress: Address;
40
40
  }>;
41
+ /**
42
+ * Signs a raw hash using the owner account.
43
+ *
44
+ * @param _hash - The hash to sign.
45
+ * @returns The signed hash.
46
+ */
47
+ sign(_hash: Hex): Promise<Hex>;
41
48
  /**
42
49
  * Signs a message using the owner account.
43
50
  *