@piprail/sdk 1.9.0 → 1.11.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/CHANGELOG.md +70 -0
- package/ERRORS.md +21 -8
- package/README.md +20 -0
- package/STANDARDS.md +5 -2
- package/dist/{algorand-IJJKE35X.cjs → algorand-MXUSKX46.cjs} +17 -17
- package/dist/{algorand-B67G4335.js → algorand-WGVF4KTU.js} +1 -1
- package/dist/{aptos-YQWTGFRZ.js → aptos-LPBLSEIQ.js} +1 -1
- package/dist/{aptos-X3G2UBYW.cjs → aptos-YT7SXWPF.cjs} +16 -16
- package/dist/{chunk-IQGT65WS.cjs → chunk-MDLZJGLY.cjs} +20 -16
- package/dist/{chunk-QDS6FBZP.js → chunk-SVMGHASK.js} +4 -0
- package/dist/index.cjs +897 -265
- package/dist/index.d.cts +504 -45
- package/dist/index.d.ts +504 -45
- package/dist/index.js +820 -188
- package/dist/{near-GGUHLXAF.cjs → near-7ZDNISUX.cjs} +19 -19
- package/dist/{near-7MBBCDUE.js → near-K6BDBABG.js} +1 -1
- package/dist/{solana-W24TCJV4.cjs → solana-PU7N2M64.cjs} +14 -14
- package/dist/{solana-7WJVZGDW.js → solana-S3UFI3FE.js} +1 -1
- package/dist/{stellar-HV6VGZX3.js → stellar-Q5PO23SC.js} +1 -1
- package/dist/{stellar-YMY3K2YB.cjs → stellar-VDQOFQEO.cjs} +21 -21
- package/dist/{sui-32KVESR5.cjs → sui-FKSMLKRF.cjs} +17 -17
- package/dist/{sui-2WFWVFJX.js → sui-WOXRKJXS.js} +1 -1
- package/dist/{ton-FIQGV2LC.cjs → ton-VK6KRJHP.cjs} +14 -14
- package/dist/{ton-DGZB7W4U.js → ton-WPTXGLVK.js} +1 -1
- package/dist/{tron-RLIL2FDI.js → tron-6GXBXTR4.js} +1 -1
- package/dist/{tron-ZSXAPZ2C.cjs → tron-WLOF5OUV.cjs} +24 -24
- package/dist/{xrpl-2PKP7HOI.cjs → xrpl-CMNI25BV.cjs} +21 -21
- package/dist/{xrpl-UEC2GYVV.js → xrpl-HEAPEXAM.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PaymentTimeoutError,
|
|
11
11
|
PipRailError,
|
|
12
12
|
RecipientNotReadyError,
|
|
13
|
+
SettlementError,
|
|
13
14
|
UnknownTokenError,
|
|
14
15
|
UnsupportedNetworkError,
|
|
15
16
|
WrongChainError,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
parseUnits,
|
|
21
22
|
rejectForeignToken,
|
|
22
23
|
toInsufficientFundsError
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-SVMGHASK.js";
|
|
24
25
|
|
|
25
26
|
// src/drivers/registry.ts
|
|
26
27
|
var byFamily = /* @__PURE__ */ new Map();
|
|
@@ -63,7 +64,7 @@ function resolveNetwork(opts) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// src/drivers/evm/index.ts
|
|
66
|
-
import { BaseError, createPublicClient, erc20Abi as erc20Abi3, getAddress as
|
|
67
|
+
import { BaseError, createPublicClient, erc20Abi as erc20Abi3, getAddress as getAddress3, http as http2, isAddress } from "viem";
|
|
67
68
|
|
|
68
69
|
// src/drivers/evm/chains.ts
|
|
69
70
|
import { defineChain } from "viem";
|
|
@@ -483,20 +484,367 @@ function sumTransfersTo(logs, asset, payTo) {
|
|
|
483
484
|
return { total, from };
|
|
484
485
|
}
|
|
485
486
|
|
|
487
|
+
// src/drivers/evm/exact.ts
|
|
488
|
+
import {
|
|
489
|
+
getAddress as getAddress2,
|
|
490
|
+
parseSignature,
|
|
491
|
+
recoverTypedDataAddress
|
|
492
|
+
} from "viem";
|
|
493
|
+
var EXACT_NETWORK_SLUGS = {
|
|
494
|
+
ethereum: 1,
|
|
495
|
+
base: 8453,
|
|
496
|
+
"base-sepolia": 84532,
|
|
497
|
+
arbitrum: 42161,
|
|
498
|
+
optimism: 10,
|
|
499
|
+
polygon: 137,
|
|
500
|
+
avalanche: 43114
|
|
501
|
+
};
|
|
502
|
+
function chainIdForExactNetwork(slug) {
|
|
503
|
+
return EXACT_NETWORK_SLUGS[slug] ?? null;
|
|
504
|
+
}
|
|
505
|
+
var EIP3009_TYPES = {
|
|
506
|
+
TransferWithAuthorization: [
|
|
507
|
+
{ name: "from", type: "address" },
|
|
508
|
+
{ name: "to", type: "address" },
|
|
509
|
+
{ name: "value", type: "uint256" },
|
|
510
|
+
{ name: "validAfter", type: "uint256" },
|
|
511
|
+
{ name: "validBefore", type: "uint256" },
|
|
512
|
+
{ name: "nonce", type: "bytes32" }
|
|
513
|
+
]
|
|
514
|
+
};
|
|
515
|
+
function parseExactRequirements(body) {
|
|
516
|
+
if (!body || typeof body !== "object") return null;
|
|
517
|
+
const accepts = body.accepts;
|
|
518
|
+
if (!Array.isArray(accepts)) return null;
|
|
519
|
+
const out = [];
|
|
520
|
+
for (const raw of accepts) {
|
|
521
|
+
if (!raw || typeof raw !== "object") continue;
|
|
522
|
+
const a = raw;
|
|
523
|
+
if (a.scheme !== "exact") continue;
|
|
524
|
+
const amount = a.maxAmountRequired ?? a.amount;
|
|
525
|
+
if (typeof a.network !== "string" || typeof amount !== "string" || typeof a.asset !== "string" || typeof a.payTo !== "string") {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
out.push({
|
|
529
|
+
scheme: "exact",
|
|
530
|
+
network: a.network,
|
|
531
|
+
maxAmountRequired: amount,
|
|
532
|
+
asset: a.asset,
|
|
533
|
+
payTo: a.payTo,
|
|
534
|
+
maxTimeoutSeconds: typeof a.maxTimeoutSeconds === "number" ? a.maxTimeoutSeconds : 600,
|
|
535
|
+
...a.extra && typeof a.extra === "object" ? { extra: a.extra } : {},
|
|
536
|
+
...typeof a.description === "string" ? { description: a.description } : {},
|
|
537
|
+
...typeof a.resource === "string" ? { resource: a.resource } : {}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
async function buildExactAuthorization(params) {
|
|
543
|
+
const { account, accept, chainId, now, nonce } = params;
|
|
544
|
+
if (!account.signTypedData) {
|
|
545
|
+
throw new Error("buildExactAuthorization: the account cannot sign EIP-712 typed data.");
|
|
546
|
+
}
|
|
547
|
+
const authorization = {
|
|
548
|
+
from: account.address,
|
|
549
|
+
to: accept.payTo,
|
|
550
|
+
value: accept.maxAmountRequired,
|
|
551
|
+
validAfter: "0",
|
|
552
|
+
validBefore: String(now + accept.maxTimeoutSeconds),
|
|
553
|
+
nonce
|
|
554
|
+
};
|
|
555
|
+
const signature = await account.signTypedData({
|
|
556
|
+
domain: {
|
|
557
|
+
name: accept.extra?.name ?? "USD Coin",
|
|
558
|
+
version: accept.extra?.version ?? "2",
|
|
559
|
+
chainId,
|
|
560
|
+
verifyingContract: accept.asset
|
|
561
|
+
},
|
|
562
|
+
types: EIP3009_TYPES,
|
|
563
|
+
primaryType: "TransferWithAuthorization",
|
|
564
|
+
message: {
|
|
565
|
+
from: authorization.from,
|
|
566
|
+
to: authorization.to,
|
|
567
|
+
value: BigInt(authorization.value),
|
|
568
|
+
validAfter: BigInt(authorization.validAfter),
|
|
569
|
+
validBefore: BigInt(authorization.validBefore),
|
|
570
|
+
nonce: authorization.nonce
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
return { authorization, signature };
|
|
574
|
+
}
|
|
575
|
+
function base64(str) {
|
|
576
|
+
if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
|
|
577
|
+
if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
|
|
578
|
+
const bytes = new TextEncoder().encode(str);
|
|
579
|
+
let binary = "";
|
|
580
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
581
|
+
return btoa(binary);
|
|
582
|
+
}
|
|
583
|
+
throw new Error("No base64 encoder available in this runtime.");
|
|
584
|
+
}
|
|
585
|
+
function encodeXPaymentHeader(input) {
|
|
586
|
+
const payload = {
|
|
587
|
+
x402Version: input.x402Version ?? 1,
|
|
588
|
+
scheme: "exact",
|
|
589
|
+
network: input.network,
|
|
590
|
+
payload: { signature: input.signature, authorization: input.authorization }
|
|
591
|
+
};
|
|
592
|
+
return base64(JSON.stringify(payload));
|
|
593
|
+
}
|
|
594
|
+
var eip3009Abi = [
|
|
595
|
+
{
|
|
596
|
+
type: "function",
|
|
597
|
+
name: "transferWithAuthorization",
|
|
598
|
+
stateMutability: "nonpayable",
|
|
599
|
+
outputs: [],
|
|
600
|
+
inputs: [
|
|
601
|
+
{ name: "from", type: "address" },
|
|
602
|
+
{ name: "to", type: "address" },
|
|
603
|
+
{ name: "value", type: "uint256" },
|
|
604
|
+
{ name: "validAfter", type: "uint256" },
|
|
605
|
+
{ name: "validBefore", type: "uint256" },
|
|
606
|
+
{ name: "nonce", type: "bytes32" },
|
|
607
|
+
{ name: "v", type: "uint8" },
|
|
608
|
+
{ name: "r", type: "bytes32" },
|
|
609
|
+
{ name: "s", type: "bytes32" }
|
|
610
|
+
]
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
type: "function",
|
|
614
|
+
name: "transferWithAuthorization",
|
|
615
|
+
stateMutability: "nonpayable",
|
|
616
|
+
outputs: [],
|
|
617
|
+
inputs: [
|
|
618
|
+
{ name: "from", type: "address" },
|
|
619
|
+
{ name: "to", type: "address" },
|
|
620
|
+
{ name: "value", type: "uint256" },
|
|
621
|
+
{ name: "validAfter", type: "uint256" },
|
|
622
|
+
{ name: "validBefore", type: "uint256" },
|
|
623
|
+
{ name: "nonce", type: "bytes32" },
|
|
624
|
+
{ name: "signature", type: "bytes" }
|
|
625
|
+
]
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
type: "function",
|
|
629
|
+
name: "authorizationState",
|
|
630
|
+
stateMutability: "view",
|
|
631
|
+
inputs: [
|
|
632
|
+
{ name: "authorizer", type: "address" },
|
|
633
|
+
{ name: "nonce", type: "bytes32" }
|
|
634
|
+
],
|
|
635
|
+
outputs: [{ type: "bool" }]
|
|
636
|
+
},
|
|
637
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
|
|
638
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }
|
|
639
|
+
];
|
|
640
|
+
var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
|
|
641
|
+
var ZERO_NONCE = `0x${"00".repeat(32)}`;
|
|
642
|
+
async function readExactDomain(publicClient, asset) {
|
|
643
|
+
if (asset === "native") return null;
|
|
644
|
+
let token;
|
|
645
|
+
try {
|
|
646
|
+
token = getAddress2(asset);
|
|
647
|
+
} catch {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
const [name, version] = await Promise.all([
|
|
652
|
+
publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "name" }),
|
|
653
|
+
publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "version" }),
|
|
654
|
+
// The EIP-3009 probe: this view exists only on EIP-3009 tokens; it reverts on
|
|
655
|
+
// a plain ERC-20 / USDT, marking the token as not exact-payable.
|
|
656
|
+
publicClient.readContract({
|
|
657
|
+
address: token,
|
|
658
|
+
abi: eip3009Abi,
|
|
659
|
+
functionName: "authorizationState",
|
|
660
|
+
args: [ZERO_ADDR, ZERO_NONCE]
|
|
661
|
+
})
|
|
662
|
+
]);
|
|
663
|
+
if (typeof name !== "string" || typeof version !== "string" || !name || !version) return null;
|
|
664
|
+
return { name, version };
|
|
665
|
+
} catch {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function shorten(msg) {
|
|
670
|
+
const oneLine = msg.replace(/\s+/g, " ").trim();
|
|
671
|
+
return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
|
|
672
|
+
}
|
|
673
|
+
async function verifyAndSettleExactEvm(input) {
|
|
674
|
+
const { publicClient, walletClient, account, chain, payload, accept } = input;
|
|
675
|
+
const token = getAddress2(accept.asset);
|
|
676
|
+
const payTo = getAddress2(accept.payTo);
|
|
677
|
+
const requiredAmount = BigInt(accept.amount);
|
|
678
|
+
let from;
|
|
679
|
+
let to;
|
|
680
|
+
let value;
|
|
681
|
+
let validAfter;
|
|
682
|
+
let validBefore;
|
|
683
|
+
let nonce;
|
|
684
|
+
try {
|
|
685
|
+
from = getAddress2(payload.authorization.from);
|
|
686
|
+
to = getAddress2(payload.authorization.to);
|
|
687
|
+
value = BigInt(payload.authorization.value);
|
|
688
|
+
validAfter = BigInt(payload.authorization.validAfter);
|
|
689
|
+
validBefore = BigInt(payload.authorization.validBefore);
|
|
690
|
+
nonce = payload.authorization.nonce;
|
|
691
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(nonce)) throw new Error("nonce must be 32-byte hex");
|
|
692
|
+
if (!/^0x[0-9a-fA-F]+$/.test(payload.signature)) throw new Error("signature must be hex");
|
|
693
|
+
} catch (err) {
|
|
694
|
+
return {
|
|
695
|
+
ok: false,
|
|
696
|
+
error: "signature_invalid",
|
|
697
|
+
detail: `Malformed exact authorization: ${err instanceof Error ? err.message : String(err)}.`
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (to !== payTo) {
|
|
701
|
+
return { ok: false, error: "wrong_recipient", detail: `Authorization pays ${to}, not ${payTo}.` };
|
|
702
|
+
}
|
|
703
|
+
if (value < requiredAmount) {
|
|
704
|
+
return { ok: false, error: "amount_too_low", detail: `Authorized ${value}, required ${requiredAmount}.` };
|
|
705
|
+
}
|
|
706
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
707
|
+
if (validBefore <= now) {
|
|
708
|
+
return { ok: false, error: "payment_expired", detail: `Authorization expired: validBefore ${validBefore} <= now ${now}.` };
|
|
709
|
+
}
|
|
710
|
+
let fromCode;
|
|
711
|
+
try {
|
|
712
|
+
fromCode = await publicClient.getCode({ address: from });
|
|
713
|
+
} catch {
|
|
714
|
+
return { ok: false, error: "tx_not_found", detail: `Could not read code at ${from} (transient RPC) \u2014 retry.` };
|
|
715
|
+
}
|
|
716
|
+
const isContractWallet = Boolean(fromCode && fromCode !== "0x");
|
|
717
|
+
if (!isContractWallet) {
|
|
718
|
+
let recovered;
|
|
719
|
+
try {
|
|
720
|
+
recovered = await recoverTypedDataAddress({
|
|
721
|
+
domain: {
|
|
722
|
+
name: accept.extra.name,
|
|
723
|
+
version: accept.extra.version,
|
|
724
|
+
chainId: chain.id,
|
|
725
|
+
verifyingContract: token
|
|
726
|
+
},
|
|
727
|
+
types: EIP3009_TYPES,
|
|
728
|
+
primaryType: "TransferWithAuthorization",
|
|
729
|
+
message: { from, to, value, validAfter, validBefore, nonce },
|
|
730
|
+
signature: payload.signature
|
|
731
|
+
});
|
|
732
|
+
} catch (err) {
|
|
733
|
+
return { ok: false, error: "signature_invalid", detail: `Not a valid EIP-712 signature: ${shorten(err instanceof Error ? err.message : String(err))}.` };
|
|
734
|
+
}
|
|
735
|
+
if (recovered !== from) {
|
|
736
|
+
return { ok: false, error: "signature_invalid", detail: `Signature recovered to ${recovered}, not the authorizer ${from}.` };
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const used = await publicClient.readContract({
|
|
741
|
+
address: token,
|
|
742
|
+
abi: eip3009Abi,
|
|
743
|
+
functionName: "authorizationState",
|
|
744
|
+
args: [from, nonce]
|
|
745
|
+
});
|
|
746
|
+
if (used) {
|
|
747
|
+
return { ok: false, error: "tx_already_used", detail: `Authorization nonce ${nonce} already used or canceled on-chain.` };
|
|
748
|
+
}
|
|
749
|
+
} catch {
|
|
750
|
+
return { ok: false, error: "tx_not_found", detail: "Could not read authorizationState (transient RPC) \u2014 retry." };
|
|
751
|
+
}
|
|
752
|
+
const baseArgs = [from, to, value, validAfter, validBefore, nonce];
|
|
753
|
+
const isEcdsa = !isContractWallet && payload.signature.length - 2 === 130;
|
|
754
|
+
let writeArgs;
|
|
755
|
+
if (isEcdsa) {
|
|
756
|
+
const { r, s, yParity } = parseSignature(payload.signature);
|
|
757
|
+
writeArgs = [...baseArgs, BigInt(yParity + 27), r, s];
|
|
758
|
+
} else {
|
|
759
|
+
writeArgs = [...baseArgs, payload.signature];
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
await publicClient.simulateContract({
|
|
763
|
+
account,
|
|
764
|
+
address: token,
|
|
765
|
+
abi: eip3009Abi,
|
|
766
|
+
functionName: "transferWithAuthorization",
|
|
767
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
768
|
+
args: writeArgs
|
|
769
|
+
});
|
|
770
|
+
} catch (err) {
|
|
771
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
772
|
+
if (/used or canceled/i.test(msg)) return { ok: false, error: "tx_already_used", detail: "Authorization is used or canceled." };
|
|
773
|
+
if (/expired|not yet valid/i.test(msg)) return { ok: false, error: "payment_expired", detail: shorten(msg) };
|
|
774
|
+
if (/invalid signature/i.test(msg)) return { ok: false, error: "signature_invalid", detail: shorten(msg) };
|
|
775
|
+
return { ok: false, error: "tx_reverted", detail: `transferWithAuthorization would revert: ${shorten(msg)}` };
|
|
776
|
+
}
|
|
777
|
+
let txHash;
|
|
778
|
+
try {
|
|
779
|
+
txHash = await walletClient.writeContract({
|
|
780
|
+
account,
|
|
781
|
+
chain,
|
|
782
|
+
address: token,
|
|
783
|
+
abi: eip3009Abi,
|
|
784
|
+
functionName: "transferWithAuthorization",
|
|
785
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
786
|
+
args: writeArgs
|
|
787
|
+
});
|
|
788
|
+
} catch (err) {
|
|
789
|
+
throw new SettlementError(
|
|
790
|
+
`exact settle: the merchant relayer failed to broadcast transferWithAuthorization (${shorten(err instanceof Error ? err.message : String(err))}). The payer's authorization is still valid and unused \u2014 fund/fix the relayer and the payer can retry.`,
|
|
791
|
+
{ cause: err }
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const confirmations = accept.extra.minConfirmations ?? 1;
|
|
796
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations });
|
|
797
|
+
if (receipt.status !== "success") {
|
|
798
|
+
return { ok: false, error: "tx_reverted", detail: `Settlement tx ${txHash} reverted on-chain.` };
|
|
799
|
+
}
|
|
800
|
+
} catch (err) {
|
|
801
|
+
throw new SettlementError(
|
|
802
|
+
`exact settle: broadcast ${txHash} but couldn't confirm it (${shorten(err instanceof Error ? err.message : String(err))}).`,
|
|
803
|
+
{ cause: err }
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
ok: true,
|
|
808
|
+
receipt: {
|
|
809
|
+
scheme: "exact",
|
|
810
|
+
success: true,
|
|
811
|
+
network: accept.network,
|
|
812
|
+
transaction: txHash,
|
|
813
|
+
asset: accept.asset,
|
|
814
|
+
amount: accept.amount,
|
|
815
|
+
payer: from,
|
|
816
|
+
payTo: accept.payTo,
|
|
817
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
486
822
|
// src/x402.ts
|
|
487
823
|
var HEADER_REQUIRED = "payment-required";
|
|
488
824
|
var HEADER_SIGNATURE = "payment-signature";
|
|
489
825
|
var HEADER_RESPONSE = "payment-response";
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
|
|
493
|
-
throw new Error("No base64 decoder available in this runtime.");
|
|
494
|
-
}
|
|
826
|
+
var HEADER_SIGNATURE_V1 = "x-payment";
|
|
827
|
+
var HEADER_RESPONSE_V1 = "x-payment-response";
|
|
495
828
|
function encodeBase64(str) {
|
|
496
|
-
if (typeof btoa === "function") return btoa(str);
|
|
497
829
|
if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
|
|
830
|
+
if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
|
|
831
|
+
const bytes = new TextEncoder().encode(str);
|
|
832
|
+
let binary = "";
|
|
833
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
834
|
+
return btoa(binary);
|
|
835
|
+
}
|
|
498
836
|
throw new Error("No base64 encoder available in this runtime.");
|
|
499
837
|
}
|
|
838
|
+
function decodeBase64(b64) {
|
|
839
|
+
if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
|
|
840
|
+
if (typeof atob === "function" && typeof TextDecoder !== "undefined") {
|
|
841
|
+
const binary = atob(b64);
|
|
842
|
+
const bytes = new Uint8Array(binary.length);
|
|
843
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
844
|
+
return new TextDecoder().decode(bytes);
|
|
845
|
+
}
|
|
846
|
+
throw new Error("No base64 decoder available in this runtime.");
|
|
847
|
+
}
|
|
500
848
|
function fromBase64Json(b64) {
|
|
501
849
|
try {
|
|
502
850
|
return JSON.parse(decodeBase64(b64));
|
|
@@ -557,6 +905,36 @@ function parseSignatureHeader(value) {
|
|
|
557
905
|
}
|
|
558
906
|
return parsed;
|
|
559
907
|
}
|
|
908
|
+
function parseExactPaymentHeader(value) {
|
|
909
|
+
const parsed = fromBase64Json(value);
|
|
910
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
911
|
+
const v = parsed;
|
|
912
|
+
const accepted = v.accepted ?? null;
|
|
913
|
+
const scheme = accepted?.scheme ?? v.scheme;
|
|
914
|
+
if (scheme !== "exact") return null;
|
|
915
|
+
const network = accepted?.network ?? v.network;
|
|
916
|
+
if (typeof network !== "string") return null;
|
|
917
|
+
const payload = v.payload;
|
|
918
|
+
if (!payload || typeof payload !== "object") return null;
|
|
919
|
+
const signature = payload.signature;
|
|
920
|
+
const authorization = payload.authorization;
|
|
921
|
+
if (typeof signature !== "string" || !authorization || typeof authorization !== "object") return null;
|
|
922
|
+
for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
|
|
923
|
+
if (typeof authorization[k] !== "string") return null;
|
|
924
|
+
}
|
|
925
|
+
const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
|
|
926
|
+
const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
|
|
927
|
+
return {
|
|
928
|
+
x402Version,
|
|
929
|
+
network,
|
|
930
|
+
...asset ? { asset } : {},
|
|
931
|
+
payload: {
|
|
932
|
+
signature,
|
|
933
|
+
authorization
|
|
934
|
+
},
|
|
935
|
+
raw: v
|
|
936
|
+
};
|
|
937
|
+
}
|
|
560
938
|
function isValidChallenge(value) {
|
|
561
939
|
if (!value || typeof value !== "object") return false;
|
|
562
940
|
const v = value;
|
|
@@ -568,7 +946,7 @@ function isValidChallenge(value) {
|
|
|
568
946
|
function isValidReceipt(value) {
|
|
569
947
|
if (!value || typeof value !== "object") return false;
|
|
570
948
|
const v = value;
|
|
571
|
-
if (v.scheme !== "onchain-proof") return false;
|
|
949
|
+
if (v.scheme !== "onchain-proof" && v.scheme !== "exact") return false;
|
|
572
950
|
if (typeof v.transaction !== "string" && typeof v.txHash !== "string") return false;
|
|
573
951
|
if (typeof v.payer !== "string") return false;
|
|
574
952
|
return true;
|
|
@@ -646,12 +1024,12 @@ function makeEvmNetwork(resolved) {
|
|
|
646
1024
|
}
|
|
647
1025
|
let normalized;
|
|
648
1026
|
try {
|
|
649
|
-
normalized =
|
|
1027
|
+
normalized = getAddress3(asset);
|
|
650
1028
|
} catch {
|
|
651
1029
|
return null;
|
|
652
1030
|
}
|
|
653
1031
|
for (const info of Object.values(resolved.tokens)) {
|
|
654
|
-
if (
|
|
1032
|
+
if (getAddress3(info.address) === normalized) {
|
|
655
1033
|
return { symbol: info.symbol, decimals: info.decimals };
|
|
656
1034
|
}
|
|
657
1035
|
}
|
|
@@ -735,7 +1113,7 @@ function makeEvmNetwork(resolved) {
|
|
|
735
1113
|
let token = null;
|
|
736
1114
|
try {
|
|
737
1115
|
token = await publicClient.readContract({
|
|
738
|
-
address:
|
|
1116
|
+
address: getAddress3(asset),
|
|
739
1117
|
abi: erc20Abi3,
|
|
740
1118
|
functionName: "balanceOf",
|
|
741
1119
|
args: [owner]
|
|
@@ -767,6 +1145,21 @@ function makeEvmNetwork(resolved) {
|
|
|
767
1145
|
accept,
|
|
768
1146
|
minConfirmations: accept.extra.minConfirmations
|
|
769
1147
|
});
|
|
1148
|
+
},
|
|
1149
|
+
// Standard x402 `exact` rail (EIP-3009), seller side — EVM only.
|
|
1150
|
+
async exactDomain(asset) {
|
|
1151
|
+
return readExactDomain(publicClient, asset);
|
|
1152
|
+
},
|
|
1153
|
+
async settleExactSelf({ relayer, payload, accept }) {
|
|
1154
|
+
const a = relayer._native;
|
|
1155
|
+
return verifyAndSettleExactEvm({
|
|
1156
|
+
publicClient,
|
|
1157
|
+
walletClient: a.walletClient,
|
|
1158
|
+
account: a.account,
|
|
1159
|
+
chain: resolved.chain,
|
|
1160
|
+
payload,
|
|
1161
|
+
accept
|
|
1162
|
+
});
|
|
770
1163
|
}
|
|
771
1164
|
};
|
|
772
1165
|
}
|
|
@@ -783,7 +1176,7 @@ var loaders = {
|
|
|
783
1176
|
solana: async () => {
|
|
784
1177
|
let mod;
|
|
785
1178
|
try {
|
|
786
|
-
mod = await import("./solana-
|
|
1179
|
+
mod = await import("./solana-S3UFI3FE.js");
|
|
787
1180
|
} catch (cause) {
|
|
788
1181
|
throw new MissingDriverError(
|
|
789
1182
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
@@ -795,7 +1188,7 @@ var loaders = {
|
|
|
795
1188
|
ton: async () => {
|
|
796
1189
|
let mod;
|
|
797
1190
|
try {
|
|
798
|
-
mod = await import("./ton-
|
|
1191
|
+
mod = await import("./ton-WPTXGLVK.js");
|
|
799
1192
|
} catch (cause) {
|
|
800
1193
|
throw new MissingDriverError(
|
|
801
1194
|
`TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
|
|
@@ -807,7 +1200,7 @@ var loaders = {
|
|
|
807
1200
|
stellar: async () => {
|
|
808
1201
|
let mod;
|
|
809
1202
|
try {
|
|
810
|
-
mod = await import("./stellar-
|
|
1203
|
+
mod = await import("./stellar-Q5PO23SC.js");
|
|
811
1204
|
} catch (cause) {
|
|
812
1205
|
throw new MissingDriverError(
|
|
813
1206
|
`Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
|
|
@@ -819,7 +1212,7 @@ var loaders = {
|
|
|
819
1212
|
xrpl: async () => {
|
|
820
1213
|
let mod;
|
|
821
1214
|
try {
|
|
822
|
-
mod = await import("./xrpl-
|
|
1215
|
+
mod = await import("./xrpl-HEAPEXAM.js");
|
|
823
1216
|
} catch (cause) {
|
|
824
1217
|
throw new MissingDriverError(
|
|
825
1218
|
`XRPL selected, but its package isn't installed. Run: npm install xrpl`,
|
|
@@ -831,7 +1224,7 @@ var loaders = {
|
|
|
831
1224
|
tron: async () => {
|
|
832
1225
|
let mod;
|
|
833
1226
|
try {
|
|
834
|
-
mod = await import("./tron-
|
|
1227
|
+
mod = await import("./tron-6GXBXTR4.js");
|
|
835
1228
|
} catch (cause) {
|
|
836
1229
|
throw new MissingDriverError(
|
|
837
1230
|
`Tron selected, but its package isn't installed. Run: npm install tronweb`,
|
|
@@ -843,7 +1236,7 @@ var loaders = {
|
|
|
843
1236
|
sui: async () => {
|
|
844
1237
|
let mod;
|
|
845
1238
|
try {
|
|
846
|
-
mod = await import("./sui-
|
|
1239
|
+
mod = await import("./sui-WOXRKJXS.js");
|
|
847
1240
|
} catch (cause) {
|
|
848
1241
|
throw new MissingDriverError(
|
|
849
1242
|
`Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
|
|
@@ -855,7 +1248,7 @@ var loaders = {
|
|
|
855
1248
|
near: async () => {
|
|
856
1249
|
let mod;
|
|
857
1250
|
try {
|
|
858
|
-
mod = await import("./near-
|
|
1251
|
+
mod = await import("./near-K6BDBABG.js");
|
|
859
1252
|
} catch (cause) {
|
|
860
1253
|
throw new MissingDriverError(
|
|
861
1254
|
`NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
|
|
@@ -867,7 +1260,7 @@ var loaders = {
|
|
|
867
1260
|
aptos: async () => {
|
|
868
1261
|
let mod;
|
|
869
1262
|
try {
|
|
870
|
-
mod = await import("./aptos-
|
|
1263
|
+
mod = await import("./aptos-LPBLSEIQ.js");
|
|
871
1264
|
} catch (cause) {
|
|
872
1265
|
throw new MissingDriverError(
|
|
873
1266
|
`Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
|
|
@@ -879,7 +1272,7 @@ var loaders = {
|
|
|
879
1272
|
algorand: async () => {
|
|
880
1273
|
let mod;
|
|
881
1274
|
try {
|
|
882
|
-
mod = await import("./algorand-
|
|
1275
|
+
mod = await import("./algorand-WGVF4KTU.js");
|
|
883
1276
|
} catch (cause) {
|
|
884
1277
|
throw new MissingDriverError(
|
|
885
1278
|
`Algorand selected, but its package isn't installed. Run: npm install algosdk`,
|
|
@@ -912,6 +1305,42 @@ async function resolveNetwork2(opts) {
|
|
|
912
1305
|
}
|
|
913
1306
|
|
|
914
1307
|
// src/indexes.ts
|
|
1308
|
+
var DIRECTORY_INFO = {
|
|
1309
|
+
"402index": {
|
|
1310
|
+
source: "402index",
|
|
1311
|
+
review: "probe-sync",
|
|
1312
|
+
auth: "none",
|
|
1313
|
+
chains: null,
|
|
1314
|
+
onSuccess: "pending-review",
|
|
1315
|
+
readByDiscover: true,
|
|
1316
|
+
caveat: "402 Index probes your URL on submit, then lists it as PENDING REVIEW \u2014 a self-registered resource is NOT in search until approved. Verify your domain on 402index.io for instant approval; otherwise it appears after manual review, so retry discover() later."
|
|
1317
|
+
},
|
|
1318
|
+
x402scan: {
|
|
1319
|
+
source: "x402scan",
|
|
1320
|
+
review: "probe-sync",
|
|
1321
|
+
auth: "siwx",
|
|
1322
|
+
chains: ["eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"],
|
|
1323
|
+
onSuccess: "live",
|
|
1324
|
+
readByDiscover: false,
|
|
1325
|
+
caveat: "x402scan lists Base/Solana only, needs one wallet signature (SIWX), and requires a resolvable input schema (from /openapi.json or the bazaar extension in the 402 body). It goes live on x402scan.com immediately on success \u2014 but discover() does NOT read x402scan, so the listing won't appear in discover() results."
|
|
1326
|
+
},
|
|
1327
|
+
bazaar: {
|
|
1328
|
+
source: "bazaar",
|
|
1329
|
+
review: "settle-coupled",
|
|
1330
|
+
auth: "facilitator-only",
|
|
1331
|
+
chains: null,
|
|
1332
|
+
onSuccess: "not-listable",
|
|
1333
|
+
readByDiscover: true,
|
|
1334
|
+
caveat: "CDP Bazaar has no register endpoint \u2014 it catalogs a resource only when its own facilitator settles a payment. PipRail verifies locally with no facilitator, so a PipRail resource cannot be listed here (you can still READ Bazaar to find others). List on 402 Index or x402scan instead."
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
function getDirectoryInfo(source) {
|
|
1338
|
+
return DIRECTORY_INFO[source];
|
|
1339
|
+
}
|
|
1340
|
+
function decorateOutcome(o) {
|
|
1341
|
+
const info = DIRECTORY_INFO[o.source];
|
|
1342
|
+
return { ...o, visibility: o.ok ? info.onSuccess : "not-listable", note: info.caveat };
|
|
1343
|
+
}
|
|
915
1344
|
var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
|
|
916
1345
|
var INDEX402_SEARCH = "https://402index.io/api/v1/services";
|
|
917
1346
|
var INDEX402_REGISTER = "https://402index.io/api/v1/register";
|
|
@@ -1064,7 +1493,13 @@ async function register402Index(input) {
|
|
|
1064
1493
|
body: JSON.stringify(payload)
|
|
1065
1494
|
});
|
|
1066
1495
|
if (res.ok) {
|
|
1067
|
-
|
|
1496
|
+
const msg = await readIndexMessage(res);
|
|
1497
|
+
return {
|
|
1498
|
+
source: "402index",
|
|
1499
|
+
ok: true,
|
|
1500
|
+
status: res.status,
|
|
1501
|
+
detail: msg ?? "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval)."
|
|
1502
|
+
};
|
|
1068
1503
|
}
|
|
1069
1504
|
const why = await readIndexError(res);
|
|
1070
1505
|
return {
|
|
@@ -1077,6 +1512,14 @@ async function register402Index(input) {
|
|
|
1077
1512
|
return { source: "402index", ok: false, detail: errMsg(err) };
|
|
1078
1513
|
}
|
|
1079
1514
|
}
|
|
1515
|
+
async function readIndexMessage(res) {
|
|
1516
|
+
try {
|
|
1517
|
+
const body = await res.json();
|
|
1518
|
+
return typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
|
|
1519
|
+
} catch {
|
|
1520
|
+
return void 0;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1080
1523
|
async function readIndexError(res) {
|
|
1081
1524
|
try {
|
|
1082
1525
|
const body = await res.json();
|
|
@@ -1515,9 +1958,15 @@ var PipRailClient = class {
|
|
|
1515
1958
|
*
|
|
1516
1959
|
* Nothing PipRail-hosted: these are third-party open directories. Never throws
|
|
1517
1960
|
* for a read problem — an index that's down or changed simply contributes
|
|
1518
|
-
* nothing. Honest
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1961
|
+
* nothing. Honest caveats (see {@link DIRECTORY_INFO}):
|
|
1962
|
+
* - Reads **`bazaar` + `402index`** only — **NOT `x402scan`** (its reads are paid). A
|
|
1963
|
+
* resource you registered on x402scan is live there but will NOT appear here; don't
|
|
1964
|
+
* read that absence as failure. (Passing `sources:['x402scan']` explicitly yields `[]`.)
|
|
1965
|
+
* - A resource just listed via {@link register} may not appear yet — 402 Index reviews
|
|
1966
|
+
* before publishing, so retry with a brief backoff if a fresh listing is missing.
|
|
1967
|
+
* - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
|
|
1968
|
+
* only `onchain-proof` rails directly (pay `exact` resources with the experimental
|
|
1969
|
+
* `drivers/evm/exact.ts`).
|
|
1521
1970
|
*/
|
|
1522
1971
|
async discover(opts = {}) {
|
|
1523
1972
|
const found = await searchOpenIndexes({
|
|
@@ -1542,12 +1991,27 @@ var PipRailClient = class {
|
|
|
1542
1991
|
}
|
|
1543
1992
|
/**
|
|
1544
1993
|
* List a resource you run on the OPEN x402 registries, so agents can find it.
|
|
1545
|
-
* Default target is **402 Index** — one POST, no auth, no signature, no payment
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1994
|
+
* Default target is **402 Index** — one POST, no auth, no signature, no payment.
|
|
1995
|
+
* Add `'x402scan'` to also register via SIWX (one wallet signature; EVM + a
|
|
1996
|
+
* Base/Solana rail). Returns one {@link RegisterOutcome} per target — a target the
|
|
1997
|
+
* chain can't satisfy comes back `{ ok:false, detail }`, never a throw. An explicit,
|
|
1998
|
+
* developer-invoked action; it moves no funds, and nothing is PipRail-hosted —
|
|
1999
|
+
* you're listing on third-party open directories.
|
|
2000
|
+
*
|
|
2001
|
+
* **Listing is asynchronous — each outcome carries a `visibility` + `note` so an
|
|
2002
|
+
* agent knows when/where the resource is findable (don't assume `ok:true` means
|
|
2003
|
+
* "searchable now"):**
|
|
2004
|
+
* - **402 Index** → `visibility:'pending-review'`. It probes your URL on submit, then lists it
|
|
2005
|
+
* PENDING REVIEW — not searchable until approved (verify your domain on 402index.io for instant
|
|
2006
|
+
* approval), so `discover()` returns nothing for a fresh listing until then. Retry later.
|
|
2007
|
+
* - **x402scan** → `visibility:'live'`, but **`discover()` does NOT read x402scan** — the
|
|
2008
|
+
* listing is real on x402scan.com yet won't show up in `discover()`. Base/Solana only;
|
|
2009
|
+
* needs a resolvable input schema (`/openapi.json` or the `extensions.bazaar` block).
|
|
2010
|
+
* - **Bazaar** → `visibility:'not-listable'` for PipRail (it lists only what its facilitator
|
|
2011
|
+
* settles; PipRail uses none). You can still READ Bazaar via {@link discover} to find others.
|
|
2012
|
+
*
|
|
2013
|
+
* The per-source facts live in {@link DIRECTORY_INFO} (importable) if you'd rather branch
|
|
2014
|
+
* on them before calling.
|
|
1551
2015
|
*/
|
|
1552
2016
|
async register(url, opts = {}) {
|
|
1553
2017
|
const targets = opts.targets ?? ["402index"];
|
|
@@ -1586,7 +2050,7 @@ var PipRailClient = class {
|
|
|
1586
2050
|
});
|
|
1587
2051
|
}
|
|
1588
2052
|
}
|
|
1589
|
-
return outcomes;
|
|
2053
|
+
return outcomes.map(decorateOutcome);
|
|
1590
2054
|
}
|
|
1591
2055
|
/**
|
|
1592
2056
|
* The discovery signer for the bound wallet (its address + a message signer),
|
|
@@ -1663,7 +2127,10 @@ var PipRailClient = class {
|
|
|
1663
2127
|
const chosen = priced.find((p) => p.quote.withinPolicy) ?? priced[0];
|
|
1664
2128
|
return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
|
|
1665
2129
|
}
|
|
1666
|
-
/** The candidate accepts this client could pay: our scheme, on the bound network.
|
|
2130
|
+
/** The candidate accepts this client could pay: our scheme, on the bound network.
|
|
2131
|
+
* A dual-advertised challenge may also carry standard `exact` rails — the PipRail
|
|
2132
|
+
* client ignores those (it pays the backendless `onchain-proof` rail); the type
|
|
2133
|
+
* predicate narrows the `X402AnyAccept` union to the rails we settle. */
|
|
1667
2134
|
gatherCandidates(net, challenge) {
|
|
1668
2135
|
return challenge.accepts.filter(
|
|
1669
2136
|
(a) => a.scheme === "onchain-proof" && net.supports(a.network)
|
|
@@ -2026,6 +2493,14 @@ function isReplayableBodyInit(value) {
|
|
|
2026
2493
|
async function readInvalidReason(response) {
|
|
2027
2494
|
try {
|
|
2028
2495
|
const body = await response.clone().json();
|
|
2496
|
+
const ext = body?.extensions;
|
|
2497
|
+
const piprail = ext?.piprail;
|
|
2498
|
+
if (piprail && typeof piprail.code === "string") {
|
|
2499
|
+
return {
|
|
2500
|
+
error: piprail.code,
|
|
2501
|
+
detail: typeof piprail.detail === "string" ? piprail.detail : ""
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2029
2504
|
if (body && (body.status === "invalid" || typeof body.error === "string")) {
|
|
2030
2505
|
return {
|
|
2031
2506
|
error: typeof body.error === "string" ? body.error : "no error code",
|
|
@@ -2223,7 +2698,7 @@ function paymentTools(client) {
|
|
|
2223
2698
|
},
|
|
2224
2699
|
{
|
|
2225
2700
|
name: "piprail_register",
|
|
2226
|
-
description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment;
|
|
2701
|
+
description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
|
|
2227
2702
|
annotations: {
|
|
2228
2703
|
title: "Register an x402 endpoint",
|
|
2229
2704
|
readOnlyHint: false,
|
|
@@ -2257,6 +2732,100 @@ function paymentTools(client) {
|
|
|
2257
2732
|
];
|
|
2258
2733
|
}
|
|
2259
2734
|
|
|
2735
|
+
// src/facilitator.ts
|
|
2736
|
+
function safeStringify(value) {
|
|
2737
|
+
return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
2738
|
+
}
|
|
2739
|
+
function mapReason(reason) {
|
|
2740
|
+
const r = (reason ?? "").toLowerCase();
|
|
2741
|
+
if (r.includes("signature")) return "signature_invalid";
|
|
2742
|
+
if (r.includes("recipient")) return "wrong_recipient";
|
|
2743
|
+
if (r.includes("value") || r.includes("amount")) return "amount_too_low";
|
|
2744
|
+
if (r.includes("valid_before") || r.includes("valid_after") || r.includes("expired")) return "payment_expired";
|
|
2745
|
+
if (r.includes("used") || r.includes("replay") || r.includes("nonce") || r.includes("transaction_state")) {
|
|
2746
|
+
return "tx_already_used";
|
|
2747
|
+
}
|
|
2748
|
+
return "tx_reverted";
|
|
2749
|
+
}
|
|
2750
|
+
async function post(url, body, headers) {
|
|
2751
|
+
const res = await fetch(url, {
|
|
2752
|
+
method: "POST",
|
|
2753
|
+
headers: { "content-type": "application/json", ...headers },
|
|
2754
|
+
body: safeStringify(body)
|
|
2755
|
+
});
|
|
2756
|
+
let json = null;
|
|
2757
|
+
try {
|
|
2758
|
+
json = await res.json();
|
|
2759
|
+
} catch {
|
|
2760
|
+
}
|
|
2761
|
+
return { status: res.status, json };
|
|
2762
|
+
}
|
|
2763
|
+
async function settleViaFacilitator(input) {
|
|
2764
|
+
const base2 = input.url.replace(/\/+$/, "");
|
|
2765
|
+
const body = {
|
|
2766
|
+
x402Version: input.x402Version,
|
|
2767
|
+
paymentPayload: input.paymentPayload,
|
|
2768
|
+
paymentRequirements: input.paymentRequirements
|
|
2769
|
+
};
|
|
2770
|
+
const auth = input.authHeaders ? await input.authHeaders() : {};
|
|
2771
|
+
let verify;
|
|
2772
|
+
try {
|
|
2773
|
+
verify = await post(`${base2}/verify`, body, auth);
|
|
2774
|
+
} catch (err) {
|
|
2775
|
+
throw new SettlementError(
|
|
2776
|
+
`exact settle (facilitator ${base2}): /verify request failed (${err instanceof Error ? err.message : String(err)}).`,
|
|
2777
|
+
{ cause: err }
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
if (verify.status !== 200) {
|
|
2781
|
+
throw new SettlementError(
|
|
2782
|
+
`exact settle (facilitator ${base2}): /verify returned HTTP ${verify.status} (transport/auth error).`
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
const vr = verify.json ?? {};
|
|
2786
|
+
if (vr.isValid === false) {
|
|
2787
|
+
return {
|
|
2788
|
+
ok: false,
|
|
2789
|
+
error: mapReason(vr.invalidReason),
|
|
2790
|
+
detail: `Facilitator rejected the payment: ${vr.invalidReason ?? "invalid"}${vr.invalidMessage ? ` \u2014 ${vr.invalidMessage}` : ""}.`
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
let settle;
|
|
2794
|
+
try {
|
|
2795
|
+
settle = await post(`${base2}/settle`, body, auth);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
throw new SettlementError(
|
|
2798
|
+
`exact settle (facilitator ${base2}): /settle request failed (${err instanceof Error ? err.message : String(err)}).`,
|
|
2799
|
+
{ cause: err }
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
if (settle.status !== 200) {
|
|
2803
|
+
throw new SettlementError(
|
|
2804
|
+
`exact settle (facilitator ${base2}): /settle returned HTTP ${settle.status} (transport/auth error).`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
const sr = settle.json ?? {};
|
|
2808
|
+
if (!sr.success) {
|
|
2809
|
+
return {
|
|
2810
|
+
ok: false,
|
|
2811
|
+
error: mapReason(sr.errorReason),
|
|
2812
|
+
detail: `Facilitator settlement failed: ${sr.errorReason ?? "unknown"}${sr.errorMessage ? ` \u2014 ${sr.errorMessage}` : ""}.`
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
const receipt = {
|
|
2816
|
+
scheme: "exact",
|
|
2817
|
+
success: true,
|
|
2818
|
+
network: input.receipt.network,
|
|
2819
|
+
transaction: sr.transaction,
|
|
2820
|
+
asset: input.receipt.asset,
|
|
2821
|
+
amount: input.receipt.amount,
|
|
2822
|
+
payer: sr.payer ?? input.payerHint ?? "",
|
|
2823
|
+
payTo: input.receipt.payTo,
|
|
2824
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2825
|
+
};
|
|
2826
|
+
return { ok: true, receipt };
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2260
2829
|
// src/server.ts
|
|
2261
2830
|
function toInvalidBody(result) {
|
|
2262
2831
|
return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
|
|
@@ -2283,9 +2852,10 @@ function createPaymentGate(options) {
|
|
|
2283
2852
|
const genNonce = options.generateNonce ?? (() => globalThis.crypto.randomUUID());
|
|
2284
2853
|
let resolved;
|
|
2285
2854
|
function ready() {
|
|
2286
|
-
|
|
2855
|
+
if (resolved) return resolved;
|
|
2856
|
+
const p = (async () => {
|
|
2287
2857
|
const accepts = normaliseAccepts(options);
|
|
2288
|
-
|
|
2858
|
+
const specs = await Promise.all(
|
|
2289
2859
|
accepts.map(async (a) => {
|
|
2290
2860
|
const net = await resolveNetwork2({ chain: a.chain, rpcUrl: a.rpcUrl ?? options.rpcUrl });
|
|
2291
2861
|
const payTo = a.payTo ?? options.payTo;
|
|
@@ -2297,10 +2867,52 @@ function createPaymentGate(options) {
|
|
|
2297
2867
|
net.assertValidPayTo(payTo);
|
|
2298
2868
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
2299
2869
|
const amountBase = parseUnits(a.amount, decimals);
|
|
2300
|
-
|
|
2870
|
+
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
2871
|
+
if (options.exact) spec.exact = await resolveExactRail(net, asset);
|
|
2872
|
+
return spec;
|
|
2301
2873
|
})
|
|
2302
2874
|
);
|
|
2875
|
+
if (options.exact && !specs.some((s) => s.exact)) {
|
|
2876
|
+
throw new Error(
|
|
2877
|
+
"requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM + EIP-3009 only (USDC / EURC) \u2014 not native coins, not USDT, not non-EVM chains. Offer an EVM EIP-3009 token, or drop `exact`."
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
return specs;
|
|
2303
2881
|
})();
|
|
2882
|
+
p.catch(() => {
|
|
2883
|
+
if (resolved === p) resolved = void 0;
|
|
2884
|
+
});
|
|
2885
|
+
resolved = p;
|
|
2886
|
+
return p;
|
|
2887
|
+
}
|
|
2888
|
+
async function resolveExactRail(net, asset) {
|
|
2889
|
+
const cfg = options.exact;
|
|
2890
|
+
if (net.family !== "evm" || asset === "native" || !net.exactDomain || !net.settleExactSelf) {
|
|
2891
|
+
return void 0;
|
|
2892
|
+
}
|
|
2893
|
+
const domain = await net.exactDomain(asset);
|
|
2894
|
+
if (!domain) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`requirePayment: \`exact\` requested for asset ${asset} on ${net.network}, but it isn't an EIP-3009 token (couldn't read name()/version()/authorizationState). The exact rail supports USDC / EURC and other EIP-3009 tokens \u2014 USDT and native coins need onchain-proof. (Or check your rpcUrl is reachable.)`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
if (cfg.settle === "self") {
|
|
2900
|
+
if (cfg.relayer === void 0) {
|
|
2901
|
+
throw new Error(
|
|
2902
|
+
"requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts transferWithAuthorization), e.g. exact: { settle: 'self', relayer: { privateKey } }."
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
const relayer = net.bindWallet(cfg.relayer);
|
|
2906
|
+
return { domain, mode: { kind: "self", relayer } };
|
|
2907
|
+
}
|
|
2908
|
+
return {
|
|
2909
|
+
domain,
|
|
2910
|
+
mode: {
|
|
2911
|
+
kind: "facilitator",
|
|
2912
|
+
url: cfg.settle.facilitator,
|
|
2913
|
+
...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2304
2916
|
}
|
|
2305
2917
|
const hasCustomStore = Boolean(options.isUsed || options.markUsed);
|
|
2306
2918
|
const localUsed = /* @__PURE__ */ new Set();
|
|
@@ -2337,93 +2949,191 @@ function createPaymentGate(options) {
|
|
|
2337
2949
|
}
|
|
2338
2950
|
};
|
|
2339
2951
|
}
|
|
2340
|
-
|
|
2952
|
+
function buildExactAccept(s) {
|
|
2953
|
+
const d = s.exact.domain;
|
|
2954
|
+
return {
|
|
2955
|
+
scheme: "exact",
|
|
2956
|
+
network: s.net.network,
|
|
2957
|
+
amount: s.amountBase.toString(),
|
|
2958
|
+
asset: s.asset,
|
|
2959
|
+
payTo: s.payTo,
|
|
2960
|
+
maxTimeoutSeconds,
|
|
2961
|
+
extra: {
|
|
2962
|
+
assetTransferMethod: "eip3009",
|
|
2963
|
+
name: d.name,
|
|
2964
|
+
version: d.version,
|
|
2965
|
+
minConfirmations,
|
|
2966
|
+
decimals: s.decimals,
|
|
2967
|
+
amountFormatted: s.amountFormatted,
|
|
2968
|
+
...s.symbol ? { symbol: s.symbol } : {}
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
function buildAccepts(specs, nonce) {
|
|
2973
|
+
const out = [];
|
|
2974
|
+
for (const s of specs) {
|
|
2975
|
+
if (s.exact) out.push(buildExactAccept(s));
|
|
2976
|
+
out.push(buildAccept(s, nonce));
|
|
2977
|
+
}
|
|
2978
|
+
return out;
|
|
2979
|
+
}
|
|
2980
|
+
async function makeChallenge(resourceUrl, opts) {
|
|
2341
2981
|
const specs = await ready();
|
|
2342
2982
|
const nonce = genNonce();
|
|
2343
2983
|
const challenge2 = {
|
|
2344
2984
|
x402Version: 2,
|
|
2345
|
-
error: null,
|
|
2346
2985
|
resource: {
|
|
2347
2986
|
url: resourceUrl,
|
|
2348
2987
|
...options.description ? { description: options.description } : {}
|
|
2349
2988
|
},
|
|
2350
|
-
accepts: specs
|
|
2989
|
+
accepts: buildAccepts(specs, nonce),
|
|
2990
|
+
...opts?.error ? { error: opts.error } : {},
|
|
2991
|
+
...opts?.extensions ? { extensions: opts.extensions } : {}
|
|
2351
2992
|
};
|
|
2352
2993
|
return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
|
|
2353
2994
|
}
|
|
2995
|
+
async function challenge(resourceUrl = "") {
|
|
2996
|
+
return makeChallenge(resourceUrl);
|
|
2997
|
+
}
|
|
2354
2998
|
async function asChallenge() {
|
|
2355
|
-
const { challenge: c, requiredHeader } = await
|
|
2999
|
+
const { challenge: c, requiredHeader } = await makeChallenge("");
|
|
2356
3000
|
return { kind: "challenge", challenge: c, requiredHeader, statusCode: 402 };
|
|
2357
3001
|
}
|
|
3002
|
+
async function rejection(code, detail) {
|
|
3003
|
+
const { challenge: c, requiredHeader } = await makeChallenge("", {
|
|
3004
|
+
error: `${code}: ${detail}`,
|
|
3005
|
+
extensions: { piprail: { code, detail } }
|
|
3006
|
+
});
|
|
3007
|
+
return { kind: "invalid", error: code, detail, challenge: c, requiredHeader, statusCode: 402 };
|
|
3008
|
+
}
|
|
3009
|
+
function fireOnPaid(receipt) {
|
|
3010
|
+
if (options.onPaid) {
|
|
3011
|
+
try {
|
|
3012
|
+
options.onPaid(receipt);
|
|
3013
|
+
} catch {
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
2358
3017
|
async function describe(resourceUrl = "") {
|
|
2359
3018
|
const specs = await ready();
|
|
2360
|
-
const accepts =
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
3019
|
+
const accepts = [];
|
|
3020
|
+
for (const s of specs) {
|
|
3021
|
+
const base2 = {
|
|
3022
|
+
network: s.net.network,
|
|
3023
|
+
asset: s.asset,
|
|
3024
|
+
payTo: s.payTo,
|
|
3025
|
+
amount: s.amountBase.toString(),
|
|
3026
|
+
amountFormatted: s.amountFormatted,
|
|
3027
|
+
decimals: s.decimals,
|
|
3028
|
+
maxTimeoutSeconds,
|
|
3029
|
+
...s.symbol ? { symbol: s.symbol } : {}
|
|
3030
|
+
};
|
|
3031
|
+
if (s.exact) accepts.push({ scheme: "exact", ...base2 });
|
|
3032
|
+
accepts.push({ scheme: "onchain-proof", ...base2 });
|
|
3033
|
+
}
|
|
2371
3034
|
return {
|
|
2372
3035
|
url: resourceUrl,
|
|
2373
3036
|
...options.description ? { description: options.description } : {},
|
|
2374
3037
|
accepts
|
|
2375
3038
|
};
|
|
2376
3039
|
}
|
|
2377
|
-
async function
|
|
2378
|
-
const raw = normaliseHeader(paymentSignature);
|
|
2379
|
-
if (!raw) return asChallenge();
|
|
2380
|
-
const sig = parseSignatureHeader(raw);
|
|
2381
|
-
if (!sig || !sig.accepted || typeof sig.accepted.network !== "string" || typeof sig.accepted.asset !== "string") {
|
|
2382
|
-
return asChallenge();
|
|
2383
|
-
}
|
|
3040
|
+
async function verifyOnchainProof(sig) {
|
|
2384
3041
|
const specs = await ready();
|
|
2385
3042
|
const spec = specs.find(
|
|
2386
3043
|
(s) => s.net.network === sig.accepted.network && s.asset === sig.accepted.asset
|
|
2387
3044
|
);
|
|
2388
3045
|
if (!spec) {
|
|
2389
|
-
return
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
statusCode: 402
|
|
2394
|
-
};
|
|
3046
|
+
return rejection(
|
|
3047
|
+
"transfer_not_found",
|
|
3048
|
+
`Proof claims ${sig.accepted.asset} on ${sig.accepted.network}, which this resource doesn't accept (offered: ${specs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
|
|
3049
|
+
);
|
|
2395
3050
|
}
|
|
2396
3051
|
const ref = sig.payload.txHash;
|
|
2397
|
-
if (await claimTx(ref)) {
|
|
2398
|
-
return {
|
|
2399
|
-
kind: "invalid",
|
|
2400
|
-
error: "tx_already_used",
|
|
2401
|
-
detail: `Proof ${ref} was already redeemed.`,
|
|
2402
|
-
statusCode: 402
|
|
2403
|
-
};
|
|
2404
|
-
}
|
|
3052
|
+
if (await claimTx(ref)) return rejection("tx_already_used", `Proof ${ref} was already redeemed.`);
|
|
2405
3053
|
const result = await spec.net.verify(ref, buildAccept(spec, sig.payload.nonce));
|
|
2406
3054
|
if (!result.ok) {
|
|
2407
3055
|
await settleTx(ref, false);
|
|
2408
|
-
return
|
|
2409
|
-
kind: "invalid",
|
|
2410
|
-
error: result.error,
|
|
2411
|
-
detail: result.detail,
|
|
2412
|
-
statusCode: 402
|
|
2413
|
-
};
|
|
3056
|
+
return rejection(result.error, result.detail);
|
|
2414
3057
|
}
|
|
2415
3058
|
await settleTx(ref, true);
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
3059
|
+
fireOnPaid(result.receipt);
|
|
3060
|
+
return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
|
|
3061
|
+
}
|
|
3062
|
+
async function verifyExact(exact) {
|
|
3063
|
+
const specs = await ready();
|
|
3064
|
+
const exactSpecs = specs.filter((s) => s.exact);
|
|
3065
|
+
if (exactSpecs.length === 0) {
|
|
3066
|
+
return rejection("transfer_not_found", "This resource offers no standard `exact` rail.");
|
|
3067
|
+
}
|
|
3068
|
+
const isCaip = exact.network.startsWith("eip155:");
|
|
3069
|
+
let candidates = isCaip ? exactSpecs.filter((s) => s.net.network === exact.network) : exactSpecs;
|
|
3070
|
+
if (exact.asset) {
|
|
3071
|
+
candidates = candidates.filter((s) => s.asset.toLowerCase() === exact.asset.toLowerCase());
|
|
3072
|
+
}
|
|
3073
|
+
let spec = candidates[0];
|
|
3074
|
+
if (!isCaip && !exact.asset && exactSpecs.length > 1) spec = void 0;
|
|
3075
|
+
if (!spec && !isCaip && !exact.asset && exactSpecs.length === 1) spec = exactSpecs[0];
|
|
3076
|
+
if (!spec || !spec.exact) {
|
|
3077
|
+
return rejection(
|
|
3078
|
+
"transfer_not_found",
|
|
3079
|
+
`No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
const nonce = exact.payload.authorization.nonce;
|
|
3083
|
+
if (await claimTx(nonce)) {
|
|
3084
|
+
return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
|
|
3085
|
+
}
|
|
3086
|
+
const accept = buildExactAccept(spec);
|
|
3087
|
+
const mode = spec.exact.mode;
|
|
3088
|
+
let result;
|
|
3089
|
+
try {
|
|
3090
|
+
if (mode.kind === "self") {
|
|
3091
|
+
result = await spec.net.settleExactSelf({ relayer: mode.relayer, payload: exact.payload, accept });
|
|
3092
|
+
} else {
|
|
3093
|
+
result = await settleViaFacilitator({
|
|
3094
|
+
url: mode.url,
|
|
3095
|
+
...mode.authHeaders ? { authHeaders: mode.authHeaders } : {},
|
|
3096
|
+
// PipRail always builds a v2-shaped paymentRequirements (CAIP-2 network + `amount`),
|
|
3097
|
+
// so force x402Version:2 — echoing a v1 client's version here would hand the facilitator
|
|
3098
|
+
// a self-inconsistent request (v1 envelope, v2 requirements). The inner payload is
|
|
3099
|
+
// byte-identical across versions, so forwarding it verbatim is fine.
|
|
3100
|
+
x402Version: 2,
|
|
3101
|
+
paymentPayload: exact.raw,
|
|
3102
|
+
paymentRequirements: {
|
|
3103
|
+
scheme: "exact",
|
|
3104
|
+
network: accept.network,
|
|
3105
|
+
asset: accept.asset,
|
|
3106
|
+
amount: accept.amount,
|
|
3107
|
+
payTo: accept.payTo,
|
|
3108
|
+
maxTimeoutSeconds: accept.maxTimeoutSeconds,
|
|
3109
|
+
extra: { name: accept.extra.name, version: accept.extra.version }
|
|
3110
|
+
},
|
|
3111
|
+
receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
|
|
3112
|
+
payerHint: exact.payload.authorization.from
|
|
3113
|
+
});
|
|
2420
3114
|
}
|
|
3115
|
+
} catch (err) {
|
|
3116
|
+
await settleTx(nonce, false);
|
|
3117
|
+
throw err;
|
|
2421
3118
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
3119
|
+
if (!result.ok) {
|
|
3120
|
+
await settleTx(nonce, false);
|
|
3121
|
+
return rejection(result.error, result.detail);
|
|
3122
|
+
}
|
|
3123
|
+
await settleTx(nonce, true);
|
|
3124
|
+
fireOnPaid(result.receipt);
|
|
3125
|
+
return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
|
|
3126
|
+
}
|
|
3127
|
+
async function verify(paymentSignature) {
|
|
3128
|
+
const raw = normaliseHeader(paymentSignature);
|
|
3129
|
+
if (!raw) return asChallenge();
|
|
3130
|
+
const sig = parseSignatureHeader(raw);
|
|
3131
|
+
if (sig && sig.accepted && typeof sig.accepted.network === "string" && typeof sig.accepted.asset === "string") {
|
|
3132
|
+
return verifyOnchainProof(sig);
|
|
3133
|
+
}
|
|
3134
|
+
const exact = parseExactPaymentHeader(raw);
|
|
3135
|
+
if (exact) return verifyExact(exact);
|
|
3136
|
+
return asChallenge();
|
|
2427
3137
|
}
|
|
2428
3138
|
return { challenge, verify, describe };
|
|
2429
3139
|
}
|
|
@@ -2432,14 +3142,20 @@ function requirePayment(options) {
|
|
|
2432
3142
|
return async (req, res, next) => {
|
|
2433
3143
|
let result;
|
|
2434
3144
|
try {
|
|
2435
|
-
result = await gate.verify(req.headers[HEADER_SIGNATURE]);
|
|
3145
|
+
result = await gate.verify(req.headers[HEADER_SIGNATURE] ?? req.headers[HEADER_SIGNATURE_V1]);
|
|
2436
3146
|
} catch (err) {
|
|
3147
|
+
if (err instanceof SettlementError) {
|
|
3148
|
+
res.status(502);
|
|
3149
|
+
res.json({ x402Version: 2, error: "settlement_failed", detail: err.message });
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
2437
3152
|
next(err);
|
|
2438
3153
|
return;
|
|
2439
3154
|
}
|
|
2440
3155
|
switch (result.kind) {
|
|
2441
3156
|
case "paid":
|
|
2442
3157
|
res.setHeader(HEADER_RESPONSE, result.receiptHeader);
|
|
3158
|
+
res.setHeader(HEADER_RESPONSE_V1, result.receiptHeader);
|
|
2443
3159
|
return next();
|
|
2444
3160
|
case "challenge":
|
|
2445
3161
|
res.setHeader(HEADER_REQUIRED, result.requiredHeader);
|
|
@@ -2447,8 +3163,9 @@ function requirePayment(options) {
|
|
|
2447
3163
|
res.json(result.challenge);
|
|
2448
3164
|
return;
|
|
2449
3165
|
case "invalid":
|
|
3166
|
+
res.setHeader(HEADER_REQUIRED, result.requiredHeader);
|
|
2450
3167
|
res.status(result.statusCode);
|
|
2451
|
-
res.json(
|
|
3168
|
+
res.json(result.challenge);
|
|
2452
3169
|
return;
|
|
2453
3170
|
}
|
|
2454
3171
|
};
|
|
@@ -2458,104 +3175,6 @@ function normaliseHeader(value) {
|
|
|
2458
3175
|
return value;
|
|
2459
3176
|
}
|
|
2460
3177
|
|
|
2461
|
-
// src/drivers/evm/exact.ts
|
|
2462
|
-
var EXACT_NETWORK_SLUGS = {
|
|
2463
|
-
ethereum: 1,
|
|
2464
|
-
base: 8453,
|
|
2465
|
-
"base-sepolia": 84532,
|
|
2466
|
-
arbitrum: 42161,
|
|
2467
|
-
optimism: 10,
|
|
2468
|
-
polygon: 137,
|
|
2469
|
-
avalanche: 43114
|
|
2470
|
-
};
|
|
2471
|
-
function chainIdForExactNetwork(slug) {
|
|
2472
|
-
return EXACT_NETWORK_SLUGS[slug] ?? null;
|
|
2473
|
-
}
|
|
2474
|
-
var EIP3009_TYPES = {
|
|
2475
|
-
TransferWithAuthorization: [
|
|
2476
|
-
{ name: "from", type: "address" },
|
|
2477
|
-
{ name: "to", type: "address" },
|
|
2478
|
-
{ name: "value", type: "uint256" },
|
|
2479
|
-
{ name: "validAfter", type: "uint256" },
|
|
2480
|
-
{ name: "validBefore", type: "uint256" },
|
|
2481
|
-
{ name: "nonce", type: "bytes32" }
|
|
2482
|
-
]
|
|
2483
|
-
};
|
|
2484
|
-
function parseExactRequirements(body) {
|
|
2485
|
-
if (!body || typeof body !== "object") return null;
|
|
2486
|
-
const accepts = body.accepts;
|
|
2487
|
-
if (!Array.isArray(accepts)) return null;
|
|
2488
|
-
const out = [];
|
|
2489
|
-
for (const raw of accepts) {
|
|
2490
|
-
if (!raw || typeof raw !== "object") continue;
|
|
2491
|
-
const a = raw;
|
|
2492
|
-
if (a.scheme !== "exact") continue;
|
|
2493
|
-
const amount = a.maxAmountRequired ?? a.amount;
|
|
2494
|
-
if (typeof a.network !== "string" || typeof amount !== "string" || typeof a.asset !== "string" || typeof a.payTo !== "string") {
|
|
2495
|
-
continue;
|
|
2496
|
-
}
|
|
2497
|
-
out.push({
|
|
2498
|
-
scheme: "exact",
|
|
2499
|
-
network: a.network,
|
|
2500
|
-
maxAmountRequired: amount,
|
|
2501
|
-
asset: a.asset,
|
|
2502
|
-
payTo: a.payTo,
|
|
2503
|
-
maxTimeoutSeconds: typeof a.maxTimeoutSeconds === "number" ? a.maxTimeoutSeconds : 600,
|
|
2504
|
-
...a.extra && typeof a.extra === "object" ? { extra: a.extra } : {},
|
|
2505
|
-
...typeof a.description === "string" ? { description: a.description } : {},
|
|
2506
|
-
...typeof a.resource === "string" ? { resource: a.resource } : {}
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2509
|
-
return out;
|
|
2510
|
-
}
|
|
2511
|
-
async function buildExactAuthorization(params) {
|
|
2512
|
-
const { account, accept, chainId, now, nonce } = params;
|
|
2513
|
-
if (!account.signTypedData) {
|
|
2514
|
-
throw new Error("buildExactAuthorization: the account cannot sign EIP-712 typed data.");
|
|
2515
|
-
}
|
|
2516
|
-
const authorization = {
|
|
2517
|
-
from: account.address,
|
|
2518
|
-
to: accept.payTo,
|
|
2519
|
-
value: accept.maxAmountRequired,
|
|
2520
|
-
validAfter: "0",
|
|
2521
|
-
validBefore: String(now + accept.maxTimeoutSeconds),
|
|
2522
|
-
nonce
|
|
2523
|
-
};
|
|
2524
|
-
const signature = await account.signTypedData({
|
|
2525
|
-
domain: {
|
|
2526
|
-
name: accept.extra?.name ?? "USD Coin",
|
|
2527
|
-
version: accept.extra?.version ?? "2",
|
|
2528
|
-
chainId,
|
|
2529
|
-
verifyingContract: accept.asset
|
|
2530
|
-
},
|
|
2531
|
-
types: EIP3009_TYPES,
|
|
2532
|
-
primaryType: "TransferWithAuthorization",
|
|
2533
|
-
message: {
|
|
2534
|
-
from: authorization.from,
|
|
2535
|
-
to: authorization.to,
|
|
2536
|
-
value: BigInt(authorization.value),
|
|
2537
|
-
validAfter: BigInt(authorization.validAfter),
|
|
2538
|
-
validBefore: BigInt(authorization.validBefore),
|
|
2539
|
-
nonce: authorization.nonce
|
|
2540
|
-
}
|
|
2541
|
-
});
|
|
2542
|
-
return { authorization, signature };
|
|
2543
|
-
}
|
|
2544
|
-
function base64(str) {
|
|
2545
|
-
if (typeof btoa === "function") return btoa(str);
|
|
2546
|
-
if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
|
|
2547
|
-
throw new Error("No base64 encoder available in this runtime.");
|
|
2548
|
-
}
|
|
2549
|
-
function encodeXPaymentHeader(input) {
|
|
2550
|
-
const payload = {
|
|
2551
|
-
x402Version: input.x402Version ?? 1,
|
|
2552
|
-
scheme: "exact",
|
|
2553
|
-
network: input.network,
|
|
2554
|
-
payload: { signature: input.signature, authorization: input.authorization }
|
|
2555
|
-
};
|
|
2556
|
-
return base64(JSON.stringify(payload));
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
3178
|
// src/discovery.ts
|
|
2560
3179
|
var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
|
|
2561
3180
|
function pathOf(url) {
|
|
@@ -2613,9 +3232,15 @@ function buildX402DnsTxt(input) {
|
|
|
2613
3232
|
export {
|
|
2614
3233
|
CHAINS,
|
|
2615
3234
|
ConfirmationTimeoutError,
|
|
3235
|
+
DIRECTORY_INFO,
|
|
2616
3236
|
EIP3009_TYPES,
|
|
2617
3237
|
EXACT_NETWORK_SLUGS,
|
|
2618
3238
|
GENERATOR,
|
|
3239
|
+
HEADER_REQUIRED,
|
|
3240
|
+
HEADER_RESPONSE,
|
|
3241
|
+
HEADER_RESPONSE_V1,
|
|
3242
|
+
HEADER_SIGNATURE,
|
|
3243
|
+
HEADER_SIGNATURE_V1,
|
|
2619
3244
|
InsufficientFundsError,
|
|
2620
3245
|
InvalidEnvelopeError,
|
|
2621
3246
|
MaxRetriesExceededError,
|
|
@@ -2627,6 +3252,7 @@ export {
|
|
|
2627
3252
|
PipRailClient,
|
|
2628
3253
|
PipRailError,
|
|
2629
3254
|
RecipientNotReadyError,
|
|
3255
|
+
SettlementError,
|
|
2630
3256
|
UnknownTokenError,
|
|
2631
3257
|
UnsupportedNetworkError,
|
|
2632
3258
|
WrongChainError,
|
|
@@ -2640,22 +3266,28 @@ export {
|
|
|
2640
3266
|
buildX402DnsTxt,
|
|
2641
3267
|
chainIdForExactNetwork,
|
|
2642
3268
|
createPaymentGate,
|
|
3269
|
+
decorateOutcome,
|
|
3270
|
+
eip3009Abi,
|
|
2643
3271
|
encodeXPaymentHeader,
|
|
2644
3272
|
evaluatePolicy,
|
|
3273
|
+
getDirectoryInfo,
|
|
2645
3274
|
normalizeNetwork,
|
|
2646
3275
|
parseChallenge,
|
|
3276
|
+
parseExactPaymentHeader,
|
|
2647
3277
|
parseExactRequirements,
|
|
2648
3278
|
parseReceipt,
|
|
2649
3279
|
parseSignatureHeader,
|
|
2650
3280
|
paymentTools,
|
|
2651
3281
|
pickAccept,
|
|
2652
3282
|
planAcross,
|
|
3283
|
+
readExactDomain,
|
|
2653
3284
|
register402Index,
|
|
2654
3285
|
registerDriver,
|
|
2655
3286
|
registerX402Scan,
|
|
2656
3287
|
requirePayment,
|
|
2657
3288
|
resolveChain,
|
|
2658
3289
|
searchOpenIndexes,
|
|
3290
|
+
settleViaFacilitator,
|
|
2659
3291
|
toInsufficientFundsError,
|
|
2660
3292
|
toInvalidBody
|
|
2661
3293
|
};
|