@routedock/routedock 0.3.0 → 0.4.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/README.md +113 -12
- package/dist/{MppSessionHandler-1EL8QBAn.d.cts → MppSessionHandler-3QfCHI5x.d.ts} +40 -2
- package/dist/{MppSessionHandler-Bq4S-JTE.d.ts → MppSessionHandler-xu4Kxu6N.d.cts} +40 -2
- package/dist/{ProviderRegistry-CA_TdREG.d.ts → ProviderRegistry-D9KqyuH-.d.cts} +16 -36
- package/dist/{ProviderRegistry-D-0rqK0N.d.cts → ProviderRegistry-DVi2uzgk.d.ts} +16 -36
- package/dist/{RouteDockClient-go9aopfF.d.cts → RouteDockClient-6rmgkbYb.d.cts} +124 -142
- package/dist/{RouteDockClient-Bh86bVly.d.ts → RouteDockClient-CnAkxwrW.d.ts} +124 -142
- package/dist/{SessionReconciler-Bw-PoBVl.d.ts → SessionReconciler-Cce9MXO1.d.ts} +10 -3
- package/dist/{SessionReconciler-Cz2nuOlJ.d.cts → SessionReconciler-Dpe9qXdD.d.cts} +10 -3
- package/dist/{SessionStore-BaYiegP0.d.ts → SessionStore-DkLBxFQu.d.cts} +28 -3
- package/dist/{SessionStore-B4-Hi4H8.d.cts → SessionStore-_ZkoBzFY.d.ts} +28 -3
- package/dist/SpendStore-BUclV02i.d.ts +73 -0
- package/dist/SpendStore-s_dfcemj.d.cts +73 -0
- package/dist/client.cjs +767 -487
- package/dist/client.d.cts +80 -10
- package/dist/client.d.ts +80 -10
- package/dist/client.js +733 -468
- package/dist/index.cjs +1295 -590
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/index.js +1247 -559
- package/dist/logger-DVa7VT4c.d.cts +41 -0
- package/dist/logger-DVa7VT4c.d.ts +41 -0
- package/dist/network.cjs +57 -0
- package/dist/network.d.cts +72 -0
- package/dist/network.d.ts +72 -0
- package/dist/network.js +49 -0
- package/dist/node.cjs +166 -0
- package/dist/node.d.cts +77 -0
- package/dist/node.d.ts +77 -0
- package/dist/node.js +159 -0
- package/dist/provider/fastify.cjs +847 -62
- package/dist/provider/fastify.d.cts +10 -3
- package/dist/provider/fastify.d.ts +10 -3
- package/dist/provider/fastify.js +848 -63
- package/dist/provider/hono.cjs +936 -60
- package/dist/provider/hono.d.cts +23 -8
- package/dist/provider/hono.d.ts +23 -8
- package/dist/provider/hono.js +937 -61
- package/dist/provider.cjs +894 -70
- package/dist/provider.d.cts +9 -8
- package/dist/provider.d.ts +9 -8
- package/dist/provider.js +895 -72
- package/dist/react.cjs +736 -436
- package/dist/react.d.cts +7 -5
- package/dist/react.d.ts +7 -5
- package/dist/react.js +725 -425
- package/dist/{routedockMiddleware-57gUaEMf.d.ts → routedockMiddleware-Cq9HOJEa.d.ts} +9 -2
- package/dist/{routedockMiddleware-DlyLrxp7.d.cts → routedockMiddleware-CxsWkSKa.d.cts} +9 -2
- package/dist/schema.cjs +7 -0
- package/dist/schema.js +7 -0
- package/dist/{sign-AKH67sft.d.cts → sign-Bq08SeFq.d.ts} +2 -2
- package/dist/{sign-BEq-OsSq.d.ts → sign-DqGE0Plv.d.cts} +2 -2
- package/dist/testing.d.cts +4 -3
- package/dist/testing.d.ts +4 -3
- package/dist/{logger-D8_Q2ytp.d.cts → types-DXaS5Wip.d.cts} +85 -47
- package/dist/{logger-D8_Q2ytp.d.ts → types-DXaS5Wip.d.ts} +85 -47
- package/package.json +50 -4
package/dist/client.cjs
CHANGED
|
@@ -2,24 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
var nulthSdk = require('@routedock/nulth-sdk');
|
|
4
4
|
var stellarSdk = require('@stellar/stellar-sdk');
|
|
5
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
5
6
|
var jsonSchema = require('@cfworker/json-schema');
|
|
6
|
-
var crypto = require('crypto');
|
|
7
7
|
var stellar = require('@x402/stellar');
|
|
8
|
-
var client = require('@x402/
|
|
9
|
-
var client$1 = require('@x402/core/client');
|
|
8
|
+
var client = require('@x402/core/client');
|
|
10
9
|
var http = require('@x402/core/http');
|
|
11
|
-
var client$
|
|
12
|
-
var client$
|
|
13
|
-
var mpp = require('@stellar/mpp');
|
|
14
|
-
var client$4 = require('@stellar/mpp/channel/client');
|
|
10
|
+
var client$2 = require('@stellar/mpp/charge/client');
|
|
11
|
+
var client$1 = require('mppx/client');
|
|
15
12
|
var mppx = require('mppx');
|
|
16
|
-
var
|
|
17
|
-
var
|
|
18
|
-
|
|
19
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
20
|
-
|
|
21
|
-
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
22
|
-
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
13
|
+
var mpp = require('@stellar/mpp');
|
|
14
|
+
var client$3 = require('@stellar/mpp/channel/client');
|
|
23
15
|
|
|
24
16
|
// src/errors.ts
|
|
25
17
|
var RouteDockError = class extends Error {
|
|
@@ -117,6 +109,20 @@ var RouteDockPolicyRejectError = class extends RouteDockError {
|
|
|
117
109
|
this.reason = reason;
|
|
118
110
|
}
|
|
119
111
|
};
|
|
112
|
+
var RouteDockPaymentRejectedError = class extends RouteDockError {
|
|
113
|
+
status;
|
|
114
|
+
/** The provider's `reason` field, if its response body was JSON and had one. */
|
|
115
|
+
reason;
|
|
116
|
+
/** The provider's `error` field (short summary), if present. */
|
|
117
|
+
providerError;
|
|
118
|
+
constructor(message, status, details = {}, options = {}) {
|
|
119
|
+
super(message, "PAYMENT_REJECTED", false, options);
|
|
120
|
+
this.name = "RouteDockPaymentRejectedError";
|
|
121
|
+
this.status = status;
|
|
122
|
+
if (details.reason !== void 0) this.reason = details.reason;
|
|
123
|
+
if (details.providerError !== void 0) this.providerError = details.providerError;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
120
126
|
var RouteDockChannelStateError = class extends RouteDockError {
|
|
121
127
|
constructor(message, options = {}) {
|
|
122
128
|
super(message, "CHANNEL_STATE", false, options);
|
|
@@ -161,6 +167,59 @@ function httpStatusToError(message, status, response) {
|
|
|
161
167
|
}
|
|
162
168
|
return new RouteDockNetworkError(message, opts);
|
|
163
169
|
}
|
|
170
|
+
var MAX_REJECTION_BODY_BYTES = 64 * 1024;
|
|
171
|
+
var MAX_REJECTION_FIELD_CHARS = 500;
|
|
172
|
+
async function readBoundedText(response, limit) {
|
|
173
|
+
const body = response.body;
|
|
174
|
+
if (!body) return null;
|
|
175
|
+
const reader = body.getReader();
|
|
176
|
+
const chunks = [];
|
|
177
|
+
let total = 0;
|
|
178
|
+
try {
|
|
179
|
+
for (; ; ) {
|
|
180
|
+
const { done, value } = await reader.read();
|
|
181
|
+
if (done) break;
|
|
182
|
+
total += value.byteLength;
|
|
183
|
+
if (total > limit) {
|
|
184
|
+
await reader.cancel().catch(() => void 0);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
chunks.push(value);
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const bytes = new Uint8Array(total);
|
|
193
|
+
let offset = 0;
|
|
194
|
+
for (const chunk of chunks) {
|
|
195
|
+
bytes.set(chunk, offset);
|
|
196
|
+
offset += chunk.byteLength;
|
|
197
|
+
}
|
|
198
|
+
return new TextDecoder().decode(bytes);
|
|
199
|
+
}
|
|
200
|
+
async function paymentRejectedError(label, status, response) {
|
|
201
|
+
let reason;
|
|
202
|
+
let providerError;
|
|
203
|
+
try {
|
|
204
|
+
const text = await readBoundedText(response, MAX_REJECTION_BODY_BYTES);
|
|
205
|
+
const body = text === null ? null : JSON.parse(text);
|
|
206
|
+
if (body !== null && typeof body === "object") {
|
|
207
|
+
const { reason: r, error: e } = body;
|
|
208
|
+
if (typeof r === "string" && r.length > 0) reason = r.slice(0, MAX_REJECTION_FIELD_CHARS);
|
|
209
|
+
if (typeof e === "string" && e.length > 0) providerError = e.slice(0, MAX_REJECTION_FIELD_CHARS);
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
const detail = reason ?? providerError;
|
|
214
|
+
return new RouteDockPaymentRejectedError(
|
|
215
|
+
`${label}: HTTP ${status}${detail ? ` \u2014 ${detail}` : ""}`,
|
|
216
|
+
status,
|
|
217
|
+
{
|
|
218
|
+
...reason !== void 0 ? { reason } : {},
|
|
219
|
+
...providerError !== void 0 ? { providerError } : {}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
164
223
|
function wrapFetchError(err, context) {
|
|
165
224
|
if (err instanceof RouteDockError) return err;
|
|
166
225
|
return new RouteDockNetworkError(`${context}: ${String(err)}`, { cause: err });
|
|
@@ -225,7 +284,10 @@ async function withRetry(fn, policy = {}) {
|
|
|
225
284
|
if (!err.retryable || attempt >= maxAttempts - 1) {
|
|
226
285
|
throw err;
|
|
227
286
|
}
|
|
228
|
-
const delay =
|
|
287
|
+
const delay = Math.min(
|
|
288
|
+
err.retryAfterMs ?? backoffDelayMs(attempt, baseDelayMs, maxDelayMs),
|
|
289
|
+
maxDelayMs
|
|
290
|
+
);
|
|
229
291
|
if (onRetry) {
|
|
230
292
|
try {
|
|
231
293
|
await onRetry(attempt, err, delay);
|
|
@@ -238,10 +300,18 @@ async function withRetry(fn, policy = {}) {
|
|
|
238
300
|
}
|
|
239
301
|
throw lastError ?? new RouteDockError("Retry exhausted", "NETWORK", false);
|
|
240
302
|
}
|
|
303
|
+
|
|
304
|
+
// src/network.ts
|
|
241
305
|
var USDC_ISSUERS = {
|
|
242
306
|
testnet: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
243
307
|
mainnet: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
|
|
244
308
|
};
|
|
309
|
+
var HORIZON_URLS = {
|
|
310
|
+
testnet: "https://horizon-testnet.stellar.org",
|
|
311
|
+
mainnet: "https://horizon.stellar.org"
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/internal/usdc.ts
|
|
245
315
|
var usdcToUnits = nulthSdk.usdcToStroops;
|
|
246
316
|
|
|
247
317
|
// src/internal/assetUtils.ts
|
|
@@ -322,6 +392,109 @@ function isAssetEligible(manifest, assetIdentifier, mode, endpoint) {
|
|
|
322
392
|
);
|
|
323
393
|
}
|
|
324
394
|
|
|
395
|
+
// package.json
|
|
396
|
+
var package_default = {
|
|
397
|
+
version: "0.4.0"};
|
|
398
|
+
|
|
399
|
+
// src/internal/encoding.ts
|
|
400
|
+
var encoder = new TextEncoder();
|
|
401
|
+
var decoder = new TextDecoder();
|
|
402
|
+
function utf8ToBytes(text) {
|
|
403
|
+
return encoder.encode(text);
|
|
404
|
+
}
|
|
405
|
+
function bytesToUtf8(bytes) {
|
|
406
|
+
return decoder.decode(bytes);
|
|
407
|
+
}
|
|
408
|
+
function base64ToBytes(b64) {
|
|
409
|
+
const end = b64.indexOf("=");
|
|
410
|
+
let clean = (end === -1 ? b64 : b64.slice(0, end)).replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
|
|
411
|
+
if (clean.length % 4 === 1) clean = clean.slice(0, -1);
|
|
412
|
+
const binary = atob(clean);
|
|
413
|
+
const bytes = new Uint8Array(binary.length);
|
|
414
|
+
for (let i = 0; i < binary.length; i++) {
|
|
415
|
+
bytes[i] = binary.charCodeAt(i);
|
|
416
|
+
}
|
|
417
|
+
return bytes;
|
|
418
|
+
}
|
|
419
|
+
function base64ToUtf8(b64) {
|
|
420
|
+
return bytesToUtf8(base64ToBytes(b64));
|
|
421
|
+
}
|
|
422
|
+
function bytesToHex(bytes) {
|
|
423
|
+
let out = "";
|
|
424
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
425
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function sha256(data) {
|
|
430
|
+
return sha2_js.sha256(typeof data === "string" ? utf8ToBytes(data) : data);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/manifest/sign.ts
|
|
434
|
+
var MANIFEST_SIGNATURE_VERSION = "2";
|
|
435
|
+
function canonicalize(value) {
|
|
436
|
+
if (Array.isArray(value)) {
|
|
437
|
+
return value.map(canonicalize);
|
|
438
|
+
}
|
|
439
|
+
if (value !== null && typeof value === "object") {
|
|
440
|
+
return Object.fromEntries(
|
|
441
|
+
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])])
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
function manifestDigest(manifest) {
|
|
447
|
+
const { signature: _omit, ...rest } = manifest;
|
|
448
|
+
const canonical = JSON.stringify(canonicalize(rest));
|
|
449
|
+
return sha256(utf8ToBytes(canonical));
|
|
450
|
+
}
|
|
451
|
+
function verifyManifestSignature(manifest, expectedPayee) {
|
|
452
|
+
if (!manifest.signature) {
|
|
453
|
+
throw new RouteDockSignatureError(
|
|
454
|
+
"Manifest is missing a signature field \u2014 cannot verify payee authenticity"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (manifest.signature_version !== MANIFEST_SIGNATURE_VERSION) {
|
|
458
|
+
throw new RouteDockSignatureError(
|
|
459
|
+
`Manifest signature version must be ${MANIFEST_SIGNATURE_VERSION}; legacy signatures are not safe because nested fields were excluded from their digest`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (expectedPayee !== void 0 && manifest.payee !== expectedPayee) {
|
|
463
|
+
throw new RouteDockSignatureError(
|
|
464
|
+
`Manifest payee ${manifest.payee} does not match expected payee ${expectedPayee}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
let keypair;
|
|
468
|
+
try {
|
|
469
|
+
keypair = stellarSdk.Keypair.fromPublicKey(manifest.payee);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
throw new RouteDockSignatureError(
|
|
472
|
+
`Manifest payee ${manifest.payee} is not a valid Stellar public key`,
|
|
473
|
+
{ cause: err }
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const digest = manifestDigest(manifest);
|
|
477
|
+
let sigBytes;
|
|
478
|
+
try {
|
|
479
|
+
sigBytes = base64ToBytes(manifest.signature);
|
|
480
|
+
} catch {
|
|
481
|
+
throw new RouteDockSignatureError("Manifest signature is not valid base64");
|
|
482
|
+
}
|
|
483
|
+
let valid = false;
|
|
484
|
+
try {
|
|
485
|
+
valid = keypair.verify(digest, sigBytes);
|
|
486
|
+
} catch {
|
|
487
|
+
throw new RouteDockSignatureError(
|
|
488
|
+
`Manifest signature verification failed for payee ${manifest.payee}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
if (!valid) {
|
|
492
|
+
throw new RouteDockSignatureError(
|
|
493
|
+
`Manifest signature verification failed for payee ${manifest.payee}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
325
498
|
// src/schemas/routedock.schema.json
|
|
326
499
|
var routedock_schema_default = {
|
|
327
500
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -399,6 +572,7 @@ var routedock_schema_default = {
|
|
|
399
572
|
},
|
|
400
573
|
asset_contract: {
|
|
401
574
|
type: "string",
|
|
575
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
402
576
|
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
403
577
|
},
|
|
404
578
|
assets: {
|
|
@@ -411,6 +585,7 @@ var routedock_schema_default = {
|
|
|
411
585
|
},
|
|
412
586
|
payee: {
|
|
413
587
|
type: "string",
|
|
588
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
414
589
|
description: "Stellar address (G...) that receives payments"
|
|
415
590
|
},
|
|
416
591
|
pricing: {
|
|
@@ -581,6 +756,7 @@ var routedock_schema_default = {
|
|
|
581
756
|
properties: {
|
|
582
757
|
amount: {
|
|
583
758
|
type: "string",
|
|
759
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
584
760
|
description: "Cost per request in the payment asset (decimal string, e.g. '0.001')"
|
|
585
761
|
},
|
|
586
762
|
per: {
|
|
@@ -594,6 +770,7 @@ var routedock_schema_default = {
|
|
|
594
770
|
},
|
|
595
771
|
payee: {
|
|
596
772
|
type: "string",
|
|
773
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
597
774
|
description: "Optional per-mode payee override (Stellar G... address). Directs settlements for this mode to a separate account for treasury separation. Falls back to the top-level 'payee' when omitted."
|
|
598
775
|
}
|
|
599
776
|
}
|
|
@@ -606,6 +783,7 @@ var routedock_schema_default = {
|
|
|
606
783
|
properties: {
|
|
607
784
|
rate: {
|
|
608
785
|
type: "string",
|
|
786
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
609
787
|
description: "Cost per voucher in the payment asset (decimal string, e.g. '0.0001')"
|
|
610
788
|
},
|
|
611
789
|
per: {
|
|
@@ -615,10 +793,12 @@ var routedock_schema_default = {
|
|
|
615
793
|
},
|
|
616
794
|
channel_factory: {
|
|
617
795
|
type: "string",
|
|
796
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
618
797
|
description: "Stable Soroban factory address (C...) used to deploy a per-session channel"
|
|
619
798
|
},
|
|
620
799
|
min_deposit: {
|
|
621
800
|
type: "string",
|
|
801
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
622
802
|
description: "Minimum initial deposit to open a channel (decimal string)"
|
|
623
803
|
},
|
|
624
804
|
refund_waiting_period_ledgers: {
|
|
@@ -716,68 +896,110 @@ var routedock_schema_default = {
|
|
|
716
896
|
}
|
|
717
897
|
};
|
|
718
898
|
|
|
719
|
-
//
|
|
720
|
-
var
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
if (value !== null && typeof value === "object") {
|
|
728
|
-
return Object.fromEntries(
|
|
729
|
-
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])])
|
|
730
|
-
);
|
|
899
|
+
// src/manifest/validate.ts
|
|
900
|
+
var validator = new jsonSchema.Validator(routedock_schema_default, "7");
|
|
901
|
+
function validateManifestSchema(manifest, source) {
|
|
902
|
+
const result = validator.validate(manifest);
|
|
903
|
+
if (!result.valid) {
|
|
904
|
+
const msgs = result.errors.map((err) => err.error).join("; ");
|
|
905
|
+
throw new RouteDockManifestError(`Invalid manifest${source ? ` at ${source}` : ""}: ${msgs}`);
|
|
731
906
|
}
|
|
732
|
-
return value;
|
|
733
907
|
}
|
|
734
|
-
function
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
908
|
+
function assertManifestValid(manifest, baseUrl) {
|
|
909
|
+
const context = baseUrl ? ` at ${baseUrl}` : "";
|
|
910
|
+
if (manifest.latency_hints !== void 0) {
|
|
911
|
+
if (!Array.isArray(manifest.regions) || manifest.regions.length === 0) {
|
|
912
|
+
throw new RouteDockManifestError(
|
|
913
|
+
`Invalid manifest${context}: latency_hints defined without declaring regions`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
const regionSet = new Set(manifest.regions);
|
|
917
|
+
for (const region of Object.keys(manifest.latency_hints)) {
|
|
918
|
+
if (!regionSet.has(region)) {
|
|
919
|
+
throw new RouteDockManifestError(
|
|
920
|
+
`Invalid manifest${context}: latency_hints key '${region}' is not declared in regions (${manifest.regions.join(", ")})`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
749
924
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
925
|
+
if (manifest.assets !== void 0) {
|
|
926
|
+
if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
|
|
927
|
+
throw new RouteDockManifestError(
|
|
928
|
+
`Invalid manifest${context}: assets must be a non-empty array when defined`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
for (let i = 0; i < manifest.assets.length; i++) {
|
|
932
|
+
const a = manifest.assets[i];
|
|
933
|
+
if (!a || typeof a.asset !== "string" || typeof a.asset_contract !== "string") {
|
|
934
|
+
throw new RouteDockManifestError(
|
|
935
|
+
`Invalid manifest${context}: assets[${i}] must define asset and asset_contract strings`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const first = manifest.assets[0];
|
|
940
|
+
if (first.asset !== manifest.asset || first.asset_contract !== manifest.asset_contract) {
|
|
941
|
+
throw new RouteDockManifestError(
|
|
942
|
+
`Invalid manifest${context}: assets[0] (${first.asset}:${first.asset_contract}) must match root asset fields (${manifest.asset}:${manifest.asset_contract})`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
754
945
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
946
|
+
assertPricingMatchesModes(manifest, context);
|
|
947
|
+
assertDeprecatedModesListed(manifest, context);
|
|
948
|
+
assertPricesParse(manifest, context);
|
|
949
|
+
}
|
|
950
|
+
function assertPricingMatchesModes(manifest, context) {
|
|
951
|
+
const pricing = manifest.pricing ?? {};
|
|
952
|
+
for (const mode of manifest.modes ?? []) {
|
|
953
|
+
if (pricing[mode] === void 0 || pricing[mode] === null) {
|
|
954
|
+
throw new RouteDockManifestError(
|
|
955
|
+
`Invalid manifest${context}: mode '${mode}' is listed in modes but pricing.${mode} is missing`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
762
958
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
959
|
+
}
|
|
960
|
+
function assertDeprecatedModesListed(manifest, context) {
|
|
961
|
+
if (manifest.deprecated_modes === void 0) return;
|
|
962
|
+
const modes = new Set(manifest.modes ?? []);
|
|
963
|
+
for (const mode of manifest.deprecated_modes) {
|
|
964
|
+
if (!modes.has(mode)) {
|
|
965
|
+
throw new RouteDockManifestError(
|
|
966
|
+
`Invalid manifest${context}: deprecated_modes entry '${mode}' is not listed in modes`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
770
969
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
970
|
+
}
|
|
971
|
+
var PRICE_FIELDS = {
|
|
972
|
+
x402: ["amount"],
|
|
973
|
+
"mpp-charge": ["amount"],
|
|
974
|
+
"mpp-session": ["rate", "min_deposit"],
|
|
975
|
+
"mpp-session-ws": ["rate", "min_deposit"]
|
|
976
|
+
};
|
|
977
|
+
function assertPricesParse(manifest, context) {
|
|
978
|
+
const pricing = manifest.pricing ?? {};
|
|
979
|
+
for (const [mode, fields] of Object.entries(PRICE_FIELDS)) {
|
|
980
|
+
const entry = pricing[mode];
|
|
981
|
+
if (entry === void 0 || entry === null) continue;
|
|
982
|
+
for (const field of fields) {
|
|
983
|
+
const value = entry[field];
|
|
984
|
+
try {
|
|
985
|
+
if (typeof value !== "string") {
|
|
986
|
+
throw new RangeError(`expected a decimal string, got ${typeof value}`);
|
|
987
|
+
}
|
|
988
|
+
nulthSdk.usdcToStroops(value);
|
|
989
|
+
} catch (err) {
|
|
990
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
991
|
+
throw new RouteDockManifestError(
|
|
992
|
+
`Invalid manifest${context}: pricing.${mode}.${field} ${JSON.stringify(value)} is not a valid amount (${detail})`,
|
|
993
|
+
{ cause: err }
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
775
997
|
}
|
|
776
998
|
}
|
|
777
999
|
|
|
778
1000
|
// src/client/ModeRouter.ts
|
|
779
|
-
var validator = new jsonSchema.Validator(routedock_schema_default, "7");
|
|
780
1001
|
var SDK_VERSION = package_default.version;
|
|
1002
|
+
var SUPPORTED_MANIFEST_MAJOR = 1;
|
|
781
1003
|
function parseMajorMinor(version) {
|
|
782
1004
|
const parts = version.split(".");
|
|
783
1005
|
return [Number.parseInt(parts[0] ?? "0", 10), Number.parseInt(parts[1] ?? "0", 10)];
|
|
@@ -795,6 +1017,14 @@ function assertClientVersionSupported(manifest, baseUrl) {
|
|
|
795
1017
|
);
|
|
796
1018
|
}
|
|
797
1019
|
}
|
|
1020
|
+
function assertManifestVersionSupported(manifest, baseUrl) {
|
|
1021
|
+
const [major] = parseMajorMinor(manifest.routedock);
|
|
1022
|
+
if (major > SUPPORTED_MANIFEST_MAJOR) {
|
|
1023
|
+
throw new RouteDockClientVersionError(
|
|
1024
|
+
`Manifest version ${manifest.routedock} from provider at ${baseUrl} has schema major version ${major}, which is newer than the highest version this SDK supports (${SUPPORTED_MANIFEST_MAJOR}.x). Please upgrade the SDK.`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
798
1028
|
function assertManifestActive(manifest, baseUrl) {
|
|
799
1029
|
const sunsetAt = manifest.sunset_at;
|
|
800
1030
|
if (!sunsetAt) return;
|
|
@@ -810,11 +1040,16 @@ function assertManifestActive(manifest, baseUrl) {
|
|
|
810
1040
|
);
|
|
811
1041
|
}
|
|
812
1042
|
}
|
|
813
|
-
function assertEndpointActive(manifest, url, logger) {
|
|
1043
|
+
function assertEndpointActive(manifest, url, logger, method) {
|
|
814
1044
|
const pathname = new URL(url).pathname;
|
|
815
|
-
const
|
|
1045
|
+
const pathMatches = Object.entries(manifest.endpoints).filter(
|
|
816
1046
|
([, descriptor]) => descriptor.path === pathname
|
|
817
1047
|
);
|
|
1048
|
+
if (pathMatches.length === 0) return;
|
|
1049
|
+
const requestedMethod = method?.toUpperCase();
|
|
1050
|
+
const matches = requestedMethod ? pathMatches.filter(
|
|
1051
|
+
([, descriptor]) => descriptor.method.toUpperCase() === requestedMethod
|
|
1052
|
+
) : pathMatches;
|
|
818
1053
|
if (matches.length === 0) return;
|
|
819
1054
|
const now = Date.now();
|
|
820
1055
|
const allSunset = matches.every(([, descriptor]) => {
|
|
@@ -837,45 +1072,6 @@ function assertEndpointActive(manifest, url, logger) {
|
|
|
837
1072
|
);
|
|
838
1073
|
}
|
|
839
1074
|
}
|
|
840
|
-
function assertManifestValid(manifest, baseUrl) {
|
|
841
|
-
const context = baseUrl ? ` at ${baseUrl}` : "";
|
|
842
|
-
if (manifest.latency_hints !== void 0) {
|
|
843
|
-
if (!Array.isArray(manifest.regions) || manifest.regions.length === 0) {
|
|
844
|
-
throw new RouteDockManifestError(
|
|
845
|
-
`Invalid manifest${context}: latency_hints defined without declaring regions`
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
const regionSet = new Set(manifest.regions);
|
|
849
|
-
for (const region of Object.keys(manifest.latency_hints)) {
|
|
850
|
-
if (!regionSet.has(region)) {
|
|
851
|
-
throw new RouteDockManifestError(
|
|
852
|
-
`Invalid manifest${context}: latency_hints key '${region}' is not declared in regions (${manifest.regions.join(", ")})`
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
if (manifest.assets !== void 0) {
|
|
858
|
-
if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
|
|
859
|
-
throw new RouteDockManifestError(
|
|
860
|
-
`Invalid manifest${context}: assets must be a non-empty array when defined`
|
|
861
|
-
);
|
|
862
|
-
}
|
|
863
|
-
for (let i = 0; i < manifest.assets.length; i++) {
|
|
864
|
-
const a = manifest.assets[i];
|
|
865
|
-
if (!a || typeof a.asset !== "string" || typeof a.asset_contract !== "string") {
|
|
866
|
-
throw new RouteDockManifestError(
|
|
867
|
-
`Invalid manifest${context}: assets[${i}] must define asset and asset_contract strings`
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
const first = manifest.assets[0];
|
|
872
|
-
if (first.asset !== manifest.asset || first.asset_contract !== manifest.asset_contract) {
|
|
873
|
-
throw new RouteDockManifestError(
|
|
874
|
-
`Invalid manifest${context}: assets[0] (${first.asset}:${first.asset_contract}) must match root asset fields (${manifest.asset}:${manifest.asset_contract})`
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
1075
|
var CACHE_TTL_MS = 6e4;
|
|
880
1076
|
var DEFAULT_MANIFEST_CACHE_MAX_SIZE = 512;
|
|
881
1077
|
var DEFAULT_MANIFEST_TIMEOUT_MS = 5e3;
|
|
@@ -916,6 +1112,7 @@ var LruCache = class {
|
|
|
916
1112
|
}
|
|
917
1113
|
};
|
|
918
1114
|
var manifestCache = new LruCache(DEFAULT_MANIFEST_CACHE_MAX_SIZE);
|
|
1115
|
+
var inflightManifestFetches = /* @__PURE__ */ new Map();
|
|
919
1116
|
function configureManifestCache(maxSize) {
|
|
920
1117
|
if (!Number.isInteger(maxSize) || maxSize <= 0) {
|
|
921
1118
|
throw new RouteDockManifestError("Invalid manifest cache size: " + maxSize);
|
|
@@ -951,10 +1148,27 @@ async function fetchManifest(baseUrl, retryPolicy, manifestTimeoutMs = DEFAULT_M
|
|
|
951
1148
|
if (cached && Date.now() < cached.expiresAt) {
|
|
952
1149
|
verifyManifestSignature(cached.manifest, expectedPayee);
|
|
953
1150
|
assertClientVersionSupported(cached.manifest, baseUrl);
|
|
1151
|
+
assertManifestVersionSupported(cached.manifest, baseUrl);
|
|
954
1152
|
assertManifestValid(cached.manifest, baseUrl);
|
|
955
1153
|
assertManifestActive(cached.manifest, baseUrl);
|
|
956
1154
|
return cached.manifest;
|
|
957
1155
|
}
|
|
1156
|
+
let inflight = inflightManifestFetches.get(baseUrl);
|
|
1157
|
+
if (!inflight) {
|
|
1158
|
+
inflight = fetchAndCacheManifest(baseUrl, retryPolicy, manifestTimeoutMs).finally(() => {
|
|
1159
|
+
inflightManifestFetches.delete(baseUrl);
|
|
1160
|
+
});
|
|
1161
|
+
inflightManifestFetches.set(baseUrl, inflight);
|
|
1162
|
+
}
|
|
1163
|
+
const manifest = await inflight;
|
|
1164
|
+
verifyManifestSignature(manifest, expectedPayee);
|
|
1165
|
+
assertClientVersionSupported(manifest, baseUrl);
|
|
1166
|
+
assertManifestVersionSupported(manifest, baseUrl);
|
|
1167
|
+
assertManifestValid(manifest, baseUrl);
|
|
1168
|
+
assertManifestActive(manifest, baseUrl);
|
|
1169
|
+
return manifest;
|
|
1170
|
+
}
|
|
1171
|
+
async function fetchAndCacheManifest(baseUrl, retryPolicy, manifestTimeoutMs = DEFAULT_MANIFEST_TIMEOUT_MS) {
|
|
958
1172
|
const url = baseUrl.replace(/\/$/, "") + "/.well-known/routedock.json";
|
|
959
1173
|
return withRetry(async () => {
|
|
960
1174
|
let raw;
|
|
@@ -991,15 +1205,12 @@ async function fetchManifest(baseUrl, retryPolicy, manifestTimeoutMs = DEFAULT_M
|
|
|
991
1205
|
} catch (err) {
|
|
992
1206
|
throw new RouteDockManifestError(`Manifest at ${url} is not valid JSON`, { cause: err });
|
|
993
1207
|
}
|
|
994
|
-
|
|
995
|
-
if (!result.valid) {
|
|
996
|
-
const msgs = result.errors.map((err) => err.error).join("; ");
|
|
997
|
-
throw new RouteDockManifestError(`Invalid manifest at ${url}: ${msgs}`);
|
|
998
|
-
}
|
|
1208
|
+
validateManifestSchema(raw, url);
|
|
999
1209
|
const manifest = raw;
|
|
1000
1210
|
assertManifestValid(manifest, baseUrl);
|
|
1001
|
-
verifyManifestSignature(manifest
|
|
1211
|
+
verifyManifestSignature(manifest);
|
|
1002
1212
|
assertClientVersionSupported(manifest, baseUrl);
|
|
1213
|
+
assertManifestVersionSupported(manifest, baseUrl);
|
|
1003
1214
|
assertManifestActive(manifest, baseUrl);
|
|
1004
1215
|
manifestCache.set(baseUrl, { manifest, fetchedAt: Date.now(), expiresAt });
|
|
1005
1216
|
return manifest;
|
|
@@ -1114,6 +1325,48 @@ function rankProvidersByLatency(manifests, targetRegion) {
|
|
|
1114
1325
|
});
|
|
1115
1326
|
}
|
|
1116
1327
|
|
|
1328
|
+
// src/internal/settleResult.ts
|
|
1329
|
+
function readSettleResult(result) {
|
|
1330
|
+
const settle = result;
|
|
1331
|
+
const failed = (reason) => reason ? { ok: false, reason } : { ok: false };
|
|
1332
|
+
if (!settle || settle.success !== true) return failed(settle?.errorReason);
|
|
1333
|
+
if (typeof settle.transaction !== "string" || settle.transaction.length === 0) {
|
|
1334
|
+
return failed(settle.errorReason);
|
|
1335
|
+
}
|
|
1336
|
+
return { ok: true, txHash: settle.transaction };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// src/internal/request.ts
|
|
1340
|
+
var DEFAULT_METHOD = "GET";
|
|
1341
|
+
var BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
1342
|
+
function isReplayable(body) {
|
|
1343
|
+
return typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || typeof Blob !== "undefined" && body instanceof Blob || typeof FormData !== "undefined" && body instanceof FormData || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams;
|
|
1344
|
+
}
|
|
1345
|
+
async function bufferBody(body) {
|
|
1346
|
+
if (isReplayable(body)) return body;
|
|
1347
|
+
return new Response(body).arrayBuffer();
|
|
1348
|
+
}
|
|
1349
|
+
async function prepareRequest(request) {
|
|
1350
|
+
const method = (request?.method ?? DEFAULT_METHOD).toUpperCase();
|
|
1351
|
+
const headers = new Headers(request?.headers);
|
|
1352
|
+
const body = request?.body;
|
|
1353
|
+
if (body === void 0 || BODYLESS_METHODS.has(method)) {
|
|
1354
|
+
return { method, headers };
|
|
1355
|
+
}
|
|
1356
|
+
return { method, headers, body: await bufferBody(body) };
|
|
1357
|
+
}
|
|
1358
|
+
function toRequestInit(prepared, overrides) {
|
|
1359
|
+
const headers = new Headers(prepared.headers);
|
|
1360
|
+
if (overrides) {
|
|
1361
|
+
new Headers(overrides).forEach((value, name) => headers.set(name, value));
|
|
1362
|
+
}
|
|
1363
|
+
const init = { method: prepared.method, headers };
|
|
1364
|
+
if (prepared.body !== void 0) {
|
|
1365
|
+
init.body = prepared.body;
|
|
1366
|
+
}
|
|
1367
|
+
return init;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1117
1370
|
// src/internal/payee.ts
|
|
1118
1371
|
function resolvePayee(manifest, mode) {
|
|
1119
1372
|
const override = manifest.pricing[mode]?.payee;
|
|
@@ -1174,6 +1427,102 @@ function checkChargeChallenge(request, manifest, network, maxAmount) {
|
|
|
1174
1427
|
}
|
|
1175
1428
|
return checkAmount(request["amount"], maxAmount);
|
|
1176
1429
|
}
|
|
1430
|
+
var defaultIo = {
|
|
1431
|
+
async expirationLedger(network, maxTimeoutSeconds) {
|
|
1432
|
+
const { sequence } = await stellar.getRpcClient(network).getLatestLedger();
|
|
1433
|
+
const secondsPerLedger = await stellar.getEstimatedLedgerCloseTimeSeconds(network);
|
|
1434
|
+
return sequence + Math.ceil(maxTimeoutSeconds / secondsPerLedger);
|
|
1435
|
+
},
|
|
1436
|
+
async assemble({ asset, from, payTo, amount, networkPassphrase, rpcUrl }) {
|
|
1437
|
+
return stellarSdk.contract.AssembledTransaction.build({
|
|
1438
|
+
contractId: asset,
|
|
1439
|
+
method: "transfer",
|
|
1440
|
+
// SEP-41: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0041.md#interface
|
|
1441
|
+
args: [
|
|
1442
|
+
stellarSdk.nativeToScVal(from, { type: "address" }),
|
|
1443
|
+
stellarSdk.nativeToScVal(payTo, { type: "address" }),
|
|
1444
|
+
stellarSdk.nativeToScVal(amount, { type: "i128" })
|
|
1445
|
+
],
|
|
1446
|
+
networkPassphrase,
|
|
1447
|
+
rpcUrl,
|
|
1448
|
+
parseResultXdr: (result) => result
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
var NulthStellarScheme = class {
|
|
1453
|
+
constructor(signer, io = defaultIo) {
|
|
1454
|
+
this.signer = signer;
|
|
1455
|
+
this.io = io;
|
|
1456
|
+
}
|
|
1457
|
+
signer;
|
|
1458
|
+
io;
|
|
1459
|
+
scheme = "exact";
|
|
1460
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
1461
|
+
try {
|
|
1462
|
+
this.validateRequirements(paymentRequirements);
|
|
1463
|
+
} catch (err) {
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
`Invalid input parameters for creating Nulth Stellar payment, cause: ${String(err)}`
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
const from = this.signer.address;
|
|
1469
|
+
const { network, payTo, asset, amount, maxTimeoutSeconds } = paymentRequirements;
|
|
1470
|
+
if (!paymentRequirements.extra.areFeesSponsored) {
|
|
1471
|
+
throw new Error("Nulth exact scheme requires areFeesSponsored to be true");
|
|
1472
|
+
}
|
|
1473
|
+
const maxLedger = await this.io.expirationLedger(network, maxTimeoutSeconds);
|
|
1474
|
+
const tx = await this.io.assemble({
|
|
1475
|
+
asset,
|
|
1476
|
+
from,
|
|
1477
|
+
payTo,
|
|
1478
|
+
amount,
|
|
1479
|
+
networkPassphrase: stellar.getNetworkPassphrase(network),
|
|
1480
|
+
rpcUrl: stellar.getRpcUrl(network)
|
|
1481
|
+
});
|
|
1482
|
+
stellar.handleSimulationResult(tx.simulation);
|
|
1483
|
+
const signers = tx.needsNonInvokerSigningBy();
|
|
1484
|
+
if (!signers.includes(from) || signers.length > 1) {
|
|
1485
|
+
throw new Error(`Expected to sign with [${from}], but got [${signers.join(", ")}]`);
|
|
1486
|
+
}
|
|
1487
|
+
await tx.signAuthEntries({
|
|
1488
|
+
address: from,
|
|
1489
|
+
signAuthEntry: this.signer.signAuthEntry,
|
|
1490
|
+
expiration: maxLedger,
|
|
1491
|
+
authorizeEntry: nulthSdk.nulthAuthorizeEntry
|
|
1492
|
+
});
|
|
1493
|
+
await tx.simulate();
|
|
1494
|
+
stellar.handleSimulationResult(tx.simulation);
|
|
1495
|
+
const remaining = tx.needsNonInvokerSigningBy();
|
|
1496
|
+
if (remaining.length > 0) {
|
|
1497
|
+
throw new Error(`unexpected signer(s) required: [${remaining.join(", ")}]`);
|
|
1498
|
+
}
|
|
1499
|
+
if (!tx.built) {
|
|
1500
|
+
throw new Error("Nulth payment transaction has no assembled envelope to send");
|
|
1501
|
+
}
|
|
1502
|
+
return { x402Version, payload: { transaction: tx.built.toXDR() } };
|
|
1503
|
+
}
|
|
1504
|
+
validateRequirements(requirements) {
|
|
1505
|
+
const { scheme, network, payTo, asset, amount } = requirements;
|
|
1506
|
+
if (typeof amount !== "string" || !Number.isInteger(Number(amount)) || Number(amount) <= 0) {
|
|
1507
|
+
throw new Error(`Invalid amount: ${amount}. Amount must be a positive integer.`);
|
|
1508
|
+
}
|
|
1509
|
+
if (scheme !== this.scheme) {
|
|
1510
|
+
throw new Error(`Unsupported scheme: ${scheme}`);
|
|
1511
|
+
}
|
|
1512
|
+
if (!stellar.isStellarNetwork(network)) {
|
|
1513
|
+
throw new Error(`Unsupported Stellar network: ${network}`);
|
|
1514
|
+
}
|
|
1515
|
+
if (!stellar.validateStellarDestinationAddress(payTo)) {
|
|
1516
|
+
throw new Error(`Invalid Stellar destination address: ${payTo}`);
|
|
1517
|
+
}
|
|
1518
|
+
if (!stellar.validateStellarAssetAddress(asset)) {
|
|
1519
|
+
throw new Error(`Invalid Stellar asset address: ${asset}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
function selectStellarScheme(signer) {
|
|
1524
|
+
return nulthSdk.isNulthStellarSigner(signer) ? new NulthStellarScheme(signer) : new stellar.ExactStellarScheme(signer);
|
|
1525
|
+
}
|
|
1177
1526
|
|
|
1178
1527
|
// src/client/x402Client.ts
|
|
1179
1528
|
var CAIP2 = {
|
|
@@ -1181,25 +1530,29 @@ var CAIP2 = {
|
|
|
1181
1530
|
mainnet: "stellar:pubnet"
|
|
1182
1531
|
};
|
|
1183
1532
|
var X402Client = class _X402Client {
|
|
1184
|
-
constructor(secretKeyOrSigner, network, retryPolicy) {
|
|
1533
|
+
constructor(secretKeyOrSigner, network, retryPolicy, preSignHook, logger) {
|
|
1185
1534
|
this.network = network;
|
|
1186
1535
|
this.retryPolicy = retryPolicy;
|
|
1536
|
+
this.preSignHook = preSignHook;
|
|
1187
1537
|
const caip2 = CAIP2[network];
|
|
1188
1538
|
this.signer = typeof secretKeyOrSigner === "string" ? stellar.createEd25519Signer(secretKeyOrSigner, caip2) : secretKeyOrSigner;
|
|
1189
|
-
const scheme =
|
|
1190
|
-
const core = new client
|
|
1539
|
+
const scheme = selectStellarScheme(this.signer);
|
|
1540
|
+
const core = new client.x402Client();
|
|
1191
1541
|
core.register(caip2, scheme);
|
|
1192
|
-
this.httpClient = new client
|
|
1542
|
+
this.httpClient = new client.x402HTTPClient(core);
|
|
1543
|
+
this.logger = logger;
|
|
1193
1544
|
}
|
|
1194
1545
|
network;
|
|
1195
1546
|
retryPolicy;
|
|
1547
|
+
preSignHook;
|
|
1196
1548
|
httpClient;
|
|
1197
1549
|
signer;
|
|
1550
|
+
logger;
|
|
1198
1551
|
/** Replace signer (e.g. swap to Nulth ZK account payer before pay) */
|
|
1199
1552
|
withSigner(signer) {
|
|
1200
|
-
return new _X402Client(signer, this.network, this.retryPolicy);
|
|
1553
|
+
return new _X402Client(signer, this.network, this.retryPolicy, this.preSignHook, this.logger);
|
|
1201
1554
|
}
|
|
1202
|
-
async pay(url, manifest) {
|
|
1555
|
+
async pay(url, manifest, request) {
|
|
1203
1556
|
const pricing = manifest.pricing.x402;
|
|
1204
1557
|
if (!pricing) {
|
|
1205
1558
|
throw new RouteDockManifestError("manifest.pricing.x402 missing");
|
|
@@ -1208,13 +1561,15 @@ var X402Client = class _X402Client {
|
|
|
1208
1561
|
throw new RouteDockManifestError("manifest.pricing.x402.facilitator missing");
|
|
1209
1562
|
}
|
|
1210
1563
|
const caip2 = CAIP2[this.network];
|
|
1564
|
+
const prepared = await prepareRequest(request);
|
|
1211
1565
|
const probe = await withRetry(async () => {
|
|
1212
1566
|
let init2;
|
|
1213
1567
|
try {
|
|
1214
|
-
init2 = await fetch(url,
|
|
1568
|
+
init2 = await fetch(url, toRequestInit(prepared, { "X-Preferred-Mode": "x402" }));
|
|
1215
1569
|
} catch (err) {
|
|
1216
1570
|
throw wrapFetchError(err, "x402 initial request");
|
|
1217
1571
|
}
|
|
1572
|
+
this.logger?.("debug", "[x402] unpaid probe response", { status: init2.status });
|
|
1218
1573
|
if (init2.status !== 402) {
|
|
1219
1574
|
if (!init2.ok) {
|
|
1220
1575
|
throw httpStatusToError(
|
|
@@ -1231,6 +1586,11 @@ var X402Client = class _X402Client {
|
|
|
1231
1586
|
`Failed to parse JSON from non-402 response (HTTP ${init2.status})`
|
|
1232
1587
|
);
|
|
1233
1588
|
}
|
|
1589
|
+
this.logger?.(
|
|
1590
|
+
"debug",
|
|
1591
|
+
"[x402] unpaid probe returned non-402 success \u2014 serving as free response",
|
|
1592
|
+
{ status: init2.status }
|
|
1593
|
+
);
|
|
1234
1594
|
return { kind: "free", data: data2 };
|
|
1235
1595
|
}
|
|
1236
1596
|
return { kind: "payment-required", init: init2 };
|
|
@@ -1259,6 +1619,18 @@ var X402Client = class _X402Client {
|
|
|
1259
1619
|
if (allowed.length === 0) {
|
|
1260
1620
|
throw checkX402Accept(firstAccept, manifest, caip2, pricing.amount);
|
|
1261
1621
|
}
|
|
1622
|
+
if (this.preSignHook) {
|
|
1623
|
+
let decision;
|
|
1624
|
+
try {
|
|
1625
|
+
decision = await this.preSignHook({ url, manifest, accepts: allowed });
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
throw new RouteDockPolicyRejectError("pre_sign_hook_error", { cause: err });
|
|
1628
|
+
}
|
|
1629
|
+
if (!decision || decision.approve !== true) {
|
|
1630
|
+
const reason = decision && decision.approve === false ? decision.reason : void 0;
|
|
1631
|
+
throw new RouteDockPolicyRejectError(reason ?? "pre_sign_rejected");
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1262
1634
|
let paymentPayload;
|
|
1263
1635
|
try {
|
|
1264
1636
|
paymentPayload = await this.httpClient.createPaymentPayload({
|
|
@@ -1272,13 +1644,15 @@ var X402Client = class _X402Client {
|
|
|
1272
1644
|
}
|
|
1273
1645
|
const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
1274
1646
|
const signedAmount = nulthSdk.stroopsToUsdc(BigInt(paymentPayload.accepted.amount));
|
|
1647
|
+
this.logger?.("debug", "[x402] sending paid request with payment header");
|
|
1275
1648
|
const settled = await withRetry(async () => {
|
|
1276
1649
|
let response;
|
|
1277
1650
|
try {
|
|
1278
|
-
response = await fetch(url,
|
|
1651
|
+
response = await fetch(url, toRequestInit(prepared, paymentHeaders));
|
|
1279
1652
|
} catch (err) {
|
|
1280
1653
|
throw wrapFetchError(err, "x402 settlement request");
|
|
1281
1654
|
}
|
|
1655
|
+
this.logger?.("debug", "[x402] paid request response", { status: response.status });
|
|
1282
1656
|
if (!response.ok) {
|
|
1283
1657
|
if (response.status >= 500 || response.status === 429 || response.status === 503) {
|
|
1284
1658
|
throw httpStatusToError(
|
|
@@ -1287,17 +1661,28 @@ var X402Client = class _X402Client {
|
|
|
1287
1661
|
response
|
|
1288
1662
|
);
|
|
1289
1663
|
}
|
|
1290
|
-
throw
|
|
1664
|
+
throw await paymentRejectedError("x402 payment failed", response.status, response);
|
|
1291
1665
|
}
|
|
1292
1666
|
return response;
|
|
1293
1667
|
}, this.retryPolicy);
|
|
1294
1668
|
let txHash = null;
|
|
1295
1669
|
const responseHeader = settled.headers.get("X-Payment-Response");
|
|
1296
|
-
if (responseHeader) {
|
|
1670
|
+
if (!responseHeader) {
|
|
1671
|
+
this.logger?.("debug", "[x402] settled 2xx carried no X-Payment-Response header");
|
|
1672
|
+
} else {
|
|
1297
1673
|
try {
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1674
|
+
const outcome = readSettleResult(http.decodePaymentResponseHeader(responseHeader));
|
|
1675
|
+
if (outcome.ok) {
|
|
1676
|
+
txHash = outcome.txHash;
|
|
1677
|
+
} else {
|
|
1678
|
+
this.logger?.("debug", "[x402] X-Payment-Response reports an unsettled payment", {
|
|
1679
|
+
...outcome.reason !== void 0 ? { reason: outcome.reason } : {}
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
this.logger?.("debug", "[x402] X-Payment-Response header present but undecodable", {
|
|
1684
|
+
error: err
|
|
1685
|
+
});
|
|
1301
1686
|
}
|
|
1302
1687
|
}
|
|
1303
1688
|
let data;
|
|
@@ -1318,33 +1703,28 @@ var MppChargeClient = class {
|
|
|
1318
1703
|
keypair;
|
|
1319
1704
|
network;
|
|
1320
1705
|
retryPolicy;
|
|
1321
|
-
async pay(url, manifest) {
|
|
1706
|
+
async pay(url, manifest, request) {
|
|
1322
1707
|
const pricing = manifest.pricing["mpp-charge"];
|
|
1323
1708
|
if (!pricing) {
|
|
1324
1709
|
throw new RouteDockManifestError("manifest.pricing.mpp-charge missing");
|
|
1325
1710
|
}
|
|
1326
1711
|
const caip2 = this.network === "mainnet" ? "stellar:pubnet" : "stellar:testnet";
|
|
1327
|
-
|
|
1712
|
+
const prepared = await prepareRequest(request);
|
|
1328
1713
|
let signedAmount;
|
|
1329
1714
|
let credential;
|
|
1330
|
-
const mppx = client$
|
|
1715
|
+
const mppx = client$1.Mppx.create({
|
|
1331
1716
|
polyfill: false,
|
|
1332
1717
|
methods: [
|
|
1333
|
-
client$
|
|
1718
|
+
client$2.stellar.charge({
|
|
1334
1719
|
keypair: this.keypair,
|
|
1335
|
-
mode: "pull"
|
|
1336
|
-
onProgress(event) {
|
|
1337
|
-
if (event.type === "paid") {
|
|
1338
|
-
txHash = event.hash;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1720
|
+
mode: "pull"
|
|
1341
1721
|
})
|
|
1342
1722
|
],
|
|
1343
1723
|
onChallenge: async (challenge, { createCredential }) => {
|
|
1344
|
-
const
|
|
1345
|
-
const rejection = checkChargeChallenge(
|
|
1724
|
+
const request2 = challenge.request;
|
|
1725
|
+
const rejection = checkChargeChallenge(request2, manifest, caip2, pricing.amount);
|
|
1346
1726
|
if (rejection) throw rejection;
|
|
1347
|
-
const amount =
|
|
1727
|
+
const amount = request2["amount"];
|
|
1348
1728
|
if (typeof amount !== "string") {
|
|
1349
1729
|
throw new RouteDockPolicyRejectError("challenge_amount_invalid");
|
|
1350
1730
|
}
|
|
@@ -1356,7 +1736,7 @@ var MppChargeClient = class {
|
|
|
1356
1736
|
return withRetry(async () => {
|
|
1357
1737
|
let response;
|
|
1358
1738
|
try {
|
|
1359
|
-
response = credential ? await mppx.rawFetch(url,
|
|
1739
|
+
response = credential ? await mppx.rawFetch(url, toRequestInit(prepared, { Authorization: credential })) : await mppx.fetch(url, toRequestInit(prepared));
|
|
1360
1740
|
} catch (err) {
|
|
1361
1741
|
throw wrapMppError(err, "MPP charge request");
|
|
1362
1742
|
}
|
|
@@ -1368,8 +1748,9 @@ var MppChargeClient = class {
|
|
|
1368
1748
|
response
|
|
1369
1749
|
);
|
|
1370
1750
|
}
|
|
1371
|
-
throw
|
|
1751
|
+
throw await paymentRejectedError("MPP charge failed", response.status, response);
|
|
1372
1752
|
}
|
|
1753
|
+
const txHash = readSettlementHash(response);
|
|
1373
1754
|
let data;
|
|
1374
1755
|
try {
|
|
1375
1756
|
data = await response.json();
|
|
@@ -1389,8 +1770,40 @@ var MppChargeClient = class {
|
|
|
1389
1770
|
}, this.retryPolicy);
|
|
1390
1771
|
}
|
|
1391
1772
|
};
|
|
1773
|
+
function readSettlementHash(response) {
|
|
1774
|
+
try {
|
|
1775
|
+
return mppx.Receipt.fromResponse(response).reference;
|
|
1776
|
+
} catch {
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1392
1780
|
var MIN_REFUND_WAITING_PERIOD = 17280;
|
|
1393
1781
|
var STREAM_CLEANUP_TIMEOUT_MS = 3e3;
|
|
1782
|
+
var TX_CONFIRM_INTERVAL_MS = 1e3;
|
|
1783
|
+
var TX_CONFIRM_TIMEOUT_MS = 3e4;
|
|
1784
|
+
async function confirmTransaction(server, hash, label, timeoutMs = TX_CONFIRM_TIMEOUT_MS, intervalMs = TX_CONFIRM_INTERVAL_MS) {
|
|
1785
|
+
const deadline = Date.now() + timeoutMs;
|
|
1786
|
+
while (true) {
|
|
1787
|
+
const txResponse = await server.getTransaction(hash);
|
|
1788
|
+
const status = txResponse?.status;
|
|
1789
|
+
if (status === "SUCCESS") {
|
|
1790
|
+
return hash;
|
|
1791
|
+
}
|
|
1792
|
+
if (status === "FAILED") {
|
|
1793
|
+
throw new RouteDockDisputeError(
|
|
1794
|
+
`${label} transaction failed on-chain: status FAILED (resultXdr: ${txResponse.resultXdr.toXDR("base64")})`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
if (Date.now() >= deadline) {
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1800
|
+
const remaining = deadline - Date.now();
|
|
1801
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, Math.max(1, remaining))));
|
|
1802
|
+
}
|
|
1803
|
+
throw new RouteDockDisputeError(
|
|
1804
|
+
`${label} transaction confirmation timed out after ${timeoutMs}ms (hash: ${hash})`
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1394
1807
|
var WS_CONNECTING = 0;
|
|
1395
1808
|
var WS_OPEN = 1;
|
|
1396
1809
|
function defaultWebSocketFactory(url, init, handlers) {
|
|
@@ -1404,9 +1817,9 @@ function defaultWebSocketFactory(url, init, handlers) {
|
|
|
1404
1817
|
if (typeof data === "string") {
|
|
1405
1818
|
handlers.onMessage(data);
|
|
1406
1819
|
} else if (data instanceof ArrayBuffer) {
|
|
1407
|
-
handlers.onMessage(
|
|
1820
|
+
handlers.onMessage(bytesToUtf8(new Uint8Array(data)));
|
|
1408
1821
|
} else {
|
|
1409
|
-
void data.arrayBuffer().then((buffer) => handlers.onMessage(
|
|
1822
|
+
void data.arrayBuffer().then((buffer) => handlers.onMessage(bytesToUtf8(new Uint8Array(buffer)))).catch(() => {
|
|
1410
1823
|
});
|
|
1411
1824
|
}
|
|
1412
1825
|
};
|
|
@@ -1559,11 +1972,11 @@ var MppSessionClient = class {
|
|
|
1559
1972
|
throw err;
|
|
1560
1973
|
}
|
|
1561
1974
|
};
|
|
1562
|
-
const streamWs = (u, m, createCredential, onSigned) => this.streamWebSocket(u, m, createCredential, onSigned);
|
|
1563
|
-
const mppx = client$
|
|
1975
|
+
const streamWs = (u, m, createCredential, onSigned, request) => this.streamWebSocket(u, m, createCredential, onSigned, request);
|
|
1976
|
+
const mppx = client$1.Mppx.create({
|
|
1564
1977
|
polyfill: false,
|
|
1565
1978
|
methods: [
|
|
1566
|
-
client$
|
|
1979
|
+
client$3.stellar.channel({
|
|
1567
1980
|
commitmentKey,
|
|
1568
1981
|
sourceAccount: this.keypair.publicKey(),
|
|
1569
1982
|
...store ? { store } : {},
|
|
@@ -1631,6 +2044,7 @@ var MppSessionClient = class {
|
|
|
1631
2044
|
};
|
|
1632
2045
|
},
|
|
1633
2046
|
async *stream(options2) {
|
|
2047
|
+
const prepared = await prepareRequest(options2?.request);
|
|
1634
2048
|
const checkSpend = () => {
|
|
1635
2049
|
if (!onSpend) return Promise.resolve();
|
|
1636
2050
|
return onSpend(pricing.rate);
|
|
@@ -1640,7 +2054,13 @@ var MppSessionClient = class {
|
|
|
1640
2054
|
const onSigned = () => {
|
|
1641
2055
|
vouchersIssued++;
|
|
1642
2056
|
};
|
|
1643
|
-
for await (const item of streamWs(
|
|
2057
|
+
for await (const item of streamWs(
|
|
2058
|
+
url,
|
|
2059
|
+
mppx,
|
|
2060
|
+
createGuardedCredential,
|
|
2061
|
+
onSigned,
|
|
2062
|
+
prepared
|
|
2063
|
+
)) {
|
|
1644
2064
|
yield item;
|
|
1645
2065
|
}
|
|
1646
2066
|
return;
|
|
@@ -1655,7 +2075,10 @@ var MppSessionClient = class {
|
|
|
1655
2075
|
const doFetch = () => withRetry(async () => {
|
|
1656
2076
|
let resp;
|
|
1657
2077
|
try {
|
|
1658
|
-
resp = await mppx.fetch(url, {
|
|
2078
|
+
resp = await mppx.fetch(url, {
|
|
2079
|
+
...toRequestInit(prepared),
|
|
2080
|
+
signal: abortController.signal
|
|
2081
|
+
});
|
|
1659
2082
|
} catch (err) {
|
|
1660
2083
|
if (abortController.signal.aborted) {
|
|
1661
2084
|
throw err;
|
|
@@ -1740,10 +2163,10 @@ var MppSessionClient = class {
|
|
|
1740
2163
|
async close() {
|
|
1741
2164
|
clearSessionTimer();
|
|
1742
2165
|
closed = true;
|
|
1743
|
-
const { rpc: rpcMod, Contract, nativeToScVal, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
2166
|
+
const { rpc: rpcMod, Contract, nativeToScVal: nativeToScVal2, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
1744
2167
|
const rpcUrl = network === "testnet" ? "https://soroban-testnet.stellar.org" : "https://soroban.stellar.org";
|
|
1745
2168
|
const server = new rpcMod.Server(rpcUrl);
|
|
1746
|
-
const
|
|
2169
|
+
const contract2 = new Contract(channelFactory);
|
|
1747
2170
|
const passphrase = network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
|
|
1748
2171
|
const account = await withRetry(async () => {
|
|
1749
2172
|
try {
|
|
@@ -1758,9 +2181,9 @@ var MppSessionClient = class {
|
|
|
1758
2181
|
fee: BASE_FEE,
|
|
1759
2182
|
networkPassphrase: passphrase
|
|
1760
2183
|
}).addOperation(
|
|
1761
|
-
|
|
2184
|
+
contract2.call(
|
|
1762
2185
|
"prepare_commitment",
|
|
1763
|
-
|
|
2186
|
+
nativeToScVal2(currentCumulative, { type: "i128" })
|
|
1764
2187
|
)
|
|
1765
2188
|
).setTimeout(30).build();
|
|
1766
2189
|
return await server.simulateTransaction(simTx);
|
|
@@ -1779,7 +2202,7 @@ var MppSessionClient = class {
|
|
|
1779
2202
|
}
|
|
1780
2203
|
let signature;
|
|
1781
2204
|
try {
|
|
1782
|
-
signature = commitmentKey.sign(
|
|
2205
|
+
signature = commitmentKey.sign(commitmentBytes);
|
|
1783
2206
|
} catch (err) {
|
|
1784
2207
|
throw new RouteDockSignatureError("Channel close commitment signing failed", {
|
|
1785
2208
|
cause: err
|
|
@@ -1793,7 +2216,7 @@ var MppSessionClient = class {
|
|
|
1793
2216
|
headers: { "Content-Type": "application/json" },
|
|
1794
2217
|
body: JSON.stringify({
|
|
1795
2218
|
amount: currentCumulative.toString(),
|
|
1796
|
-
signature:
|
|
2219
|
+
signature: bytesToHex(signature)
|
|
1797
2220
|
})
|
|
1798
2221
|
});
|
|
1799
2222
|
} catch (err) {
|
|
@@ -1835,15 +2258,19 @@ var MppSessionClient = class {
|
|
|
1835
2258
|
vouchersIssued
|
|
1836
2259
|
};
|
|
1837
2260
|
},
|
|
1838
|
-
|
|
2261
|
+
/**
|
|
2262
|
+
* Request refund from the channel contract (initiates dispute).
|
|
2263
|
+
* Returns the transaction hash only after on-chain confirmation (SUCCESS).
|
|
2264
|
+
*/
|
|
2265
|
+
async requestRefund(options2) {
|
|
1839
2266
|
const { rpc: rpcMod, Contract, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
1840
2267
|
const rpcUrl = network === "testnet" ? "https://soroban-testnet.stellar.org" : "https://soroban.stellar.org";
|
|
1841
2268
|
const server = new rpcMod.Server(rpcUrl);
|
|
1842
|
-
const
|
|
2269
|
+
const contract2 = new Contract(channelFactory);
|
|
1843
2270
|
const passphrase = network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
|
|
1844
2271
|
try {
|
|
1845
2272
|
const account = await server.getAccount(agentPublicKey);
|
|
1846
|
-
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(
|
|
2273
|
+
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(contract2.call("request_refund")).setTimeout(30).build();
|
|
1847
2274
|
const simResult = await server.simulateTransaction(tx);
|
|
1848
2275
|
if (rpcMod.Api.isSimulationError(simResult)) {
|
|
1849
2276
|
throw new RouteDockDisputeError(`request_refund simulation failed: ${simResult.error}`);
|
|
@@ -1855,37 +2282,50 @@ var MppSessionClient = class {
|
|
|
1855
2282
|
const errDetail = result.errorResult ? JSON.stringify(result.errorResult) : "status ERROR";
|
|
1856
2283
|
throw new RouteDockDisputeError(`Refund request transaction failed: ${errDetail}`);
|
|
1857
2284
|
}
|
|
2285
|
+
if (result.status === "TRY_AGAIN_LATER") {
|
|
2286
|
+
throw new RouteDockDisputeError("Refund request transaction rejected with TRY_AGAIN_LATER: transaction not queued");
|
|
2287
|
+
}
|
|
1858
2288
|
if (!result.hash) {
|
|
1859
2289
|
throw new RouteDockDisputeError("Refund request transaction not sent");
|
|
1860
2290
|
}
|
|
1861
|
-
return
|
|
2291
|
+
return await confirmTransaction(
|
|
2292
|
+
server,
|
|
2293
|
+
result.hash,
|
|
2294
|
+
"Refund request",
|
|
2295
|
+
options2?.timeoutMs,
|
|
2296
|
+
options2?.intervalMs
|
|
2297
|
+
);
|
|
1862
2298
|
} catch (err) {
|
|
1863
2299
|
if (err instanceof RouteDockDisputeError) throw err;
|
|
1864
2300
|
throw new RouteDockDisputeError(`Failed to request refund: ${err instanceof Error ? err.message : String(err)}`);
|
|
1865
2301
|
}
|
|
1866
2302
|
},
|
|
1867
|
-
|
|
1868
|
-
|
|
2303
|
+
/**
|
|
2304
|
+
* Server-side counter-mechanism to settle with latest voucher before refund window expires.
|
|
2305
|
+
* Returns the transaction hash only after on-chain confirmation (SUCCESS).
|
|
2306
|
+
*/
|
|
2307
|
+
async settleWithLatestVoucher(options2) {
|
|
2308
|
+
const { rpc: rpcMod, Contract, nativeToScVal: nativeToScVal2, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
1869
2309
|
const rpcUrl = network === "testnet" ? "https://soroban-testnet.stellar.org" : "https://soroban.stellar.org";
|
|
1870
2310
|
const server = new rpcMod.Server(rpcUrl);
|
|
1871
|
-
const
|
|
2311
|
+
const contract2 = new Contract(channelFactory);
|
|
1872
2312
|
const passphrase = network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
|
|
1873
2313
|
try {
|
|
1874
2314
|
const account = await server.getAccount(agentPublicKey);
|
|
1875
|
-
const simTx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(
|
|
2315
|
+
const simTx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(contract2.call("prepare_commitment", nativeToScVal2(currentCumulative, { type: "i128" }))).setTimeout(30).build();
|
|
1876
2316
|
const simResult = await server.simulateTransaction(simTx);
|
|
1877
2317
|
if (rpcMod.Api.isSimulationError(simResult)) {
|
|
1878
2318
|
throw new RouteDockDisputeError(`prepare_commitment simulation failed: ${simResult.error}`);
|
|
1879
2319
|
}
|
|
1880
2320
|
const commitmentBytes = simResult.result?.retval?.bytes();
|
|
1881
2321
|
if (!commitmentBytes) throw new RouteDockDisputeError("prepare_commitment returned no bytes");
|
|
1882
|
-
const signature = commitmentKey.sign(
|
|
2322
|
+
const signature = commitmentKey.sign(commitmentBytes);
|
|
1883
2323
|
const settleAccount = await server.getAccount(agentPublicKey);
|
|
1884
2324
|
const settleTx = new TransactionBuilder(settleAccount, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(
|
|
1885
|
-
|
|
2325
|
+
contract2.call(
|
|
1886
2326
|
"settle_with_signature",
|
|
1887
|
-
|
|
1888
|
-
|
|
2327
|
+
nativeToScVal2(currentCumulative, { type: "i128" }),
|
|
2328
|
+
nativeToScVal2(signature)
|
|
1889
2329
|
)
|
|
1890
2330
|
).setTimeout(30).build();
|
|
1891
2331
|
const preparedSettleTx = await server.prepareTransaction(settleTx);
|
|
@@ -1895,10 +2335,19 @@ var MppSessionClient = class {
|
|
|
1895
2335
|
const errDetail = settleResult.errorResult ? JSON.stringify(settleResult.errorResult) : "status ERROR";
|
|
1896
2336
|
throw new RouteDockDisputeError(`Settlement transaction failed: ${errDetail}`);
|
|
1897
2337
|
}
|
|
2338
|
+
if (settleResult.status === "TRY_AGAIN_LATER") {
|
|
2339
|
+
throw new RouteDockDisputeError("Settlement transaction rejected with TRY_AGAIN_LATER: transaction not queued");
|
|
2340
|
+
}
|
|
1898
2341
|
if (!settleResult.hash) {
|
|
1899
2342
|
throw new RouteDockDisputeError("Settlement transaction not sent");
|
|
1900
2343
|
}
|
|
1901
|
-
return
|
|
2344
|
+
return await confirmTransaction(
|
|
2345
|
+
server,
|
|
2346
|
+
settleResult.hash,
|
|
2347
|
+
"Settlement",
|
|
2348
|
+
options2?.timeoutMs,
|
|
2349
|
+
options2?.intervalMs
|
|
2350
|
+
);
|
|
1902
2351
|
} catch (err) {
|
|
1903
2352
|
if (err instanceof RouteDockDisputeError) throw err;
|
|
1904
2353
|
throw new RouteDockDisputeError(`Failed to settle with latest voucher: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1908,11 +2357,11 @@ var MppSessionClient = class {
|
|
|
1908
2357
|
const { rpc: rpcMod, Contract, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
1909
2358
|
const rpcUrl = network === "testnet" ? "https://soroban-testnet.stellar.org" : "https://soroban.stellar.org";
|
|
1910
2359
|
const server = new rpcMod.Server(rpcUrl);
|
|
1911
|
-
const
|
|
2360
|
+
const contract2 = new Contract(channelFactory);
|
|
1912
2361
|
const passphrase = network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
|
|
1913
2362
|
try {
|
|
1914
2363
|
const account = await server.getAccount(agentPublicKey);
|
|
1915
|
-
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(
|
|
2364
|
+
const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(contract2.call("get_channel_state")).setTimeout(30).build();
|
|
1916
2365
|
const simResult = await server.simulateTransaction(tx);
|
|
1917
2366
|
if (rpcMod.Api.isSimulationError(simResult)) {
|
|
1918
2367
|
throw new RouteDockChannelStateError(`Failed to query channel state: ${simResult.error}`);
|
|
@@ -1978,11 +2427,11 @@ var MppSessionClient = class {
|
|
|
1978
2427
|
* close code (1000/1001); abnormal closes and connection failures surface as
|
|
1979
2428
|
* RouteDockChannelStateError.
|
|
1980
2429
|
*/
|
|
1981
|
-
async *streamWebSocket(url, mppx$1, createCredential, onSigned) {
|
|
1982
|
-
const
|
|
2430
|
+
async *streamWebSocket(url, mppx$1, createCredential, onSigned, request) {
|
|
2431
|
+
const probeInit = toRequestInit(request);
|
|
1983
2432
|
let probe;
|
|
1984
2433
|
try {
|
|
1985
|
-
probe = await mppx$1.rawFetch(url,
|
|
2434
|
+
probe = await mppx$1.rawFetch(url, probeInit);
|
|
1986
2435
|
} catch (err) {
|
|
1987
2436
|
throw wrapFetchError(err, "Voucher request");
|
|
1988
2437
|
}
|
|
@@ -2086,235 +2535,19 @@ var MppSessionClient = class {
|
|
|
2086
2535
|
}
|
|
2087
2536
|
}
|
|
2088
2537
|
};
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
this.code = code;
|
|
2095
|
-
}
|
|
2096
|
-
};
|
|
2097
|
-
function sha256Hex(data) {
|
|
2098
|
-
const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
2099
|
-
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
2100
|
-
}
|
|
2101
|
-
function commitDailyCap(dailyCapStroops, witnessSecret) {
|
|
2102
|
-
return sha256Hex(`${witnessSecret}:cap:${dailyCapStroops.toString()}`);
|
|
2103
|
-
}
|
|
2104
|
-
function commitAllowlist(payees, witnessSecret) {
|
|
2105
|
-
const sorted = [...payees].sort().join(",");
|
|
2106
|
-
return sha256Hex(`${witnessSecret}:allowlist:${sorted}`);
|
|
2107
|
-
}
|
|
2108
|
-
function hashPayee(payee) {
|
|
2109
|
-
return sha256Hex(`payee:${payee}`);
|
|
2110
|
-
}
|
|
2111
|
-
function dayBucketFromLedger(ledgerSequence) {
|
|
2112
|
-
return Math.floor(ledgerSequence / 17280);
|
|
2113
|
-
}
|
|
2114
|
-
function authDigestFromEntry(authEntryBase64) {
|
|
2115
|
-
const entryHash = stellarSdk.hash(Buffer.from(authEntryBase64, "base64"));
|
|
2116
|
-
return sha256Hex(entryHash);
|
|
2117
|
-
}
|
|
2118
|
-
function assertAuthEntryMatchesContext(authEntry, from, ctx) {
|
|
2119
|
-
let preimage;
|
|
2538
|
+
function createNulthSigner(config) {
|
|
2539
|
+
const sharedConfig = { ...config, logger: resolveLogger(config.logger) };
|
|
2540
|
+
Object.defineProperty(sharedConfig, "paymentContext", {
|
|
2541
|
+
get: () => config.paymentContext
|
|
2542
|
+
});
|
|
2120
2543
|
try {
|
|
2121
|
-
|
|
2122
|
-
} catch {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
if (preimage.switch() !== stellarSdk.xdr.EnvelopeType.envelopeTypeSorobanAuthorization()) {
|
|
2126
|
-
throw new NulthPolicyError(
|
|
2127
|
-
"auth_entry_mismatch",
|
|
2128
|
-
"authEntry is not a Soroban authorization preimage"
|
|
2129
|
-
);
|
|
2130
|
-
}
|
|
2131
|
-
const inv = preimage.sorobanAuthorization().invocation();
|
|
2132
|
-
if (inv.subInvocations().length !== 0) {
|
|
2133
|
-
throw new NulthPolicyError("auth_entry_mismatch", "authEntry has unexpected sub-invocations");
|
|
2134
|
-
}
|
|
2135
|
-
if (inv.function().switch() !== stellarSdk.xdr.SorobanAuthorizedFunctionType.sorobanAuthorizedFunctionTypeContractFn()) {
|
|
2136
|
-
throw new NulthPolicyError("auth_entry_mismatch", "authEntry does not invoke a contract function");
|
|
2137
|
-
}
|
|
2138
|
-
const fn = inv.function().contractFn();
|
|
2139
|
-
const contractAddress = stellarSdk.Address.fromScAddress(fn.contractAddress()).toString();
|
|
2140
|
-
if (contractAddress !== ctx.assetContract) {
|
|
2141
|
-
throw new NulthPolicyError(
|
|
2142
|
-
"auth_entry_mismatch",
|
|
2143
|
-
`authEntry contract ${contractAddress} does not match assetContract ${ctx.assetContract}`
|
|
2144
|
-
);
|
|
2145
|
-
}
|
|
2146
|
-
const functionName = fn.functionName().toString();
|
|
2147
|
-
if (functionName !== "transfer") {
|
|
2148
|
-
throw new NulthPolicyError(
|
|
2149
|
-
"auth_entry_mismatch",
|
|
2150
|
-
`authEntry function ${functionName} does not match expected transfer`
|
|
2151
|
-
);
|
|
2152
|
-
}
|
|
2153
|
-
const args = fn.args();
|
|
2154
|
-
if (args.length !== 3) {
|
|
2155
|
-
throw new NulthPolicyError("auth_entry_mismatch", `authEntry transfer has ${args.length} args, expected 3`);
|
|
2156
|
-
}
|
|
2157
|
-
const entryFrom = stellarSdk.scValToNative(args[0]);
|
|
2158
|
-
if (entryFrom !== from) {
|
|
2159
|
-
throw new NulthPolicyError(
|
|
2160
|
-
"auth_entry_mismatch",
|
|
2161
|
-
`authEntry from ${entryFrom} does not match nulthAccount ${from}`
|
|
2162
|
-
);
|
|
2163
|
-
}
|
|
2164
|
-
const entryTo = stellarSdk.scValToNative(args[1]);
|
|
2165
|
-
if (entryTo !== ctx.payee) {
|
|
2166
|
-
throw new NulthPolicyError(
|
|
2167
|
-
"auth_entry_mismatch",
|
|
2168
|
-
`authEntry to ${entryTo} does not match paymentContext.payee ${ctx.payee}`
|
|
2169
|
-
);
|
|
2170
|
-
}
|
|
2171
|
-
const entryAmount = stellarSdk.scValToNative(args[2]);
|
|
2172
|
-
if (entryAmount !== ctx.amountStroops) {
|
|
2173
|
-
throw new NulthPolicyError(
|
|
2174
|
-
"auth_entry_mismatch",
|
|
2175
|
-
`authEntry amount ${entryAmount} does not match paymentContext.amountStroops ${ctx.amountStroops}`
|
|
2176
|
-
);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
function insecureMockProof(preimage) {
|
|
2180
|
-
return crypto.createHash("sha512").update(`nulth:${preimage}`).digest("base64");
|
|
2181
|
-
}
|
|
2182
|
-
function encodeAuthSignature(proof) {
|
|
2183
|
-
const payload = { nulth: "zk-v1", proof };
|
|
2184
|
-
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
2185
|
-
}
|
|
2186
|
-
function usdcToStroops2(amount) {
|
|
2187
|
-
const [whole = "0", frac = ""] = amount.split(".");
|
|
2188
|
-
const padded = (frac + "0000000").slice(0, 7);
|
|
2189
|
-
return BigInt(whole) * 10000000n + BigInt(padded);
|
|
2190
|
-
}
|
|
2191
|
-
var NulthClient = class {
|
|
2192
|
-
constructor(config) {
|
|
2193
|
-
this.config = config;
|
|
2194
|
-
this.policy = { ...config.policy };
|
|
2195
|
-
this.prover = config.prover ?? "mock";
|
|
2196
|
-
if (this.prover !== "mock") {
|
|
2197
|
-
throw new RouteDockManifestError(
|
|
2198
|
-
`NulthClient: unknown prover backend ${String(config.prover)}; only 'mock' is implemented`
|
|
2199
|
-
);
|
|
2200
|
-
}
|
|
2201
|
-
if (config.network !== "testnet") {
|
|
2202
|
-
throw new RouteDockManifestError(
|
|
2203
|
-
`NulthClient cannot use the insecure mock prover on mainnet; a production prover is required (network must be 'testnet', got ${String(config.network)})`
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
resolveLogger(config.logger)(
|
|
2207
|
-
"warn",
|
|
2208
|
-
"NulthClient is using the insecure mock prover; proofs are not cryptographically sound"
|
|
2209
|
-
);
|
|
2210
|
-
}
|
|
2211
|
-
config;
|
|
2212
|
-
policy;
|
|
2213
|
-
prover;
|
|
2214
|
-
get proverBackend() {
|
|
2215
|
-
return this.prover;
|
|
2216
|
-
}
|
|
2217
|
-
get nulthAccount() {
|
|
2218
|
-
return this.config.nulthAccount;
|
|
2219
|
-
}
|
|
2220
|
-
get allowlistCommitment() {
|
|
2221
|
-
return this.policy.allowlistCommitment;
|
|
2222
|
-
}
|
|
2223
|
-
get capCommitment() {
|
|
2224
|
-
return commitDailyCap(this.policy.dailyCapStroops, this.policy.witnessSecret);
|
|
2225
|
-
}
|
|
2226
|
-
buildPaymentAuthProof(context) {
|
|
2227
|
-
this.enforcePolicy(context);
|
|
2228
|
-
const bucket = dayBucketFromLedger(context.ledgerSequence);
|
|
2229
|
-
if (bucket !== this.policy.dayBucket) {
|
|
2230
|
-
this.policy.dayBucket = bucket;
|
|
2231
|
-
this.policy.dailySpendStroops = 0n;
|
|
2232
|
-
}
|
|
2233
|
-
const authDigest = authDigestFromEntry(context.authEntry);
|
|
2234
|
-
const capCommitment = this.capCommitment;
|
|
2235
|
-
const payeeHash = hashPayee(context.payee);
|
|
2236
|
-
const preimage = [
|
|
2237
|
-
authDigest,
|
|
2238
|
-
this.policy.allowlistCommitment,
|
|
2239
|
-
capCommitment,
|
|
2240
|
-
payeeHash,
|
|
2241
|
-
context.amountStroops.toString(),
|
|
2242
|
-
bucket.toString(),
|
|
2243
|
-
this.config.nulthAccount
|
|
2244
|
-
].join("|");
|
|
2245
|
-
const proof = {
|
|
2246
|
-
version: 1,
|
|
2247
|
-
proof: insecureMockProof(preimage),
|
|
2248
|
-
publicInputs: {
|
|
2249
|
-
authDigest,
|
|
2250
|
-
allowlistCommitment: this.policy.allowlistCommitment,
|
|
2251
|
-
capCommitment,
|
|
2252
|
-
payeeHash,
|
|
2253
|
-
amountStroops: context.amountStroops.toString(),
|
|
2254
|
-
dayBucket: bucket.toString()
|
|
2255
|
-
}
|
|
2256
|
-
};
|
|
2257
|
-
this.policy.dailySpendStroops += context.amountStroops;
|
|
2258
|
-
return proof;
|
|
2259
|
-
}
|
|
2260
|
-
enforcePolicy(context) {
|
|
2261
|
-
if (context.amountStroops < 0n) {
|
|
2262
|
-
throw new RangeError(`Invalid amountStroops: ${context.amountStroops}`);
|
|
2263
|
-
}
|
|
2264
|
-
if (!Number.isSafeInteger(context.ledgerSequence) || context.ledgerSequence < 0) {
|
|
2265
|
-
throw new RangeError(`Invalid ledgerSequence: ${context.ledgerSequence}`);
|
|
2266
|
-
}
|
|
2267
|
-
if (this.policy.expiryLedger !== void 0 && context.ledgerSequence > this.policy.expiryLedger) {
|
|
2268
|
-
throw new NulthPolicyError("session_expired", "Nulth session expired");
|
|
2269
|
-
}
|
|
2270
|
-
if (!this.policy.allowedPayees.includes(context.payee)) {
|
|
2271
|
-
throw new NulthPolicyError("payee_not_allowed", `Payee ${context.payee} not in off-chain allowlist`);
|
|
2272
|
-
}
|
|
2273
|
-
const bucket = dayBucketFromLedger(context.ledgerSequence);
|
|
2274
|
-
const spend = bucket === this.policy.dayBucket ? this.policy.dailySpendStroops : 0n;
|
|
2275
|
-
if (spend + context.amountStroops > this.policy.dailyCapStroops) {
|
|
2276
|
-
throw new NulthPolicyError("daily_cap_exceeded", "Nulth daily cap exceeded");
|
|
2544
|
+
return nulthSdk.createNulthSigner(sharedConfig);
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (error instanceof nulthSdk.NulthConfigError) {
|
|
2547
|
+
throw new RouteDockManifestError(error.message, { cause: error });
|
|
2277
2548
|
}
|
|
2549
|
+
throw error;
|
|
2278
2550
|
}
|
|
2279
|
-
};
|
|
2280
|
-
function createNulthSigner(config) {
|
|
2281
|
-
const client = new NulthClient(config);
|
|
2282
|
-
const nulthAccount = config.nulthAccount;
|
|
2283
|
-
return {
|
|
2284
|
-
address: nulthAccount,
|
|
2285
|
-
signAuthEntry: async (authEntry) => {
|
|
2286
|
-
const ctx = config.paymentContext;
|
|
2287
|
-
if (!ctx) throw new Error("Nulth paymentContext must be set before signing");
|
|
2288
|
-
assertAuthEntryMatchesContext(authEntry, nulthAccount, ctx);
|
|
2289
|
-
const proof = client.buildPaymentAuthProof({ authEntry, ...ctx });
|
|
2290
|
-
return { signedAuthEntry: encodeAuthSignature(proof), signerAddress: nulthAccount };
|
|
2291
|
-
}
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
function setNulthPaymentContext(config, context) {
|
|
2295
|
-
config.paymentContext = context;
|
|
2296
|
-
}
|
|
2297
|
-
function paymentContextFromManifest(manifest, mode, ledgerSequence) {
|
|
2298
|
-
const pricing = manifest.pricing[mode];
|
|
2299
|
-
if (!pricing) throw new Error(`manifest.pricing.${mode} missing`);
|
|
2300
|
-
return {
|
|
2301
|
-
payee: manifest.payee,
|
|
2302
|
-
amountStroops: usdcToStroops2(pricing.amount),
|
|
2303
|
-
assetContract: manifest.asset_contract,
|
|
2304
|
-
ledgerSequence
|
|
2305
|
-
};
|
|
2306
|
-
}
|
|
2307
|
-
function createPolicyState(input) {
|
|
2308
|
-
const dailyCapStroops = usdcToStroops2(input.dailyCapUsdc);
|
|
2309
|
-
return {
|
|
2310
|
-
dailyCapStroops,
|
|
2311
|
-
allowlistCommitment: commitAllowlist(input.allowedPayees, input.witnessSecret),
|
|
2312
|
-
allowedPayees: input.allowedPayees,
|
|
2313
|
-
dailySpendStroops: 0n,
|
|
2314
|
-
dayBucket: dayBucketFromLedger(input.ledgerSequence ?? 0),
|
|
2315
|
-
witnessSecret: input.witnessSecret,
|
|
2316
|
-
...input.expiryLedger !== void 0 ? { expiryLedger: input.expiryLedger } : {}
|
|
2317
|
-
};
|
|
2318
2551
|
}
|
|
2319
2552
|
function assertNulthVaultManifest(manifest, nulthAccount) {
|
|
2320
2553
|
if (manifest.vault !== void 0 && manifest.vault !== "nulth") {
|
|
@@ -2339,7 +2572,7 @@ async function prepareNulthSigner(vault, manifest, mode, network, ledgerSequence
|
|
|
2339
2572
|
);
|
|
2340
2573
|
}
|
|
2341
2574
|
const ledgerSequence = ledgerSequenceOverride ?? await fetchLedgerSequence(network);
|
|
2342
|
-
const policy = createPolicyState({
|
|
2575
|
+
const policy = nulthSdk.createPolicyState({
|
|
2343
2576
|
dailyCapUsdc: vault.dailyCapUsdc,
|
|
2344
2577
|
allowedPayees: vault.allowedPayees,
|
|
2345
2578
|
witnessSecret: vault.witnessSecret,
|
|
@@ -2354,53 +2587,13 @@ async function prepareNulthSigner(vault, manifest, mode, network, ledgerSequence
|
|
|
2354
2587
|
...vault.verifierContract !== void 0 ? { verifierContract: vault.verifierContract } : {}
|
|
2355
2588
|
};
|
|
2356
2589
|
if (mode === "x402" || mode === "mpp-charge") {
|
|
2357
|
-
setNulthPaymentContext(config, paymentContextFromManifest(manifest, mode, ledgerSequence));
|
|
2590
|
+
nulthSdk.setNulthPaymentContext(config, nulthSdk.paymentContextFromManifest(manifest, mode, ledgerSequence));
|
|
2358
2591
|
} else {
|
|
2359
2592
|
throw new RouteDockManifestError(`Nulth ZK vault does not support payment mode: ${mode}`);
|
|
2360
2593
|
}
|
|
2361
2594
|
const signer = createNulthSigner(config);
|
|
2362
2595
|
return { signer, config };
|
|
2363
2596
|
}
|
|
2364
|
-
function decodeAuthSignature(encoded) {
|
|
2365
|
-
const raw = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
|
2366
|
-
if (typeof raw !== "object" || raw === null || raw.nulth !== "zk-v1") {
|
|
2367
|
-
throw new Error("Invalid Nulth ZK auth signature");
|
|
2368
|
-
}
|
|
2369
|
-
return raw;
|
|
2370
|
-
}
|
|
2371
|
-
var FileSpendStore = class {
|
|
2372
|
-
filePath;
|
|
2373
|
-
constructor(filePath) {
|
|
2374
|
-
this.filePath = filePath;
|
|
2375
|
-
}
|
|
2376
|
-
async read() {
|
|
2377
|
-
let data;
|
|
2378
|
-
try {
|
|
2379
|
-
data = await fs__default.default.readFile(this.filePath, "utf-8");
|
|
2380
|
-
} catch (err) {
|
|
2381
|
-
if (err.code === "ENOENT") {
|
|
2382
|
-
return null;
|
|
2383
|
-
}
|
|
2384
|
-
throw new Error(
|
|
2385
|
-
`[FileSpendStore] Failed to read spend file at ${this.filePath}: ${err.message}`
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
try {
|
|
2389
|
-
return JSON.parse(data);
|
|
2390
|
-
} catch {
|
|
2391
|
-
throw new Error(
|
|
2392
|
-
`[FileSpendStore] Spend file at ${this.filePath} contains invalid JSON. Delete or repair it before resuming. Raw content: ${data.slice(0, 120)}`
|
|
2393
|
-
);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
async write(state) {
|
|
2397
|
-
const dir = path__default.default.dirname(this.filePath);
|
|
2398
|
-
await fs__default.default.mkdir(dir, { recursive: true });
|
|
2399
|
-
const tmpPath = `${this.filePath}.tmp`;
|
|
2400
|
-
await fs__default.default.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
2401
|
-
await fs__default.default.rename(tmpPath, this.filePath);
|
|
2402
|
-
}
|
|
2403
|
-
};
|
|
2404
2597
|
|
|
2405
2598
|
// src/store/SpendStore.ts
|
|
2406
2599
|
var InMemorySpendStore = class {
|
|
@@ -2514,7 +2707,7 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2514
2707
|
}
|
|
2515
2708
|
const secretKey = this.keypair.secret();
|
|
2516
2709
|
const retryPolicy = this.logger ? { ...this.retryPolicy, logger: this.logger } : this.retryPolicy;
|
|
2517
|
-
this.x402 = new X402Client(secretKey, this.network, retryPolicy);
|
|
2710
|
+
this.x402 = new X402Client(secretKey, this.network, retryPolicy, config.preSignHook, this.logger);
|
|
2518
2711
|
this.charge = new MppChargeClient(this.keypair, this.network, retryPolicy);
|
|
2519
2712
|
this.session = new MppSessionClient(this.keypair, this.network, retryPolicy, void 0, this.logger);
|
|
2520
2713
|
}
|
|
@@ -2522,7 +2715,7 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2522
2715
|
async _resolveManifest(url, options) {
|
|
2523
2716
|
const baseUrl = new URL(url).origin;
|
|
2524
2717
|
const manifest = await fetchManifest(baseUrl, this.retryPolicy, this.manifestTimeoutMs, this.expectedPayee);
|
|
2525
|
-
assertEndpointActive(manifest, url, this.logger);
|
|
2718
|
+
assertEndpointActive(manifest, url, this.logger, options?.request?.method);
|
|
2526
2719
|
const mode = selectMode(manifest, options);
|
|
2527
2720
|
return { manifest, mode };
|
|
2528
2721
|
}
|
|
@@ -2539,13 +2732,9 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2539
2732
|
});
|
|
2540
2733
|
return next;
|
|
2541
2734
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
* call before committing to a payment — for approval gates and manual
|
|
2546
|
-
* trustline remediation.
|
|
2547
|
-
*/
|
|
2548
|
-
async preflight(manifest, mode, endpoint) {
|
|
2735
|
+
async preflight(target, mode, endpoint) {
|
|
2736
|
+
if (typeof target === "string") return this._preflightUrl(target);
|
|
2737
|
+
const manifest = target;
|
|
2549
2738
|
assertManifestValid(manifest);
|
|
2550
2739
|
const selectedAsset = mode ? selectAsset(manifest, mode, endpoint).asset : manifest.asset;
|
|
2551
2740
|
await this._checkTrustline(manifest, selectedAsset);
|
|
@@ -2558,6 +2747,42 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2558
2747
|
...manifest.capabilities && { capabilities: manifest.capabilities }
|
|
2559
2748
|
};
|
|
2560
2749
|
}
|
|
2750
|
+
/** Manifest-only preflight for a URL — no trustline check, no payment. */
|
|
2751
|
+
async _preflightUrl(url) {
|
|
2752
|
+
const baseUrl = new URL(url).origin;
|
|
2753
|
+
const manifest = await fetchManifest(baseUrl, this.retryPolicy, this.manifestTimeoutMs, this.expectedPayee);
|
|
2754
|
+
assertEndpointActive(manifest, url);
|
|
2755
|
+
const costs = manifest.modes.map((mode) => {
|
|
2756
|
+
const asset = selectAsset(manifest, mode, url).asset;
|
|
2757
|
+
switch (mode) {
|
|
2758
|
+
case "x402":
|
|
2759
|
+
case "mpp-charge": {
|
|
2760
|
+
const pricing = manifest.pricing[mode];
|
|
2761
|
+
return { mode, amount: pricing.amount, per: "request", asset };
|
|
2762
|
+
}
|
|
2763
|
+
case "mpp-session":
|
|
2764
|
+
case "mpp-session-ws": {
|
|
2765
|
+
const pricing = manifest.pricing[mode];
|
|
2766
|
+
return { mode, amount: pricing.rate, per: "voucher", asset, min_deposit: pricing.min_deposit };
|
|
2767
|
+
}
|
|
2768
|
+
default:
|
|
2769
|
+
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
2770
|
+
}
|
|
2771
|
+
});
|
|
2772
|
+
const pathname = new URL(url).pathname;
|
|
2773
|
+
const matches = Object.values(manifest.endpoints).filter((descriptor) => descriptor.path === pathname);
|
|
2774
|
+
const endpointSunsets = matches.map((descriptor) => descriptor.sunset_at).filter((value) => typeof value === "string").sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
2775
|
+
return {
|
|
2776
|
+
modes: manifest.modes,
|
|
2777
|
+
costs,
|
|
2778
|
+
compliance: {
|
|
2779
|
+
deprecated_modes: manifest.deprecated_modes ?? [],
|
|
2780
|
+
...manifest.sunset_at && { sunset_at: manifest.sunset_at },
|
|
2781
|
+
endpoint_deprecated: matches.some((descriptor) => descriptor.deprecated === true),
|
|
2782
|
+
...endpointSunsets[0] && { endpoint_sunset_at: endpointSunsets[0] }
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2561
2786
|
/**
|
|
2562
2787
|
* Verify via Horizon that the payer account has a trustline for the
|
|
2563
2788
|
* payment asset declared in the manifest. Throws RouteDockTrustlineError
|
|
@@ -2569,8 +2794,7 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2569
2794
|
const cacheKey = `${this.network}:${this.keypair.publicKey()}:${targetAsset}`;
|
|
2570
2795
|
const cached = _RouteDockClient._trustlineCache.get(cacheKey);
|
|
2571
2796
|
if (cached && Date.now() < cached.expiresAt) return;
|
|
2572
|
-
const
|
|
2573
|
-
const server = new stellarSdk.Horizon.Server(horizonUrl);
|
|
2797
|
+
const server = new stellarSdk.Horizon.Server(HORIZON_URLS[this.network]);
|
|
2574
2798
|
try {
|
|
2575
2799
|
const account = await server.loadAccount(this.keypair.publicKey());
|
|
2576
2800
|
const balances = account.balances;
|
|
@@ -2606,9 +2830,11 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2606
2830
|
async pay(url, options) {
|
|
2607
2831
|
const baseUrl = new URL(url).origin;
|
|
2608
2832
|
const manifest = await fetchManifest(baseUrl, this.retryPolicy, this.manifestTimeoutMs, this.expectedPayee);
|
|
2609
|
-
|
|
2833
|
+
const request = options?.request;
|
|
2834
|
+
assertEndpointActive(manifest, url, this.logger, request?.method);
|
|
2610
2835
|
const mode = selectMode(manifest, { ...options, ...this.logger && { logger: this.logger } });
|
|
2611
2836
|
const selectedAsset = selectAsset(manifest, mode, url).asset;
|
|
2837
|
+
this._assertCapAsset(selectedAsset);
|
|
2612
2838
|
await this._checkTrustline(manifest, selectedAsset);
|
|
2613
2839
|
let amount;
|
|
2614
2840
|
switch (mode) {
|
|
@@ -2626,18 +2852,18 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2626
2852
|
default:
|
|
2627
2853
|
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
2628
2854
|
}
|
|
2629
|
-
const reserveId = await this._checkAndReserveSpend(amount, baseUrl);
|
|
2855
|
+
const reserveId = await this._checkAndReserveSpend(amount, baseUrl, selectedAsset);
|
|
2630
2856
|
let result;
|
|
2631
2857
|
try {
|
|
2632
2858
|
if (this.vault?.mode === "nulth") {
|
|
2633
|
-
result = await this._payWithNulthVault(url, manifest, mode);
|
|
2859
|
+
result = await this._payWithNulthVault(url, manifest, mode, request);
|
|
2634
2860
|
} else {
|
|
2635
2861
|
switch (mode) {
|
|
2636
2862
|
case "x402":
|
|
2637
|
-
result = await this.x402.pay(url, manifest);
|
|
2863
|
+
result = await this.x402.pay(url, manifest, request);
|
|
2638
2864
|
break;
|
|
2639
2865
|
case "mpp-charge":
|
|
2640
|
-
result = await this.charge.pay(url, manifest);
|
|
2866
|
+
result = await this.charge.pay(url, manifest, request);
|
|
2641
2867
|
break;
|
|
2642
2868
|
default:
|
|
2643
2869
|
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
@@ -2656,7 +2882,7 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2656
2882
|
return result;
|
|
2657
2883
|
}
|
|
2658
2884
|
/** Nulth ZK vault path — proof built off-chain, attached as auth signature */
|
|
2659
|
-
async _payWithNulthVault(url, manifest, mode) {
|
|
2885
|
+
async _payWithNulthVault(url, manifest, mode, request) {
|
|
2660
2886
|
const prover = this.vault?.prover ?? "mock";
|
|
2661
2887
|
if (this.network === "mainnet" && prover === "mock") {
|
|
2662
2888
|
throw new RouteDockManifestError("nulth vault uses a MOCK Groth16 prover and cannot be used on mainnet");
|
|
@@ -2669,10 +2895,10 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2669
2895
|
try {
|
|
2670
2896
|
const { signer } = await prepareNulthSigner(this.vault, manifest, mode, this.network);
|
|
2671
2897
|
const x402 = this.x402.withSigner(signer);
|
|
2672
|
-
const result = await x402.pay(url, manifest);
|
|
2898
|
+
const result = await x402.pay(url, manifest, request);
|
|
2673
2899
|
return result;
|
|
2674
2900
|
} catch (err) {
|
|
2675
|
-
if (err instanceof NulthPolicyError) {
|
|
2901
|
+
if (err instanceof nulthSdk.NulthPolicyError) {
|
|
2676
2902
|
throw new RouteDockPolicyRejectError(err.code);
|
|
2677
2903
|
}
|
|
2678
2904
|
throw err;
|
|
@@ -2738,17 +2964,35 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2738
2964
|
);
|
|
2739
2965
|
}
|
|
2740
2966
|
const endpointKey = new URL(url).origin;
|
|
2741
|
-
|
|
2967
|
+
let onSpend;
|
|
2968
|
+
if (this.spendCap) {
|
|
2969
|
+
const sessionAsset = selectAsset(manifest, mode, url).asset;
|
|
2970
|
+
this._assertCapAsset(sessionAsset);
|
|
2971
|
+
onSpend = (amount) => this._recordSessionSpend(amount, endpointKey, sessionAsset);
|
|
2972
|
+
}
|
|
2742
2973
|
return this.session.openSession(url, manifest, secret, options, onSpend);
|
|
2743
2974
|
}
|
|
2975
|
+
/**
|
|
2976
|
+
* The local cap accumulates one USDC total. Refuse to count any other asset
|
|
2977
|
+
* against it (or let it through uncounted) when a cap is configured.
|
|
2978
|
+
*/
|
|
2979
|
+
_assertCapAsset(asset) {
|
|
2980
|
+
if (this.spendCap && asset !== this.spendCap.asset) {
|
|
2981
|
+
throw new RouteDockPolicyRejectError("local_cap_asset_mismatch", {
|
|
2982
|
+
cause: new Error(
|
|
2983
|
+
`spendCap is denominated in ${this.spendCap.asset} but the selected payment asset is ${asset}`
|
|
2984
|
+
)
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2744
2988
|
/**
|
|
2745
2989
|
* Record spend for an MPP session voucher. Called before each voucher
|
|
2746
2990
|
* fetch in stream(). Enforces the same local daily and per-endpoint caps
|
|
2747
2991
|
* as discrete pay() calls. Returns a commit function to clean up the
|
|
2748
2992
|
* reservation after the voucher fetch succeeds.
|
|
2749
2993
|
*/
|
|
2750
|
-
async _recordSessionSpend(amount, endpointKey) {
|
|
2751
|
-
const reserveId = await this._checkAndReserveSpend(amount, endpointKey);
|
|
2994
|
+
async _recordSessionSpend(amount, endpointKey, asset) {
|
|
2995
|
+
const reserveId = await this._checkAndReserveSpend(amount, endpointKey, asset);
|
|
2752
2996
|
if (reserveId) {
|
|
2753
2997
|
await this._commitSpend(reserveId);
|
|
2754
2998
|
}
|
|
@@ -2787,15 +3031,16 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2787
3031
|
* Both limits are independent: a payment is only reserved after **both** pass.
|
|
2788
3032
|
* All spend (regardless of endpoint) counts toward the global accumulator.
|
|
2789
3033
|
*/
|
|
2790
|
-
async _checkAndReserveSpend(amount, endpointKey) {
|
|
3034
|
+
async _checkAndReserveSpend(amount, endpointKey, asset) {
|
|
2791
3035
|
if (!this.spendCap) return "";
|
|
2792
|
-
|
|
3036
|
+
this._assertCapAsset(asset);
|
|
3037
|
+
const spendCap = this.spendCap;
|
|
3038
|
+
const amountMicros = nulthSdk.usdcToStroops(amount);
|
|
3039
|
+
const reserve = (prev) => {
|
|
2793
3040
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2794
|
-
const
|
|
2795
|
-
const current = persisted && persisted.date === today ? persisted : { date: today, totalMicros: "0", endpoints: {} };
|
|
2796
|
-
const amountMicros = nulthSdk.usdcToStroops(amount);
|
|
3041
|
+
const current = prev && prev.date === today ? { ...prev, endpoints: { ...prev.endpoints } } : { date: today, totalMicros: "0", endpoints: {} };
|
|
2797
3042
|
const total = BigInt(current.totalMicros);
|
|
2798
|
-
const endpointCapStr =
|
|
3043
|
+
const endpointCapStr = spendCap.endpointCaps?.[endpointKey];
|
|
2799
3044
|
if (endpointCapStr !== void 0) {
|
|
2800
3045
|
const endpointCapMicros = nulthSdk.usdcToStroops(endpointCapStr);
|
|
2801
3046
|
const endpointTotal = BigInt(current.endpoints[endpointKey] ?? "0");
|
|
@@ -2803,17 +3048,25 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2803
3048
|
throw new RouteDockPolicyRejectError("local_endpoint_cap_exceeded");
|
|
2804
3049
|
}
|
|
2805
3050
|
}
|
|
2806
|
-
const globalCapMicros = nulthSdk.usdcToStroops(
|
|
3051
|
+
const globalCapMicros = nulthSdk.usdcToStroops(spendCap.daily);
|
|
2807
3052
|
if (total + amountMicros > globalCapMicros) {
|
|
2808
3053
|
throw new RouteDockPolicyRejectError("local_daily_cap_exceeded");
|
|
2809
3054
|
}
|
|
2810
|
-
const reserveId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2811
|
-
this._pendingReserves.set(reserveId, { endpointKey, amountMicros });
|
|
2812
3055
|
current.totalMicros = (total + amountMicros).toString();
|
|
2813
3056
|
if (endpointCapStr !== void 0) {
|
|
2814
3057
|
current.endpoints[endpointKey] = (BigInt(current.endpoints[endpointKey] ?? "0") + amountMicros).toString();
|
|
2815
3058
|
}
|
|
2816
|
-
|
|
3059
|
+
return current;
|
|
3060
|
+
};
|
|
3061
|
+
return this._withSpendMutex(async () => {
|
|
3062
|
+
const store = this.spendStore;
|
|
3063
|
+
if (store.update) {
|
|
3064
|
+
await store.update(reserve);
|
|
3065
|
+
} else {
|
|
3066
|
+
await store.write(reserve(await store.read()));
|
|
3067
|
+
}
|
|
3068
|
+
const reserveId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3069
|
+
this._pendingReserves.set(reserveId, { endpointKey, amountMicros });
|
|
2817
3070
|
return reserveId;
|
|
2818
3071
|
});
|
|
2819
3072
|
}
|
|
@@ -2835,22 +3088,29 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2835
3088
|
if (!reserve) return;
|
|
2836
3089
|
this._pendingReserves.delete(reserveId);
|
|
2837
3090
|
const { endpointKey, amountMicros } = reserve;
|
|
2838
|
-
|
|
3091
|
+
const release = (prev) => {
|
|
2839
3092
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
persisted.totalMicros = (currentTotal - amountMicros).toString();
|
|
3093
|
+
if (!prev || prev.date !== today) return void 0;
|
|
3094
|
+
const persisted = { ...prev, endpoints: { ...prev.endpoints } };
|
|
3095
|
+
persisted.totalMicros = (BigInt(persisted.totalMicros) - amountMicros).toString();
|
|
2844
3096
|
if (persisted.endpoints[endpointKey]) {
|
|
2845
|
-
const
|
|
2846
|
-
const newEndpointTotal = endpointTotal - amountMicros;
|
|
3097
|
+
const newEndpointTotal = BigInt(persisted.endpoints[endpointKey]) - amountMicros;
|
|
2847
3098
|
if (newEndpointTotal <= 0n) {
|
|
2848
3099
|
delete persisted.endpoints[endpointKey];
|
|
2849
3100
|
} else {
|
|
2850
3101
|
persisted.endpoints[endpointKey] = newEndpointTotal.toString();
|
|
2851
3102
|
}
|
|
2852
3103
|
}
|
|
2853
|
-
|
|
3104
|
+
return persisted;
|
|
3105
|
+
};
|
|
3106
|
+
await this._withSpendMutex(async () => {
|
|
3107
|
+
const store = this.spendStore;
|
|
3108
|
+
if (store.update) {
|
|
3109
|
+
await store.update(release);
|
|
3110
|
+
} else {
|
|
3111
|
+
const next = release(await store.read());
|
|
3112
|
+
if (next) await store.write(next);
|
|
3113
|
+
}
|
|
2854
3114
|
});
|
|
2855
3115
|
}
|
|
2856
3116
|
};
|
|
@@ -2939,7 +3199,7 @@ function tryDecodeBase64(value) {
|
|
|
2939
3199
|
return value;
|
|
2940
3200
|
}
|
|
2941
3201
|
try {
|
|
2942
|
-
const decoded =
|
|
3202
|
+
const decoded = base64ToUtf8(value);
|
|
2943
3203
|
if (decoded.length > 0 && decoded !== value) return decoded;
|
|
2944
3204
|
} catch {
|
|
2945
3205
|
}
|
|
@@ -3076,21 +3336,40 @@ var ProviderRegistry = class {
|
|
|
3076
3336
|
}
|
|
3077
3337
|
};
|
|
3078
3338
|
|
|
3339
|
+
Object.defineProperty(exports, "NulthPolicyError", {
|
|
3340
|
+
enumerable: true,
|
|
3341
|
+
get: function () { return nulthSdk.NulthPolicyError; }
|
|
3342
|
+
});
|
|
3079
3343
|
Object.defineProperty(exports, "USDC_DECIMALS", {
|
|
3080
3344
|
enumerable: true,
|
|
3081
3345
|
get: function () { return nulthSdk.USDC_DECIMALS; }
|
|
3082
3346
|
});
|
|
3347
|
+
Object.defineProperty(exports, "createPolicyState", {
|
|
3348
|
+
enumerable: true,
|
|
3349
|
+
get: function () { return nulthSdk.createPolicyState; }
|
|
3350
|
+
});
|
|
3351
|
+
Object.defineProperty(exports, "decodeAuthSignature", {
|
|
3352
|
+
enumerable: true,
|
|
3353
|
+
get: function () { return nulthSdk.decodeAuthSignature; }
|
|
3354
|
+
});
|
|
3355
|
+
Object.defineProperty(exports, "paymentContextFromManifest", {
|
|
3356
|
+
enumerable: true,
|
|
3357
|
+
get: function () { return nulthSdk.paymentContextFromManifest; }
|
|
3358
|
+
});
|
|
3359
|
+
Object.defineProperty(exports, "setNulthPaymentContext", {
|
|
3360
|
+
enumerable: true,
|
|
3361
|
+
get: function () { return nulthSdk.setNulthPaymentContext; }
|
|
3362
|
+
});
|
|
3083
3363
|
Object.defineProperty(exports, "usdcToStroops", {
|
|
3084
3364
|
enumerable: true,
|
|
3085
3365
|
get: function () { return nulthSdk.usdcToStroops; }
|
|
3086
3366
|
});
|
|
3087
3367
|
exports.DEFAULT_MAX_SESSION_DURATION_MS = DEFAULT_MAX_SESSION_DURATION_MS;
|
|
3088
3368
|
exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY;
|
|
3089
|
-
exports.FileSpendStore = FileSpendStore;
|
|
3090
3369
|
exports.InMemorySpendStore = InMemorySpendStore;
|
|
3091
3370
|
exports.MppChargeClient = MppChargeClient;
|
|
3092
3371
|
exports.MppSessionClient = MppSessionClient;
|
|
3093
|
-
exports.
|
|
3372
|
+
exports.NulthStellarScheme = NulthStellarScheme;
|
|
3094
3373
|
exports.OnChainRegistry = OnChainRegistry;
|
|
3095
3374
|
exports.ProviderRegistry = ProviderRegistry;
|
|
3096
3375
|
exports.RouteDockChannelStateError = RouteDockChannelStateError;
|
|
@@ -3104,6 +3383,7 @@ exports.RouteDockManifestSunsetError = RouteDockManifestSunsetError;
|
|
|
3104
3383
|
exports.RouteDockManifestTimeoutError = RouteDockManifestTimeoutError;
|
|
3105
3384
|
exports.RouteDockNetworkError = RouteDockNetworkError;
|
|
3106
3385
|
exports.RouteDockNoSupportedModeError = RouteDockNoSupportedModeError;
|
|
3386
|
+
exports.RouteDockPaymentRejectedError = RouteDockPaymentRejectedError;
|
|
3107
3387
|
exports.RouteDockPolicyRejectError = RouteDockPolicyRejectError;
|
|
3108
3388
|
exports.RouteDockPolicyRejectedError = RouteDockPolicyRejectedError;
|
|
3109
3389
|
exports.RouteDockRefundWindowError = RouteDockRefundWindowError;
|
|
@@ -3112,6 +3392,8 @@ exports.RouteDockSignatureError = RouteDockSignatureError;
|
|
|
3112
3392
|
exports.RouteDockTrustlineError = RouteDockTrustlineError;
|
|
3113
3393
|
exports.RouteDockVoucherMonotonicityError = RouteDockVoucherMonotonicityError;
|
|
3114
3394
|
exports.SupabaseSessionStore = SupabaseSessionStore;
|
|
3395
|
+
exports.TX_CONFIRM_INTERVAL_MS = TX_CONFIRM_INTERVAL_MS;
|
|
3396
|
+
exports.TX_CONFIRM_TIMEOUT_MS = TX_CONFIRM_TIMEOUT_MS;
|
|
3115
3397
|
exports.X402Client = X402Client;
|
|
3116
3398
|
exports.assertEndpointActive = assertEndpointActive;
|
|
3117
3399
|
exports.assertManifestValid = assertManifestValid;
|
|
@@ -3121,8 +3403,6 @@ exports.configureManifestCache = configureManifestCache;
|
|
|
3121
3403
|
exports.consoleLogger = consoleLogger;
|
|
3122
3404
|
exports.createConsoleLogger = createConsoleLogger;
|
|
3123
3405
|
exports.createNulthSigner = createNulthSigner;
|
|
3124
|
-
exports.createPolicyState = createPolicyState;
|
|
3125
|
-
exports.decodeAuthSignature = decodeAuthSignature;
|
|
3126
3406
|
exports.fetchLedgerSequence = fetchLedgerSequence;
|
|
3127
3407
|
exports.fetchManifest = fetchManifest;
|
|
3128
3408
|
exports.getEligibleAssets = getEligibleAssets;
|
|
@@ -3132,14 +3412,14 @@ exports.isAssetEligible = isAssetEligible;
|
|
|
3132
3412
|
exports.noopLogger = noopLogger;
|
|
3133
3413
|
exports.normalizeManifestAssets = normalizeManifestAssets;
|
|
3134
3414
|
exports.parseRetryAfterMs = parseRetryAfterMs;
|
|
3135
|
-
exports.
|
|
3415
|
+
exports.paymentRejectedError = paymentRejectedError;
|
|
3136
3416
|
exports.prepareNulthSigner = prepareNulthSigner;
|
|
3137
3417
|
exports.rankProvidersByLatency = rankProvidersByLatency;
|
|
3138
3418
|
exports.resolveAssetContract = resolveAssetContract;
|
|
3139
3419
|
exports.resolveLogger = resolveLogger;
|
|
3140
3420
|
exports.selectAsset = selectAsset;
|
|
3141
3421
|
exports.selectMode = selectMode;
|
|
3142
|
-
exports.
|
|
3422
|
+
exports.selectStellarScheme = selectStellarScheme;
|
|
3143
3423
|
exports.usdcToMicros = usdcToMicros;
|
|
3144
3424
|
exports.usdcToUnits = usdcToUnits;
|
|
3145
3425
|
exports.withRetry = withRetry;
|