@routedock/routedock 0.2.0 → 0.3.1
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 +305 -0
- package/dist/MppSessionHandler-1EL8QBAn.d.cts +215 -0
- package/dist/MppSessionHandler-Bq4S-JTE.d.ts +215 -0
- package/dist/{ProviderRegistry-BJ9oJLVz.d.cts → ProviderRegistry-CyZ5eryO.d.cts} +2 -2
- package/dist/{ProviderRegistry-C50nyhaF.d.ts → ProviderRegistry-w4_hkI_1.d.ts} +2 -2
- package/dist/{RouteDockClient-BbkqjhMG.d.ts → RouteDockClient-CXvWXix5.d.cts} +25 -9
- package/dist/{RouteDockClient-B1roCa8h.d.cts → RouteDockClient-CjM2VyOd.d.ts} +25 -9
- package/dist/{SessionReconciler-Dscw43F3.d.ts → SessionReconciler-Bw-PoBVl.d.ts} +14 -5
- package/dist/{SessionReconciler-bi0z2qtN.d.cts → SessionReconciler-Cz2nuOlJ.d.cts} +14 -5
- package/dist/{SessionStore-DvkZv8-E.d.ts → SessionStore-B4-Hi4H8.d.cts} +34 -2
- package/dist/{SessionStore-BpK8vv0L.d.cts → SessionStore-BaYiegP0.d.ts} +34 -2
- package/dist/client.cjs +432 -125
- package/dist/client.d.cts +9 -8
- package/dist/client.d.ts +9 -8
- package/dist/client.js +424 -127
- package/dist/index.cjs +655 -228
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +646 -229
- package/dist/{types-DmhmfLzP.d.cts → logger-D8_Q2ytp.d.cts} +58 -1
- package/dist/{types-DmhmfLzP.d.ts → logger-D8_Q2ytp.d.ts} +58 -1
- package/dist/provider/fastify.cjs +306 -89
- package/dist/provider/fastify.d.cts +9 -4
- package/dist/provider/fastify.d.ts +9 -4
- package/dist/provider/fastify.js +305 -88
- package/dist/provider/hono.cjs +286 -82
- package/dist/provider/hono.d.cts +9 -7
- package/dist/provider/hono.d.ts +9 -7
- package/dist/provider/hono.js +282 -78
- package/dist/provider.cjs +326 -98
- package/dist/provider.d.cts +14 -10
- package/dist/provider.d.ts +14 -10
- package/dist/provider.js +318 -98
- package/dist/react.cjs +410 -125
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +411 -126
- package/dist/{routedockMiddleware-BlDsHuPl.d.ts → routedockMiddleware-57gUaEMf.d.ts} +9 -4
- package/dist/{routedockMiddleware-D97A5ptQ.d.cts → routedockMiddleware-DlyLrxp7.d.cts} +9 -4
- package/dist/schema.cjs +40 -0
- package/dist/schema.js +40 -0
- package/dist/{sign-Dpj2Mp7k.d.ts → sign-AKH67sft.d.cts} +1 -1
- package/dist/{sign-CUf3rhTx.d.cts → sign-BEq-OsSq.d.ts} +1 -1
- package/dist/testing.cjs +6 -4
- package/dist/testing.d.cts +32 -13
- package/dist/testing.d.ts +32 -13
- package/dist/testing.js +6 -4
- package/package.json +2 -2
- package/dist/MppSessionHandler-B4TyD7Xg.d.cts +0 -121
- package/dist/MppSessionHandler-CLB5SzgN.d.ts +0 -121
package/dist/client.cjs
CHANGED
|
@@ -165,10 +165,35 @@ function wrapFetchError(err, context) {
|
|
|
165
165
|
if (err instanceof RouteDockError) return err;
|
|
166
166
|
return new RouteDockNetworkError(`${context}: ${String(err)}`, { cause: err });
|
|
167
167
|
}
|
|
168
|
+
function wrapMppError(err, context) {
|
|
169
|
+
if (err instanceof RouteDockError) return err;
|
|
170
|
+
if (err instanceof TypeError || err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) {
|
|
171
|
+
return new RouteDockNetworkError(`${context}: ${String(err)}`, { cause: err });
|
|
172
|
+
}
|
|
173
|
+
return new RouteDockSignatureError(`${context}: ${String(err)}`, { cause: err });
|
|
174
|
+
}
|
|
168
175
|
|
|
169
176
|
// src/types.ts
|
|
170
177
|
var DEFAULT_MAX_SESSION_DURATION_MS = 36e5;
|
|
171
178
|
|
|
179
|
+
// src/internal/logger.ts
|
|
180
|
+
function createConsoleLogger() {
|
|
181
|
+
return (level, message, fields) => {
|
|
182
|
+
const values = fields ? Object.values(fields) : [];
|
|
183
|
+
if (values.length > 0) {
|
|
184
|
+
console[level](message, ...values);
|
|
185
|
+
} else {
|
|
186
|
+
console[level](message);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var consoleLogger = createConsoleLogger();
|
|
191
|
+
var noopLogger = () => {
|
|
192
|
+
};
|
|
193
|
+
function resolveLogger(logger, fallback = consoleLogger) {
|
|
194
|
+
return logger ?? fallback;
|
|
195
|
+
}
|
|
196
|
+
|
|
172
197
|
// src/internal/retry.ts
|
|
173
198
|
var DEFAULT_RETRY_POLICY = {
|
|
174
199
|
maxAttempts: 4,
|
|
@@ -183,10 +208,11 @@ function sleep(ms) {
|
|
|
183
208
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
209
|
}
|
|
185
210
|
async function withRetry(fn, policy = {}) {
|
|
186
|
-
const { maxAttempts, baseDelayMs, maxDelayMs, onRetry } = {
|
|
211
|
+
const { maxAttempts, baseDelayMs, maxDelayMs, onRetry, logger } = {
|
|
187
212
|
...DEFAULT_RETRY_POLICY,
|
|
188
213
|
...policy
|
|
189
214
|
};
|
|
215
|
+
const log = resolveLogger(logger);
|
|
190
216
|
let lastError;
|
|
191
217
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
192
218
|
try {
|
|
@@ -204,7 +230,7 @@ async function withRetry(fn, policy = {}) {
|
|
|
204
230
|
try {
|
|
205
231
|
await onRetry(attempt, err, delay);
|
|
206
232
|
} catch (cbErr) {
|
|
207
|
-
|
|
233
|
+
log("error", "[withRetry] onRetry callback threw:", { error: cbErr });
|
|
208
234
|
}
|
|
209
235
|
}
|
|
210
236
|
await sleep(delay);
|
|
@@ -218,6 +244,84 @@ var USDC_ISSUERS = {
|
|
|
218
244
|
};
|
|
219
245
|
var usdcToUnits = nulthSdk.usdcToStroops;
|
|
220
246
|
|
|
247
|
+
// src/internal/assetUtils.ts
|
|
248
|
+
function normalizeManifestAssets(manifest) {
|
|
249
|
+
if (manifest.assets && manifest.assets.length > 0) {
|
|
250
|
+
return manifest.assets;
|
|
251
|
+
}
|
|
252
|
+
if (manifest.asset && manifest.asset_contract) {
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
asset: manifest.asset,
|
|
256
|
+
asset_contract: manifest.asset_contract
|
|
257
|
+
}
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
throw new RouteDockManifestError(
|
|
261
|
+
"Manifest must define either assets[] or root asset/asset_contract fields"
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
function getEligibleAssets(manifest, mode, endpoint) {
|
|
265
|
+
const assets = normalizeManifestAssets(manifest);
|
|
266
|
+
return assets.filter((asset) => {
|
|
267
|
+
if (asset.modes && !asset.modes.includes(mode)) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (endpoint && asset.endpoints && asset.endpoints.length > 0) {
|
|
271
|
+
const trimmedEndpoint = endpoint.replace(/^\//, "");
|
|
272
|
+
if (asset.endpoints.includes(endpoint) || asset.endpoints.includes(trimmedEndpoint)) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
if (manifest.endpoints) {
|
|
276
|
+
let pathname = endpoint;
|
|
277
|
+
if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) {
|
|
278
|
+
try {
|
|
279
|
+
pathname = new URL(endpoint).pathname;
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const trimmedPathname = pathname.replace(/^\//, "");
|
|
284
|
+
const matchesEndpoint = Object.entries(manifest.endpoints).some(([key, desc]) => {
|
|
285
|
+
const pathMatches = desc.path === pathname || desc.path === endpoint || desc.path.replace(/\/+$/, "") === pathname.replace(/\/+$/, "") || desc.path.replace(/^\//, "") === trimmedPathname;
|
|
286
|
+
const keyMatches = key === endpoint || key === trimmedEndpoint;
|
|
287
|
+
if (pathMatches || keyMatches) {
|
|
288
|
+
return asset.endpoints.includes(key) || asset.endpoints.includes(desc.path) || asset.endpoints.includes(desc.path.replace(/^\//, ""));
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
});
|
|
292
|
+
if (!matchesEndpoint) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function resolveAssetContract(manifest, mode, endpoint, explicitContract) {
|
|
303
|
+
if (manifest.assets && manifest.assets.length > 0) {
|
|
304
|
+
return selectAsset(manifest, mode, endpoint).asset_contract;
|
|
305
|
+
}
|
|
306
|
+
return explicitContract ?? selectAsset(manifest, mode, endpoint).asset_contract;
|
|
307
|
+
}
|
|
308
|
+
function selectAsset(manifest, mode, endpoint) {
|
|
309
|
+
const eligible = getEligibleAssets(manifest, mode, endpoint);
|
|
310
|
+
if (eligible.length === 0) {
|
|
311
|
+
const endpointMsg = endpoint ? ` for endpoint '${endpoint}'` : "";
|
|
312
|
+
throw new RouteDockManifestError(
|
|
313
|
+
`No eligible assets found for mode '${mode}'${endpointMsg}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return eligible[0];
|
|
317
|
+
}
|
|
318
|
+
function isAssetEligible(manifest, assetIdentifier, mode, endpoint) {
|
|
319
|
+
const eligible = getEligibleAssets(manifest, mode, endpoint);
|
|
320
|
+
return eligible.some(
|
|
321
|
+
(asset) => asset.asset === assetIdentifier || asset.asset_contract === assetIdentifier
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
221
325
|
// src/schemas/routedock.schema.json
|
|
222
326
|
var routedock_schema_default = {
|
|
223
327
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -297,6 +401,14 @@ var routedock_schema_default = {
|
|
|
297
401
|
type: "string",
|
|
298
402
|
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
299
403
|
},
|
|
404
|
+
assets: {
|
|
405
|
+
type: "array",
|
|
406
|
+
minItems: 1,
|
|
407
|
+
items: {
|
|
408
|
+
$ref: "#/definitions/AssetConfig"
|
|
409
|
+
},
|
|
410
|
+
description: "Payment assets accepted by this provider. Each asset can be scoped to specific modes and endpoints. When present, assets[0] must match the root-level asset and asset_contract."
|
|
411
|
+
},
|
|
300
412
|
payee: {
|
|
301
413
|
type: "string",
|
|
302
414
|
description: "Stellar address (G...) that receives payments"
|
|
@@ -429,6 +541,38 @@ var routedock_schema_default = {
|
|
|
429
541
|
enum: ["x402", "mpp-charge", "mpp-session", "mpp-session-ws"],
|
|
430
542
|
description: "A supported RouteDock payment mode"
|
|
431
543
|
},
|
|
544
|
+
AssetConfig: {
|
|
545
|
+
type: "object",
|
|
546
|
+
description: "Payment asset configuration with optional mode and endpoint scoping",
|
|
547
|
+
required: ["asset", "asset_contract"],
|
|
548
|
+
additionalProperties: false,
|
|
549
|
+
properties: {
|
|
550
|
+
asset: {
|
|
551
|
+
type: "string",
|
|
552
|
+
description: "Asset ticker symbol (e.g. 'USDC' or 'XLM')"
|
|
553
|
+
},
|
|
554
|
+
asset_contract: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
557
|
+
},
|
|
558
|
+
modes: {
|
|
559
|
+
type: "array",
|
|
560
|
+
items: {
|
|
561
|
+
$ref: "#/definitions/PaymentMode"
|
|
562
|
+
},
|
|
563
|
+
uniqueItems: true,
|
|
564
|
+
description: "Optional: restrict this asset to specific payment modes. If omitted, available for all modes."
|
|
565
|
+
},
|
|
566
|
+
endpoints: {
|
|
567
|
+
type: "array",
|
|
568
|
+
items: {
|
|
569
|
+
type: "string"
|
|
570
|
+
},
|
|
571
|
+
uniqueItems: true,
|
|
572
|
+
description: "Optional: restrict this asset to specific endpoints (by name). If omitted, available for all endpoints."
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
},
|
|
432
576
|
PricingConfig: {
|
|
433
577
|
type: "object",
|
|
434
578
|
description: "Per-request pricing for x402 or mpp-charge modes",
|
|
@@ -574,7 +718,7 @@ var routedock_schema_default = {
|
|
|
574
718
|
|
|
575
719
|
// package.json
|
|
576
720
|
var package_default = {
|
|
577
|
-
version: "0.
|
|
721
|
+
version: "0.3.1"};
|
|
578
722
|
var MANIFEST_SIGNATURE_VERSION = "2";
|
|
579
723
|
function canonicalize(value) {
|
|
580
724
|
if (Array.isArray(value)) {
|
|
@@ -688,6 +832,7 @@ function assertEndpointActive(manifest, url, logger) {
|
|
|
688
832
|
const [key, descriptor] = deprecated;
|
|
689
833
|
const sunsetSuffix = descriptor.sunset_at ? `; sunset_at ${descriptor.sunset_at}` : "";
|
|
690
834
|
logger(
|
|
835
|
+
"warn",
|
|
691
836
|
`[RouteDock] WARNING: ${manifest.name} \u2192 ${pathname}; endpoint '${key}' is deprecated${sunsetSuffix}`
|
|
692
837
|
);
|
|
693
838
|
}
|
|
@@ -709,6 +854,27 @@ function assertManifestValid(manifest, baseUrl) {
|
|
|
709
854
|
}
|
|
710
855
|
}
|
|
711
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
|
+
}
|
|
712
878
|
}
|
|
713
879
|
var CACHE_TTL_MS = 6e4;
|
|
714
880
|
var DEFAULT_MANIFEST_CACHE_MAX_SIZE = 512;
|
|
@@ -888,16 +1054,16 @@ function logSelection(manifest, selection, log, deprecated) {
|
|
|
888
1054
|
const reason = selection.reason ? ` (${selection.reason})` : "";
|
|
889
1055
|
if (deprecated) {
|
|
890
1056
|
log(
|
|
1057
|
+
"warn",
|
|
891
1058
|
`[RouteDock] WARNING: ${manifest.name} \u2192 ${selection.mode}${reason}; selected deprecated mode because no active supported mode is available`
|
|
892
1059
|
);
|
|
893
1060
|
return;
|
|
894
1061
|
}
|
|
895
|
-
log(`[RouteDock] ${manifest.name} \u2192 ${selection.mode}${reason}`);
|
|
1062
|
+
log("info", `[RouteDock] ${manifest.name} \u2192 ${selection.mode}${reason}`);
|
|
896
1063
|
}
|
|
897
1064
|
function selectMode(manifest, options = {}) {
|
|
898
1065
|
const modes = manifest.modes;
|
|
899
|
-
const log = options.logger ??
|
|
900
|
-
});
|
|
1066
|
+
const log = options.logger ?? noopLogger;
|
|
901
1067
|
const deprecatedSet = new Set(manifest.deprecated_modes ?? []);
|
|
902
1068
|
if (options.forceMode) {
|
|
903
1069
|
if (!modes.includes(options.forceMode)) {
|
|
@@ -907,10 +1073,11 @@ function selectMode(manifest, options = {}) {
|
|
|
907
1073
|
}
|
|
908
1074
|
if (deprecatedSet.has(options.forceMode)) {
|
|
909
1075
|
log(
|
|
1076
|
+
"warn",
|
|
910
1077
|
`[RouteDock] WARNING: ${manifest.name} \u2192 ${options.forceMode} (forced); selected mode is deprecated`
|
|
911
1078
|
);
|
|
912
1079
|
} else {
|
|
913
|
-
log(`[RouteDock] ${manifest.name} \u2192 ${options.forceMode} (forced)`);
|
|
1080
|
+
log("info", `[RouteDock] ${manifest.name} \u2192 ${options.forceMode} (forced)`);
|
|
914
1081
|
}
|
|
915
1082
|
return options.forceMode;
|
|
916
1083
|
}
|
|
@@ -946,6 +1113,69 @@ function rankProvidersByLatency(manifests, targetRegion) {
|
|
|
946
1113
|
return 0;
|
|
947
1114
|
});
|
|
948
1115
|
}
|
|
1116
|
+
|
|
1117
|
+
// src/internal/payee.ts
|
|
1118
|
+
function resolvePayee(manifest, mode) {
|
|
1119
|
+
const override = manifest.pricing[mode]?.payee;
|
|
1120
|
+
return override ?? manifest.payee;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/client/challenge.ts
|
|
1124
|
+
function isRecord(value) {
|
|
1125
|
+
return typeof value === "object" && value !== null;
|
|
1126
|
+
}
|
|
1127
|
+
function isPositiveIntegerString(value) {
|
|
1128
|
+
return typeof value === "string" && /^[1-9]\d*$/.test(value);
|
|
1129
|
+
}
|
|
1130
|
+
function checkAmount(amount, maxAmount) {
|
|
1131
|
+
if (!isPositiveIntegerString(amount)) {
|
|
1132
|
+
return new RouteDockPolicyRejectError("challenge_amount_invalid");
|
|
1133
|
+
}
|
|
1134
|
+
let max;
|
|
1135
|
+
try {
|
|
1136
|
+
max = nulthSdk.usdcToStroops(maxAmount);
|
|
1137
|
+
} catch {
|
|
1138
|
+
return new RouteDockPolicyRejectError("challenge_amount_invalid");
|
|
1139
|
+
}
|
|
1140
|
+
if (BigInt(amount) > max) {
|
|
1141
|
+
return new RouteDockPolicyRejectError("challenge_amount_exceeds_manifest");
|
|
1142
|
+
}
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
function checkX402Accept(accept, manifest, network, maxAmount) {
|
|
1146
|
+
if (accept.network !== network) {
|
|
1147
|
+
return new RouteDockPolicyRejectError("challenge_network_mismatch");
|
|
1148
|
+
}
|
|
1149
|
+
if (accept.payTo !== resolvePayee(manifest, "x402")) {
|
|
1150
|
+
return new RouteDockPolicyRejectError("challenge_payee_mismatch");
|
|
1151
|
+
}
|
|
1152
|
+
if (accept.asset !== manifest.asset_contract) {
|
|
1153
|
+
return new RouteDockPolicyRejectError("challenge_asset_mismatch");
|
|
1154
|
+
}
|
|
1155
|
+
return checkAmount(accept.amount, maxAmount);
|
|
1156
|
+
}
|
|
1157
|
+
function filterX402Accepts(accepts, manifest, network, maxAmount) {
|
|
1158
|
+
return accepts.filter((accept) => checkX402Accept(accept, manifest, network, maxAmount) === null);
|
|
1159
|
+
}
|
|
1160
|
+
function checkChargeChallenge(request, manifest, network, maxAmount) {
|
|
1161
|
+
const methodDetails = request["methodDetails"];
|
|
1162
|
+
const rawNetwork = isRecord(methodDetails) ? methodDetails["network"] : void 0;
|
|
1163
|
+
const challengeNetwork = rawNetwork === void 0 ? "stellar:testnet" : rawNetwork;
|
|
1164
|
+
if (challengeNetwork !== network) {
|
|
1165
|
+
return new RouteDockPolicyRejectError("challenge_network_mismatch");
|
|
1166
|
+
}
|
|
1167
|
+
const recipient = request["recipient"];
|
|
1168
|
+
if (typeof recipient !== "string" || recipient !== resolvePayee(manifest, "mpp-charge")) {
|
|
1169
|
+
return new RouteDockPolicyRejectError("challenge_payee_mismatch");
|
|
1170
|
+
}
|
|
1171
|
+
const currency = request["currency"];
|
|
1172
|
+
if (typeof currency !== "string" || currency !== manifest.asset_contract) {
|
|
1173
|
+
return new RouteDockPolicyRejectError("challenge_asset_mismatch");
|
|
1174
|
+
}
|
|
1175
|
+
return checkAmount(request["amount"], maxAmount);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/client/x402Client.ts
|
|
949
1179
|
var CAIP2 = {
|
|
950
1180
|
testnet: "stellar:testnet",
|
|
951
1181
|
mainnet: "stellar:pubnet"
|
|
@@ -977,83 +1207,106 @@ var X402Client = class _X402Client {
|
|
|
977
1207
|
if (!pricing.facilitator) {
|
|
978
1208
|
throw new RouteDockManifestError("manifest.pricing.x402.facilitator missing");
|
|
979
1209
|
}
|
|
980
|
-
|
|
981
|
-
|
|
1210
|
+
const caip2 = CAIP2[this.network];
|
|
1211
|
+
const probe = await withRetry(async () => {
|
|
1212
|
+
let init2;
|
|
982
1213
|
try {
|
|
983
|
-
|
|
1214
|
+
init2 = await fetch(url, { headers: { "X-Preferred-Mode": "x402" } });
|
|
984
1215
|
} catch (err) {
|
|
985
1216
|
throw wrapFetchError(err, "x402 initial request");
|
|
986
1217
|
}
|
|
987
|
-
if (
|
|
988
|
-
if (!
|
|
1218
|
+
if (init2.status !== 402) {
|
|
1219
|
+
if (!init2.ok) {
|
|
989
1220
|
throw httpStatusToError(
|
|
990
|
-
`x402 initial request failed: HTTP ${
|
|
991
|
-
|
|
992
|
-
|
|
1221
|
+
`x402 initial request failed: HTTP ${init2.status}`,
|
|
1222
|
+
init2.status,
|
|
1223
|
+
init2
|
|
993
1224
|
);
|
|
994
1225
|
}
|
|
995
1226
|
let data2;
|
|
996
1227
|
try {
|
|
997
|
-
data2 = await
|
|
1228
|
+
data2 = await init2.json();
|
|
998
1229
|
} catch {
|
|
999
|
-
throw new RouteDockManifestError(
|
|
1230
|
+
throw new RouteDockManifestError(
|
|
1231
|
+
`Failed to parse JSON from non-402 response (HTTP ${init2.status})`
|
|
1232
|
+
);
|
|
1000
1233
|
}
|
|
1001
|
-
return {
|
|
1002
|
-
}
|
|
1003
|
-
const reqHeader = init.headers.get("X-Payment-Requirements");
|
|
1004
|
-
if (!reqHeader) {
|
|
1005
|
-
throw new RouteDockManifestError("402 response missing X-Payment-Requirements header");
|
|
1006
|
-
}
|
|
1007
|
-
let paymentRequired;
|
|
1008
|
-
try {
|
|
1009
|
-
paymentRequired = http.decodePaymentRequiredHeader(reqHeader);
|
|
1010
|
-
} catch (err) {
|
|
1011
|
-
throw new RouteDockManifestError("402 X-Payment-Requirements header is malformed", {
|
|
1012
|
-
cause: err
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
let paymentPayload;
|
|
1016
|
-
try {
|
|
1017
|
-
paymentPayload = await this.httpClient.createPaymentPayload(paymentRequired);
|
|
1018
|
-
} catch (err) {
|
|
1019
|
-
throw new RouteDockSignatureError(`x402 payment signing failed: ${String(err)}`, {
|
|
1020
|
-
cause: err
|
|
1021
|
-
});
|
|
1234
|
+
return { kind: "free", data: data2 };
|
|
1022
1235
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1236
|
+
return { kind: "payment-required", init: init2 };
|
|
1237
|
+
}, this.retryPolicy);
|
|
1238
|
+
if (probe.kind === "free") {
|
|
1239
|
+
return { data: probe.data, txHash: null, mode: "x402", amount: "0", timestamp: Date.now() };
|
|
1240
|
+
}
|
|
1241
|
+
const init = probe.init;
|
|
1242
|
+
const reqHeader = init.headers.get("X-Payment-Requirements");
|
|
1243
|
+
if (!reqHeader) {
|
|
1244
|
+
throw new RouteDockManifestError("402 response missing X-Payment-Requirements header");
|
|
1245
|
+
}
|
|
1246
|
+
let paymentRequired;
|
|
1247
|
+
try {
|
|
1248
|
+
paymentRequired = http.decodePaymentRequiredHeader(reqHeader);
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
throw new RouteDockManifestError("402 X-Payment-Requirements header is malformed", {
|
|
1251
|
+
cause: err
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const firstAccept = paymentRequired.accepts[0];
|
|
1255
|
+
if (!firstAccept) {
|
|
1256
|
+
throw new RouteDockManifestError("402 response carries no payment accepts");
|
|
1257
|
+
}
|
|
1258
|
+
const allowed = filterX402Accepts(paymentRequired.accepts, manifest, caip2, pricing.amount);
|
|
1259
|
+
if (allowed.length === 0) {
|
|
1260
|
+
throw checkX402Accept(firstAccept, manifest, caip2, pricing.amount);
|
|
1261
|
+
}
|
|
1262
|
+
let paymentPayload;
|
|
1263
|
+
try {
|
|
1264
|
+
paymentPayload = await this.httpClient.createPaymentPayload({
|
|
1265
|
+
...paymentRequired,
|
|
1266
|
+
accepts: allowed
|
|
1267
|
+
});
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
throw new RouteDockSignatureError(`x402 payment signing failed: ${String(err)}`, {
|
|
1270
|
+
cause: err
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
1274
|
+
const signedAmount = nulthSdk.stroopsToUsdc(BigInt(paymentPayload.accepted.amount));
|
|
1275
|
+
const settled = await withRetry(async () => {
|
|
1276
|
+
let response;
|
|
1025
1277
|
try {
|
|
1026
|
-
|
|
1278
|
+
response = await fetch(url, { headers: paymentHeaders });
|
|
1027
1279
|
} catch (err) {
|
|
1028
1280
|
throw wrapFetchError(err, "x402 settlement request");
|
|
1029
1281
|
}
|
|
1030
|
-
if (!
|
|
1031
|
-
if (
|
|
1282
|
+
if (!response.ok) {
|
|
1283
|
+
if (response.status >= 500 || response.status === 429 || response.status === 503) {
|
|
1032
1284
|
throw httpStatusToError(
|
|
1033
|
-
`x402 payment failed: HTTP ${
|
|
1034
|
-
|
|
1035
|
-
|
|
1285
|
+
`x402 payment failed: HTTP ${response.status}`,
|
|
1286
|
+
response.status,
|
|
1287
|
+
response
|
|
1036
1288
|
);
|
|
1037
1289
|
}
|
|
1038
|
-
throw new RouteDockManifestError(`x402 payment failed: HTTP ${
|
|
1039
|
-
}
|
|
1040
|
-
let txHash = null;
|
|
1041
|
-
const responseHeader = settled.headers.get("X-Payment-Response");
|
|
1042
|
-
if (responseHeader) {
|
|
1043
|
-
try {
|
|
1044
|
-
const settleResponse = http.decodePaymentResponseHeader(responseHeader);
|
|
1045
|
-
txHash = settleResponse.transaction ?? null;
|
|
1046
|
-
} catch {
|
|
1047
|
-
}
|
|
1290
|
+
throw new RouteDockManifestError(`x402 payment failed: HTTP ${response.status}`);
|
|
1048
1291
|
}
|
|
1049
|
-
|
|
1292
|
+
return response;
|
|
1293
|
+
}, this.retryPolicy);
|
|
1294
|
+
let txHash = null;
|
|
1295
|
+
const responseHeader = settled.headers.get("X-Payment-Response");
|
|
1296
|
+
if (responseHeader) {
|
|
1050
1297
|
try {
|
|
1051
|
-
|
|
1298
|
+
const settleResponse = http.decodePaymentResponseHeader(responseHeader);
|
|
1299
|
+
txHash = settleResponse.transaction ?? null;
|
|
1052
1300
|
} catch {
|
|
1053
|
-
throw new RouteDockManifestError("Failed to parse JSON from settled response");
|
|
1054
1301
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1302
|
+
}
|
|
1303
|
+
let data;
|
|
1304
|
+
try {
|
|
1305
|
+
data = await settled.json();
|
|
1306
|
+
} catch {
|
|
1307
|
+
throw new RouteDockManifestError("Failed to parse JSON from settled response");
|
|
1308
|
+
}
|
|
1309
|
+
return { data, txHash, mode: "x402", amount: signedAmount, timestamp: Date.now() };
|
|
1057
1310
|
}
|
|
1058
1311
|
};
|
|
1059
1312
|
var MppChargeClient = class {
|
|
@@ -1070,27 +1323,42 @@ var MppChargeClient = class {
|
|
|
1070
1323
|
if (!pricing) {
|
|
1071
1324
|
throw new RouteDockManifestError("manifest.pricing.mpp-charge missing");
|
|
1072
1325
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1326
|
+
const caip2 = this.network === "mainnet" ? "stellar:pubnet" : "stellar:testnet";
|
|
1327
|
+
let txHash = null;
|
|
1328
|
+
let signedAmount;
|
|
1329
|
+
let credential;
|
|
1330
|
+
const mppx = client$2.Mppx.create({
|
|
1331
|
+
polyfill: false,
|
|
1332
|
+
methods: [
|
|
1333
|
+
client$3.stellar.charge({
|
|
1334
|
+
keypair: this.keypair,
|
|
1335
|
+
mode: "pull",
|
|
1336
|
+
onProgress(event) {
|
|
1337
|
+
if (event.type === "paid") {
|
|
1338
|
+
txHash = event.hash;
|
|
1085
1339
|
}
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1340
|
+
}
|
|
1341
|
+
})
|
|
1342
|
+
],
|
|
1343
|
+
onChallenge: async (challenge, { createCredential }) => {
|
|
1344
|
+
const request = challenge.request;
|
|
1345
|
+
const rejection = checkChargeChallenge(request, manifest, caip2, pricing.amount);
|
|
1346
|
+
if (rejection) throw rejection;
|
|
1347
|
+
const amount = request["amount"];
|
|
1348
|
+
if (typeof amount !== "string") {
|
|
1349
|
+
throw new RouteDockPolicyRejectError("challenge_amount_invalid");
|
|
1350
|
+
}
|
|
1351
|
+
signedAmount = nulthSdk.stroopsToUsdc(BigInt(amount));
|
|
1352
|
+
credential ??= await createCredential();
|
|
1353
|
+
return credential;
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
return withRetry(async () => {
|
|
1089
1357
|
let response;
|
|
1090
1358
|
try {
|
|
1091
|
-
response = await mppx.fetch(url);
|
|
1359
|
+
response = credential ? await mppx.rawFetch(url, { headers: { Authorization: credential } }) : await mppx.fetch(url);
|
|
1092
1360
|
} catch (err) {
|
|
1093
|
-
throw
|
|
1361
|
+
throw wrapMppError(err, "MPP charge request");
|
|
1094
1362
|
}
|
|
1095
1363
|
if (!response.ok) {
|
|
1096
1364
|
if (response.status >= 500 || response.status === 429 || response.status === 503) {
|
|
@@ -1111,7 +1379,13 @@ var MppChargeClient = class {
|
|
|
1111
1379
|
{ cause }
|
|
1112
1380
|
);
|
|
1113
1381
|
}
|
|
1114
|
-
return {
|
|
1382
|
+
return {
|
|
1383
|
+
data,
|
|
1384
|
+
txHash,
|
|
1385
|
+
mode: "mpp-charge",
|
|
1386
|
+
amount: signedAmount ?? pricing.amount,
|
|
1387
|
+
timestamp: Date.now()
|
|
1388
|
+
};
|
|
1115
1389
|
}, this.retryPolicy);
|
|
1116
1390
|
}
|
|
1117
1391
|
};
|
|
@@ -1150,16 +1424,18 @@ function resolveStoreNetworkId(network) {
|
|
|
1150
1424
|
return network === mpp.STELLAR_PUBNET ? mpp.STELLAR_PUBNET : mpp.STELLAR_TESTNET;
|
|
1151
1425
|
}
|
|
1152
1426
|
var MppSessionClient = class {
|
|
1153
|
-
constructor(keypair, network, retryPolicy, webSocketFactory = defaultWebSocketFactory) {
|
|
1427
|
+
constructor(keypair, network, retryPolicy, webSocketFactory = defaultWebSocketFactory, logger = consoleLogger) {
|
|
1154
1428
|
this.keypair = keypair;
|
|
1155
1429
|
this.network = network;
|
|
1156
1430
|
this.retryPolicy = retryPolicy;
|
|
1157
1431
|
this.webSocketFactory = webSocketFactory;
|
|
1432
|
+
this.logger = logger;
|
|
1158
1433
|
}
|
|
1159
1434
|
keypair;
|
|
1160
1435
|
network;
|
|
1161
1436
|
retryPolicy;
|
|
1162
1437
|
webSocketFactory;
|
|
1438
|
+
logger;
|
|
1163
1439
|
async openSession(url, manifest, commitmentSecret, options, onSpend) {
|
|
1164
1440
|
const mode = options?.mode ?? "mpp-session";
|
|
1165
1441
|
const pricing = manifest.pricing[mode];
|
|
@@ -1384,7 +1660,7 @@ var MppSessionClient = class {
|
|
|
1384
1660
|
if (abortController.signal.aborted) {
|
|
1385
1661
|
throw err;
|
|
1386
1662
|
}
|
|
1387
|
-
throw
|
|
1663
|
+
throw wrapMppError(err, "Voucher request");
|
|
1388
1664
|
}
|
|
1389
1665
|
if (!resp.ok) {
|
|
1390
1666
|
if (resp.status >= 500 || resp.status === 429 || resp.status === 503) {
|
|
@@ -1677,9 +1953,10 @@ var MppSessionClient = class {
|
|
|
1677
1953
|
emit("session:timeout", { maxDurationMs });
|
|
1678
1954
|
void handle.close().catch((error) => {
|
|
1679
1955
|
emit("session:close-failed", { maxDurationMs, error });
|
|
1680
|
-
|
|
1956
|
+
this.logger(
|
|
1957
|
+
"warn",
|
|
1681
1958
|
`RouteDock: maxDuration auto-close failed after ${maxDurationMs}ms \u2014 the channel may still hold collateral; retry close() or call requestRefund().`,
|
|
1682
|
-
error
|
|
1959
|
+
{ error }
|
|
1683
1960
|
);
|
|
1684
1961
|
});
|
|
1685
1962
|
}, maxDurationMs);
|
|
@@ -1729,7 +2006,7 @@ var MppSessionClient = class {
|
|
|
1729
2006
|
);
|
|
1730
2007
|
onSigned?.();
|
|
1731
2008
|
} catch (err) {
|
|
1732
|
-
throw
|
|
2009
|
+
throw wrapMppError(err, "Voucher credential");
|
|
1733
2010
|
}
|
|
1734
2011
|
const wsUrl = url.replace(/^http/, "ws");
|
|
1735
2012
|
const queue = [];
|
|
@@ -1906,11 +2183,6 @@ function encodeAuthSignature(proof) {
|
|
|
1906
2183
|
const payload = { nulth: "zk-v1", proof };
|
|
1907
2184
|
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
1908
2185
|
}
|
|
1909
|
-
function usdcToStroops2(amount) {
|
|
1910
|
-
const [whole = "0", frac = ""] = amount.split(".");
|
|
1911
|
-
const padded = (frac + "0000000").slice(0, 7);
|
|
1912
|
-
return BigInt(whole) * 10000000n + BigInt(padded);
|
|
1913
|
-
}
|
|
1914
2186
|
var NulthClient = class {
|
|
1915
2187
|
constructor(config) {
|
|
1916
2188
|
this.config = config;
|
|
@@ -1926,7 +2198,10 @@ var NulthClient = class {
|
|
|
1926
2198
|
`NulthClient cannot use the insecure mock prover on mainnet; a production prover is required (network must be 'testnet', got ${String(config.network)})`
|
|
1927
2199
|
);
|
|
1928
2200
|
}
|
|
1929
|
-
|
|
2201
|
+
resolveLogger(config.logger)(
|
|
2202
|
+
"warn",
|
|
2203
|
+
"NulthClient is using the insecure mock prover; proofs are not cryptographically sound"
|
|
2204
|
+
);
|
|
1930
2205
|
}
|
|
1931
2206
|
config;
|
|
1932
2207
|
policy;
|
|
@@ -1978,6 +2253,12 @@ var NulthClient = class {
|
|
|
1978
2253
|
return proof;
|
|
1979
2254
|
}
|
|
1980
2255
|
enforcePolicy(context) {
|
|
2256
|
+
if (context.amountStroops < 0n) {
|
|
2257
|
+
throw new RangeError(`Invalid amountStroops: ${context.amountStroops}`);
|
|
2258
|
+
}
|
|
2259
|
+
if (!Number.isSafeInteger(context.ledgerSequence) || context.ledgerSequence < 0) {
|
|
2260
|
+
throw new RangeError(`Invalid ledgerSequence: ${context.ledgerSequence}`);
|
|
2261
|
+
}
|
|
1981
2262
|
if (this.policy.expiryLedger !== void 0 && context.ledgerSequence > this.policy.expiryLedger) {
|
|
1982
2263
|
throw new NulthPolicyError("session_expired", "Nulth session expired");
|
|
1983
2264
|
}
|
|
@@ -2012,14 +2293,14 @@ function paymentContextFromManifest(manifest, mode, ledgerSequence) {
|
|
|
2012
2293
|
const pricing = manifest.pricing[mode];
|
|
2013
2294
|
if (!pricing) throw new Error(`manifest.pricing.${mode} missing`);
|
|
2014
2295
|
return {
|
|
2015
|
-
payee: manifest.payee,
|
|
2016
|
-
amountStroops:
|
|
2296
|
+
payee: pricing.payee ?? manifest.payee,
|
|
2297
|
+
amountStroops: nulthSdk.usdcToStroops(pricing.amount),
|
|
2017
2298
|
assetContract: manifest.asset_contract,
|
|
2018
2299
|
ledgerSequence
|
|
2019
2300
|
};
|
|
2020
2301
|
}
|
|
2021
2302
|
function createPolicyState(input) {
|
|
2022
|
-
const dailyCapStroops =
|
|
2303
|
+
const dailyCapStroops = nulthSdk.usdcToStroops(input.dailyCapUsdc);
|
|
2023
2304
|
return {
|
|
2024
2305
|
dailyCapStroops,
|
|
2025
2306
|
allowlistCommitment: commitAllowlist(input.allowedPayees, input.witnessSecret),
|
|
@@ -2121,7 +2402,8 @@ var InMemorySpendStore = class {
|
|
|
2121
2402
|
state = null;
|
|
2122
2403
|
constructor(options = {}) {
|
|
2123
2404
|
if (options.warn !== false) {
|
|
2124
|
-
|
|
2405
|
+
resolveLogger(options.logger)(
|
|
2406
|
+
"warn",
|
|
2125
2407
|
"[RouteDock] Using in-memory SpendStore: the daily spend cap is NOT durable and resets on every process restart. Inject a persistent SpendStore via RouteDockClientConfig.spendStore for production safety."
|
|
2126
2408
|
);
|
|
2127
2409
|
}
|
|
@@ -2211,7 +2493,10 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2211
2493
|
this.network = config.network;
|
|
2212
2494
|
this.spendCap = config.spendCap ? normalizeSpendCap(config.spendCap) : void 0;
|
|
2213
2495
|
this.retryPolicy = config.retryPolicy;
|
|
2214
|
-
this.spendStore = config.spendStore ?? new InMemorySpendStore({
|
|
2496
|
+
this.spendStore = config.spendStore ?? new InMemorySpendStore({
|
|
2497
|
+
warn: !!config.spendCap,
|
|
2498
|
+
...config.logger && { logger: config.logger }
|
|
2499
|
+
});
|
|
2215
2500
|
this.logger = config.logger;
|
|
2216
2501
|
this.manifestTimeoutMs = config.manifestTimeoutMs;
|
|
2217
2502
|
this.expectedPayee = config.expectedPayee;
|
|
@@ -2223,9 +2508,10 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2223
2508
|
_secrets.set(this, config.commitmentSecret);
|
|
2224
2509
|
}
|
|
2225
2510
|
const secretKey = this.keypair.secret();
|
|
2226
|
-
this.
|
|
2227
|
-
this.
|
|
2228
|
-
this.
|
|
2511
|
+
const retryPolicy = this.logger ? { ...this.retryPolicy, logger: this.logger } : this.retryPolicy;
|
|
2512
|
+
this.x402 = new X402Client(secretKey, this.network, retryPolicy);
|
|
2513
|
+
this.charge = new MppChargeClient(this.keypair, this.network, retryPolicy);
|
|
2514
|
+
this.session = new MppSessionClient(this.keypair, this.network, retryPolicy, void 0, this.logger);
|
|
2229
2515
|
}
|
|
2230
2516
|
/** Fetch manifest and select mode — shared by pay() and estimateCost(). */
|
|
2231
2517
|
async _resolveManifest(url, options) {
|
|
@@ -2254,12 +2540,13 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2254
2540
|
* call before committing to a payment — for approval gates and manual
|
|
2255
2541
|
* trustline remediation.
|
|
2256
2542
|
*/
|
|
2257
|
-
async preflight(manifest) {
|
|
2543
|
+
async preflight(manifest, mode, endpoint) {
|
|
2258
2544
|
assertManifestValid(manifest);
|
|
2259
|
-
|
|
2545
|
+
const selectedAsset = mode ? selectAsset(manifest, mode, endpoint).asset : manifest.asset;
|
|
2546
|
+
await this._checkTrustline(manifest, selectedAsset);
|
|
2260
2547
|
return {
|
|
2261
2548
|
hasTrustline: true,
|
|
2262
|
-
asset:
|
|
2549
|
+
asset: selectedAsset,
|
|
2263
2550
|
modes: manifest.modes,
|
|
2264
2551
|
...manifest.regions && { regions: manifest.regions },
|
|
2265
2552
|
...manifest.latency_hints && { latency_hints: manifest.latency_hints },
|
|
@@ -2273,8 +2560,8 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2273
2560
|
* trustline is missing. Results are cached per (account, asset) so the
|
|
2274
2561
|
* per-payment overhead is one RPC call amortized to zero after the first.
|
|
2275
2562
|
*/
|
|
2276
|
-
async _checkTrustline(manifest) {
|
|
2277
|
-
const cacheKey = `${this.network}:${this.keypair.publicKey()}:${
|
|
2563
|
+
async _checkTrustline(manifest, targetAsset = manifest.asset) {
|
|
2564
|
+
const cacheKey = `${this.network}:${this.keypair.publicKey()}:${targetAsset}`;
|
|
2278
2565
|
const cached = _RouteDockClient._trustlineCache.get(cacheKey);
|
|
2279
2566
|
if (cached && Date.now() < cached.expiresAt) return;
|
|
2280
2567
|
const horizonUrl = this.network === "testnet" ? "https://horizon-testnet.stellar.org" : "https://horizon.stellar.org";
|
|
@@ -2282,13 +2569,13 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2282
2569
|
try {
|
|
2283
2570
|
const account = await server.loadAccount(this.keypair.publicKey());
|
|
2284
2571
|
const balances = account.balances;
|
|
2285
|
-
const expectedIssuer = getAssetIssuer(
|
|
2572
|
+
const expectedIssuer = getAssetIssuer(targetAsset, this.network);
|
|
2286
2573
|
const hasTrustline = balances.some(
|
|
2287
|
-
(b) => typeof b === "object" && b !== null && "asset_code" in b && b.asset_code ===
|
|
2574
|
+
(b) => typeof b === "object" && b !== null && (targetAsset === "XLM" && b.asset_type === "native" || "asset_code" in b && b.asset_code === targetAsset && (!expectedIssuer || b.asset_issuer === expectedIssuer))
|
|
2288
2575
|
);
|
|
2289
2576
|
if (!hasTrustline) {
|
|
2290
|
-
const remediation = expectedIssuer ? `Run: stellar tx new --source ${this.keypair.publicKey()} --network ${this.network} change-trust --asset ${
|
|
2291
|
-
throw new RouteDockTrustlineError(
|
|
2577
|
+
const remediation = expectedIssuer ? `Run: stellar tx new --source ${this.keypair.publicKey()} --network ${this.network} change-trust --asset ${targetAsset}:${expectedIssuer} --limit 100000` : `Establish a trustline for ${targetAsset} with the appropriate issuer on ${this.network}`;
|
|
2578
|
+
throw new RouteDockTrustlineError(targetAsset, expectedIssuer || "unknown", remediation);
|
|
2292
2579
|
}
|
|
2293
2580
|
_RouteDockClient._trustlineCache.set(cacheKey, {
|
|
2294
2581
|
exists: true,
|
|
@@ -2297,7 +2584,8 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2297
2584
|
} catch (err) {
|
|
2298
2585
|
if (err instanceof RouteDockTrustlineError) throw err;
|
|
2299
2586
|
this.logger?.(
|
|
2300
|
-
|
|
2587
|
+
"warn",
|
|
2588
|
+
`[RouteDock] Trustline preflight: could not verify trustline for ${targetAsset} \u2014 continuing`
|
|
2301
2589
|
);
|
|
2302
2590
|
}
|
|
2303
2591
|
}
|
|
@@ -2305,18 +2593,18 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2305
2593
|
* Pay for one request at `url`. Fetches manifest, selects payment mode,
|
|
2306
2594
|
* runs trustline preflight, reserves local spend cap BEFORE executing the
|
|
2307
2595
|
* payment, then commits the spend record on success. Rolls back the
|
|
2308
|
-
* reservation if the on-chain payment fails
|
|
2309
|
-
*
|
|
2596
|
+
* reservation if the on-chain payment fails, and also when the call
|
|
2597
|
+
* completes without paying anything (an x402 endpoint answering a plain
|
|
2598
|
+
* 200 with no challenge). Concurrent pay() calls are serialized through a
|
|
2599
|
+
* per-instance mutex to prevent spend-cap overrun.
|
|
2310
2600
|
*/
|
|
2311
2601
|
async pay(url, options) {
|
|
2312
2602
|
const baseUrl = new URL(url).origin;
|
|
2313
2603
|
const manifest = await fetchManifest(baseUrl, this.retryPolicy, this.manifestTimeoutMs, this.expectedPayee);
|
|
2314
2604
|
assertEndpointActive(manifest, url, this.logger);
|
|
2315
2605
|
const mode = selectMode(manifest, { ...options, ...this.logger && { logger: this.logger } });
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
return this._payWithNulthVault(url, manifest, mode);
|
|
2319
|
-
}
|
|
2606
|
+
const selectedAsset = selectAsset(manifest, mode, url).asset;
|
|
2607
|
+
await this._checkTrustline(manifest, selectedAsset);
|
|
2320
2608
|
let amount;
|
|
2321
2609
|
switch (mode) {
|
|
2322
2610
|
case "x402":
|
|
@@ -2336,22 +2624,30 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2336
2624
|
const reserveId = await this._checkAndReserveSpend(amount, baseUrl);
|
|
2337
2625
|
let result;
|
|
2338
2626
|
try {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2627
|
+
if (this.vault?.mode === "nulth") {
|
|
2628
|
+
result = await this._payWithNulthVault(url, manifest, mode);
|
|
2629
|
+
} else {
|
|
2630
|
+
switch (mode) {
|
|
2631
|
+
case "x402":
|
|
2632
|
+
result = await this.x402.pay(url, manifest);
|
|
2633
|
+
break;
|
|
2634
|
+
case "mpp-charge":
|
|
2635
|
+
result = await this.charge.pay(url, manifest);
|
|
2636
|
+
break;
|
|
2637
|
+
default:
|
|
2638
|
+
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
2639
|
+
}
|
|
2348
2640
|
}
|
|
2349
2641
|
} catch (err) {
|
|
2350
2642
|
await this._rollbackSpend(reserveId).catch(() => {
|
|
2351
2643
|
});
|
|
2352
2644
|
throw err;
|
|
2353
2645
|
}
|
|
2354
|
-
|
|
2646
|
+
if (result.txHash === null && result.amount === "0") {
|
|
2647
|
+
await this._rollbackSpend(reserveId);
|
|
2648
|
+
} else {
|
|
2649
|
+
await this._commitSpend(reserveId);
|
|
2650
|
+
}
|
|
2355
2651
|
return result;
|
|
2356
2652
|
}
|
|
2357
2653
|
/** Nulth ZK vault path — proof built off-chain, attached as auth signature */
|
|
@@ -2401,9 +2697,10 @@ var RouteDockClient = class _RouteDockClient {
|
|
|
2401
2697
|
default:
|
|
2402
2698
|
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
2403
2699
|
}
|
|
2700
|
+
const selectedAsset = selectAsset(manifest, mode, url).asset;
|
|
2404
2701
|
return {
|
|
2405
2702
|
amount,
|
|
2406
|
-
asset:
|
|
2703
|
+
asset: selectedAsset,
|
|
2407
2704
|
mode,
|
|
2408
2705
|
manifest,
|
|
2409
2706
|
...manifest.regions && { regions: manifest.regions },
|
|
@@ -2816,20 +3113,30 @@ exports.assertManifestValid = assertManifestValid;
|
|
|
2816
3113
|
exports.assertNulthVaultManifest = assertNulthVaultManifest;
|
|
2817
3114
|
exports.backoffDelayMs = backoffDelayMs;
|
|
2818
3115
|
exports.configureManifestCache = configureManifestCache;
|
|
3116
|
+
exports.consoleLogger = consoleLogger;
|
|
3117
|
+
exports.createConsoleLogger = createConsoleLogger;
|
|
2819
3118
|
exports.createNulthSigner = createNulthSigner;
|
|
2820
3119
|
exports.createPolicyState = createPolicyState;
|
|
2821
3120
|
exports.decodeAuthSignature = decodeAuthSignature;
|
|
2822
3121
|
exports.fetchLedgerSequence = fetchLedgerSequence;
|
|
2823
3122
|
exports.fetchManifest = fetchManifest;
|
|
3123
|
+
exports.getEligibleAssets = getEligibleAssets;
|
|
2824
3124
|
exports.httpStatusToError = httpStatusToError;
|
|
2825
3125
|
exports.invalidateManifest = invalidateManifest;
|
|
3126
|
+
exports.isAssetEligible = isAssetEligible;
|
|
3127
|
+
exports.noopLogger = noopLogger;
|
|
3128
|
+
exports.normalizeManifestAssets = normalizeManifestAssets;
|
|
2826
3129
|
exports.parseRetryAfterMs = parseRetryAfterMs;
|
|
2827
3130
|
exports.paymentContextFromManifest = paymentContextFromManifest;
|
|
2828
3131
|
exports.prepareNulthSigner = prepareNulthSigner;
|
|
2829
3132
|
exports.rankProvidersByLatency = rankProvidersByLatency;
|
|
3133
|
+
exports.resolveAssetContract = resolveAssetContract;
|
|
3134
|
+
exports.resolveLogger = resolveLogger;
|
|
3135
|
+
exports.selectAsset = selectAsset;
|
|
2830
3136
|
exports.selectMode = selectMode;
|
|
2831
3137
|
exports.setNulthPaymentContext = setNulthPaymentContext;
|
|
2832
3138
|
exports.usdcToMicros = usdcToMicros;
|
|
2833
3139
|
exports.usdcToUnits = usdcToUnits;
|
|
2834
3140
|
exports.withRetry = withRetry;
|
|
2835
3141
|
exports.wrapFetchError = wrapFetchError;
|
|
3142
|
+
exports.wrapMppError = wrapMppError;
|