@aura-payments/sdk 2.1.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 ADDED
@@ -0,0 +1,1013 @@
1
+ 'use strict';
2
+
3
+ var crypto$1 = require('crypto');
4
+
5
+ // src/errors.ts
6
+ var AuraError = class _AuraError extends Error {
7
+ constructor(message, code, statusCode, details) {
8
+ super(message);
9
+ this.code = code;
10
+ this.statusCode = statusCode;
11
+ this.details = details;
12
+ this.name = "AuraError";
13
+ Object.setPrototypeOf(this, _AuraError.prototype);
14
+ }
15
+ };
16
+ var AuraAPIError = class _AuraAPIError extends AuraError {
17
+ constructor(message, statusCode, code, details) {
18
+ super(message, code, statusCode, details);
19
+ this.name = "AuraAPIError";
20
+ Object.setPrototypeOf(this, _AuraAPIError.prototype);
21
+ }
22
+ };
23
+ var AuraNetworkError = class _AuraNetworkError extends AuraError {
24
+ constructor(message, cause) {
25
+ super(message, "NETWORK_ERROR");
26
+ this.cause = cause;
27
+ this.name = "AuraNetworkError";
28
+ Object.setPrototypeOf(this, _AuraNetworkError.prototype);
29
+ }
30
+ };
31
+ var AuraTimeoutError = class _AuraTimeoutError extends AuraError {
32
+ constructor(message = "Request timeout") {
33
+ super(message, "TIMEOUT");
34
+ this.name = "AuraTimeoutError";
35
+ Object.setPrototypeOf(this, _AuraTimeoutError.prototype);
36
+ }
37
+ };
38
+ var AuraValidationError = class _AuraValidationError extends AuraError {
39
+ constructor(message, details) {
40
+ super(message, "VALIDATION_ERROR", 400, details);
41
+ this.name = "AuraValidationError";
42
+ Object.setPrototypeOf(this, _AuraValidationError.prototype);
43
+ }
44
+ };
45
+ var AuraAuthenticationError = class _AuraAuthenticationError extends AuraError {
46
+ constructor(message = "Authentication failed") {
47
+ super(message, "AUTHENTICATION_ERROR", 401);
48
+ this.name = "AuraAuthenticationError";
49
+ Object.setPrototypeOf(this, _AuraAuthenticationError.prototype);
50
+ }
51
+ };
52
+ var AuraNotFoundError = class _AuraNotFoundError extends AuraError {
53
+ constructor(resource) {
54
+ super(`${resource} not found`, "NOT_FOUND", 404);
55
+ this.name = "AuraNotFoundError";
56
+ Object.setPrototypeOf(this, _AuraNotFoundError.prototype);
57
+ }
58
+ };
59
+ var AuraRateLimitError = class _AuraRateLimitError extends AuraError {
60
+ constructor(message = "Rate limit exceeded", retryAfter) {
61
+ super(message, "RATE_LIMIT_EXCEEDED", 429);
62
+ this.retryAfter = retryAfter;
63
+ this.name = "AuraRateLimitError";
64
+ Object.setPrototypeOf(this, _AuraRateLimitError.prototype);
65
+ }
66
+ };
67
+ var AuraFaucetUnavailableError = class _AuraFaucetUnavailableError extends AuraError {
68
+ constructor(message = "Testnet faucet is not available on this deployment") {
69
+ super(message, "FAUCET_UNAVAILABLE", 501);
70
+ this.name = "AuraFaucetUnavailableError";
71
+ Object.setPrototypeOf(this, _AuraFaucetUnavailableError.prototype);
72
+ }
73
+ };
74
+ function isAuraError(error) {
75
+ return error instanceof AuraError;
76
+ }
77
+ function isAuraFaucetUnavailableError(error) {
78
+ return error instanceof AuraFaucetUnavailableError;
79
+ }
80
+ function isAuraAPIError(error) {
81
+ return error instanceof AuraAPIError;
82
+ }
83
+ function isAuraNetworkError(error) {
84
+ return error instanceof AuraNetworkError;
85
+ }
86
+ function isAuraTimeoutError(error) {
87
+ return error instanceof AuraTimeoutError;
88
+ }
89
+ function isRetryableError(error) {
90
+ if (!(error instanceof AuraError)) return false;
91
+ if (error instanceof AuraNetworkError) return true;
92
+ if (error instanceof AuraTimeoutError) return true;
93
+ if (error instanceof AuraRateLimitError) return true;
94
+ if (error.statusCode && error.statusCode >= 500) {
95
+ return true;
96
+ }
97
+ return false;
98
+ }
99
+
100
+ // src/resources/agents.ts
101
+ var Agents = class {
102
+ constructor(client) {
103
+ this.client = client;
104
+ }
105
+ /**
106
+ * Create a new AI agent with a dedicated wallet and (optional) initial
107
+ * policy. The platform provisions a Circle wallet on the requested chain
108
+ * during this call; expect a few seconds of latency for the on-chain part.
109
+ */
110
+ async create(params, idempotencyKey) {
111
+ return this.client["request"](
112
+ "POST",
113
+ "/v1/agents",
114
+ params,
115
+ { idempotencyKey }
116
+ );
117
+ }
118
+ /**
119
+ * List agents for the authenticated account, paginated.
120
+ */
121
+ async list(params) {
122
+ const query = new URLSearchParams();
123
+ if (params?.status) query.set("status", params.status);
124
+ if (params?.type) query.set("type", params.type);
125
+ if (params?.limit !== void 0) query.set("limit", String(params.limit));
126
+ if (params?.offset !== void 0) query.set("offset", String(params.offset));
127
+ if (params?.orderBy) query.set("orderBy", params.orderBy);
128
+ if (params?.orderDirection) query.set("orderDirection", params.orderDirection);
129
+ const qs = query.toString();
130
+ return this.client["request"](
131
+ "GET",
132
+ qs ? `/v1/agents?${qs}` : "/v1/agents"
133
+ );
134
+ }
135
+ /**
136
+ * Get a single agent by id.
137
+ */
138
+ async get(agentId) {
139
+ return this.client["request"]("GET", `/v1/agents/${agentId}`);
140
+ }
141
+ /**
142
+ * Patch an agent's metadata fields. Cannot be used to flip status — use
143
+ * `freeze`/`unfreeze` for that so the mandate-cascade and activity events
144
+ * fire correctly.
145
+ */
146
+ async update(agentId, params, idempotencyKey) {
147
+ return this.client["request"](
148
+ "PATCH",
149
+ `/v1/agents/${agentId}`,
150
+ params,
151
+ { idempotencyKey }
152
+ );
153
+ }
154
+ /**
155
+ * Freeze (kill switch) — sets status to `paused` and cascade-rejects every
156
+ * pending mandate the agent has in flight. Idempotent.
157
+ */
158
+ async freeze(params, idempotencyKey) {
159
+ const { agentId, ...body } = params;
160
+ return this.client["request"](
161
+ "POST",
162
+ `/v1/agents/${agentId}/freeze`,
163
+ body,
164
+ { idempotencyKey }
165
+ );
166
+ }
167
+ /**
168
+ * Unfreeze — sets status back to `active`. Previously cascade-rejected
169
+ * mandates are NOT restored; the agent re-creates them on its next loop.
170
+ */
171
+ async unfreeze(params, idempotencyKey) {
172
+ const { agentId } = params;
173
+ return this.client["request"](
174
+ "POST",
175
+ `/v1/agents/${agentId}/unfreeze`,
176
+ void 0,
177
+ { idempotencyKey }
178
+ );
179
+ }
180
+ /**
181
+ * Get the agent's wallet balance (USDC, on-chain at request time).
182
+ */
183
+ async getBalance(agentId) {
184
+ return this.client["request"](
185
+ "GET",
186
+ `/v1/agents/${agentId}/balance`
187
+ );
188
+ }
189
+ /**
190
+ * Run a hypothetical transaction through the policy engine without
191
+ * executing it. Returns the engine's decision (`auto_approve`, `auto_deny`,
192
+ * or `requires_human_approval`) along with the matched rules. When the
193
+ * decision is `requires_human_approval`, the engine creates a mandate row
194
+ * and returns its id so the caller can watch for the operator's decision.
195
+ */
196
+ async evaluate(params, idempotencyKey) {
197
+ const { agentId, ...body } = params;
198
+ return this.client["request"](
199
+ "POST",
200
+ `/v1/agents/${agentId}/evaluate`,
201
+ body,
202
+ { idempotencyKey }
203
+ );
204
+ }
205
+ };
206
+
207
+ // src/utils.ts
208
+ function generateIdempotencyKey() {
209
+ const timestamp = Date.now();
210
+ const random = Math.random().toString(36).substring(2, 15);
211
+ return `${timestamp}-${random}`;
212
+ }
213
+ function sleep(ms) {
214
+ return new Promise((resolve) => setTimeout(resolve, ms));
215
+ }
216
+ function calculateBackoff(attempt, baseDelay = 1e3, maxDelay = 3e4) {
217
+ const exponentialDelay = baseDelay * Math.pow(2, attempt);
218
+ const jitter = exponentialDelay * Math.random() * 0.25;
219
+ return Math.min(exponentialDelay + jitter, maxDelay);
220
+ }
221
+ async function retryWithBackoff(fn, maxRetries = 3, shouldRetry = isRetryableError) {
222
+ let lastError;
223
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
224
+ try {
225
+ return await fn();
226
+ } catch (error) {
227
+ lastError = error;
228
+ if (attempt === maxRetries) {
229
+ throw error;
230
+ }
231
+ if (!shouldRetry(error)) {
232
+ throw error;
233
+ }
234
+ const delay = calculateBackoff(attempt);
235
+ await sleep(delay);
236
+ }
237
+ }
238
+ throw lastError;
239
+ }
240
+ async function withTimeout(promise, timeoutMs, timeoutMessage) {
241
+ const timeoutPromise = new Promise((_, reject) => {
242
+ setTimeout(() => {
243
+ reject(new AuraTimeoutError(timeoutMessage || `Request timeout after ${timeoutMs}ms`));
244
+ }, timeoutMs);
245
+ });
246
+ return Promise.race([promise, timeoutPromise]);
247
+ }
248
+
249
+ // src/resources/escrows.ts
250
+ function isEscrowDeployed(e) {
251
+ if (e.blockchain?.deployed) return true;
252
+ return ["deployed", "funded", "locked", "released"].includes(e.state ?? "");
253
+ }
254
+ function isEscrowFunded(e) {
255
+ return ["funded", "locked", "released"].includes(e.state ?? "");
256
+ }
257
+ function parseUsdc(value) {
258
+ const n = Number.parseFloat(value);
259
+ return Number.isFinite(n) ? n : 0;
260
+ }
261
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
262
+ var Escrows = class {
263
+ constructor(client) {
264
+ this.client = client;
265
+ }
266
+ /**
267
+ * Create a new escrow
268
+ */
269
+ async create(params, idempotencyKey) {
270
+ return this.client["request"](
271
+ "POST",
272
+ "/v1/escrow/create",
273
+ params,
274
+ { idempotencyKey }
275
+ );
276
+ }
277
+ /**
278
+ * Get escrow by ID
279
+ */
280
+ async get(escrowId) {
281
+ return this.client["request"]("GET", `/v1/escrow/${escrowId}`);
282
+ }
283
+ /**
284
+ * List escrows with optional filters
285
+ */
286
+ async list(params) {
287
+ const queryParams = new URLSearchParams();
288
+ if (params?.search) queryParams.set("search", params.search);
289
+ if (params?.state) queryParams.set("state", params.state);
290
+ if (params?.sortBy) queryParams.set("sortBy", params.sortBy);
291
+ if (params?.limit) queryParams.set("limit", params.limit.toString());
292
+ if (params?.offset) queryParams.set("offset", params.offset.toString());
293
+ const query = queryParams.toString();
294
+ const path = query ? `/v1/escrow?${query}` : "/v1/escrow";
295
+ return this.client["request"]("GET", path);
296
+ }
297
+ /**
298
+ * Fund an escrow
299
+ */
300
+ async fund(params, idempotencyKey) {
301
+ const { escrowId, ...body } = params;
302
+ return this.client["request"](
303
+ "POST",
304
+ `/v1/escrow/${escrowId}/fund`,
305
+ body,
306
+ { idempotencyKey }
307
+ );
308
+ }
309
+ /**
310
+ * Release an escrow
311
+ */
312
+ async release(params, idempotencyKey) {
313
+ const { escrowId, ...body } = params;
314
+ return this.client["request"](
315
+ "POST",
316
+ `/v1/escrow/${escrowId}/release`,
317
+ body,
318
+ { idempotencyKey }
319
+ );
320
+ }
321
+ /**
322
+ * Refund an escrow
323
+ */
324
+ async refund(params, idempotencyKey) {
325
+ return this.client["request"](
326
+ "POST",
327
+ `/v1/escrow/refund`,
328
+ params,
329
+ { idempotencyKey }
330
+ );
331
+ }
332
+ /**
333
+ * Receive USDC and split it across recipients in a single call (board D4).
334
+ *
335
+ * Composes the existing, crash-safe escrow path:
336
+ * create → poll-until-deployed → pre-flight balance → fund → poll-until-funded → release.
337
+ *
338
+ * Funds move from an Aura wallet owned by `payerOwnerId` (the agent itself or a
339
+ * counterparty). The result's `stage` makes a partial run explicit:
340
+ * - `created` — deployment didn't confirm within `waitForDeploymentMs`
341
+ * - `deployed` — payer wallet lacks funds (see `fundingRequired`); no money moved
342
+ * - `funded` — funded but not released (`autoRelease:false`, or funding didn't
343
+ * settle within `waitForFundingMs` → `fundingPending: true`)
344
+ * - `released` — split to recipients (happy path)
345
+ *
346
+ * Resume note: a partial run is resumed by acting on the returned
347
+ * `escrow.escrowId` (call `fund`/`release` directly) — NOT by re-calling
348
+ * `receiveAndSplit`, which re-`create`s and would hit a duplicate-orderId error.
349
+ * Pass a stable `idempotencyKey` for retry-safe fund/release.
350
+ *
351
+ * Funding is asynchronous and the platform's fund/release endpoints return
352
+ * operation metadata (not a full escrow), so this method re-`get`s the escrow
353
+ * for authoritative state + splits rather than trusting those response bodies.
354
+ *
355
+ * @throws AuraValidationError if split percentages don't sum to 100 (pre-flight).
356
+ */
357
+ async receiveAndSplit(params) {
358
+ const {
359
+ orderId,
360
+ payerOwnerId,
361
+ amount,
362
+ splits,
363
+ chain,
364
+ actorId,
365
+ autoRelease = true,
366
+ waitForDeploymentMs = 6e4,
367
+ waitForFundingMs = 6e4,
368
+ pollIntervalMs = 2e3,
369
+ idempotencyKey
370
+ } = params;
371
+ const pctSum = splits.reduce((acc, s) => acc + s.percentage, 0);
372
+ if (Math.round(pctSum * 100) !== 1e4) {
373
+ throw new AuraValidationError(
374
+ `Split percentages must sum to 100 (got ${pctSum}).`,
375
+ { splits }
376
+ );
377
+ }
378
+ const runKey = idempotencyKey ?? generateIdempotencyKey();
379
+ const pollMs = Math.max(pollIntervalMs, 250);
380
+ const created = await this.create(
381
+ { orderId, buyerOwnerId: payerOwnerId, amount, chain, splits },
382
+ `${runKey}-create`
383
+ );
384
+ const escrowId = created.escrowId;
385
+ const deployed = await this.pollEscrowUntil(
386
+ escrowId,
387
+ isEscrowDeployed,
388
+ waitForDeploymentMs,
389
+ pollMs,
390
+ created
391
+ );
392
+ if (!isEscrowDeployed(deployed)) {
393
+ return {
394
+ stage: "created",
395
+ escrow: deployed,
396
+ splits: deployed.splits,
397
+ deploymentPending: true
398
+ };
399
+ }
400
+ const payerWalletId = deployed.buyerWalletId;
401
+ const balance = await this.client.wallets.getBalance(payerWalletId);
402
+ if (parseUsdc(balance.balance.usdc) < parseUsdc(amount)) {
403
+ return {
404
+ stage: "deployed",
405
+ escrow: deployed,
406
+ splits: deployed.splits,
407
+ fundingRequired: {
408
+ payerWalletId,
409
+ payerAddress: deployed.buyerWallet.address,
410
+ amount,
411
+ available: balance.balance.usdc
412
+ }
413
+ };
414
+ }
415
+ await this.fund(
416
+ { escrowId, buyerWalletId: payerWalletId, amount },
417
+ `${runKey}-fund`
418
+ );
419
+ const funded = await this.pollEscrowUntil(
420
+ escrowId,
421
+ isEscrowFunded,
422
+ waitForFundingMs,
423
+ pollMs,
424
+ deployed
425
+ );
426
+ if (!isEscrowFunded(funded)) {
427
+ return { stage: "funded", escrow: funded, splits: funded.splits, fundingPending: true };
428
+ }
429
+ if (!autoRelease) {
430
+ return { stage: "funded", escrow: funded, splits: funded.splits };
431
+ }
432
+ await this.release(
433
+ { escrowId, actorId: actorId ?? payerOwnerId, reason: "receiveAndSplit auto-release" },
434
+ `${runKey}-release`
435
+ );
436
+ const final = await this.get(escrowId);
437
+ return { stage: "released", escrow: final, splits: final.splits };
438
+ }
439
+ /**
440
+ * Poll `get(escrowId)` until `predicate` holds or `budgetMs` elapses. Returns
441
+ * the last-observed escrow; the caller re-checks the predicate to branch.
442
+ * `seed` is the escrow already in hand, checked before any poll so a
443
+ * synchronously-settled escrow returns without a network round-trip.
444
+ */
445
+ async pollEscrowUntil(escrowId, predicate, budgetMs, pollMs, seed) {
446
+ let escrow = seed;
447
+ if (predicate(escrow)) return escrow;
448
+ const deadline = Date.now() + budgetMs;
449
+ while (Date.now() < deadline) {
450
+ await sleep2(pollMs);
451
+ escrow = await this.get(escrowId);
452
+ if (predicate(escrow)) break;
453
+ }
454
+ return escrow;
455
+ }
456
+ /**
457
+ * Create a dispute for an escrow
458
+ */
459
+ async createDispute(params, idempotencyKey) {
460
+ return this.client["request"](
461
+ "POST",
462
+ `/v1/escrow/${params.escrowId}/dispute`,
463
+ params,
464
+ { idempotencyKey }
465
+ );
466
+ }
467
+ /**
468
+ * Get dispute details
469
+ */
470
+ async getDispute(escrowId, disputeId) {
471
+ return this.client["request"](
472
+ "GET",
473
+ `/v1/escrow/${escrowId}/dispute/${disputeId}`
474
+ );
475
+ }
476
+ };
477
+ var Mandates = class {
478
+ constructor(client) {
479
+ this.client = client;
480
+ }
481
+ /**
482
+ * List mandates for the authenticated account. Defaults to `status=pending`
483
+ * so this acts as the operator's inbox out of the box.
484
+ */
485
+ async list(params) {
486
+ const query = new URLSearchParams();
487
+ if (params?.status) query.set("status", params.status);
488
+ if (params?.agentId) query.set("agentId", params.agentId);
489
+ if (params?.limit !== void 0) query.set("limit", String(params.limit));
490
+ if (params?.cursor) query.set("cursor", params.cursor);
491
+ const qs = query.toString();
492
+ return this.client["request"](
493
+ "GET",
494
+ qs ? `/v1/agent/mandates?${qs}` : "/v1/agent/mandates"
495
+ );
496
+ }
497
+ /**
498
+ * Convenience wrapper for the most common case: pending mandates in
499
+ * urgency order (most-expiring first).
500
+ */
501
+ async listPending(extras) {
502
+ return this.list({ ...extras, status: "pending" });
503
+ }
504
+ /**
505
+ * Get a single mandate by id.
506
+ */
507
+ async get(mandateId) {
508
+ return this.client["request"](
509
+ "GET",
510
+ `/v1/agent/mandates/${mandateId}`
511
+ );
512
+ }
513
+ /**
514
+ * Approve a pending mandate. Caller is responsible for computing
515
+ * `decisionSignature` — use `MandateSignature.compute()` below.
516
+ *
517
+ * Idempotent: re-approving an already-`approved` mandate returns 200 with
518
+ * the existing decision. Approving a `rejected`/`cancelled` mandate
519
+ * returns 409.
520
+ */
521
+ async approve(params, idempotencyKey) {
522
+ const { mandateId, ...body } = params;
523
+ return this.client["request"](
524
+ "POST",
525
+ `/v1/agent/mandates/${mandateId}/approve`,
526
+ body,
527
+ { idempotencyKey }
528
+ );
529
+ }
530
+ /**
531
+ * Reject a pending mandate. Same signature scheme as `approve`.
532
+ */
533
+ async reject(params, idempotencyKey) {
534
+ const { mandateId, ...body } = params;
535
+ return this.client["request"](
536
+ "POST",
537
+ `/v1/agent/mandates/${mandateId}/reject`,
538
+ body,
539
+ { idempotencyKey }
540
+ );
541
+ }
542
+ };
543
+ var MandateSignature = {
544
+ compute(input) {
545
+ const payload = `${input.mandateId}:${input.decision}:${input.decidedAt}`;
546
+ return crypto$1.createHmac("sha256", input.secret).update(payload).digest("hex");
547
+ },
548
+ verify(input) {
549
+ const expected = MandateSignature.compute({
550
+ mandateId: input.mandateId,
551
+ decision: input.decision,
552
+ decidedAt: input.decidedAt,
553
+ secret: input.secret
554
+ });
555
+ if (expected.length !== input.signature.length) return false;
556
+ let diff = 0;
557
+ for (let i = 0; i < expected.length; i++) {
558
+ diff |= expected.charCodeAt(i) ^ input.signature.charCodeAt(i);
559
+ }
560
+ return diff === 0;
561
+ }
562
+ };
563
+
564
+ // src/resources/policies.ts
565
+ var Policies = class {
566
+ constructor(client) {
567
+ this.client = client;
568
+ }
569
+ /**
570
+ * List all policies attached to an agent.
571
+ */
572
+ async list(agentId) {
573
+ return this.client["request"](
574
+ "GET",
575
+ `/v1/agents/${agentId}/policies`
576
+ );
577
+ }
578
+ /**
579
+ * Get a single policy by id (scoped to the parent agent).
580
+ */
581
+ async get(agentId, policyId) {
582
+ return this.client["request"](
583
+ "GET",
584
+ `/v1/agents/${agentId}/policies/${policyId}`
585
+ );
586
+ }
587
+ /**
588
+ * Create a new policy for an agent. The agent's policy engine will start
589
+ * matching against this policy on the next evaluation.
590
+ */
591
+ async create(params, idempotencyKey) {
592
+ const { agentId, ...body } = params;
593
+ return this.client["request"](
594
+ "POST",
595
+ `/v1/agents/${agentId}/policies`,
596
+ body,
597
+ { idempotencyKey }
598
+ );
599
+ }
600
+ };
601
+
602
+ // src/resources/wallets.ts
603
+ var Wallets = class {
604
+ constructor(client) {
605
+ this.client = client;
606
+ }
607
+ /**
608
+ * Create a new wallet
609
+ */
610
+ async create(params, idempotencyKey) {
611
+ return this.client["request"](
612
+ "POST",
613
+ "/v1/wallets",
614
+ params,
615
+ { idempotencyKey }
616
+ );
617
+ }
618
+ /**
619
+ * Get wallet by ID
620
+ */
621
+ async get(walletId) {
622
+ return this.client["request"]("GET", `/v1/wallets/${walletId}`);
623
+ }
624
+ /**
625
+ * List wallets with optional filters
626
+ */
627
+ async list(params) {
628
+ const queryParams = new URLSearchParams();
629
+ if (params?.entityId) queryParams.set("entityId", params.entityId);
630
+ if (params?.chain) queryParams.set("chain", params.chain);
631
+ if (params?.type) queryParams.set("type", params.type);
632
+ if (params?.page) queryParams.set("page", params.page.toString());
633
+ if (params?.limit) queryParams.set("limit", params.limit.toString());
634
+ const query = queryParams.toString();
635
+ const path = query ? `/v1/wallets?${query}` : "/v1/wallets";
636
+ return this.client["request"]("GET", path);
637
+ }
638
+ /**
639
+ * Get wallet balance
640
+ */
641
+ async getBalance(walletId) {
642
+ return this.client["request"](
643
+ "GET",
644
+ `/v1/wallets/${walletId}/balance`
645
+ );
646
+ }
647
+ /**
648
+ * Transfer funds from wallet
649
+ */
650
+ async transfer(params) {
651
+ const idempotencyKey = params.idempotencyKey || this.generateIdempotencyKey();
652
+ return this.client["request"](
653
+ "POST",
654
+ `/v1/wallets/${params.fromWalletId}/transfer`,
655
+ params,
656
+ { idempotencyKey }
657
+ );
658
+ }
659
+ /**
660
+ * Request testnet funds (USDC) for a wallet from the platform faucet.
661
+ *
662
+ * Testnet only. Backed by `POST /v1/wallets/:id/faucet`, provided by the
663
+ * platform (AURA-010). Until that endpoint ships, this capability-probes and
664
+ * raises {@link AuraFaucetUnavailableError} (rather than a generic 404/501)
665
+ * so callers can degrade gracefully to manual funding.
666
+ */
667
+ async requestTestnetFunds(walletId, params) {
668
+ try {
669
+ return await this.client["request"](
670
+ "POST",
671
+ `/v1/wallets/${walletId}/faucet`,
672
+ params ?? {}
673
+ );
674
+ } catch (err) {
675
+ if (err instanceof AuraAPIError && err.statusCode === 501) {
676
+ throw new AuraFaucetUnavailableError(
677
+ "Testnet faucet is not enabled on this deployment yet (POST /v1/wallets/:id/faucet returned 501). Fund the wallet manually, or ask the operator to enable the faucet endpoint."
678
+ );
679
+ }
680
+ throw err;
681
+ }
682
+ }
683
+ /**
684
+ * Get transfer by ID
685
+ */
686
+ async getTransfer(walletId, transferId) {
687
+ return this.client["request"](
688
+ "GET",
689
+ `/v1/wallets/${walletId}/transfers/${transferId}`
690
+ );
691
+ }
692
+ /**
693
+ * Generate a unique idempotency key
694
+ */
695
+ generateIdempotencyKey() {
696
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
697
+ }
698
+ };
699
+ var Webhooks = class {
700
+ constructor(client) {
701
+ this.client = client;
702
+ }
703
+ /**
704
+ * Configure webhook endpoint and events
705
+ */
706
+ async configure(params) {
707
+ return this.client["request"](
708
+ "POST",
709
+ "/v1/webhooks/config",
710
+ params
711
+ );
712
+ }
713
+ /**
714
+ * Get current webhook configuration
715
+ */
716
+ async getConfig() {
717
+ return this.client["request"]("GET", "/v1/webhooks/config");
718
+ }
719
+ /**
720
+ * Delete webhook configuration
721
+ */
722
+ async delete() {
723
+ return this.client["request"](
724
+ "DELETE",
725
+ "/v1/webhooks/config"
726
+ );
727
+ }
728
+ /**
729
+ * Validate webhook signature
730
+ * Use this in your webhook handler to verify authenticity
731
+ */
732
+ static validateSignature(options) {
733
+ try {
734
+ const { payload, signature, secret, toleranceMs = 3e5 } = options;
735
+ const payloadString = typeof payload === "string" ? payload : JSON.stringify(payload);
736
+ const { timestamp, v1 } = this.parseSignatureHeader(signature);
737
+ const ageMs = Math.abs(Date.now() - timestamp);
738
+ if (ageMs > toleranceMs) {
739
+ return { valid: false, error: "Signature timestamp expired" };
740
+ }
741
+ const expectedSignature = this.computeHmacSignature(`${timestamp}.${payloadString}`, secret);
742
+ const valid = this.secureCompare(v1, expectedSignature);
743
+ if (!valid) {
744
+ return {
745
+ valid: false,
746
+ error: "Invalid signature"
747
+ };
748
+ }
749
+ const event = typeof payload === "string" ? JSON.parse(payload) : payload;
750
+ return {
751
+ valid: true,
752
+ event
753
+ };
754
+ } catch (error) {
755
+ return {
756
+ valid: false,
757
+ error: error instanceof Error ? error.message : "Unknown error"
758
+ };
759
+ }
760
+ }
761
+ /**
762
+ * Parse signature header: "t=timestamp,v1=signature"
763
+ */
764
+ static parseSignatureHeader(signatureHeader) {
765
+ const parts = signatureHeader.split(",").map((p) => p.trim());
766
+ const tPart = parts.find((p) => p.startsWith("t="));
767
+ const v1Part = parts.find((p) => p.startsWith("v1="));
768
+ if (!tPart || !v1Part) {
769
+ throw new Error("Invalid signature header format (expected: t=...,v1=...)");
770
+ }
771
+ const timestamp = parseInt(tPart.slice(2), 10);
772
+ const v1 = v1Part.slice(3);
773
+ if (!Number.isFinite(timestamp) || timestamp <= 0) {
774
+ throw new Error("Invalid signature timestamp");
775
+ }
776
+ if (!v1) {
777
+ throw new Error("Invalid signature value");
778
+ }
779
+ return { timestamp, v1 };
780
+ }
781
+ /**
782
+ * Compute HMAC-SHA256 signature
783
+ */
784
+ static computeHmacSignature(payload, secret) {
785
+ if (typeof crypto !== "undefined" && crypto.subtle) {
786
+ throw new Error("Web Crypto implementation needed for browser");
787
+ }
788
+ return crypto$1.createHmac("sha256", secret).update(payload).digest("hex");
789
+ }
790
+ /**
791
+ * Constant-time string comparison
792
+ */
793
+ static secureCompare(a, b) {
794
+ if (a.length !== b.length) {
795
+ return false;
796
+ }
797
+ let result = 0;
798
+ for (let i = 0; i < a.length; i++) {
799
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
800
+ }
801
+ return result === 0;
802
+ }
803
+ };
804
+
805
+ // src/client.ts
806
+ var AuraClient = class _AuraClient {
807
+ constructor(config) {
808
+ this.apiKey = config.apiKey;
809
+ this.baseUrl = _AuraClient.normalizeBaseUrl(config.baseUrl);
810
+ this.timeout = config.timeout || 3e4;
811
+ this.maxRetries = config.maxRetries || 3;
812
+ this.autoIdempotency = config.autoIdempotency !== false;
813
+ if (!this.apiKey) {
814
+ throw new AuraError("API key is required", "MISSING_API_KEY");
815
+ }
816
+ this.escrows = new Escrows(this);
817
+ this.wallets = new Wallets(this);
818
+ this.webhooks = new Webhooks(this);
819
+ this.agents = new Agents(this);
820
+ this.policies = new Policies(this);
821
+ this.mandates = new Mandates(this);
822
+ }
823
+ /**
824
+ * Normalize the base URL so resource paths like `/v1/escrow` reach the
825
+ * platform's `/api/v1/escrow` route regardless of how the caller framed
826
+ * `baseUrl`. Trailing slashes are trimmed; missing `/api` is appended.
827
+ *
828
+ * @internal exported pattern for tests; do not call directly.
829
+ */
830
+ static normalizeBaseUrl(input) {
831
+ const raw = (input || "https://getaura.sh/api").replace(/\/+$/, "");
832
+ if (/\/api(\/v\d+)?$/.test(raw) || /\/api$/.test(raw)) {
833
+ return raw;
834
+ }
835
+ return `${raw}/api`;
836
+ }
837
+ /**
838
+ * Get the API key
839
+ */
840
+ getApiKey() {
841
+ return this.apiKey;
842
+ }
843
+ /**
844
+ * Get the base URL
845
+ */
846
+ getBaseUrl() {
847
+ return this.baseUrl;
848
+ }
849
+ /**
850
+ * Get the timeout value
851
+ */
852
+ getTimeout() {
853
+ return this.timeout;
854
+ }
855
+ /**
856
+ * Get the max retries value
857
+ */
858
+ getMaxRetries() {
859
+ return this.maxRetries;
860
+ }
861
+ /**
862
+ * Make an authenticated request to the API with automatic retry logic
863
+ */
864
+ async request(method, path, body, options) {
865
+ const requestFn = () => this.executeRequest(method, path, body, options);
866
+ if (options?.skipRetry || method === "GET") {
867
+ return requestFn();
868
+ }
869
+ return retryWithBackoff(requestFn, this.maxRetries);
870
+ }
871
+ /**
872
+ * Execute a single HTTP request (internal)
873
+ */
874
+ async executeRequest(method, path, body, options) {
875
+ const url = `${this.baseUrl}${path}`;
876
+ const headers = {
877
+ "Content-Type": "application/json",
878
+ "Authorization": `Bearer ${this.apiKey}`,
879
+ "User-Agent": "@aura-payments/sdk/2.1.0"
880
+ };
881
+ if (options?.idempotencyKey) {
882
+ headers["Idempotency-Key"] = options.idempotencyKey;
883
+ } else if (this.autoIdempotency && (method === "POST" || method === "PUT")) {
884
+ headers["Idempotency-Key"] = generateIdempotencyKey();
885
+ }
886
+ try {
887
+ const response = await withTimeout(
888
+ fetch(url, {
889
+ method,
890
+ headers,
891
+ body: body ? JSON.stringify(body) : void 0
892
+ }),
893
+ this.timeout
894
+ );
895
+ if (!response.ok) {
896
+ await this.handleErrorResponse(response);
897
+ }
898
+ if (response.status === 204) {
899
+ return void 0;
900
+ }
901
+ const data = await response.json();
902
+ return this.normalizeResponse(data);
903
+ } catch (error) {
904
+ if (error instanceof TypeError && error.message.includes("fetch")) {
905
+ throw new AuraNetworkError("Network request failed", error);
906
+ }
907
+ if (error instanceof AuraError) {
908
+ throw error;
909
+ }
910
+ throw new AuraError(
911
+ error instanceof Error ? error.message : "Unknown error occurred",
912
+ "UNKNOWN_ERROR"
913
+ );
914
+ }
915
+ }
916
+ /**
917
+ * Normalize API responses across different wrapper formats.
918
+ *
919
+ * The platform API currently uses two common success wrappers:
920
+ * - { success: true, data: T, ... }
921
+ * - { data: T, meta: {...} } (and sometimes { data: T[], meta, pagination })
922
+ *
923
+ * SDK methods generally return the inner `data` payload, except for paginated
924
+ * endpoints where pagination metadata is meaningful and preserved.
925
+ */
926
+ normalizeResponse(payload) {
927
+ if (!payload || typeof payload !== "object") return payload;
928
+ const obj = payload;
929
+ if ("success" in obj && "data" in obj) {
930
+ return obj.data;
931
+ }
932
+ if ("data" in obj && ("meta" in obj || "metadata" in obj)) {
933
+ if ("pagination" in obj) {
934
+ return obj;
935
+ }
936
+ return obj.data;
937
+ }
938
+ if ("data" in obj && Object.keys(obj).length <= 2) {
939
+ return obj.data;
940
+ }
941
+ return payload;
942
+ }
943
+ /**
944
+ * Handle HTTP error responses and throw appropriate errors
945
+ */
946
+ async handleErrorResponse(response) {
947
+ const statusCode = response.status;
948
+ let errorData = {};
949
+ try {
950
+ const data = await response.json();
951
+ if (typeof data === "object" && data !== null) {
952
+ errorData = data;
953
+ }
954
+ } catch {
955
+ errorData = { message: response.statusText };
956
+ }
957
+ const message = errorData.message || `HTTP ${statusCode} error`;
958
+ const code = errorData.code;
959
+ const details = errorData.details;
960
+ switch (statusCode) {
961
+ case 400:
962
+ throw new AuraValidationError(message, details);
963
+ case 401:
964
+ throw new AuraAuthenticationError(message);
965
+ case 404:
966
+ throw new AuraNotFoundError(message);
967
+ case 429: {
968
+ const retryAfter = response.headers.get("Retry-After");
969
+ throw new AuraRateLimitError(message, retryAfter ? parseInt(retryAfter) : void 0);
970
+ }
971
+ case 408:
972
+ throw new AuraTimeoutError(message);
973
+ default:
974
+ throw new AuraAPIError(message, statusCode, code, details);
975
+ }
976
+ }
977
+ /**
978
+ * Get the auto-idempotency setting
979
+ */
980
+ getAutoIdempotency() {
981
+ return this.autoIdempotency;
982
+ }
983
+ };
984
+
985
+ exports.Agents = Agents;
986
+ exports.AuraAPIError = AuraAPIError;
987
+ exports.AuraAuthenticationError = AuraAuthenticationError;
988
+ exports.AuraClient = AuraClient;
989
+ exports.AuraError = AuraError;
990
+ exports.AuraFaucetUnavailableError = AuraFaucetUnavailableError;
991
+ exports.AuraNetworkError = AuraNetworkError;
992
+ exports.AuraNotFoundError = AuraNotFoundError;
993
+ exports.AuraRateLimitError = AuraRateLimitError;
994
+ exports.AuraTimeoutError = AuraTimeoutError;
995
+ exports.AuraValidationError = AuraValidationError;
996
+ exports.Escrows = Escrows;
997
+ exports.MandateSignature = MandateSignature;
998
+ exports.Mandates = Mandates;
999
+ exports.Policies = Policies;
1000
+ exports.Wallets = Wallets;
1001
+ exports.Webhooks = Webhooks;
1002
+ exports.calculateBackoff = calculateBackoff;
1003
+ exports.generateIdempotencyKey = generateIdempotencyKey;
1004
+ exports.isAuraAPIError = isAuraAPIError;
1005
+ exports.isAuraError = isAuraError;
1006
+ exports.isAuraFaucetUnavailableError = isAuraFaucetUnavailableError;
1007
+ exports.isAuraNetworkError = isAuraNetworkError;
1008
+ exports.isAuraTimeoutError = isAuraTimeoutError;
1009
+ exports.isRetryableError = isRetryableError;
1010
+ exports.retryWithBackoff = retryWithBackoff;
1011
+ exports.withTimeout = withTimeout;
1012
+ //# sourceMappingURL=index.js.map
1013
+ //# sourceMappingURL=index.js.map