@oneshot-agent/sdk 0.28.1 → 0.31.0

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/dist/index.js CHANGED
@@ -37,9 +37,11 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
40
+ const deadline_1 = require("./deadline");
41
+ const errors_1 = require("./errors");
40
42
  const ethers_1 = require("ethers");
41
43
  const ethers_2 = require("./providers/ethers");
42
- const errors_1 = require("./errors");
44
+ const errors_2 = require("./errors");
43
45
  var ethers_3 = require("./providers/ethers");
44
46
  Object.defineProperty(exports, "EthersWalletProvider", { enumerable: true, get: function () { return ethers_3.EthersWalletProvider; } });
45
47
  var cdp_1 = require("./providers/cdp");
@@ -49,7 +51,7 @@ Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function
49
51
  Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
50
52
  __exportStar(require("./errors"), exports);
51
53
  // Keep in sync with package.json `version`. Guarded by version.test.ts.
52
- const SDK_VERSION = '0.28.1';
54
+ const SDK_VERSION = '0.31.0';
53
55
  /** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
54
56
  const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
55
57
  /** HTTP poll cadence once the WebSocket has delivered for this request. */
@@ -66,6 +68,29 @@ function chainIdFromNetwork(network) {
66
68
  return m ? Number(m[1]) : undefined;
67
69
  }
68
70
  const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
71
+ // ETH-currency mode. A payment is an EIP-3009 authorization that the
72
+ // facilitator settles AFTER the API has responded, so for seconds after a
73
+ // successful call `balanceOf` still shows pre-payment funds. The SDK therefore
74
+ // keeps a ledger of signed-but-unsettled authorizations and works from
75
+ // `balanceOf − pending`, over-reserving (one early buffered swap) rather than
76
+ // under-reserving (a failed settlement).
77
+ /** One balanceOf read per paid call, deduped within roughly one Base block. */
78
+ const USDC_BALANCE_CACHE_MS = 2000;
79
+ /** How long a signed authorization stays subtracted from the on-chain balance (settlement lands well within this). */
80
+ const USDC_RESERVATION_TTL_MS = 90000;
81
+ const DEFAULT_SWAP_BUFFER_MULTIPLIER = 10;
82
+ const MAX_SWAP_BUFFER_MULTIPLIER = 1000;
83
+ /** Fixed-point scale for the multiplier (6 decimals, matching USDC). */
84
+ const SWAP_MULTIPLIER_SCALE = 1000000;
85
+ const ERC20_BALANCE_ABI = ['function balanceOf(address) view returns (uint256)'];
86
+ function validateSwapBufferMultiplier(m) {
87
+ if (m === undefined)
88
+ return DEFAULT_SWAP_BUFFER_MULTIPLIER;
89
+ if (typeof m !== 'number' || !Number.isFinite(m) || m < 1 || m > MAX_SWAP_BUFFER_MULTIPLIER) {
90
+ throw new errors_2.ValidationError(`swapBufferMultiplier must be a finite number between 1 and ${MAX_SWAP_BUFFER_MULTIPLIER}`, 'swapBufferMultiplier');
91
+ }
92
+ return m;
93
+ }
69
94
  // ============================================================================
70
95
  // Public types — defined in ./types.ts. Re-exported below so existing
71
96
  // consumer imports (`import { EmailToolOptions, ... } from '@oneshot-agent/sdk'`)
@@ -95,21 +120,21 @@ function validateBudgetConfig(budgets) {
95
120
  // A typo'd key (`daliy`) from an untyped caller must not silently mean "no cap".
96
121
  for (const key of Object.keys(budgets)) {
97
122
  if (!['daily', 'perTransaction', 'alertAt', 'pauseAt'].includes(key)) {
98
- throw new errors_1.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
123
+ throw new errors_2.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
99
124
  }
100
125
  }
101
126
  const positive = (v, field) => {
102
127
  if (v === undefined)
103
128
  return;
104
129
  if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
105
- throw new errors_1.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
130
+ throw new errors_2.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
106
131
  }
107
132
  };
108
133
  const fraction = (v, field) => {
109
134
  if (v === undefined)
110
135
  return;
111
136
  if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0 || v > 1) {
112
- throw new errors_1.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
137
+ throw new errors_2.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
113
138
  }
114
139
  };
115
140
  positive(budgets.daily, 'daily');
@@ -147,18 +172,24 @@ class OneShot {
147
172
  const walletProvider = new ethers_2.EthersWalletProvider(config.privateKey, rpcProvider);
148
173
  return new OneShot(config, walletProvider);
149
174
  }
150
- throw new errors_1.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
175
+ throw new errors_2.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
151
176
  }
152
177
  /**
153
178
  * Sync constructor — works with privateKey (backwards compatible).
154
179
  * For CDP wallets, use OneShot.create() instead.
155
180
  */
156
181
  constructor(config, walletProvider) {
182
+ /** ETH mode: signed payments not yet observed as settled, keyed by reservation id. */
183
+ this._usdcPending = new Map();
184
+ this._usdcReservationSeq = 0;
185
+ /** ETH mode: serializes read → decide → swap → reserve per instance (also prevents concurrent swap nonce races). */
186
+ this._usdcLock = Promise.resolve();
157
187
  this.baseUrl = config.baseUrl ?? BASE_URL;
158
188
  this.debug = config.debug ?? false;
159
189
  this.logger = config.logger ?? console.log;
160
190
  this._currency = config.currency ?? 'USDC';
161
191
  this._slippage = config.slippage ?? 0.01;
192
+ this._swapBufferMultiplier = validateSwapBufferMultiplier(config.swapBufferMultiplier);
162
193
  this._budgets = validateBudgetConfig(config.budgets);
163
194
  this._alertEmail = config.alerts?.email;
164
195
  this.rpcProvider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl ?? RPC_URL);
@@ -169,10 +200,10 @@ class OneShot {
169
200
  this.provider = new ethers_2.EthersWalletProvider(config.privateKey, this.rpcProvider);
170
201
  }
171
202
  else {
172
- throw new errors_1.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
203
+ throw new errors_2.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
173
204
  }
174
205
  if (this._currency === 'ETH' && !this.provider.sendTransaction) {
175
- throw new errors_1.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
206
+ throw new errors_2.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
176
207
  }
177
208
  if (this.debug) {
178
209
  this.log(`SDK initialized — chain=${CHAIN_ID} currency=${this._currency}`);
@@ -196,6 +227,10 @@ class OneShot {
196
227
  get slippage() {
197
228
  return this._slippage;
198
229
  }
230
+ /** ETH mode: how many payments' worth of USDC a swap buys (default 10). */
231
+ get swapBufferMultiplier() {
232
+ return this._swapBufferMultiplier;
233
+ }
199
234
  /** The budget config this instance was constructed with, if any. */
200
235
  get budgetConfig() {
201
236
  return this._budgets;
@@ -280,7 +315,7 @@ class OneShot {
280
315
  headers: this.headers(),
281
316
  });
282
317
  if (!response.ok) {
283
- throw new errors_1.ToolError('Failed to list domains', response.status, await response.text());
318
+ throw new errors_2.ToolError('Failed to list domains', response.status, await response.text());
284
319
  }
285
320
  return response.json();
286
321
  }
@@ -292,7 +327,7 @@ class OneShot {
292
327
  headers: this.headers(),
293
328
  });
294
329
  if (!response.ok) {
295
- throw new errors_1.ToolError('Failed to pause domain', response.status, await response.text());
330
+ throw new errors_2.ToolError('Failed to pause domain', response.status, await response.text());
296
331
  }
297
332
  return response.json();
298
333
  }
@@ -304,7 +339,7 @@ class OneShot {
304
339
  headers: this.headers(),
305
340
  });
306
341
  if (!response.ok) {
307
- throw new errors_1.ToolError('Failed to resume domain', response.status, await response.text());
342
+ throw new errors_2.ToolError('Failed to resume domain', response.status, await response.text());
308
343
  }
309
344
  return response.json();
310
345
  }
@@ -317,7 +352,7 @@ class OneShot {
317
352
  }
318
353
  async enrichProfile(options) {
319
354
  if (!options.linkedin_url && !options.email && !options.name) {
320
- throw new errors_1.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
355
+ throw new errors_2.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
321
356
  }
322
357
  return this.tool('enrich/profile', { ...options });
323
358
  }
@@ -326,14 +361,58 @@ class OneShot {
326
361
  }
327
362
  async enrichCompany(options) {
328
363
  if (!options.domain && !options.name && !options.linkedin_url && !options.ticker) {
329
- throw new errors_1.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
364
+ throw new errors_2.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
330
365
  }
331
366
  return this.tool('enrich/company', { ...options });
332
367
  }
368
+ /**
369
+ * Discover local businesses (restaurants, contractors, practices) by
370
+ * category/keywords × location. Flat price per search, not per row.
371
+ */
372
+ async localSearch(options) {
373
+ if (!options.location || options.location.length === 0) {
374
+ throw new errors_2.ValidationError('location is required (e.g. ["Austin, TX"])', 'location');
375
+ }
376
+ if (!(options.category && options.category.length) && !(options.keywords && options.keywords.length)) {
377
+ throw new errors_2.ValidationError('At least one of category or keywords is required', 'category');
378
+ }
379
+ return this.tool('local/search', { ...options, limit: options.limit ?? 100 });
380
+ }
381
+ /**
382
+ * Resolve a business name + one locating field to its domain, phone,
383
+ * category and operating status. A miss resolves with `found: false`
384
+ * (a completed job), never a rejection.
385
+ */
386
+ async localResolve(options) {
387
+ this.validate(options.name, 'name');
388
+ if (!options.address && !options.city && !options.postal_code && !options.phone) {
389
+ throw new errors_2.ValidationError('name plus at least one of address, city, postal_code, or phone is required', 'address');
390
+ }
391
+ return this.tool('local/resolve', { ...options });
392
+ }
393
+ /**
394
+ * Federal contract opportunities (SAM.gov) by NAICS code — Sources Sought
395
+ * and Presolicitation notices with the contracting officer's published
396
+ * contact. Flat price per search; zero notices is a completed result.
397
+ */
398
+ async govSolicitations(options) {
399
+ if (!options.naics || options.naics.length === 0) {
400
+ throw new errors_2.ValidationError('naics is required (one or more 6-digit codes, e.g. ["541511"])', 'naics');
401
+ }
402
+ const bad = options.naics.find(c => !/^\d{6}$/.test(String(c)));
403
+ if (bad !== undefined) {
404
+ throw new errors_2.ValidationError(`NAICS codes are 6 digits (got ${JSON.stringify(bad)})`, 'naics');
405
+ }
406
+ return this.tool('gov/solicitations', {
407
+ ...options,
408
+ notice_types: options.notice_types ?? ['r', 'p'],
409
+ limit: options.limit ?? 100,
410
+ });
411
+ }
333
412
  async findEmail(options) {
334
413
  this.validate(options.company_domain, 'company_domain');
335
414
  if (!options.full_name && !(options.first_name && options.last_name)) {
336
- throw new errors_1.ValidationError('Either full_name or both first_name and last_name required', 'name');
415
+ throw new errors_2.ValidationError('Either full_name or both first_name and last_name required', 'name');
337
416
  }
338
417
  return this.tool('enrich/email', { ...options });
339
418
  }
@@ -343,13 +422,13 @@ class OneShot {
343
422
  }
344
423
  async deepResearchPerson(options) {
345
424
  if (!options.email && !options.social_media_url && !options.name) {
346
- throw new errors_1.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
425
+ throw new errors_2.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
347
426
  }
348
427
  return this.tool('research/person', { ...options });
349
428
  }
350
429
  async socialProfiles(options) {
351
430
  if (!options.email && !options.social_media_url) {
352
- throw new errors_1.ValidationError('At least one of email or social_media_url is required', 'identifier');
431
+ throw new errors_2.ValidationError('At least one of email or social_media_url is required', 'identifier');
353
432
  }
354
433
  return this.tool('research/social', { ...options });
355
434
  }
@@ -364,7 +443,7 @@ class OneShot {
364
443
  }
365
444
  async personInterests(options) {
366
445
  if (!options.email && !options.phone && !options.social_media_url) {
367
- throw new errors_1.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
446
+ throw new errors_2.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
368
447
  }
369
448
  return this.tool('research/interests', { ...options });
370
449
  }
@@ -382,7 +461,7 @@ class OneShot {
382
461
  headers: await this.signedReadHeaders()
383
462
  });
384
463
  if (!response.ok) {
385
- throw new errors_1.ToolError('Failed to list inbox', response.status, await response.text());
464
+ throw new errors_2.ToolError('Failed to list inbox', response.status, await response.text());
386
465
  }
387
466
  return response.json();
388
467
  }
@@ -392,10 +471,10 @@ class OneShot {
392
471
  headers: await this.signedReadHeaders()
393
472
  });
394
473
  if (response.status === 404) {
395
- throw new errors_1.ToolError('Email not found', 404, 'Email not found');
474
+ throw new errors_2.ToolError('Email not found', 404, 'Email not found');
396
475
  }
397
476
  if (!response.ok) {
398
- throw new errors_1.ToolError('Failed to get email', response.status, await response.text());
477
+ throw new errors_2.ToolError('Failed to get email', response.status, await response.text());
399
478
  }
400
479
  return response.json();
401
480
  }
@@ -460,13 +539,13 @@ class OneShot {
460
539
  this.validate(options.objective, 'objective');
461
540
  this.validate(options.target_number, 'target_number');
462
541
  if (Array.isArray(options.target_number) && options.target_number.length === 0) {
463
- throw new errors_1.ValidationError('target_number array cannot be empty', 'target_number');
542
+ throw new errors_2.ValidationError('target_number array cannot be empty', 'target_number');
464
543
  }
465
544
  if (options.objective.length < 10) {
466
- throw new errors_1.ValidationError('Objective must be at least 10 characters', 'objective');
545
+ throw new errors_2.ValidationError('Objective must be at least 10 characters', 'objective');
467
546
  }
468
547
  if (options.max_duration_minutes !== undefined && (options.max_duration_minutes < 1 || options.max_duration_minutes > 30)) {
469
- throw new errors_1.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
548
+ throw new errors_2.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
470
549
  }
471
550
  const payload = {
472
551
  objective: options.objective,
@@ -489,12 +568,12 @@ class OneShot {
489
568
  on400: async (resp) => {
490
569
  const errorData = await resp.json();
491
570
  if (errorData.error === 'content_blocked') {
492
- throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
571
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
493
572
  }
494
573
  if (errorData.error === 'emergency_number_blocked') {
495
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
574
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
496
575
  }
497
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
576
+ throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
498
577
  },
499
578
  });
500
579
  if (callResp.status !== 202) {
@@ -523,17 +602,17 @@ class OneShot {
523
602
  this.validate(options.message, 'message');
524
603
  this.validate(options.to_number, 'to_number');
525
604
  if (Array.isArray(options.to_number) && options.to_number.length === 0) {
526
- throw new errors_1.ValidationError('to_number array cannot be empty', 'to_number');
605
+ throw new errors_2.ValidationError('to_number array cannot be empty', 'to_number');
527
606
  }
528
607
  if (options.message.length < 1) {
529
- throw new errors_1.ValidationError('Message is required', 'message');
608
+ throw new errors_2.ValidationError('Message is required', 'message');
530
609
  }
531
610
  if (options.message.length > 1600) {
532
- throw new errors_1.ValidationError('Message must be 1600 characters or less', 'message');
611
+ throw new errors_2.ValidationError('Message must be 1600 characters or less', 'message');
533
612
  }
534
613
  const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
535
614
  if (recipientCount > 10) {
536
- throw new errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
615
+ throw new errors_2.ValidationError('Maximum 10 recipients allowed', 'to_number');
537
616
  }
538
617
  const payload = {
539
618
  message: options.message,
@@ -551,12 +630,12 @@ class OneShot {
551
630
  on400: async (resp) => {
552
631
  const errorData = await resp.json();
553
632
  if (errorData.error === 'content_blocked') {
554
- throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
633
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
555
634
  }
556
635
  if (errorData.error === 'emergency_number_blocked') {
557
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
636
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
558
637
  }
559
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
638
+ throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
560
639
  },
561
640
  });
562
641
  if (sendResp.status !== 202) {
@@ -590,7 +669,7 @@ class OneShot {
590
669
  this.validate(options.product?.name, 'product.name');
591
670
  this.validate(options.product?.description, 'product.description');
592
671
  if (options.product.description.length < 10) {
593
- throw new errors_1.ValidationError('Product description must be at least 10 characters', 'product.description');
672
+ throw new errors_2.ValidationError('Product description must be at least 10 characters', 'product.description');
594
673
  }
595
674
  const payload = {
596
675
  type: options.type ?? 'saas',
@@ -625,7 +704,7 @@ class OneShot {
625
704
  },
626
705
  on400: async (resp) => {
627
706
  const errorData = await resp.json();
628
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
707
+ throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
629
708
  },
630
709
  });
631
710
  if (buildResp.status !== 202) {
@@ -653,10 +732,10 @@ class OneShot {
653
732
  async browser(options) {
654
733
  this.validate(options.task, 'task');
655
734
  if (options.task.length < 10) {
656
- throw new errors_1.ValidationError('Task must be at least 10 characters', 'task');
735
+ throw new errors_2.ValidationError('Task must be at least 10 characters', 'task');
657
736
  }
658
737
  if (options.max_steps !== undefined && (options.max_steps < 1 || options.max_steps > 100)) {
659
- throw new errors_1.ValidationError('max_steps must be between 1 and 100', 'max_steps');
738
+ throw new errors_2.ValidationError('max_steps must be between 1 and 100', 'max_steps');
660
739
  }
661
740
  const payload = {
662
741
  task: options.task,
@@ -687,7 +766,7 @@ class OneShot {
687
766
  onQuote: (ctx) => this.log(`Browser quote: $${ctx.estimated_cost} for ~${ctx.estimated_steps} steps`),
688
767
  on400: async (resp) => {
689
768
  const errorData = await resp.json();
690
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
769
+ throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
691
770
  },
692
771
  });
693
772
  if (execResp.status !== 202) {
@@ -717,7 +796,7 @@ class OneShot {
717
796
  body: JSON.stringify({ name }),
718
797
  });
719
798
  if (!response.ok) {
720
- throw new errors_1.ToolError('Failed to create browser profile', response.status, await response.text());
799
+ throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text());
721
800
  }
722
801
  return response.json();
723
802
  }
@@ -737,7 +816,7 @@ class OneShot {
737
816
  headers: await this.signedReadHeaders(),
738
817
  });
739
818
  if (!response.ok) {
740
- throw new errors_1.ToolError('Failed to list browser profiles', response.status, await response.text());
819
+ throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text());
741
820
  }
742
821
  const data = await response.json();
743
822
  return data.profiles;
@@ -757,7 +836,7 @@ class OneShot {
757
836
  headers: await this.signedReadHeaders(),
758
837
  });
759
838
  if (!response.ok) {
760
- throw new errors_1.ToolError('Failed to delete browser profile', response.status, await response.text());
839
+ throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text());
761
840
  }
762
841
  }
763
842
  /**
@@ -803,7 +882,7 @@ class OneShot {
803
882
  headers: await this.signedReadHeaders()
804
883
  });
805
884
  if (!response.ok) {
806
- throw new errors_1.ToolError('Failed to list SMS inbox', response.status, await response.text());
885
+ throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text());
807
886
  }
808
887
  return response.json();
809
888
  }
@@ -822,10 +901,10 @@ class OneShot {
822
901
  headers: await this.signedReadHeaders()
823
902
  });
824
903
  if (response.status === 404) {
825
- throw new errors_1.ToolError('SMS message not found', 404, 'Message not found');
904
+ throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
826
905
  }
827
906
  if (!response.ok) {
828
- throw new errors_1.ToolError('Failed to get SMS message', response.status, await response.text());
907
+ throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text());
829
908
  }
830
909
  return response.json();
831
910
  }
@@ -850,7 +929,7 @@ class OneShot {
850
929
  headers: await this.signedReadHeaders()
851
930
  });
852
931
  if (!response.ok) {
853
- throw new errors_1.ToolError('Failed to list notifications', response.status, await response.text());
932
+ throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text());
854
933
  }
855
934
  return response.json();
856
935
  }
@@ -869,10 +948,10 @@ class OneShot {
869
948
  headers: await this.signedReadHeaders()
870
949
  });
871
950
  if (response.status === 404) {
872
- throw new errors_1.ToolError('Notification not found', 404, 'Notification not found');
951
+ throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
873
952
  }
874
953
  if (!response.ok) {
875
- throw new errors_1.ToolError('Failed to mark notification as read', response.status, await response.text());
954
+ throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text());
876
955
  }
877
956
  }
878
957
  async getUnifiedBalance() {
@@ -880,7 +959,7 @@ class OneShot {
880
959
  headers: await this.signedReadHeaders()
881
960
  });
882
961
  if (!response.ok) {
883
- throw new errors_1.ToolError('Failed to fetch balance', response.status, await response.text());
962
+ throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text());
884
963
  }
885
964
  return response.json();
886
965
  }
@@ -950,9 +1029,9 @@ class OneShot {
950
1029
  on400: async (resp) => {
951
1030
  const errorData = await resp.json();
952
1031
  if (errorData.error === 'content_blocked') {
953
- throw new errors_1.ContentBlockedError(errorData.message, []);
1032
+ throw new errors_2.ContentBlockedError(errorData.message, []);
954
1033
  }
955
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
1034
+ throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
956
1035
  },
957
1036
  });
958
1037
  if (createResp.status !== 202) {
@@ -975,10 +1054,10 @@ class OneShot {
975
1054
  headers: this.headers()
976
1055
  });
977
1056
  if (response.status === 404) {
978
- throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
1057
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
979
1058
  }
980
1059
  if (!response.ok) {
981
- throw new errors_1.ToolError('Failed to get compute goal', response.status, await response.text());
1060
+ throw new errors_2.ToolError('Failed to get compute goal', response.status, await response.text());
982
1061
  }
983
1062
  const json = await response.json();
984
1063
  return json.data;
@@ -1000,7 +1079,7 @@ class OneShot {
1000
1079
  headers: this.headers()
1001
1080
  });
1002
1081
  if (!response.ok) {
1003
- throw new errors_1.ToolError('Failed to get compute tasks', response.status, await response.text());
1082
+ throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text());
1004
1083
  }
1005
1084
  const json = await response.json();
1006
1085
  return json.data;
@@ -1020,10 +1099,10 @@ class OneShot {
1020
1099
  headers: this.headers()
1021
1100
  });
1022
1101
  if (response.status === 404) {
1023
- throw new errors_1.ToolError('Budget not found', 404, 'Budget not found for this goal');
1102
+ throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
1024
1103
  }
1025
1104
  if (!response.ok) {
1026
- throw new errors_1.ToolError('Failed to get compute budget', response.status, await response.text());
1105
+ throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text());
1027
1106
  }
1028
1107
  const json = await response.json();
1029
1108
  return json.data;
@@ -1045,10 +1124,10 @@ class OneShot {
1045
1124
  body: JSON.stringify({ reason })
1046
1125
  });
1047
1126
  if (response.status === 404) {
1048
- throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
1127
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
1049
1128
  }
1050
1129
  if (!response.ok) {
1051
- throw new errors_1.ToolError('Failed to cancel compute goal', response.status, await response.text());
1130
+ throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text());
1052
1131
  }
1053
1132
  const json = await response.json();
1054
1133
  return json.data;
@@ -1074,10 +1153,10 @@ class OneShot {
1074
1153
  body: JSON.stringify(input)
1075
1154
  });
1076
1155
  if (response.status === 404) {
1077
- throw new errors_1.ToolError('Goal or task not found', 404, await response.text());
1156
+ throw new errors_2.ToolError('Goal or task not found', 404, await response.text());
1078
1157
  }
1079
1158
  if (!response.ok) {
1080
- throw new errors_1.ToolError('Failed to respond to compute task', response.status, await response.text());
1159
+ throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text());
1081
1160
  }
1082
1161
  const json = await response.json();
1083
1162
  return json.data;
@@ -1098,7 +1177,7 @@ class OneShot {
1098
1177
  body: JSON.stringify({ reason })
1099
1178
  });
1100
1179
  if (!response.ok) {
1101
- throw new errors_1.ToolError('Failed to pause compute goal', response.status, await response.text());
1180
+ throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text());
1102
1181
  }
1103
1182
  const json = await response.json();
1104
1183
  return json.data;
@@ -1120,7 +1199,7 @@ class OneShot {
1120
1199
  body: JSON.stringify({})
1121
1200
  });
1122
1201
  if (!response.ok) {
1123
- throw new errors_1.ToolError('Failed to resume compute goal', response.status, await response.text());
1202
+ throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text());
1124
1203
  }
1125
1204
  const json = await response.json();
1126
1205
  return json.data;
@@ -1140,7 +1219,7 @@ class OneShot {
1140
1219
  async fundComputeGoal(goalId, amount) {
1141
1220
  this.validate(goalId, 'goalId');
1142
1221
  if (!amount || amount <= 0) {
1143
- throw new errors_1.ValidationError('amount must be a positive number', 'amount');
1222
+ throw new errors_2.ValidationError('amount must be a positive number', 'amount');
1144
1223
  }
1145
1224
  const path = `/v1/compute/${goalId}/fund`;
1146
1225
  const payload = { amount };
@@ -1164,8 +1243,8 @@ class OneShot {
1164
1243
  };
1165
1244
  const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, path, payload, quoteData.context.quote_id);
1166
1245
  paymentInfo.amount = this.chargeAmount(accepted, quoteData.payment_request.amount);
1167
- const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1168
- const fundResp = await this.makeRequest(path, payload, auth, quoteData.context.quote_id);
1246
+ const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1247
+ const fundResp = await this.makePaidRequest(signed, path, payload, quoteData.context.quote_id);
1169
1248
  if (!fundResp.ok) {
1170
1249
  await this.failFromResponse('Failed to fund compute goal', fundResp);
1171
1250
  }
@@ -1193,7 +1272,7 @@ class OneShot {
1193
1272
  headers: this.headers()
1194
1273
  });
1195
1274
  if (!response.ok) {
1196
- throw new errors_1.ToolError('Failed to get spend breakdown', response.status, await response.text());
1275
+ throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text());
1197
1276
  }
1198
1277
  return response.json();
1199
1278
  }
@@ -1212,7 +1291,7 @@ class OneShot {
1212
1291
  headers: this.headers()
1213
1292
  });
1214
1293
  if (!response.ok) {
1215
- throw new errors_1.ToolError('Failed to get RoCS', response.status, await response.text());
1294
+ throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text());
1216
1295
  }
1217
1296
  return response.json();
1218
1297
  }
@@ -1248,7 +1327,7 @@ class OneShot {
1248
1327
  headers: this.headers()
1249
1328
  });
1250
1329
  if (!response.ok) {
1251
- throw new errors_1.ToolError('Failed to list receipts', response.status, await response.text());
1330
+ throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text());
1252
1331
  }
1253
1332
  return response.json();
1254
1333
  }
@@ -1287,7 +1366,7 @@ class OneShot {
1287
1366
  body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
1288
1367
  });
1289
1368
  if (!response.ok) {
1290
- throw new errors_1.ToolError('Failed to record outcome value', response.status, await response.text());
1369
+ throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text());
1291
1370
  }
1292
1371
  return;
1293
1372
  }
@@ -1299,10 +1378,10 @@ class OneShot {
1299
1378
  body: JSON.stringify(valueTag)
1300
1379
  });
1301
1380
  if (response.status === 404) {
1302
- throw new errors_1.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
1381
+ throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
1303
1382
  }
1304
1383
  if (!response.ok) {
1305
- throw new errors_1.ToolError('Failed to tag receipt value', response.status, await response.text());
1384
+ throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text());
1306
1385
  }
1307
1386
  }
1308
1387
  /**
@@ -1328,7 +1407,7 @@ class OneShot {
1328
1407
  headers: this.headers()
1329
1408
  });
1330
1409
  if (!response.ok) {
1331
- throw new errors_1.ToolError('Failed to get RoCS by goal', response.status, await response.text());
1410
+ throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text());
1332
1411
  }
1333
1412
  return response.json();
1334
1413
  }
@@ -1341,7 +1420,7 @@ class OneShot {
1341
1420
  }
1342
1421
  validate(value, field) {
1343
1422
  if (!value)
1344
- throw new errors_1.ValidationError(`${field} is required`, field);
1423
+ throw new errors_2.ValidationError(`${field} is required`, field);
1345
1424
  }
1346
1425
  headers() {
1347
1426
  return {
@@ -1435,11 +1514,11 @@ class OneShot {
1435
1514
  });
1436
1515
  }
1437
1516
  catch (err) {
1438
- throw new errors_1.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
1517
+ throw new errors_2.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
1439
1518
  }
1440
1519
  if (!response.ok) {
1441
1520
  const text = await response.text();
1442
- throw new errors_1.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
1521
+ throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
1443
1522
  }
1444
1523
  this.log('Budget synced');
1445
1524
  })();
@@ -1467,7 +1546,7 @@ class OneShot {
1467
1546
  return;
1468
1547
  const amount = parseFloat(total);
1469
1548
  if (Number.isFinite(amount) && amount > cap) {
1470
- throw new errors_1.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
1549
+ throw new errors_2.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
1471
1550
  }
1472
1551
  }
1473
1552
  /**
@@ -1484,7 +1563,7 @@ class OneShot {
1484
1563
  headers: await this.signedReadHeaders(),
1485
1564
  });
1486
1565
  if (!response.ok) {
1487
- throw new errors_1.ToolError('Failed to fetch budgets', response.status, await response.text());
1566
+ throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text());
1488
1567
  }
1489
1568
  const body = await response.json();
1490
1569
  return (body.data ?? body);
@@ -1492,7 +1571,7 @@ class OneShot {
1492
1571
  /** Local fast-fail guard: throw when a quote total exceeds the caller's cap. */
1493
1572
  assertWithinMaxCost(total, maxCost) {
1494
1573
  if (maxCost && parseFloat(total) > maxCost) {
1495
- throw new errors_1.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
1574
+ throw new errors_2.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
1496
1575
  }
1497
1576
  }
1498
1577
  /**
@@ -1538,7 +1617,7 @@ class OneShot {
1538
1617
  const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
1539
1618
  if (budget)
1540
1619
  throw budget;
1541
- throw new errors_1.ToolError(message, response.status, text);
1620
+ throw new errors_2.ToolError(message, response.status, text);
1542
1621
  }
1543
1622
  /**
1544
1623
  * Map a 403 `budget_exceeded` body onto a typed error, so callers can catch
@@ -1551,7 +1630,7 @@ class OneShot {
1551
1630
  if (body.error !== 'budget_exceeded')
1552
1631
  return undefined;
1553
1632
  const b = body.budget ?? {};
1554
- return new errors_1.BudgetExceededError(body.message ?? 'Agent spend budget exceeded', b.reason === 'per_transaction' ? 'per_transaction' : 'daily', b.cap !== undefined ? parseFloat(b.cap) : undefined, b.spent !== undefined ? parseFloat(b.spent) : undefined, b.charge !== undefined ? parseFloat(b.charge) : undefined, b.resets_at);
1633
+ return new errors_2.BudgetExceededError(body.message ?? 'Agent spend budget exceeded', b.reason === 'per_transaction' ? 'per_transaction' : 'daily', b.cap !== undefined ? parseFloat(b.cap) : undefined, b.spent !== undefined ? parseFloat(b.spent) : undefined, b.charge !== undefined ? parseFloat(b.charge) : undefined, b.resets_at);
1555
1634
  }
1556
1635
  catch {
1557
1636
  return undefined;
@@ -1574,7 +1653,7 @@ class OneShot {
1574
1653
  expectedAmount ? `expected $${expectedAmount}` : null,
1575
1654
  receivedAmount ? `signed $${receivedAmount}` : null,
1576
1655
  ].filter(Boolean).join(', ');
1577
- return new errors_1.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
1656
+ return new errors_2.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
1578
1657
  amount: expectedAmount,
1579
1658
  asset: data.expected?.asset,
1580
1659
  network: data.expected?.network,
@@ -1610,8 +1689,59 @@ class OneShot {
1610
1689
  return undefined;
1611
1690
  return { 'Idempotency-Key': idempotencyKey };
1612
1691
  }
1692
+ async readReliabilityJson(path, signed, allowDegraded = false) {
1693
+ const scope = (0, deadline_1.deadlineScope)(undefined, signed ? 10000 : 5000);
1694
+ try {
1695
+ return await (0, deadline_1.abortable)((async () => {
1696
+ const headers = signed ? await this.signedReadHeaders() : undefined;
1697
+ if (scope.signal.aborted)
1698
+ throw new errors_2.OneShotError('Read deadline exceeded');
1699
+ const response = await fetch(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
1700
+ if (!response.ok && !(allowDegraded && response.status === 503))
1701
+ await this.failFromResponse('Reliability read failed', response);
1702
+ return await response.json();
1703
+ })(), scope.signal);
1704
+ }
1705
+ finally {
1706
+ scope.close();
1707
+ }
1708
+ }
1709
+ async recoverRequest(options) {
1710
+ const query = new URLSearchParams({ endpoint: options.endpoint, key: options.idempotencyKey });
1711
+ return this.readReliabilityJson(`/v1/submissions/recover?${query}`, true);
1712
+ }
1713
+ async getServiceStatus() {
1714
+ return this.readReliabilityJson('/v1/status', false, true);
1715
+ }
1613
1716
  async executeToolRequest(endpoint, options, quoteId) {
1614
- const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
1717
+ const reliable = /(?:^|\/)(enrich\/(profile|email)|verify\/email)$/.test(endpoint);
1718
+ const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
1719
+ if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
1720
+ throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
1721
+ }
1722
+ const scope = (0, deadline_1.deadlineScope)(options.signal, options.totalTimeoutMs);
1723
+ const context = { phase: 'initialization' };
1724
+ const started = Date.now();
1725
+ try {
1726
+ if (scope.signal.aborted)
1727
+ throw new errors_2.OneShotError('Operation cancelled');
1728
+ if (key)
1729
+ options.onRequestCreated?.({ idempotencyKey: key });
1730
+ return await (0, deadline_1.abortable)(this.executeToolRequestImpl(endpoint, { ...options, idempotencyKey: key, signal: scope.signal }, quoteId, context), scope.signal);
1731
+ }
1732
+ catch (error) {
1733
+ if (scope.timedOut())
1734
+ throw new errors_1.RequestTimeoutError(Date.now() - started, context.phase, key, context.requestId, context.receiptId);
1735
+ if (error instanceof Error)
1736
+ Object.assign(error, { idempotencyKey: key, requestId: context.requestId, receiptId: context.receiptId, phase: context.phase });
1737
+ throw error;
1738
+ }
1739
+ finally {
1740
+ scope.close();
1741
+ }
1742
+ }
1743
+ async executeToolRequestImpl(endpoint, options, quoteId, context = { phase: "initialization" }) {
1744
+ const { totalTimeoutMs, onRequestCreated, onAccepted, signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
1615
1745
  const extraHeaders = {
1616
1746
  ...this.maxCostHeader(maxCost),
1617
1747
  ...this.idempotencyHeader(idempotencyKey),
@@ -1640,11 +1770,18 @@ class OneShot {
1640
1770
  }
1641
1771
  }
1642
1772
  if (signal?.aborted) {
1643
- throw new errors_1.OneShotError('Operation cancelled');
1773
+ throw new errors_2.OneShotError('Operation cancelled');
1644
1774
  }
1645
1775
  // One-time push of config.budgets before the first paid call, so the
1646
1776
  // server-side gate knows about them on this very request.
1647
1777
  await this.ensureBudgetsSynced();
1778
+ if (signal?.aborted)
1779
+ throw new errors_2.OneShotError('Operation cancelled');
1780
+ if (idempotencyKey)
1781
+ Object.assign(extraHeaders, { 'x-agent-proof': 'required' });
1782
+ if (signal?.aborted)
1783
+ throw new errors_2.OneShotError('Operation cancelled');
1784
+ context.phase = 'submission';
1648
1785
  let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
1649
1786
  // Handle 402 Payment Required
1650
1787
  if (response.status === 402) {
@@ -1665,8 +1802,11 @@ class OneShot {
1665
1802
  this.log(`Payment required: ${paymentInfo.amount} USDC`);
1666
1803
  this.assertWithinBudget(paymentInfo.amount);
1667
1804
  this.checkAbortBeforePayment(signal);
1668
- const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1669
- response = await this.makeRequest(endpoint, payload, auth, quoteId, signal, undefined, extraHeaders);
1805
+ context.phase = 'payment';
1806
+ const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1807
+ this.checkAbortBeforePayment(signal);
1808
+ context.phase = 'submission';
1809
+ response = await this.makePaidRequest(signed, endpoint, payload, quoteId, signal, undefined, extraHeaders);
1670
1810
  }
1671
1811
  if (!response.ok) {
1672
1812
  await this.failFromResponse('Tool request failed', response);
@@ -1674,11 +1814,19 @@ class OneShot {
1674
1814
  const result = await response.json();
1675
1815
  // Handle async jobs
1676
1816
  if ((result.status === 'pending' || result.status === 'processing') && result.request_id) {
1817
+ context.requestId = String(result.request_id);
1818
+ context.receiptId = typeof result.receipt_id === 'string' ? result.receipt_id : undefined;
1819
+ onAccepted?.({ request_id: context.requestId, receipt_id: context.receiptId, idempotencyKey });
1820
+ context.phase = 'polling';
1677
1821
  this.log(`Job queued: ${result.request_id}`);
1678
1822
  if (!wait) {
1679
- return { request_id: result.request_id, status: result.status };
1823
+ return { ...result, idempotencyKey };
1824
+ }
1825
+ const completed = await this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
1826
+ if (completed && typeof completed === 'object' && !Array.isArray(completed)) {
1827
+ return { ...completed, request_id: context.requestId, receipt_id: completed.receipt_id ?? context.receiptId, idempotencyKey };
1680
1828
  }
1681
- return this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
1829
+ return completed;
1682
1830
  }
1683
1831
  return (result.data ?? result);
1684
1832
  }
@@ -1716,8 +1864,8 @@ class OneShot {
1716
1864
  this.checkAbortBeforePayment(cfg.signal);
1717
1865
  const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal);
1718
1866
  paymentInfo.amount = this.chargeAmount(accepted, quoteData.payment_request.amount);
1719
- const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1720
- const execResp = await this.makeRequest(cfg.endpoint, cfg.payload, auth, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs);
1867
+ const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1868
+ const execResp = await this.makePaidRequest(signed, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs);
1721
1869
  return { context: quoteData.context, execResp };
1722
1870
  }
1723
1871
  /**
@@ -1757,7 +1905,7 @@ class OneShot {
1757
1905
  const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
1758
1906
  // A failed job or a cancellation is a real outcome. Anything else is a
1759
1907
  // transport problem: never settle the race on it, HTTP carries on.
1760
- if (err instanceof errors_1.OneShotError)
1908
+ if (err instanceof errors_2.OneShotError)
1761
1909
  throw err;
1762
1910
  this.log(`WebSocket unavailable (${err instanceof Error ? err.message : String(err)}) — relying on HTTP polling`);
1763
1911
  return new Promise(() => { });
@@ -1827,7 +1975,7 @@ class OneShot {
1827
1975
  let lastResult = initialResult;
1828
1976
  while (Date.now() < deadline) {
1829
1977
  if (signal?.aborted)
1830
- throw new errors_1.OneShotError('Operation cancelled');
1978
+ throw new errors_2.OneShotError('Operation cancelled');
1831
1979
  try {
1832
1980
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1833
1981
  headers: this.headers(),
@@ -1838,7 +1986,7 @@ class OneShot {
1838
1986
  // whatever we last had so consumers don't lose the sync result.
1839
1987
  if (lastResult !== undefined)
1840
1988
  return lastResult;
1841
- throw new errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
1989
+ throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text());
1842
1990
  }
1843
1991
  const job = await resp.json();
1844
1992
  lastResult = (job.result ?? job);
@@ -1847,7 +1995,7 @@ class OneShot {
1847
1995
  }
1848
1996
  }
1849
1997
  catch (err) {
1850
- if (err instanceof errors_1.OneShotError)
1998
+ if (err instanceof errors_2.OneShotError)
1851
1999
  throw err;
1852
2000
  if (lastResult !== undefined)
1853
2001
  return lastResult;
@@ -1873,7 +2021,7 @@ class OneShot {
1873
2021
  return reject(new Error('WebSocket not available'));
1874
2022
  }
1875
2023
  if (signal.aborted) {
1876
- return reject(new errors_1.OneShotError('Operation cancelled'));
2024
+ return reject(new errors_2.OneShotError('Operation cancelled'));
1877
2025
  }
1878
2026
  const wsUrl = this.baseUrl.replace(/^http/, 'ws') +
1879
2027
  `/v1/requests/subscribe?wallet=${encodeURIComponent(this.provider.address)}`;
@@ -1901,7 +2049,7 @@ class OneShot {
1901
2049
  const onAbort = () => {
1902
2050
  settle(() => {
1903
2051
  cleanup();
1904
- reject(new errors_1.OneShotError('Operation cancelled'));
2052
+ reject(new errors_2.OneShotError('Operation cancelled'));
1905
2053
  });
1906
2054
  };
1907
2055
  signal.addEventListener('abort', onAbort, { once: true });
@@ -1934,7 +2082,7 @@ class OneShot {
1934
2082
  settle(() => {
1935
2083
  cleanup();
1936
2084
  wait.via = 'ws';
1937
- reject(new errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
2085
+ reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
1938
2086
  });
1939
2087
  }
1940
2088
  else {
@@ -1967,6 +2115,21 @@ class OneShot {
1967
2115
  * Owns the caller's deadline (`JobTimeoutError`).
1968
2116
  */
1969
2117
  async pollJobHttp(requestId, timeoutSec, signal, emit, wait) {
2118
+ const scope = (0, deadline_1.deadlineScope)(signal, (timeoutSec ?? 120) * 1000);
2119
+ const started = Date.now();
2120
+ try {
2121
+ return await (0, deadline_1.abortable)(this.pollJobHttpImpl(requestId, timeoutSec, scope.signal, emit, wait), scope.signal);
2122
+ }
2123
+ catch (error) {
2124
+ if (scope.timedOut())
2125
+ throw new errors_2.JobTimeoutError(requestId, Date.now() - started);
2126
+ throw error;
2127
+ }
2128
+ finally {
2129
+ scope.close();
2130
+ }
2131
+ }
2132
+ async pollJobHttpImpl(requestId, timeoutSec, signal, emit, wait) {
1970
2133
  const maxWaitMs = (timeoutSec ?? 120) * 1000;
1971
2134
  const startTime = Date.now();
1972
2135
  let retries = 0;
@@ -1974,7 +2137,7 @@ class OneShot {
1974
2137
  let polls = 0;
1975
2138
  while (Date.now() - startTime < maxWaitMs) {
1976
2139
  if (signal?.aborted)
1977
- throw new errors_1.OneShotError('Operation cancelled');
2140
+ throw new errors_2.OneShotError('Operation cancelled');
1978
2141
  try {
1979
2142
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1980
2143
  headers: this.headers(),
@@ -1987,7 +2150,7 @@ class OneShot {
1987
2150
  if (resp.status >= 500 || resp.status === 429) {
1988
2151
  throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
1989
2152
  }
1990
- throw new errors_1.ToolError('Failed to check job status', resp.status, body);
2153
+ throw new errors_2.ToolError('Failed to check job status', resp.status, body);
1991
2154
  }
1992
2155
  const job = await resp.json();
1993
2156
  if (job.status === 'completed') {
@@ -1999,12 +2162,18 @@ class OneShot {
1999
2162
  if (job.request_id && typeof result === 'object' && result !== null && !('request_id' in result)) {
2000
2163
  result.request_id = job.request_id;
2001
2164
  }
2165
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
2166
+ if (job.receipt_id && !result.receipt_id)
2167
+ result.receipt_id = job.receipt_id;
2168
+ if (job.settlement_status && !result.settlement_status)
2169
+ result.settlement_status = job.settlement_status;
2170
+ }
2002
2171
  return result;
2003
2172
  }
2004
2173
  if (job.status === 'failed') {
2005
2174
  if (wait)
2006
2175
  wait.via = 'http';
2007
- throw new errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
2176
+ throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
2008
2177
  }
2009
2178
  emit?.(String(job.status));
2010
2179
  retries = 0;
@@ -2018,27 +2187,27 @@ class OneShot {
2018
2187
  await this.sleep(Math.min(interval, remaining), signal);
2019
2188
  }
2020
2189
  catch (err) {
2021
- if (err instanceof errors_1.OneShotError)
2190
+ if (err instanceof errors_2.OneShotError)
2022
2191
  throw err;
2023
2192
  if (++retries > maxRetries) {
2024
- throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
2193
+ throw new errors_2.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
2025
2194
  }
2026
2195
  const backoff = 2000 * Math.pow(2, retries - 1);
2027
2196
  this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
2028
2197
  await this.sleep(backoff, signal);
2029
2198
  }
2030
2199
  }
2031
- throw new errors_1.JobTimeoutError(requestId, Date.now() - startTime);
2200
+ throw new errors_2.JobTimeoutError(requestId, Date.now() - startTime);
2032
2201
  }
2033
2202
  sleep(ms, signal) {
2034
2203
  return new Promise((resolve, reject) => {
2035
2204
  if (signal?.aborted) {
2036
- return reject(new errors_1.OneShotError('Operation cancelled'));
2205
+ return reject(new errors_2.OneShotError('Operation cancelled'));
2037
2206
  }
2038
- const timer = setTimeout(resolve, ms);
2207
+ const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
2039
2208
  const onAbort = () => {
2040
2209
  clearTimeout(timer);
2041
- reject(new errors_1.OneShotError('Operation cancelled'));
2210
+ reject(new errors_2.OneShotError('Operation cancelled'));
2042
2211
  };
2043
2212
  signal?.addEventListener('abort', onAbort, { once: true });
2044
2213
  });
@@ -2047,7 +2216,8 @@ class OneShot {
2047
2216
  const headers = {
2048
2217
  'Content-Type': 'application/json',
2049
2218
  ...this.headers(),
2050
- ...(extraHeaders ?? {})
2219
+ ...(extraHeaders ?? {}),
2220
+ ...(extraHeaders?.['x-agent-proof'] ? await this.signedReadHeaders(/(enrich\/(profile|email)|verify\/email)$/.test(endpoint) ? 'submit' : 'read') : {})
2051
2221
  };
2052
2222
  if (payment) {
2053
2223
  const paymentJson = JSON.stringify(payment);
@@ -2058,42 +2228,179 @@ class OneShot {
2058
2228
  }
2059
2229
  if (quoteId)
2060
2230
  headers['x-quote-id'] = quoteId;
2061
- let fetchSignal = signal;
2062
- let timeoutId;
2063
- if (timeoutMs && !signal) {
2064
- const controller = new AbortController();
2065
- timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2066
- fetchSignal = controller.signal;
2067
- }
2231
+ const transportDeadline = timeoutMs === undefined ? undefined : (0, deadline_1.deadlineScope)(signal, timeoutMs);
2232
+ const fetchSignal = transportDeadline?.signal ?? signal;
2233
+ // Node 18 compatibility: do not require AbortSignal.any. Retain the deadline
2234
+ // through response-body consumption; cleanup occurs when it expires/aborts.
2235
+ transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
2236
+ if (fetchSignal?.aborted)
2237
+ throw new errors_2.OneShotError('Operation cancelled');
2238
+ const work = fetch(`${this.baseUrl}${endpoint}`, {
2239
+ method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
2240
+ });
2068
2241
  try {
2069
- return await fetch(`${this.baseUrl}${endpoint}`, {
2070
- method: 'POST',
2071
- headers,
2072
- body: JSON.stringify(data),
2073
- signal: fetchSignal
2074
- });
2242
+ return await (fetchSignal ? (0, deadline_1.abortable)(work, fetchSignal) : work);
2075
2243
  }
2076
- finally {
2077
- if (timeoutId)
2078
- clearTimeout(timeoutId);
2244
+ catch (error) {
2245
+ transportDeadline?.close();
2246
+ throw error;
2079
2247
  }
2080
2248
  }
2081
2249
  checkAbortBeforePayment(signal) {
2082
2250
  if (signal?.aborted) {
2083
- throw new errors_1.OneShotError('Operation cancelled before payment');
2251
+ throw new errors_2.OneShotError('Operation cancelled before payment');
2084
2252
  }
2085
2253
  }
2254
+ // ---------------------------------------------------------------------------
2255
+ // ETH-currency mode: USDC ledger + buffered swaps
2256
+ // ---------------------------------------------------------------------------
2086
2257
  /**
2087
- * If currency is ETH, swap ETH→USDC to ensure the wallet has enough USDC for payment.
2088
- * This is called before signing the x402 payment authorization.
2258
+ * ETH mode only. Make sure the wallet's *effective* USDC (on-chain balance
2259
+ * minus payments signed but not yet settled) covers this charge, swapping
2260
+ * ETH→USDC for a buffer of `swapBufferMultiplier` payments when it does not,
2261
+ * then reserve the charge. Returns the reservation so the caller can release
2262
+ * it if the payment never settles (request failed). Runs under a per-instance
2263
+ * lock so concurrent calls never double-swap or race the wallet nonce.
2089
2264
  */
2090
2265
  async ensureUsdcBalance(paymentInfo) {
2091
2266
  if (this._currency !== 'ETH')
2092
- return;
2267
+ return undefined;
2268
+ const { chainId, usdcAddress } = this.assertEthModeSupported(paymentInfo);
2269
+ const charge = ethers_1.ethers.parseUnits(paymentInfo.amount, paymentInfo.token.decimals);
2270
+ return this.withUsdcLock(async () => {
2271
+ let balance = await this.readUsdcBalance(usdcAddress);
2272
+ const effective = this.effectiveUsdcBalance(balance);
2273
+ if (effective >= charge) {
2274
+ this.log(`USDC balance covers payment (${ethers_1.ethers.formatUnits(effective, 6)} available for ${paymentInfo.amount}); skipping ETH swap`);
2275
+ }
2276
+ else {
2277
+ const swapAmount = await this.sizeSwap(charge, effective, chainId);
2278
+ const swapAmountStr = ethers_1.ethers.formatUnits(swapAmount, 6);
2279
+ this.log(`USDC ${ethers_1.ethers.formatUnits(effective, 6)} is below ${paymentInfo.amount}; swapping ETH→USDC for ${swapAmountStr} USDC (${this._swapBufferMultiplier}x buffer, slippage: ${this._slippage * 100}%)`);
2280
+ try {
2281
+ const result = await this.executeUsdcSwap(swapAmountStr, chainId);
2282
+ balance += result.usdcReceived;
2283
+ // exactOutput delivers exactly amountOut and tx.wait() has confirmed it.
2284
+ this._usdcBalanceCache = { balance, at: Date.now() };
2285
+ this.log(`Swap complete: tx=${result.txHash}, USDC received=${ethers_1.ethers.formatUnits(result.usdcReceived, 6)}`);
2286
+ }
2287
+ catch (err) {
2288
+ this._usdcBalanceCache = undefined;
2289
+ throw err;
2290
+ }
2291
+ }
2292
+ return this.reserveUsdc(charge);
2293
+ });
2294
+ }
2295
+ /** ETH mode is Base-mainnet-only (that is where the Uniswap route lives); fail clearly before any RPC. */
2296
+ assertEthModeSupported(paymentInfo) {
2297
+ const chainId = chainIdFromNetwork(paymentInfo.network) ?? CHAIN_ID;
2298
+ if (chainId !== CHAIN_ID) {
2299
+ throw new errors_2.ValidationError(`ETH currency mode is only supported on Base mainnet (eip155:${CHAIN_ID}); this payment is on ${paymentInfo.network}. Fund the wallet with USDC or use currency: 'USDC'.`, 'currency');
2300
+ }
2301
+ const usdcAddress = paymentInfo.token.address;
2302
+ if (usdcAddress.toLowerCase() !== USDC_ADDRESS.toLowerCase()) {
2303
+ throw new errors_2.ValidationError(`ETH→USDC swap buys ${USDC_ADDRESS} but this payment requires ${usdcAddress}`, 'currency');
2304
+ }
2305
+ return { chainId, usdcAddress };
2306
+ }
2307
+ withUsdcLock(fn) {
2308
+ // A failed predecessor must not poison the chain: the waiter runs its own attempt.
2309
+ const run = this._usdcLock.then(fn, fn);
2310
+ this._usdcLock = run.then(() => undefined, () => undefined);
2311
+ return run;
2312
+ }
2313
+ /** On-chain USDC balance (atomic units), deduped within one block. Overridable in tests. */
2314
+ async readUsdcBalance(usdcAddress) {
2315
+ const cached = this._usdcBalanceCache;
2316
+ if (cached && Date.now() - cached.at < USDC_BALANCE_CACHE_MS)
2317
+ return cached.balance;
2318
+ const usdc = new ethers_1.ethers.Contract(usdcAddress, ERC20_BALANCE_ABI, this.rpcProvider);
2319
+ const balance = BigInt(await usdc.balanceOf(this.provider.address));
2320
+ this._usdcBalanceCache = { balance, at: Date.now() };
2321
+ return balance;
2322
+ }
2323
+ /** `balanceOf − Σ pending` (never negative); drops reservations older than the TTL. */
2324
+ effectiveUsdcBalance(balance) {
2325
+ const cutoff = Date.now() - USDC_RESERVATION_TTL_MS;
2326
+ let pending = 0n;
2327
+ for (const [id, r] of this._usdcPending) {
2328
+ if (r.createdAt < cutoff)
2329
+ this._usdcPending.delete(id);
2330
+ else
2331
+ pending += r.amount;
2332
+ }
2333
+ return balance > pending ? balance - pending : 0n;
2334
+ }
2335
+ reserveUsdc(amount) {
2336
+ const reservation = { id: ++this._usdcReservationSeq, amount, createdAt: Date.now() };
2337
+ this._usdcPending.set(reservation.id, reservation);
2338
+ return reservation;
2339
+ }
2340
+ /** Forget a reservation whose payment will never settle (signing or request failed). */
2341
+ releaseUsdcReservation(reservation) {
2342
+ if (reservation)
2343
+ this._usdcPending.delete(reservation.id);
2344
+ }
2345
+ /**
2346
+ * How much USDC to buy: top up to `charge × swapBufferMultiplier`, counting
2347
+ * whatever effective balance is already there. When the wallet provider
2348
+ * exposes `getBalance` and its ETH cannot cover the buffered quote, fall
2349
+ * back to the bare shortfall so an ETH-poor wallet can still make the one
2350
+ * payment in front of it (a send-only provider always gets the buffer).
2351
+ */
2352
+ async sizeSwap(charge, effective, chainId) {
2353
+ const shortfall = charge - effective;
2354
+ // Integer math on a 10^6 scale (multiplier ≤ 1000, so ≤ 10^9 — always a
2355
+ // finite, exact integer): keeps the multiplier's precision, no float→BigInt
2356
+ // on an unbounded value.
2357
+ const mScaled = BigInt(Math.round(this._swapBufferMultiplier * SWAP_MULTIPLIER_SCALE));
2358
+ const target = (charge * mScaled + BigInt(SWAP_MULTIPLIER_SCALE) - 1n) / BigInt(SWAP_MULTIPLIER_SCALE);
2359
+ let amount = target > effective ? target - effective : shortfall;
2360
+ if (amount > shortfall && this.provider.getBalance) {
2361
+ try {
2362
+ const amountInMax = await this.quoteSwapAmountInMax(ethers_1.ethers.formatUnits(amount, 6), chainId);
2363
+ const eth = await this.provider.getBalance();
2364
+ if (amountInMax !== undefined && eth < amountInMax) {
2365
+ this.log(`ETH balance cannot cover a ${ethers_1.ethers.formatUnits(amount, 6)} USDC buffer; swapping only the ${ethers_1.ethers.formatUnits(shortfall, 6)} USDC shortfall`);
2366
+ amount = shortfall;
2367
+ }
2368
+ }
2369
+ catch {
2370
+ // Quoting failed — let executeSwap quote again and report properly.
2371
+ }
2372
+ }
2373
+ return amount;
2374
+ }
2375
+ /** Max ETH the buffered swap could cost (quote), or undefined if unavailable. Overridable in tests. */
2376
+ async quoteSwapAmountInMax(usdcAmount, chainId) {
2377
+ const { getSwapQuote } = await Promise.resolve().then(() => __importStar(require('./swap')));
2378
+ const quote = await getSwapQuote(this.rpcProvider, usdcAmount, chainId, this._slippage);
2379
+ return quote?.amountInMax;
2380
+ }
2381
+ /** Perform the on-chain swap. Overridable in tests. */
2382
+ async executeUsdcSwap(usdcAmount, chainId) {
2093
2383
  const { executeSwap } = await Promise.resolve().then(() => __importStar(require('./swap')));
2094
- this.log(`Swapping ETH→USDC for ${paymentInfo.amount} USDC (slippage: ${this._slippage * 100}%)`);
2095
- const result = await executeSwap(this.provider, this.rpcProvider, paymentInfo.amount, CHAIN_ID, this._slippage);
2096
- this.log(`Swap complete: tx=${result.txHash}, USDC received=${ethers_1.ethers.formatUnits(result.usdcReceived, 6)}`);
2384
+ return executeSwap(this.provider, this.rpcProvider, usdcAmount, chainId, this._slippage);
2385
+ }
2386
+ /**
2387
+ * Send the paid leg of a request. The server settles the authorization only
2388
+ * on acceptance. A transport failure on a durable endpoint is ambiguous;
2389
+ * retain its short-lived ETH-mode reservation until recovery or expiry.
2390
+ */
2391
+ async makePaidRequest(signed, endpoint, data, quoteId, signal, timeoutMs, extraHeaders) {
2392
+ let resp;
2393
+ try {
2394
+ resp = await this.makeRequest(endpoint, data, signed.auth, quoteId, signal, timeoutMs, extraHeaders);
2395
+ }
2396
+ catch (err) {
2397
+ if (!/(enrich\/(profile|email)|verify\/email)$/.test(endpoint))
2398
+ this.releaseUsdcReservation(signed.reservation);
2399
+ throw err;
2400
+ }
2401
+ if (!resp.ok && !(extraHeaders?.['Idempotency-Key'] && resp.status >= 500))
2402
+ this.releaseUsdcReservation(signed.reservation);
2403
+ return resp;
2097
2404
  }
2098
2405
  /** Parse the PAYMENT-REQUIRED header from a 402 response into the accepted requirements and Bazaar metadata. */
2099
2406
  parsePaymentRequired(header) {
@@ -2148,26 +2455,26 @@ class OneShot {
2148
2455
  // always receives a payment-signature header (avoids 402 from x402 SDK).
2149
2456
  if (parseFloat(paymentInfo.amount) === 0) {
2150
2457
  this.log('Credits cover full cost — sending zero-cost authorization');
2151
- return {
2152
- x402Version: 2,
2153
- ...(resource ? { resource } : {}),
2154
- ...(extensions ? { extensions } : {}),
2155
- accepted,
2156
- payload: {
2157
- signature: '0x',
2158
- authorization: {
2159
- from: this.provider.address,
2160
- to: paymentInfo.payTo,
2161
- value: '0',
2162
- validAfter: '0',
2163
- validBefore: '0',
2164
- nonce: '0x' + '00'.repeat(32),
2458
+ return { auth: {
2459
+ x402Version: 2,
2460
+ ...(resource ? { resource } : {}),
2461
+ ...(extensions ? { extensions } : {}),
2462
+ accepted,
2463
+ payload: {
2464
+ signature: '0x',
2465
+ authorization: {
2466
+ from: this.provider.address,
2467
+ to: paymentInfo.payTo,
2468
+ value: '0',
2469
+ validAfter: '0',
2470
+ validBefore: '0',
2471
+ nonce: '0x' + '00'.repeat(32),
2472
+ },
2165
2473
  },
2166
- },
2167
- };
2474
+ } };
2168
2475
  }
2169
- // If paying with ETH, swap to USDC first
2170
- await this.ensureUsdcBalance(paymentInfo);
2476
+ // If paying with ETH, make sure USDC covers the charge (swapping a buffer if not) and reserve it
2477
+ const reservation = await this.ensureUsdcBalance(paymentInfo);
2171
2478
  const now = Math.floor(Date.now() / 1000);
2172
2479
  const nonce = ethers_1.ethers.randomBytes(32);
2173
2480
  const value = ethers_1.ethers.parseUnits(paymentInfo.amount, paymentInfo.token.decimals);
@@ -2182,46 +2489,53 @@ class OneShot {
2182
2489
  const domainVersion = accepted.extra?.version || '2';
2183
2490
  const chainId = chainIdFromNetwork(accepted.network ?? paymentInfo.network) ?? CHAIN_ID;
2184
2491
  // Sign EIP-3009 TransferWithAuthorization
2185
- const signature = await this.provider.signTypedData({
2186
- name: domainName,
2187
- version: domainVersion,
2188
- chainId,
2189
- verifyingContract: paymentInfo.token.address
2190
- }, {
2191
- TransferWithAuthorization: [
2192
- { name: 'from', type: 'address' },
2193
- { name: 'to', type: 'address' },
2194
- { name: 'value', type: 'uint256' },
2195
- { name: 'validAfter', type: 'uint256' },
2196
- { name: 'validBefore', type: 'uint256' },
2197
- { name: 'nonce', type: 'bytes32' }
2198
- ]
2199
- }, {
2200
- from: this.provider.address,
2201
- to: paymentInfo.payTo,
2202
- value,
2203
- validAfter,
2204
- validBefore,
2205
- nonce: nonceHex
2206
- });
2492
+ let signature;
2493
+ try {
2494
+ signature = await this.provider.signTypedData({
2495
+ name: domainName,
2496
+ version: domainVersion,
2497
+ chainId,
2498
+ verifyingContract: paymentInfo.token.address
2499
+ }, {
2500
+ TransferWithAuthorization: [
2501
+ { name: 'from', type: 'address' },
2502
+ { name: 'to', type: 'address' },
2503
+ { name: 'value', type: 'uint256' },
2504
+ { name: 'validAfter', type: 'uint256' },
2505
+ { name: 'validBefore', type: 'uint256' },
2506
+ { name: 'nonce', type: 'bytes32' }
2507
+ ]
2508
+ }, {
2509
+ from: this.provider.address,
2510
+ to: paymentInfo.payTo,
2511
+ value,
2512
+ validAfter,
2513
+ validBefore,
2514
+ nonce: nonceHex
2515
+ });
2516
+ }
2517
+ catch (err) {
2518
+ this.releaseUsdcReservation(reservation);
2519
+ throw err;
2520
+ }
2207
2521
  // Return x402 PaymentPayload v2 format (including resource + extensions for Bazaar discovery)
2208
- return {
2209
- x402Version: 2,
2210
- ...(resource ? { resource } : {}),
2211
- ...(extensions ? { extensions } : {}),
2212
- accepted,
2213
- payload: {
2214
- signature,
2215
- authorization: {
2216
- from: this.provider.address,
2217
- to: paymentInfo.payTo,
2218
- value: value.toString(),
2219
- validAfter: validAfter.toString(),
2220
- validBefore: validBefore.toString(),
2221
- nonce: nonceHex,
2522
+ return { reservation, auth: {
2523
+ x402Version: 2,
2524
+ ...(resource ? { resource } : {}),
2525
+ ...(extensions ? { extensions } : {}),
2526
+ accepted,
2527
+ payload: {
2528
+ signature,
2529
+ authorization: {
2530
+ from: this.provider.address,
2531
+ to: paymentInfo.payTo,
2532
+ value: value.toString(),
2533
+ validAfter: validAfter.toString(),
2534
+ validBefore: validBefore.toString(),
2535
+ nonce: nonceHex,
2536
+ },
2222
2537
  },
2223
- },
2224
- };
2538
+ } };
2225
2539
  }
2226
2540
  }
2227
2541
  exports.OneShot = OneShot;