@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.d.ts +271 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +439 -26
- package/dist/index.js.map +1 -1
- package/dist/providers/cdp.d.ts +27 -0
- package/dist/providers/cdp.d.ts.map +1 -0
- package/dist/providers/cdp.js +119 -0
- package/dist/providers/cdp.js.map +1 -0
- package/dist/providers/ethers.d.ts +15 -0
- package/dist/providers/ethers.d.ts.map +1 -0
- package/dist/providers/ethers.js +42 -0
- package/dist/providers/ethers.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/swap.d.ts +66 -0
- package/dist/swap.d.ts.map +1 -0
- package/dist/swap.js +146 -0
- package/dist/swap.js.map +1 -0
- package/dist/wallet-provider.d.ts +54 -0
- package/dist/wallet-provider.d.ts.map +1 -0
- package/dist/wallet-provider.js +10 -0
- package/dist/wallet-provider.js.map +1 -0
- package/package.json +8 -4
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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
110
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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.
|
|
776
|
-
name: 'USD Coin', //
|
|
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.
|
|
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.
|
|
1212
|
+
from: this.provider.address,
|
|
800
1213
|
to: paymentInfo.payTo,
|
|
801
1214
|
value: value.toString(),
|
|
802
1215
|
validAfter: now - 300,
|