@molpha/sdk 0.2.0-dev-20260911175245 → 0.2.0-dev-20260913100330
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 +147 -17
- package/dist/index.d.ts +228 -12
- package/dist/index.js +506 -11
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/{wallet-udLZ5Iy0.d.ts → wallet-DwdBZgl1.d.ts} +78 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -77,6 +77,20 @@ function assertUint(value, max, label) {
|
|
|
77
77
|
throw new RangeError(`${label} out of range: ${value}`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
function bytesToBase64(bytes) {
|
|
81
|
+
let binary = "";
|
|
82
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
83
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
84
|
+
}
|
|
85
|
+
return btoa(binary);
|
|
86
|
+
}
|
|
87
|
+
function base64ToBytes(value) {
|
|
88
|
+
const padded = value.length % 4 === 0 ? value : value + "=".repeat(4 - value.length % 4);
|
|
89
|
+
const binary = atob(padded);
|
|
90
|
+
const out = new Uint8Array(binary.length);
|
|
91
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
80
94
|
|
|
81
95
|
// src/core/apiconfig.ts
|
|
82
96
|
function compareCodeUnits(a, b) {
|
|
@@ -308,6 +322,268 @@ function validateEncryptionRecipients(selectedNodes) {
|
|
|
308
322
|
return { index: node.index, signingKey };
|
|
309
323
|
});
|
|
310
324
|
}
|
|
325
|
+
var typeHash = (signature) => keccak_256(utf8(signature));
|
|
326
|
+
var EIP712_DOMAIN_TYPEHASH = typeHash(
|
|
327
|
+
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
|
|
328
|
+
);
|
|
329
|
+
var TRANSFER_WITH_AUTHORIZATION_TYPEHASH = typeHash(
|
|
330
|
+
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
|
|
331
|
+
);
|
|
332
|
+
function word(bytes) {
|
|
333
|
+
if (bytes.length > 32) throw new RangeError("ABI word overflow");
|
|
334
|
+
const out = new Uint8Array(32);
|
|
335
|
+
out.set(bytes, 32 - bytes.length);
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
function addressWord(address3) {
|
|
339
|
+
const bytes = hexToBytes(address3);
|
|
340
|
+
if (bytes.length !== 20) throw new RangeError(`Not a 20-byte address: ${address3}`);
|
|
341
|
+
return word(bytes);
|
|
342
|
+
}
|
|
343
|
+
function uintWord(value) {
|
|
344
|
+
const n = typeof value === "bigint" ? value : BigInt(value);
|
|
345
|
+
if (n < 0n) throw new RangeError(`Not an unsigned integer: ${value}`);
|
|
346
|
+
const hex = n.toString(16);
|
|
347
|
+
return word(hexToBytes(hex.length % 2 === 0 ? hex : "0" + hex));
|
|
348
|
+
}
|
|
349
|
+
function bytes32Word(value) {
|
|
350
|
+
const bytes = hexToBytes(value);
|
|
351
|
+
if (bytes.length !== 32) throw new RangeError(`Not a 32-byte value: ${value}`);
|
|
352
|
+
return bytes;
|
|
353
|
+
}
|
|
354
|
+
function domainSeparator(domain) {
|
|
355
|
+
return keccak_256(
|
|
356
|
+
concatBytes(
|
|
357
|
+
EIP712_DOMAIN_TYPEHASH,
|
|
358
|
+
keccak_256(utf8(domain.name)),
|
|
359
|
+
keccak_256(utf8(domain.version)),
|
|
360
|
+
uintWord(BigInt(domain.chainId)),
|
|
361
|
+
addressWord(domain.verifyingContract)
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
function transferWithAuthorizationHash(auth) {
|
|
366
|
+
return keccak_256(
|
|
367
|
+
concatBytes(
|
|
368
|
+
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
|
|
369
|
+
addressWord(auth.from),
|
|
370
|
+
addressWord(auth.to),
|
|
371
|
+
uintWord(auth.value),
|
|
372
|
+
uintWord(auth.validAfter),
|
|
373
|
+
uintWord(auth.validBefore),
|
|
374
|
+
bytes32Word(auth.nonce)
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
function transferWithAuthorizationDigest(domain, auth) {
|
|
379
|
+
return keccak_256(
|
|
380
|
+
concatBytes(
|
|
381
|
+
Uint8Array.from([25, 1]),
|
|
382
|
+
domainSeparator(domain),
|
|
383
|
+
transferWithAuthorizationHash(auth)
|
|
384
|
+
)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
function toChecksumAddress(address3) {
|
|
388
|
+
const lower = bytesToHex(hexToBytes(address3));
|
|
389
|
+
if (lower.length !== 40) throw new RangeError(`Not a 20-byte address: ${address3}`);
|
|
390
|
+
const digest = bytesToHex(keccak_256(utf8(lower)));
|
|
391
|
+
let out = "0x";
|
|
392
|
+
for (let i = 0; i < lower.length; i++) {
|
|
393
|
+
out += parseInt(digest[i], 16) >= 8 ? lower[i].toUpperCase() : lower[i];
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
function evmAddressFromPrivateKey(privateKey) {
|
|
398
|
+
const key = typeof privateKey === "string" ? hexToBytes(privateKey) : privateKey;
|
|
399
|
+
if (key.length !== 32) throw new RangeError("EVM private key must be 32 bytes");
|
|
400
|
+
const uncompressed = secp256k1.getPublicKey(key, false);
|
|
401
|
+
return toChecksumAddress(bytesToHex0x(keccak_256(uncompressed.subarray(1)).subarray(12)));
|
|
402
|
+
}
|
|
403
|
+
function createEvmSignerFromPrivateKey(privateKey) {
|
|
404
|
+
const key = typeof privateKey === "string" ? hexToBytes(privateKey) : privateKey;
|
|
405
|
+
const address3 = evmAddressFromPrivateKey(key);
|
|
406
|
+
return {
|
|
407
|
+
address: address3,
|
|
408
|
+
async signDigest(digest) {
|
|
409
|
+
if (digest.length !== 32) throw new RangeError("Digest must be 32 bytes");
|
|
410
|
+
const signed = secp256k1.sign(digest, key, { prehash: false, format: "recovered" });
|
|
411
|
+
const out = new Uint8Array(65);
|
|
412
|
+
out.set(signed.subarray(1), 0);
|
|
413
|
+
out[64] = 27 + signed[0];
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/gateway/x402.ts
|
|
420
|
+
var NETWORKS = {
|
|
421
|
+
"eip155:8453": { chainId: 8453, usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
|
|
422
|
+
"eip155:84532": { chainId: 84532, usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" },
|
|
423
|
+
// x402 v1 network names.
|
|
424
|
+
base: { chainId: 8453, usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
|
|
425
|
+
"base-sepolia": { chainId: 84532, usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" }
|
|
426
|
+
};
|
|
427
|
+
var UpstreamPaymentRequiredError = class extends Error {
|
|
428
|
+
constructor(quote, message) {
|
|
429
|
+
super(
|
|
430
|
+
message ?? `API source requires payment for up to ${quote.eligibleSetSize} fetches: ${quote.resource}`
|
|
431
|
+
);
|
|
432
|
+
this.quote = quote;
|
|
433
|
+
this.name = "UpstreamPaymentRequiredError";
|
|
434
|
+
}
|
|
435
|
+
quote;
|
|
436
|
+
};
|
|
437
|
+
function eligibleSetSize(signaturesRequired, registry) {
|
|
438
|
+
return effectiveSelectionSize(
|
|
439
|
+
signaturesRequired,
|
|
440
|
+
registry.redundancyBuffer,
|
|
441
|
+
registry.nodeCount
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
function parseUpstreamQuote(envelope) {
|
|
445
|
+
if (!envelope || typeof envelope !== "object") return null;
|
|
446
|
+
const extensions = envelope.extensions;
|
|
447
|
+
if (!extensions || typeof extensions !== "object") return null;
|
|
448
|
+
const quote = extensions.upstream;
|
|
449
|
+
if (!quote || typeof quote !== "object") return null;
|
|
450
|
+
return typeof quote.eligibleSetSize === "number" ? quote : null;
|
|
451
|
+
}
|
|
452
|
+
function selectTerms(envelope, resource, assetDomain) {
|
|
453
|
+
const version = envelope.x402Version === 1 ? 1 : 2;
|
|
454
|
+
const offers = Array.isArray(envelope.accepts) ? envelope.accepts : [];
|
|
455
|
+
if (offers.length === 0) {
|
|
456
|
+
throw new Error(`Source 402 for ${resource} advertised no payment terms`);
|
|
457
|
+
}
|
|
458
|
+
const supported = offers.find(
|
|
459
|
+
(o) => o.scheme === "exact" && NETWORKS[String(o.network)] !== void 0
|
|
460
|
+
);
|
|
461
|
+
if (!supported) {
|
|
462
|
+
const seen = offers.map((o) => `${o.scheme}/${o.network}`).join(", ");
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Source ${resource} requires an unsupported payment scheme or network (${seen}); this SDK signs exact payments on Base USDC only`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const network = NETWORKS[String(supported.network)];
|
|
468
|
+
const asset = String(supported.asset ?? "");
|
|
469
|
+
if (asset.toLowerCase() !== network.usdc.toLowerCase()) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
`Source ${resource} asks to be paid in ${asset} on ${supported.network}; this SDK signs USDC (${network.usdc}) only`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const domain = assetDomain ?? (supported.extra?.name && supported.extra?.version ? { name: String(supported.extra.name), version: String(supported.extra.version) } : void 0);
|
|
475
|
+
if (!domain) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
`Source ${resource} omitted extra.name/extra.version for ${asset}; supply sourcePayment.assetDomain to sign against it`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
const amount = String(supported.amount ?? supported.maxAmountRequired ?? "");
|
|
481
|
+
if (!/^\d+$/.test(amount)) {
|
|
482
|
+
throw new Error(`Source ${resource} quoted a non-integer price: ${amount}`);
|
|
483
|
+
}
|
|
484
|
+
const payTo = String(supported.payTo ?? "");
|
|
485
|
+
if (!payTo) throw new Error(`Source ${resource} omitted payTo`);
|
|
486
|
+
return {
|
|
487
|
+
x402Version: version,
|
|
488
|
+
// Echoed verbatim in the signed payload, so it must stay the source's own object.
|
|
489
|
+
requirements: supported,
|
|
490
|
+
network: String(supported.network),
|
|
491
|
+
chainId: network.chainId,
|
|
492
|
+
asset,
|
|
493
|
+
payTo,
|
|
494
|
+
amount,
|
|
495
|
+
maxTimeoutSeconds: Number(supported.maxTimeoutSeconds ?? 60),
|
|
496
|
+
domain,
|
|
497
|
+
resource
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function decodeEnvelope(headerValue, body) {
|
|
501
|
+
if (headerValue) {
|
|
502
|
+
try {
|
|
503
|
+
return JSON.parse(
|
|
504
|
+
new TextDecoder().decode(base64ToBytes(headerValue))
|
|
505
|
+
);
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return JSON.parse(body);
|
|
510
|
+
}
|
|
511
|
+
function validateSuppliedTerms(terms, resource, assetDomain) {
|
|
512
|
+
return selectTerms(
|
|
513
|
+
{
|
|
514
|
+
x402Version: terms.x402Version,
|
|
515
|
+
accepts: [terms.requirements]
|
|
516
|
+
},
|
|
517
|
+
resource,
|
|
518
|
+
assetDomain
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
async function probeSource(apiConfig, options = {}) {
|
|
522
|
+
const resolved = options.secrets ? resolveAPIConfig(apiConfig, options.secrets) : apiConfig;
|
|
523
|
+
const init = {
|
|
524
|
+
method: resolved.method ?? "GET",
|
|
525
|
+
headers: resolved.headers ?? {}
|
|
526
|
+
};
|
|
527
|
+
let res;
|
|
528
|
+
if (options.timeoutMs === void 0) {
|
|
529
|
+
res = await fetch(resolved.url, init);
|
|
530
|
+
} else {
|
|
531
|
+
const controller = new AbortController();
|
|
532
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
533
|
+
try {
|
|
534
|
+
res = await fetch(resolved.url, { ...init, signal: controller.signal });
|
|
535
|
+
} finally {
|
|
536
|
+
clearTimeout(timer);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (res.status !== 402) return null;
|
|
540
|
+
return selectTerms(
|
|
541
|
+
decodeEnvelope(res.headers.get("PAYMENT-REQUIRED"), await res.text()),
|
|
542
|
+
apiConfig.url,
|
|
543
|
+
options.assetDomain
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
async function signSourcePayments(terms, signer, count) {
|
|
547
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
548
|
+
throw new RangeError(`Cannot sign ${count} source payments`);
|
|
549
|
+
}
|
|
550
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
551
|
+
const validAfter = String(now - 600);
|
|
552
|
+
const validBefore = String(now + Math.max(terms.maxTimeoutSeconds, 60));
|
|
553
|
+
const payments = [];
|
|
554
|
+
for (let i = 0; i < count; i++) {
|
|
555
|
+
const authorization = {
|
|
556
|
+
from: toChecksumAddress(signer.address),
|
|
557
|
+
to: toChecksumAddress(terms.payTo),
|
|
558
|
+
value: terms.amount,
|
|
559
|
+
validAfter,
|
|
560
|
+
validBefore,
|
|
561
|
+
nonce: bytesToHex0x(randomBytes(32))
|
|
562
|
+
};
|
|
563
|
+
const digest = transferWithAuthorizationDigest(
|
|
564
|
+
{
|
|
565
|
+
name: terms.domain.name,
|
|
566
|
+
version: terms.domain.version,
|
|
567
|
+
chainId: terms.chainId,
|
|
568
|
+
verifyingContract: terms.asset
|
|
569
|
+
},
|
|
570
|
+
authorization
|
|
571
|
+
);
|
|
572
|
+
const signature = bytesToHex0x(await signer.signDigest(digest));
|
|
573
|
+
const payload = terms.x402Version === 1 ? {
|
|
574
|
+
x402Version: 1,
|
|
575
|
+
scheme: "exact",
|
|
576
|
+
network: terms.network,
|
|
577
|
+
payload: { signature, authorization }
|
|
578
|
+
} : {
|
|
579
|
+
x402Version: 2,
|
|
580
|
+
accepted: terms.requirements,
|
|
581
|
+
payload: { signature, authorization }
|
|
582
|
+
};
|
|
583
|
+
payments.push(bytesToBase64(utf8(JSON.stringify(payload))));
|
|
584
|
+
}
|
|
585
|
+
return payments;
|
|
586
|
+
}
|
|
311
587
|
var SEED_GATEWAY = utf8("molpha_gateway");
|
|
312
588
|
var addressEncoder = getAddressEncoder();
|
|
313
589
|
function normalizeEndpoint(input) {
|
|
@@ -392,8 +668,24 @@ var MolphaGateway = class {
|
|
|
392
668
|
}
|
|
393
669
|
/** Tries endpoints in order; returns the first node list it can fetch. */
|
|
394
670
|
async getNodes() {
|
|
671
|
+
return (await this.getNodesInfo()).nodes;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* `GET /v1/nodes` — the peer set plus the gateway's view of the registry
|
|
675
|
+
* selection policy, which sizes the eligible set a paid source must fund.
|
|
676
|
+
*
|
|
677
|
+
* The `registry` block is advisory: it is whatever the gateway read from the
|
|
678
|
+
* chain, and is absent on older gateways or when that read failed. Prefer the
|
|
679
|
+
* on-chain read (`MolphaSolanaClient.getRegistrySelectionConfig`) whenever a
|
|
680
|
+
* Solana connection is available — `requestSignedData` already does.
|
|
681
|
+
*/
|
|
682
|
+
async getNodesInfo() {
|
|
395
683
|
const data = await this.firstReachableData("/v1/nodes");
|
|
396
|
-
|
|
684
|
+
if (Array.isArray(data)) return { nodes: data };
|
|
685
|
+
return {
|
|
686
|
+
nodes: data.nodes,
|
|
687
|
+
...data.registry ? { registry: data.registry } : {}
|
|
688
|
+
};
|
|
397
689
|
}
|
|
398
690
|
async isHealthy() {
|
|
399
691
|
for (const endpoint of this.endpoints) {
|
|
@@ -454,12 +746,17 @@ var MolphaGateway = class {
|
|
|
454
746
|
* read does not report `nodeCount`). Supply `opts.context` (e.g. from
|
|
455
747
|
* {@link prepareContext}) to reuse cached inputs and skip those fetches — a
|
|
456
748
|
* fully-populated context collapses the call to a single POST round.
|
|
749
|
+
*
|
|
750
|
+
* When the API source is itself x402-paywalled, pass `opts.sourcePayment` to
|
|
751
|
+
* fund it; without that, such a source throws
|
|
752
|
+
* {@link UpstreamPaymentRequiredError} carrying the gateway's quote.
|
|
457
753
|
*/
|
|
458
754
|
async requestSignedData(opts) {
|
|
459
755
|
const {
|
|
460
756
|
apiConfig,
|
|
461
757
|
signer,
|
|
462
758
|
encrypt,
|
|
759
|
+
sourcePayment,
|
|
463
760
|
maxAge = 60,
|
|
464
761
|
maxRetries = 15,
|
|
465
762
|
timeoutMs = 5e3
|
|
@@ -494,9 +791,33 @@ var MolphaGateway = class {
|
|
|
494
791
|
if (nodeCount === void 0) {
|
|
495
792
|
throw new Error("Round context resolved neither nodeCount nor nodes");
|
|
496
793
|
}
|
|
794
|
+
let terms = null;
|
|
795
|
+
let authorizations = 0;
|
|
796
|
+
let requoted = false;
|
|
797
|
+
if (sourcePayment) {
|
|
798
|
+
terms = sourcePayment.terms ? validateSuppliedTerms(
|
|
799
|
+
sourcePayment.terms,
|
|
800
|
+
requestApiConfig.url,
|
|
801
|
+
sourcePayment.assetDomain
|
|
802
|
+
) : await probeSource(apiConfig, {
|
|
803
|
+
...encrypt ? { secrets: encrypt.secrets } : {},
|
|
804
|
+
...sourcePayment.assetDomain ? { assetDomain: sourcePayment.assetDomain } : {},
|
|
805
|
+
timeoutMs
|
|
806
|
+
});
|
|
807
|
+
if (terms) {
|
|
808
|
+
authorizations = effectiveSelectionSize(
|
|
809
|
+
signaturesRequired,
|
|
810
|
+
redundancyBuffer,
|
|
811
|
+
nodeCount
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
497
815
|
let lastError;
|
|
816
|
+
let lastTimestamp = 0;
|
|
498
817
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
499
|
-
|
|
818
|
+
let timestamp = Math.floor(Date.now() / 1e3);
|
|
819
|
+
if (timestamp <= lastTimestamp) timestamp = lastTimestamp + 1;
|
|
820
|
+
lastTimestamp = timestamp;
|
|
500
821
|
const seed = deriveSelectionSeed(sourceIdBytes, registryVersion, timestamp);
|
|
501
822
|
const groupSize = effectiveSelectionSize(signaturesRequired, redundancyBuffer, nodeCount);
|
|
502
823
|
const bitmap = deriveGroupBitmap(seed, nodeCount, groupSize);
|
|
@@ -515,6 +836,14 @@ var MolphaGateway = class {
|
|
|
515
836
|
}
|
|
516
837
|
encKeyBundle = encryptForNodes(requestApiConfig, encrypt.secrets, selected);
|
|
517
838
|
}
|
|
839
|
+
let sourcePayments;
|
|
840
|
+
if (terms && sourcePayment && authorizations > 0) {
|
|
841
|
+
sourcePayments = await signSourcePayments(
|
|
842
|
+
terms,
|
|
843
|
+
sourcePayment.signer,
|
|
844
|
+
authorizations
|
|
845
|
+
);
|
|
846
|
+
}
|
|
518
847
|
const baseBody = {
|
|
519
848
|
sourceId,
|
|
520
849
|
registryVersion,
|
|
@@ -526,6 +855,8 @@ var MolphaGateway = class {
|
|
|
526
855
|
apiConfig: requestApiConfig
|
|
527
856
|
};
|
|
528
857
|
if (encKeyBundle) baseBody.encKeyBundle = encKeyBundle;
|
|
858
|
+
if (sourcePayments) baseBody.sourcePayments = sourcePayments;
|
|
859
|
+
let requote = false;
|
|
529
860
|
for (const endpoint of this.endpoints) {
|
|
530
861
|
try {
|
|
531
862
|
let authSig = ZERO_AUTH_SIG;
|
|
@@ -547,6 +878,55 @@ var MolphaGateway = class {
|
|
|
547
878
|
body,
|
|
548
879
|
timeoutMs
|
|
549
880
|
);
|
|
881
|
+
if (res.status === 402) {
|
|
882
|
+
const quote = parseUpstreamQuote(
|
|
883
|
+
await res.json().catch(() => null)
|
|
884
|
+
);
|
|
885
|
+
if (!quote) {
|
|
886
|
+
throw new GatewayError(
|
|
887
|
+
"Gateway requires payment but returned no upstream source quote",
|
|
888
|
+
402
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
if (!sourcePayment) throw new UpstreamPaymentRequiredError(quote);
|
|
892
|
+
if (terms && requoted && authorizations >= quote.eligibleSetSize) {
|
|
893
|
+
throw new UpstreamPaymentRequiredError(
|
|
894
|
+
quote,
|
|
895
|
+
`Source payment was not accepted for ${quote.resource}: ${quote.error ?? "no detail"}`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
requoted = true;
|
|
899
|
+
if (quote.eligibleSetSize !== groupSize) {
|
|
900
|
+
throw new GatewayError(
|
|
901
|
+
`Gateway upstream quote eligibleSetSize ${quote.eligibleSetSize} does not match this round's selection size ${groupSize} (min(signaturesRequired + redundancyBuffer, nodeCount))`,
|
|
902
|
+
402
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
authorizations = groupSize;
|
|
906
|
+
if (!terms) {
|
|
907
|
+
try {
|
|
908
|
+
terms = await probeSource(apiConfig, {
|
|
909
|
+
...encrypt ? { secrets: encrypt.secrets } : {},
|
|
910
|
+
...sourcePayment.assetDomain ? { assetDomain: sourcePayment.assetDomain } : {},
|
|
911
|
+
timeoutMs
|
|
912
|
+
});
|
|
913
|
+
} catch (err) {
|
|
914
|
+
throw new UpstreamPaymentRequiredError(
|
|
915
|
+
quote,
|
|
916
|
+
`Cannot pay ${quote.resource}: ${err instanceof Error ? err.message : String(err)}`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (!terms) {
|
|
921
|
+
throw new UpstreamPaymentRequiredError(
|
|
922
|
+
quote,
|
|
923
|
+
`Gateway reports ${quote.resource} is paywalled but the source returned no payment terms`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
lastError = new UpstreamPaymentRequiredError(quote);
|
|
927
|
+
requote = true;
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
550
930
|
const errorDetail = !res.ok ? await parseGatewayErrorDetail(res) : void 0;
|
|
551
931
|
if (res.status === 400 || res.status === 401) {
|
|
552
932
|
throw new GatewayError(
|
|
@@ -581,12 +961,14 @@ var MolphaGateway = class {
|
|
|
581
961
|
}
|
|
582
962
|
lastError = new Error(`Gateway returned status: ${json.status}`);
|
|
583
963
|
} catch (err) {
|
|
584
|
-
if (err instanceof
|
|
964
|
+
if (err instanceof UpstreamPaymentRequiredError) throw err;
|
|
965
|
+
if (err instanceof GatewayError && (err.status === 400 || err.status === 401 || err.status === 402)) {
|
|
585
966
|
throw err;
|
|
586
967
|
}
|
|
587
968
|
lastError = err;
|
|
588
969
|
}
|
|
589
970
|
}
|
|
971
|
+
if (requote) continue;
|
|
590
972
|
}
|
|
591
973
|
throw lastError instanceof Error ? lastError : new Error("Gateway round failed after retries");
|
|
592
974
|
}
|
|
@@ -11177,6 +11559,40 @@ function attestationMessageHashFromResult(result) {
|
|
|
11177
11559
|
});
|
|
11178
11560
|
}
|
|
11179
11561
|
|
|
11562
|
+
// src/core/verifyCodes.ts
|
|
11563
|
+
var VERIFY_CODES = {
|
|
11564
|
+
OK: 0,
|
|
11565
|
+
/** Reserved. Never returned. */
|
|
11566
|
+
FEED_WITNESS: 1,
|
|
11567
|
+
/** The payload's `registryVersion` does not exist on this verifier. */
|
|
11568
|
+
BAD_REGISTRY_VERSION: 2,
|
|
11569
|
+
/** Structurally invalid input, or a `canonicalTimestamp` in the future when `maxAge != 0`. */
|
|
11570
|
+
MALFORMED: 3,
|
|
11571
|
+
/** `canonicalTimestamp` predates the registry version's activation. */
|
|
11572
|
+
NOT_YET_ACTIVE: 4,
|
|
11573
|
+
/** The registry version was superseded more than the grace window before `canonicalTimestamp`. */
|
|
11574
|
+
VERSION_EXPIRED: 5,
|
|
11575
|
+
/** Reserved. Never returned. */
|
|
11576
|
+
COMPROMISED_QUORUM: 6,
|
|
11577
|
+
/** The signer set is not within the round's derived selection group. */
|
|
11578
|
+
BAD_QUORUM: 7,
|
|
11579
|
+
/** The signers' aggregate public key is the point at infinity. */
|
|
11580
|
+
BAD_AGGREGATE: 8,
|
|
11581
|
+
/** The aggregate Schnorr signature does not verify. */
|
|
11582
|
+
BAD_SIGNATURE: 9,
|
|
11583
|
+
/** Older than the caller's `maxAge`. */
|
|
11584
|
+
STALE: 10
|
|
11585
|
+
};
|
|
11586
|
+
var NAMES_BY_CODE = new Map(
|
|
11587
|
+
Object.entries(VERIFY_CODES).map(([name, code]) => [
|
|
11588
|
+
code,
|
|
11589
|
+
name
|
|
11590
|
+
])
|
|
11591
|
+
);
|
|
11592
|
+
function verifyCodeName(code) {
|
|
11593
|
+
return NAMES_BY_CODE.get(code);
|
|
11594
|
+
}
|
|
11595
|
+
|
|
11180
11596
|
// src/evm/abi.ts
|
|
11181
11597
|
var MOLPHA_VERIFIER_ABI = [
|
|
11182
11598
|
{
|
|
@@ -11272,6 +11688,12 @@ function getMolphaStarknetVerifierAddress(network) {
|
|
|
11272
11688
|
}
|
|
11273
11689
|
|
|
11274
11690
|
// src/starknet/helpers.ts
|
|
11691
|
+
var U8_MAX = 0xffn;
|
|
11692
|
+
var U32_MAX2 = 0xffffffffn;
|
|
11693
|
+
var U64_MAX = 0xffffffffffffffffn;
|
|
11694
|
+
var U128_MASK = (1n << 128n) - 1n;
|
|
11695
|
+
var U256_MAX = (1n << 256n) - 1n;
|
|
11696
|
+
var ADDRESS_MAX = (1n << 160n) - 1n;
|
|
11275
11697
|
function strip0x2(value) {
|
|
11276
11698
|
return value.startsWith("0x") || value.startsWith("0X") ? value.slice(2) : value;
|
|
11277
11699
|
}
|
|
@@ -11296,26 +11718,99 @@ function fixedHexToBigInt(value, bytes, label) {
|
|
|
11296
11718
|
const raw = toFixedBytes(hex, bytes, label);
|
|
11297
11719
|
return bigIntFromBytesBe(raw);
|
|
11298
11720
|
}
|
|
11721
|
+
function assertUint2(value, max, label) {
|
|
11722
|
+
if (!Number.isSafeInteger(value) || value < 0 || BigInt(value) > max) {
|
|
11723
|
+
throw new RangeError(`${label}: expected an integer in 0..${max}, got ${value}`);
|
|
11724
|
+
}
|
|
11725
|
+
return value;
|
|
11726
|
+
}
|
|
11727
|
+
function assertBigUint(value, max, label) {
|
|
11728
|
+
if (value < 0n || value > max) {
|
|
11729
|
+
throw new RangeError(`${label}: expected an integer in 0..${max}, got ${value}`);
|
|
11730
|
+
}
|
|
11731
|
+
return value;
|
|
11732
|
+
}
|
|
11733
|
+
function toFelt(value) {
|
|
11734
|
+
return `0x${BigInt(value).toString(16)}`;
|
|
11735
|
+
}
|
|
11736
|
+
function u256Felts(value, label) {
|
|
11737
|
+
assertBigUint(value, U256_MAX, label);
|
|
11738
|
+
return [toFelt(value & U128_MASK), toFelt(value >> 128n)];
|
|
11739
|
+
}
|
|
11740
|
+
function feltToBigInt(value, label) {
|
|
11741
|
+
if (typeof value === "boolean") return value ? 1n : 0n;
|
|
11742
|
+
if (typeof value === "bigint") return value;
|
|
11743
|
+
if (typeof value === "number") {
|
|
11744
|
+
if (!Number.isSafeInteger(value)) throw new RangeError(`${label}: not an integer: ${value}`);
|
|
11745
|
+
return BigInt(value);
|
|
11746
|
+
}
|
|
11747
|
+
const text = value.trim();
|
|
11748
|
+
if (!/^(0x[0-9a-fA-F]+|[0-9]+)$/.test(text)) {
|
|
11749
|
+
throw new RangeError(`${label}: not a felt: ${JSON.stringify(value)}`);
|
|
11750
|
+
}
|
|
11751
|
+
return BigInt(text);
|
|
11752
|
+
}
|
|
11299
11753
|
function commitmentAddressToStarknetFelt(value) {
|
|
11300
11754
|
return fixedHexToBigInt(value, 20, "commitment");
|
|
11301
11755
|
}
|
|
11302
11756
|
function signersBitmapToStarknetUint256(value) {
|
|
11303
11757
|
return fixedHexToBigInt(value, 32, "signersBitmap");
|
|
11304
11758
|
}
|
|
11305
|
-
function buildStarknetVerifierArgs(result) {
|
|
11306
|
-
const
|
|
11307
|
-
source_id: fixedHexToBigInt(result.sourceId, 32, "sourceId"),
|
|
11308
|
-
registry_version: result.registryVersion,
|
|
11309
|
-
signatures_required: result.signaturesRequired,
|
|
11759
|
+
function buildStarknetVerifierArgs(result, options) {
|
|
11760
|
+
const payload = {
|
|
11310
11761
|
value: fixedHexToBigInt(result.valuePacked, 32, "valuePacked"),
|
|
11311
|
-
|
|
11762
|
+
source_id: fixedHexToBigInt(result.sourceId, 32, "sourceId"),
|
|
11763
|
+
registry_version: assertUint2(result.registryVersion, U32_MAX2, "registryVersion"),
|
|
11764
|
+
signatures_required: assertUint2(result.signaturesRequired, U8_MAX, "signaturesRequired"),
|
|
11765
|
+
canonical_timestamp: assertUint2(result.timestamp, U64_MAX, "timestamp")
|
|
11312
11766
|
};
|
|
11313
11767
|
const signature = {
|
|
11314
11768
|
signature: fixedHexToBigInt(result.s, 32, "signature"),
|
|
11315
11769
|
commitment: commitmentAddressToStarknetFelt(result.commitmentAddr),
|
|
11316
11770
|
signers_bitmap: signersBitmapToStarknetUint256(result.signersBitmap)
|
|
11317
11771
|
};
|
|
11318
|
-
return {
|
|
11772
|
+
return {
|
|
11773
|
+
attestation: { payload, signature },
|
|
11774
|
+
maxAge: assertUint2(options.maxAge, U64_MAX, "maxAge")
|
|
11775
|
+
};
|
|
11776
|
+
}
|
|
11777
|
+
function encodeStarknetVerifyCalldata(args) {
|
|
11778
|
+
const { payload, signature } = args.attestation;
|
|
11779
|
+
return [
|
|
11780
|
+
...u256Felts(payload.value, "value"),
|
|
11781
|
+
...u256Felts(payload.source_id, "source_id"),
|
|
11782
|
+
toFelt(assertUint2(payload.registry_version, U32_MAX2, "registry_version")),
|
|
11783
|
+
toFelt(assertUint2(payload.signatures_required, U8_MAX, "signatures_required")),
|
|
11784
|
+
toFelt(assertUint2(payload.canonical_timestamp, U64_MAX, "canonical_timestamp")),
|
|
11785
|
+
...u256Felts(signature.signature, "signature"),
|
|
11786
|
+
// An Ethereum-style address, so it must fit 160 bits (the verifier reports a wider one
|
|
11787
|
+
// as `MALFORMED`; one past the field prime would not even deserialize).
|
|
11788
|
+
toFelt(assertBigUint(signature.commitment, ADDRESS_MAX, "commitment")),
|
|
11789
|
+
...u256Felts(signature.signers_bitmap, "signers_bitmap"),
|
|
11790
|
+
toFelt(assertUint2(args.maxAge, U64_MAX, "maxAge"))
|
|
11791
|
+
];
|
|
11792
|
+
}
|
|
11793
|
+
function parseStarknetVerifyResult(result) {
|
|
11794
|
+
if (Array.isArray(result) && result.length !== 2) {
|
|
11795
|
+
throw new RangeError(`verify result: expected 2 felts, got ${result.length}`);
|
|
11796
|
+
}
|
|
11797
|
+
const tuple = result;
|
|
11798
|
+
const successFelt = feltToBigInt(tuple[0], "verify result success");
|
|
11799
|
+
if (successFelt !== 0n && successFelt !== 1n) {
|
|
11800
|
+
throw new RangeError(`verify result: success must be 0 or 1, got ${successFelt}`);
|
|
11801
|
+
}
|
|
11802
|
+
const codeFelt = feltToBigInt(tuple[1], "verify result code");
|
|
11803
|
+
if (codeFelt < 0n || codeFelt > U8_MAX) {
|
|
11804
|
+
throw new RangeError(`verify result: code must fit u8, got ${codeFelt}`);
|
|
11805
|
+
}
|
|
11806
|
+
const success = successFelt === 1n;
|
|
11807
|
+
const code = Number(codeFelt);
|
|
11808
|
+
if (success !== (code === VERIFY_CODES.OK)) {
|
|
11809
|
+
throw new Error(
|
|
11810
|
+
`verify result is inconsistent: success=${success} with code ${code}; the call did not reach a Molpha verifier with this interface`
|
|
11811
|
+
);
|
|
11812
|
+
}
|
|
11813
|
+
return { success, code, reason: verifyCodeName(code) ?? "UNKNOWN" };
|
|
11319
11814
|
}
|
|
11320
11815
|
|
|
11321
11816
|
// src/index.ts
|
|
@@ -11353,6 +11848,6 @@ var MolphaSDK = class {
|
|
|
11353
11848
|
}
|
|
11354
11849
|
};
|
|
11355
11850
|
|
|
11356
|
-
export { DEFAULT_GATEWAY_ENDPOINT, GatewayError, MESSAGE_PREFIX, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, MolphaGateway, MolphaSDK, MolphaSolanaClient, PlanType, REQUEST_AUTH_DOMAIN, addressToBytes, attestationMessageHash, attestationMessageHashFromResult, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, commitmentAddressToStarknetFelt, concatBytes, deriveApiConfigHash, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, effectiveSelectionSize, encodeRequestAuth, ensureLength, feedPda, gatewayPda, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseGatewayInfo, planIdFromVariant, planPda, planVariant, protocolConfigPda, registryPda, registryStatePda, resolveRemainingAccounts, secp256k1PublicKeyFromCoordinates, selectedIndices, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, subscriptionPda, toFixedBytes, toFixedHex, u256beFromBigInt, u32be, u32le, u64be, u64le, utf8 };
|
|
11851
|
+
export { DEFAULT_GATEWAY_ENDPOINT, EIP712_DOMAIN_TYPEHASH, GatewayError, MESSAGE_PREFIX, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, MolphaGateway, MolphaSDK, MolphaSolanaClient, PlanType, REQUEST_AUTH_DOMAIN, TRANSFER_WITH_AUTHORIZATION_TYPEHASH, UpstreamPaymentRequiredError, VERIFY_CODES, addressToBytes, attestationMessageHash, attestationMessageHashFromResult, base64ToBytes, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToBase64, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, commitmentAddressToStarknetFelt, concatBytes, createEvmSignerFromPrivateKey, deriveApiConfigHash, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, domainSeparator, effectiveSelectionSize, eligibleSetSize, encodeRequestAuth, encodeStarknetVerifyCalldata, ensureLength, evmAddressFromPrivateKey, feedPda, gatewayPda, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, resolveRemainingAccounts, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, subscriptionPda, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, utf8, validateSuppliedTerms, verifyCodeName };
|
|
11357
11852
|
//# sourceMappingURL=index.js.map
|
|
11358
11853
|
//# sourceMappingURL=index.js.map
|