@oneshot-agent/sdk 0.6.2 → 0.8.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
@@ -1,8 +1,49 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OneShot = exports.EmergencyNumberError = exports.ContentBlockedError = exports.ValidationError = exports.JobTimeoutError = exports.JobError = exports.ToolError = exports.OneShotError = exports.PROD_ENV = exports.TEST_ENV = void 0;
36
+ exports.OneShot = exports.EmergencyNumberError = exports.ContentBlockedError = exports.ValidationError = exports.JobTimeoutError = exports.JobError = exports.ToolError = exports.OneShotError = exports.PROD_ENV = exports.TEST_ENV = exports.executeSwap = exports.getSwapQuote = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
4
37
  const ethers_1 = require("ethers");
5
- const SDK_VERSION = '0.5.0';
38
+ const ethers_2 = require("./providers/ethers");
39
+ var ethers_3 = require("./providers/ethers");
40
+ Object.defineProperty(exports, "EthersWalletProvider", { enumerable: true, get: function () { return ethers_3.EthersWalletProvider; } });
41
+ var cdp_1 = require("./providers/cdp");
42
+ Object.defineProperty(exports, "CdpWalletProvider", { enumerable: true, get: function () { return cdp_1.CdpWalletProvider; } });
43
+ var swap_1 = require("./swap");
44
+ Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function () { return swap_1.getSwapQuote; } });
45
+ Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
46
+ const SDK_VERSION = '0.7.0';
6
47
  // ============================================================================
7
48
  // Environment Configuration
8
49
  // ============================================================================
@@ -95,10 +136,42 @@ exports.EmergencyNumberError = EmergencyNumberError;
95
136
  * ```
96
137
  */
97
138
  class OneShot {
98
- constructor(config) {
99
- if (!config.privateKey) {
100
- throw new ValidationError('privateKey is required', 'privateKey');
139
+ /**
140
+ * Async factory — required for CDP wallets (account creation is async).
141
+ * Also works with privateKey and custom walletProvider.
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * // CDP wallet (no private keys)
146
+ * const agent = await OneShot.create({ cdp: true });
147
+ *
148
+ * // Raw private key (still works)
149
+ * const agent = await OneShot.create({ privateKey: '0x...' });
150
+ * ```
151
+ */
152
+ static async create(config) {
153
+ if (config.walletProvider) {
154
+ return new OneShot(config, config.walletProvider);
101
155
  }
156
+ if (config.cdp) {
157
+ const { CdpWalletProvider } = await Promise.resolve().then(() => __importStar(require('./providers/cdp')));
158
+ const cdpOpts = typeof config.cdp === 'object' ? config.cdp : undefined;
159
+ const walletProvider = await CdpWalletProvider.create(cdpOpts);
160
+ return new OneShot(config, walletProvider);
161
+ }
162
+ if (config.privateKey) {
163
+ const env = (config.testMode ?? true) ? exports.TEST_ENV : exports.PROD_ENV;
164
+ const rpcProvider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl ?? env.rpcUrl);
165
+ const walletProvider = new ethers_2.EthersWalletProvider(config.privateKey, rpcProvider);
166
+ return new OneShot(config, walletProvider);
167
+ }
168
+ throw new ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
169
+ }
170
+ /**
171
+ * Sync constructor — works with privateKey (backwards compatible).
172
+ * For CDP wallets, use OneShot.create() instead.
173
+ */
174
+ constructor(config, walletProvider) {
102
175
  this._testMode = config.testMode ?? true;
103
176
  const env = this._testMode ? exports.TEST_ENV : exports.PROD_ENV;
104
177
  this.baseUrl = config.baseUrl ?? env.baseUrl;
@@ -106,17 +179,31 @@ class OneShot {
106
179
  this._usdcAddress = env.usdcAddress;
107
180
  this.debug = config.debug ?? false;
108
181
  this.logger = config.logger ?? console.log;
109
- const provider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl ?? env.rpcUrl);
110
- this.wallet = new ethers_1.ethers.Wallet(config.privateKey, provider);
182
+ this.rpcProvider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl ?? env.rpcUrl);
183
+ this._currency = config.currency ?? 'USDC';
184
+ this._slippage = config.slippage ?? 0.01;
185
+ if (walletProvider) {
186
+ this.provider = walletProvider;
187
+ }
188
+ else if (config.privateKey) {
189
+ this.provider = new ethers_2.EthersWalletProvider(config.privateKey, this.rpcProvider);
190
+ }
191
+ else {
192
+ throw new ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
193
+ }
194
+ // Validate ETH mode requirements
195
+ if (this._currency === 'ETH' && !this.provider.sendTransaction) {
196
+ throw new ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
197
+ }
111
198
  if (this.debug) {
112
- this.log(`SDK initialized [${this._testMode ? 'TEST' : 'PROD'}] chain=${this._expectedChainId}`);
199
+ this.log(`SDK initialized [${this._testMode ? 'TEST' : 'PROD'}] chain=${this._expectedChainId} currency=${this._currency}`);
113
200
  }
114
201
  }
115
202
  // ---------------------------------------------------------------------------
116
203
  // Public getters
117
204
  // ---------------------------------------------------------------------------
118
205
  get address() {
119
- return this.wallet.address;
206
+ return this.provider.address;
120
207
  }
121
208
  get isTestMode() {
122
209
  return this._testMode;
@@ -127,6 +214,12 @@ class OneShot {
127
214
  get expectedChainId() {
128
215
  return this._expectedChainId;
129
216
  }
217
+ get currency() {
218
+ return this._currency;
219
+ }
220
+ get slippage() {
221
+ return this._slippage;
222
+ }
130
223
  // ---------------------------------------------------------------------------
131
224
  // Public methods
132
225
  // ---------------------------------------------------------------------------
@@ -183,6 +276,37 @@ class OneShot {
183
276
  this.validate(options.email, 'email');
184
277
  return this.tool('verify/email', { ...options });
185
278
  }
279
+ async deepResearchPerson(options) {
280
+ if (!options.email && !options.social_media_url && !options.name) {
281
+ throw new ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
282
+ }
283
+ return this.tool('research/person', { ...options });
284
+ }
285
+ async socialProfiles(options) {
286
+ if (!options.email && !options.social_media_url) {
287
+ throw new ValidationError('At least one of email or social_media_url is required', 'identifier');
288
+ }
289
+ return this.tool('research/social', { ...options });
290
+ }
291
+ async articleSearch(options) {
292
+ this.validate(options.name, 'name');
293
+ this.validate(options.company, 'company');
294
+ return this.tool('research/articles', { ...options });
295
+ }
296
+ async personNewsfeed(options) {
297
+ this.validate(options.social_media_url, 'social_media_url');
298
+ return this.tool('research/newsfeed', { ...options });
299
+ }
300
+ async personInterests(options) {
301
+ if (!options.email && !options.phone && !options.social_media_url) {
302
+ throw new ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
303
+ }
304
+ return this.tool('research/interests', { ...options });
305
+ }
306
+ async personInteractions(options) {
307
+ this.validate(options.social_media_url, 'social_media_url');
308
+ return this.tool('research/interactions', { ...options });
309
+ }
186
310
  async inboxList(options = {}) {
187
311
  const params = new URLSearchParams();
188
312
  if (options.since)
@@ -223,7 +347,8 @@ class OneShot {
223
347
  quantity: options.quantity ?? 1,
224
348
  variant_id: options.variant_id
225
349
  };
226
- const quoteResp = await this.makeRequest('/v1/tools/commerce/buy', payload, undefined, undefined, options.signal);
350
+ // Commerce quotes can take up to 90s due to Rye API polling
351
+ const quoteResp = await this.makeRequest('/v1/tools/commerce/buy', payload, undefined, undefined, options.signal, 120000);
227
352
  if (quoteResp.status !== 402) {
228
353
  throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
229
354
  }
@@ -243,14 +368,14 @@ class OneShot {
243
368
  };
244
369
  this.checkAbortBeforePayment(options.signal);
245
370
  const auth = await this.signPaymentAuthorization(paymentInfo);
246
- const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal);
371
+ const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal, 60000);
247
372
  if (buyResp.status !== 202) {
248
373
  throw new ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
249
374
  }
250
375
  const result = await buyResp.json();
251
376
  this.log(`Order submitted: ${result.request_id}`);
252
377
  if (options.wait !== false && result.request_id) {
253
- return this.pollJob(result.request_id, options.timeout, options.signal, options.onStatusUpdate);
378
+ return this.pollJob(result.request_id, options.timeout ?? 180, options.signal, options.onStatusUpdate);
254
379
  }
255
380
  return result;
256
381
  }
@@ -258,6 +383,14 @@ class OneShot {
258
383
  this.validate(options.query, 'query');
259
384
  return this.tool('commerce/search', { ...options, limit: options.limit ?? 10 });
260
385
  }
386
+ async webSearch(options) {
387
+ this.validate(options.query, 'query');
388
+ return this.tool('search', { ...options, max_results: options.max_results ?? 5 });
389
+ }
390
+ async webRead(options) {
391
+ this.validate(options.url, 'url');
392
+ return this.tool('web-read', { ...options });
393
+ }
261
394
  /**
262
395
  * Make an autonomous voice call
263
396
  *
@@ -501,6 +634,78 @@ class OneShot {
501
634
  }
502
635
  return result;
503
636
  }
637
+ /**
638
+ * Automate a browser task using natural language
639
+ *
640
+ * @example
641
+ * ```typescript
642
+ * const result = await agent.browser({
643
+ * task: 'Go to CoinGecko and find the current price of Bitcoin',
644
+ * start_url: 'https://www.coingecko.com',
645
+ * });
646
+ * console.log(result.output);
647
+ * ```
648
+ */
649
+ async browser(options) {
650
+ this.validate(options.task, 'task');
651
+ if (options.task.length < 10) {
652
+ throw new ValidationError('Task must be at least 10 characters', 'task');
653
+ }
654
+ if (options.max_steps && (options.max_steps < 1 || options.max_steps > 100)) {
655
+ throw new ValidationError('max_steps must be between 1 and 100', 'max_steps');
656
+ }
657
+ const payload = {
658
+ task: options.task,
659
+ signal: options.signal,
660
+ onStatusUpdate: options.onStatusUpdate,
661
+ wait: options.wait
662
+ };
663
+ if (options.output_schema)
664
+ payload.output_schema = options.output_schema;
665
+ if (options.start_url)
666
+ payload.start_url = options.start_url;
667
+ if (options.allowed_domains)
668
+ payload.allowed_domains = options.allowed_domains;
669
+ if (options.session_id)
670
+ payload.session_id = options.session_id;
671
+ if (options.max_steps)
672
+ payload.max_steps = options.max_steps;
673
+ // Browser uses quote-to-pay flow (402 -> payment -> 202)
674
+ const quoteResp = await this.makeRequest('/v1/tools/browser', payload, undefined, undefined, options.signal);
675
+ if (quoteResp.status === 400) {
676
+ const errorData = await quoteResp.json();
677
+ throw new ValidationError(errorData.message || 'Invalid request', 'request');
678
+ }
679
+ if (quoteResp.status !== 402) {
680
+ throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
681
+ }
682
+ const quoteData = await quoteResp.json();
683
+ this.log(`Browser quote: $${quoteData.context.estimated_cost} for ~${quoteData.context.estimated_steps} steps`);
684
+ if (options.maxCost && parseFloat(quoteData.context.estimated_cost) > options.maxCost) {
685
+ throw new OneShotError(`Quote $${quoteData.context.estimated_cost} exceeds maxCost $${options.maxCost}`);
686
+ }
687
+ const paymentInfo = {
688
+ protocol: 'x402',
689
+ network: `eip155:${quoteData.payment_request.chain_id}`,
690
+ payTo: quoteData.payment_request.recipient,
691
+ amount: quoteData.payment_request.amount,
692
+ currency: 'USD',
693
+ facilitator_url: this.baseUrl,
694
+ token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
695
+ };
696
+ this.checkAbortBeforePayment(options.signal);
697
+ const auth = await this.signPaymentAuthorization(paymentInfo);
698
+ const execResp = await this.makeRequest('/v1/tools/browser', payload, auth, quoteData.context.quote_id, options.signal);
699
+ if (execResp.status !== 202) {
700
+ throw new ToolError('Browser task initiation failed', execResp.status, await execResp.text());
701
+ }
702
+ const result = await execResp.json();
703
+ this.log(`Browser task initiated: ${result.request_id}`);
704
+ if (options.wait !== false && result.request_id) {
705
+ return this.pollJob(result.request_id, options.timeout ?? 300, options.signal, options.onStatusUpdate);
706
+ }
707
+ return result;
708
+ }
504
709
  /**
505
710
  * Update an existing website build
506
711
  *
@@ -623,14 +828,118 @@ class OneShot {
623
828
  }
624
829
  async getBalance(tokenAddress) {
625
830
  this.validate(tokenAddress, 'tokenAddress');
626
- const contract = new ethers_1.ethers.Contract(tokenAddress, ['function balanceOf(address) view returns (uint256)', 'function decimals() view returns (uint8)'], this.wallet);
831
+ const contract = new ethers_1.ethers.Contract(tokenAddress, ['function balanceOf(address) view returns (uint256)', 'function decimals() view returns (uint8)'], this.rpcProvider);
627
832
  const [balance, decimals] = await Promise.all([
628
- contract.balanceOf(this.wallet.address),
833
+ contract.balanceOf(this.provider.address),
629
834
  contract.decimals()
630
835
  ]);
631
836
  return ethers_1.ethers.formatUnits(balance, decimals);
632
837
  }
633
838
  // ---------------------------------------------------------------------------
839
+ // Analytics methods
840
+ // ---------------------------------------------------------------------------
841
+ /**
842
+ * Get spend breakdown by category
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * const breakdown = await agent.spendBreakdown({ period: 30 });
847
+ * console.log(`Total: $${breakdown.total}`);
848
+ * for (const cat of breakdown.categories) {
849
+ * console.log(`${cat.category}: $${cat.total} (${cat.pct}%)`);
850
+ * }
851
+ * ```
852
+ */
853
+ async spendBreakdown(options) {
854
+ const params = new URLSearchParams();
855
+ if (options?.period)
856
+ params.set('period', String(options.period));
857
+ const qs = params.toString();
858
+ const response = await fetch(`${this.baseUrl}/v1/analytics/spend/breakdown${qs ? `?${qs}` : ''}`, {
859
+ headers: this.headers()
860
+ });
861
+ if (!response.ok) {
862
+ throw new ToolError('Failed to get spend breakdown', response.status, await response.text());
863
+ }
864
+ return response.json();
865
+ }
866
+ /**
867
+ * Get Return on Cognitive Spend (RoCS)
868
+ *
869
+ * @example
870
+ * ```typescript
871
+ * const result = await agent.rocs({ period: 30 });
872
+ * console.log(`RoCS: ${result.rocs}x (spent $${result.total_spend}, generated $${result.total_value})`);
873
+ * ```
874
+ */
875
+ async rocs(options) {
876
+ const params = new URLSearchParams();
877
+ if (options?.period)
878
+ params.set('period', String(options.period));
879
+ const qs = params.toString();
880
+ const response = await fetch(`${this.baseUrl}/v1/analytics/rocs${qs ? `?${qs}` : ''}`, {
881
+ headers: this.headers()
882
+ });
883
+ if (!response.ok) {
884
+ throw new ToolError('Failed to get RoCS', response.status, await response.text());
885
+ }
886
+ return response.json();
887
+ }
888
+ /**
889
+ * List receipts with optional filtering
890
+ *
891
+ * @example
892
+ * ```typescript
893
+ * const result = await agent.receiptsList({ period: 7, category: 'communication' });
894
+ * for (const r of result.receipts) {
895
+ * console.log(`${r.subcategory}: $${r.amount_usdc}`);
896
+ * }
897
+ * ```
898
+ */
899
+ async receiptsList(options) {
900
+ const params = new URLSearchParams();
901
+ if (options?.period)
902
+ params.set('period', String(options.period));
903
+ if (options?.category)
904
+ params.set('category', options.category);
905
+ if (options?.limit)
906
+ params.set('limit', String(options.limit));
907
+ const qs = params.toString();
908
+ const response = await fetch(`${this.baseUrl}/v1/analytics/receipts${qs ? `?${qs}` : ''}`, {
909
+ headers: this.headers()
910
+ });
911
+ if (!response.ok) {
912
+ throw new ToolError('Failed to list receipts', response.status, await response.text());
913
+ }
914
+ return response.json();
915
+ }
916
+ /**
917
+ * Tag a receipt with a value for RoCS computation
918
+ *
919
+ * @example
920
+ * ```typescript
921
+ * await agent.tagReceiptValue('rcpt_01HX...', { type: 'revenue', amount: 5.00, label: 'Sale from lead' });
922
+ * ```
923
+ */
924
+ async tagReceiptValue(receiptId, valueTag) {
925
+ this.validate(receiptId, 'receiptId');
926
+ this.validate(valueTag.type, 'valueTag.type');
927
+ const response = await fetch(`${this.baseUrl}/v1/analytics/receipts/${receiptId}/value`, {
928
+ method: 'PATCH',
929
+ headers: {
930
+ 'Content-Type': 'application/json',
931
+ ...this.headers()
932
+ },
933
+ body: JSON.stringify(valueTag)
934
+ });
935
+ if (response.status === 404) {
936
+ throw new ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
937
+ }
938
+ if (!response.ok) {
939
+ throw new ToolError('Failed to tag receipt value', response.status, await response.text());
940
+ }
941
+ }
942
+ // ---------------------------------------------------------------------------
634
943
  // Private helpers
635
944
  // ---------------------------------------------------------------------------
636
945
  log(msg) {
@@ -643,7 +952,7 @@ class OneShot {
643
952
  }
644
953
  headers() {
645
954
  return {
646
- 'X-Agent-ID': this.wallet.address,
955
+ 'X-Agent-ID': this.provider.address,
647
956
  'X-OneShot-SDK-Version': SDK_VERSION
648
957
  };
649
958
  }
@@ -685,6 +994,82 @@ class OneShot {
685
994
  return (result.data ?? result);
686
995
  }
687
996
  async pollJob(requestId, timeoutSec, signal, onStatusUpdate) {
997
+ // Try WebSocket push first, fall back to HTTP polling
998
+ try {
999
+ return await this.waitViaWebSocket(requestId, timeoutSec, signal, onStatusUpdate);
1000
+ }
1001
+ catch {
1002
+ this.log('WebSocket unavailable, falling back to HTTP polling');
1003
+ return this.pollJobHttp(requestId, timeoutSec, signal, onStatusUpdate);
1004
+ }
1005
+ }
1006
+ waitViaWebSocket(requestId, timeoutSec, signal, onStatusUpdate) {
1007
+ return new Promise((resolve, reject) => {
1008
+ const maxWaitMs = (timeoutSec ?? 120) * 1000;
1009
+ const wsUrl = this.baseUrl.replace(/^http/, 'ws') +
1010
+ `/v1/requests/subscribe?wallet=${encodeURIComponent(this.provider.address)}`;
1011
+ let ws;
1012
+ try {
1013
+ ws = new WebSocket(wsUrl);
1014
+ }
1015
+ catch {
1016
+ return reject(new Error('WebSocket not available'));
1017
+ }
1018
+ const timeout = setTimeout(() => {
1019
+ ws.close();
1020
+ reject(new JobTimeoutError(requestId, maxWaitMs));
1021
+ }, maxWaitMs);
1022
+ const cleanup = () => {
1023
+ clearTimeout(timeout);
1024
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
1025
+ ws.close();
1026
+ }
1027
+ };
1028
+ if (signal) {
1029
+ signal.addEventListener('abort', () => {
1030
+ cleanup();
1031
+ reject(new OneShotError('Operation cancelled'));
1032
+ }, { once: true });
1033
+ }
1034
+ ws.onopen = () => {
1035
+ ws.send(JSON.stringify({ subscribe: [requestId] }));
1036
+ };
1037
+ ws.onmessage = (event) => {
1038
+ try {
1039
+ const msg = JSON.parse(typeof event.data === 'string' ? event.data : event.data.toString());
1040
+ if (msg.request_id !== requestId)
1041
+ return;
1042
+ if (msg.status === 'completed') {
1043
+ this.log('Job completed (WebSocket)');
1044
+ cleanup();
1045
+ resolve((msg.result ?? msg));
1046
+ }
1047
+ else if (msg.status === 'failed') {
1048
+ cleanup();
1049
+ reject(new JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
1050
+ }
1051
+ else {
1052
+ onStatusUpdate?.(msg.status, requestId);
1053
+ }
1054
+ }
1055
+ catch {
1056
+ // Ignore malformed messages
1057
+ }
1058
+ };
1059
+ ws.onerror = () => {
1060
+ cleanup();
1061
+ reject(new Error('WebSocket error'));
1062
+ };
1063
+ ws.onclose = (event) => {
1064
+ // If closed before we got a result, reject so HTTP fallback kicks in
1065
+ if (event.code !== 1000) {
1066
+ cleanup();
1067
+ reject(new Error('WebSocket closed unexpectedly'));
1068
+ }
1069
+ };
1070
+ });
1071
+ }
1072
+ async pollJobHttp(requestId, timeoutSec, signal, onStatusUpdate) {
688
1073
  const maxWaitMs = (timeoutSec ?? 120) * 1000;
689
1074
  const startTime = Date.now();
690
1075
  const pollInterval = 2000;
@@ -739,7 +1124,7 @@ class OneShot {
739
1124
  signal?.addEventListener('abort', onAbort, { once: true });
740
1125
  });
741
1126
  }
742
- async makeRequest(endpoint, data, payment, quoteId, signal) {
1127
+ async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs) {
743
1128
  const headers = {
744
1129
  'Content-Type': 'application/json',
745
1130
  ...this.headers()
@@ -748,19 +1133,47 @@ class OneShot {
748
1133
  headers['x-payment'] = JSON.stringify(payment);
749
1134
  if (quoteId)
750
1135
  headers['x-quote-id'] = quoteId;
751
- return fetch(`${this.baseUrl}${endpoint}`, {
752
- method: 'POST',
753
- headers,
754
- body: JSON.stringify(data),
755
- signal
756
- });
1136
+ // Create timeout signal if specified
1137
+ let fetchSignal = signal;
1138
+ let timeoutId;
1139
+ if (timeoutMs && !signal) {
1140
+ const controller = new AbortController();
1141
+ timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1142
+ fetchSignal = controller.signal;
1143
+ }
1144
+ try {
1145
+ return await fetch(`${this.baseUrl}${endpoint}`, {
1146
+ method: 'POST',
1147
+ headers,
1148
+ body: JSON.stringify(data),
1149
+ signal: fetchSignal
1150
+ });
1151
+ }
1152
+ finally {
1153
+ if (timeoutId)
1154
+ clearTimeout(timeoutId);
1155
+ }
757
1156
  }
758
1157
  checkAbortBeforePayment(signal) {
759
1158
  if (signal?.aborted) {
760
1159
  throw new OneShotError('Operation cancelled before payment');
761
1160
  }
762
1161
  }
1162
+ /**
1163
+ * If currency is ETH, swap ETH→USDC to ensure the wallet has enough USDC for payment.
1164
+ * This is called before signing the x402 payment authorization.
1165
+ */
1166
+ async ensureUsdcBalance(paymentInfo) {
1167
+ if (this._currency !== 'ETH')
1168
+ return;
1169
+ const { executeSwap } = await Promise.resolve().then(() => __importStar(require('./swap')));
1170
+ this.log(`Swapping ETH→USDC for ${paymentInfo.amount} USDC (slippage: ${this._slippage * 100}%)`);
1171
+ const result = await executeSwap(this.provider, this.rpcProvider, paymentInfo.amount, this._expectedChainId, this._slippage);
1172
+ this.log(`Swap complete: tx=${result.txHash}, USDC received=${ethers_1.ethers.formatUnits(result.usdcReceived, 6)}`);
1173
+ }
763
1174
  async signPaymentAuthorization(paymentInfo) {
1175
+ // If paying with ETH, swap to USDC first
1176
+ await this.ensureUsdcBalance(paymentInfo);
764
1177
  const now = Math.floor(Date.now() / 1000);
765
1178
  const nonce = ethers_1.ethers.randomBytes(32);
766
1179
  const value = ethers_1.ethers.parseUnits(paymentInfo.amount, paymentInfo.token.decimals);
@@ -772,8 +1185,8 @@ class OneShot {
772
1185
  if (chainId !== this._expectedChainId) {
773
1186
  console.warn(`[OneShot] Chain mismatch: API returned ${chainId}, expected ${this._expectedChainId} (${this._testMode ? 'test' : 'prod'})`);
774
1187
  }
775
- const signature = await this.wallet.signTypedData({
776
- name: 'USD Coin', // EIP-712 domain name from USDC contract (not the ticker symbol)
1188
+ const signature = await this.provider.signTypedData({
1189
+ name: chainId === 84532 ? 'USDC' : 'USD Coin', // Base Sepolia uses "USDC", Base Mainnet uses "USD Coin"
777
1190
  version: '2',
778
1191
  chainId,
779
1192
  verifyingContract: paymentInfo.token.address
@@ -787,7 +1200,7 @@ class OneShot {
787
1200
  { name: 'nonce', type: 'bytes32' }
788
1201
  ]
789
1202
  }, {
790
- from: this.wallet.address,
1203
+ from: this.provider.address,
791
1204
  to: paymentInfo.payTo,
792
1205
  value,
793
1206
  validAfter: now - 300, // Buffer for clock skew
@@ -796,7 +1209,7 @@ class OneShot {
796
1209
  });
797
1210
  const sig = ethers_1.ethers.Signature.from(signature);
798
1211
  return {
799
- from: this.wallet.address,
1212
+ from: this.provider.address,
800
1213
  to: paymentInfo.payTo,
801
1214
  value: value.toString(),
802
1215
  validAfter: now - 300,