@piprail/sdk 1.13.1 → 1.15.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/CHAINS.md +3 -2
- package/CHANGELOG.md +69 -0
- package/ERRORS.md +17 -2
- package/README.md +57 -4
- package/STANDARDS.md +4 -0
- package/dist/{algorand-MXUSKX46.cjs → algorand-EJ3S2V7E.cjs} +17 -17
- package/dist/{algorand-WGVF4KTU.js → algorand-F3OYB534.js} +1 -1
- package/dist/{aptos-YT7SXWPF.cjs → aptos-GJGIZHNI.cjs} +16 -16
- package/dist/{aptos-LPBLSEIQ.js → aptos-SUXOVP7B.js} +1 -1
- package/dist/{chunk-SVMGHASK.js → chunk-ILPABTI2.js} +18 -1
- package/dist/{chunk-MDLZJGLY.cjs → chunk-PA6YD3HL.cjs} +35 -18
- package/dist/index.cjs +800 -163
- package/dist/index.d.cts +472 -42
- package/dist/index.d.ts +472 -42
- package/dist/index.js +695 -58
- package/dist/{near-K6BDBABG.js → near-LM7S3WUD.js} +1 -1
- package/dist/{near-7ZDNISUX.cjs → near-ZJLZE26R.cjs} +19 -19
- package/dist/{solana-PU7N2M64.cjs → solana-MPPE6K24.cjs} +14 -14
- package/dist/{solana-S3UFI3FE.js → solana-WDKWWF33.js} +1 -1
- package/dist/{stellar-Q5PO23SC.js → stellar-FIJPQZVW.js} +1 -1
- package/dist/{stellar-VDQOFQEO.cjs → stellar-XHLLNHQP.cjs} +21 -21
- package/dist/{sui-FKSMLKRF.cjs → sui-6CVLEXLA.cjs} +17 -17
- package/dist/{sui-WOXRKJXS.js → sui-B7AVN7NK.js} +1 -1
- package/dist/{ton-WPTXGLVK.js → ton-CHJ26BVA.js} +1 -1
- package/dist/{ton-VK6KRJHP.cjs → ton-RNEFN25G.cjs} +14 -14
- package/dist/{tron-6GXBXTR4.js → tron-DD3JDROV.js} +1 -1
- package/dist/{tron-WLOF5OUV.cjs → tron-TKJHNFGM.cjs} +24 -24
- package/dist/{xrpl-HEAPEXAM.js → xrpl-GTUPP6SK.js} +1 -1
- package/dist/{xrpl-CMNI25BV.cjs → xrpl-XN2NBNGI.cjs} +21 -21
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
var _chunkPA6YD3HLcjs = require('./chunk-PA6YD3HL.cjs');
|
|
25
26
|
|
|
26
27
|
// src/drivers/registry.ts
|
|
27
28
|
var byFamily = /* @__PURE__ */ new Map();
|
|
@@ -50,13 +51,13 @@ function resolveNetwork(opts) {
|
|
|
50
51
|
const family = familyForChain(opts.chain);
|
|
51
52
|
const driver = byFamily.get(family);
|
|
52
53
|
if (!driver) {
|
|
53
|
-
throw new (0,
|
|
54
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedNetworkError)(
|
|
54
55
|
`No driver registered for the "${family}" family \u2014 it may not be mounted yet (use the async resolveNetwork()).`
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
58
|
const net = driver.resolve(opts);
|
|
58
59
|
if (!net) {
|
|
59
|
-
throw new (0,
|
|
60
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedNetworkError)(
|
|
60
61
|
`The ${family} driver didn't recognise this chain input.`
|
|
61
62
|
);
|
|
62
63
|
}
|
|
@@ -97,13 +98,18 @@ var CHAINS = {
|
|
|
97
98
|
defaultRpc: "https://ethereum-rpc.publicnode.com",
|
|
98
99
|
tokens: {
|
|
99
100
|
USDC: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6, symbol: "USDC" },
|
|
100
|
-
USDT: { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6, symbol: "USDT" }
|
|
101
|
+
USDT: { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6, symbol: "USDT" },
|
|
102
|
+
// Circle EURC — EIP-3009 (exact-payable). On-chain EIP-712 domain name is "Euro Coin" here
|
|
103
|
+
// (NOT "EURC"); the buyer re-derives it on-chain, so the symbol below is display-only.
|
|
104
|
+
EURC: { address: "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", decimals: 6, symbol: "EURC" }
|
|
101
105
|
}
|
|
102
106
|
},
|
|
103
107
|
base: {
|
|
104
108
|
chain: _chains.base,
|
|
105
109
|
tokens: {
|
|
106
|
-
USDC: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6, symbol: "USDC" }
|
|
110
|
+
USDC: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6, symbol: "USDC" },
|
|
111
|
+
// Circle EURC — EIP-3009 (exact-payable). On-chain EIP-712 domain name is "EURC" here.
|
|
112
|
+
EURC: { address: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42", decimals: 6, symbol: "EURC" }
|
|
107
113
|
}
|
|
108
114
|
},
|
|
109
115
|
arbitrum: {
|
|
@@ -139,7 +145,9 @@ var CHAINS = {
|
|
|
139
145
|
chain: _chains.avalanche,
|
|
140
146
|
tokens: {
|
|
141
147
|
USDC: { address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", decimals: 6, symbol: "USDC" },
|
|
142
|
-
USDT: { address: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", decimals: 6, symbol: "USDT" }
|
|
148
|
+
USDT: { address: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", decimals: 6, symbol: "USDT" },
|
|
149
|
+
// Circle EURC — EIP-3009 (exact-payable). On-chain EIP-712 domain name is "Euro Coin" here.
|
|
150
|
+
EURC: { address: "0xC891EB4cbdEFf6e073e859e987815Ed1505c2ACD", decimals: 6, symbol: "EURC" }
|
|
143
151
|
}
|
|
144
152
|
},
|
|
145
153
|
// ── More popular EVM mainnets. Every address below was verified on-chain
|
|
@@ -312,12 +320,12 @@ function createWalletAdapter(config, resolved) {
|
|
|
312
320
|
}
|
|
313
321
|
const wc = config.walletClient;
|
|
314
322
|
if (!wc.account) {
|
|
315
|
-
throw new (0,
|
|
323
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
|
|
316
324
|
"chain is EVM; the provided walletClient has no attached account. Use `createWalletClient({ account, chain, transport })`, or pass { privateKey }."
|
|
317
325
|
);
|
|
318
326
|
}
|
|
319
327
|
if (wc.chain && wc.chain.id !== resolved.chainId) {
|
|
320
|
-
throw new (0,
|
|
328
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongChainError)(
|
|
321
329
|
`PipRailClient: walletClient is on chain ${wc.chain.id} but the SDK was configured with chain ${resolved.chainId}. They must match.`
|
|
322
330
|
);
|
|
323
331
|
}
|
|
@@ -591,6 +599,53 @@ function encodeXPaymentHeader(input) {
|
|
|
591
599
|
};
|
|
592
600
|
return base64(JSON.stringify(payload));
|
|
593
601
|
}
|
|
602
|
+
async function payExactEvm(input) {
|
|
603
|
+
const { publicClient, walletClient, account, chainId, accept } = input;
|
|
604
|
+
let code;
|
|
605
|
+
try {
|
|
606
|
+
code = await publicClient.getCode({ address: account.address });
|
|
607
|
+
} catch (e6) {
|
|
608
|
+
code = void 0;
|
|
609
|
+
}
|
|
610
|
+
if (code && code !== "0x") {
|
|
611
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
612
|
+
`exact buyer rail requires an EOA signer; ${account.address} is a contract / EIP-1271 / EIP-7702-delegated account (no recoverable ECDSA signature). Pay via onchain-proof.`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const domain = await readExactDomain(publicClient, accept.asset);
|
|
616
|
+
if (!domain) {
|
|
617
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
618
|
+
`exact: ${accept.asset} on ${accept.network} isn't an EIP-3009 token (USDT needs Permit2; native coin and plain ERC-20s aren't exact-payable). Pay via onchain-proof.`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const g = globalThis.crypto;
|
|
622
|
+
if (!_optionalChain([g, 'optionalAccess', _5 => _5.getRandomValues])) {
|
|
623
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
624
|
+
"this runtime lacks Web Crypto (globalThis.crypto.getRandomValues); the exact rail needs a CSPRNG nonce."
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const raw = new Uint8Array(32);
|
|
628
|
+
g.getRandomValues(raw);
|
|
629
|
+
const nonce = `0x${[...raw].map((b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
630
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
631
|
+
const from = account.address;
|
|
632
|
+
const to = _viem.getAddress.call(void 0, accept.payTo);
|
|
633
|
+
const value = accept.amount;
|
|
634
|
+
const validAfter = "0";
|
|
635
|
+
const validBefore = String(now + accept.maxTimeoutSeconds);
|
|
636
|
+
const signature = await walletClient.signTypedData({
|
|
637
|
+
account,
|
|
638
|
+
domain: { name: domain.name, version: domain.version, chainId, verifyingContract: _viem.getAddress.call(void 0, accept.asset) },
|
|
639
|
+
types: EIP3009_TYPES,
|
|
640
|
+
primaryType: "TransferWithAuthorization",
|
|
641
|
+
message: { from, to, value: BigInt(value), validAfter: 0n, validBefore: BigInt(validBefore), nonce }
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
payload: { signature, authorization: { from, to, value, validAfter, validBefore, nonce } },
|
|
645
|
+
payerFrom: from,
|
|
646
|
+
nonce
|
|
647
|
+
};
|
|
648
|
+
}
|
|
594
649
|
var eip3009Abi = [
|
|
595
650
|
{
|
|
596
651
|
type: "function",
|
|
@@ -644,7 +699,7 @@ async function readExactDomain(publicClient, asset) {
|
|
|
644
699
|
let token;
|
|
645
700
|
try {
|
|
646
701
|
token = _viem.getAddress.call(void 0, asset);
|
|
647
|
-
} catch (
|
|
702
|
+
} catch (e7) {
|
|
648
703
|
return null;
|
|
649
704
|
}
|
|
650
705
|
try {
|
|
@@ -662,7 +717,7 @@ async function readExactDomain(publicClient, asset) {
|
|
|
662
717
|
]);
|
|
663
718
|
if (typeof name !== "string" || typeof version !== "string" || !name || !version) return null;
|
|
664
719
|
return { name, version };
|
|
665
|
-
} catch (
|
|
720
|
+
} catch (e8) {
|
|
666
721
|
return null;
|
|
667
722
|
}
|
|
668
723
|
}
|
|
@@ -710,7 +765,7 @@ async function verifyAndSettleExactEvm(input) {
|
|
|
710
765
|
let fromCode;
|
|
711
766
|
try {
|
|
712
767
|
fromCode = await publicClient.getCode({ address: from });
|
|
713
|
-
} catch (
|
|
768
|
+
} catch (e9) {
|
|
714
769
|
return { ok: false, error: "tx_not_found", detail: `Could not read code at ${from} (transient RPC) \u2014 retry.` };
|
|
715
770
|
}
|
|
716
771
|
const isContractWallet = Boolean(fromCode && fromCode !== "0x");
|
|
@@ -746,7 +801,7 @@ async function verifyAndSettleExactEvm(input) {
|
|
|
746
801
|
if (used) {
|
|
747
802
|
return { ok: false, error: "tx_already_used", detail: `Authorization nonce ${nonce} already used or canceled on-chain.` };
|
|
748
803
|
}
|
|
749
|
-
} catch (
|
|
804
|
+
} catch (e10) {
|
|
750
805
|
return { ok: false, error: "tx_not_found", detail: "Could not read authorizationState (transient RPC) \u2014 retry." };
|
|
751
806
|
}
|
|
752
807
|
const baseArgs = [from, to, value, validAfter, validBefore, nonce];
|
|
@@ -786,7 +841,7 @@ async function verifyAndSettleExactEvm(input) {
|
|
|
786
841
|
args: writeArgs
|
|
787
842
|
});
|
|
788
843
|
} catch (err) {
|
|
789
|
-
throw new (0,
|
|
844
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
790
845
|
`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
846
|
{ cause: err }
|
|
792
847
|
);
|
|
@@ -798,7 +853,7 @@ async function verifyAndSettleExactEvm(input) {
|
|
|
798
853
|
return { ok: false, error: "tx_reverted", detail: `Settlement tx ${txHash} reverted on-chain.` };
|
|
799
854
|
}
|
|
800
855
|
} catch (err) {
|
|
801
|
-
throw new (0,
|
|
856
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
802
857
|
`exact settle: broadcast ${txHash} but couldn't confirm it (${shorten(err instanceof Error ? err.message : String(err))}).`,
|
|
803
858
|
{ cause: err }
|
|
804
859
|
);
|
|
@@ -848,7 +903,7 @@ function decodeBase64(b64) {
|
|
|
848
903
|
function fromBase64Json(b64) {
|
|
849
904
|
try {
|
|
850
905
|
return JSON.parse(decodeBase64(b64));
|
|
851
|
-
} catch (
|
|
906
|
+
} catch (e11) {
|
|
852
907
|
return null;
|
|
853
908
|
}
|
|
854
909
|
}
|
|
@@ -873,6 +928,9 @@ function buildReceiptHeader(receipt) {
|
|
|
873
928
|
function buildSignatureHeader(signature) {
|
|
874
929
|
return toBase64Json(signature);
|
|
875
930
|
}
|
|
931
|
+
function buildExactSignatureHeader(input) {
|
|
932
|
+
return toBase64Json({ x402Version: 2, accepted: input.accepted, payload: input.payload });
|
|
933
|
+
}
|
|
876
934
|
async function parseChallenge(response) {
|
|
877
935
|
const headerValue = response.headers.get(HEADER_REQUIRED);
|
|
878
936
|
if (headerValue) {
|
|
@@ -882,22 +940,35 @@ async function parseChallenge(response) {
|
|
|
882
940
|
try {
|
|
883
941
|
const body = await response.clone().json();
|
|
884
942
|
if (isValidChallenge(body)) return body;
|
|
885
|
-
} catch (
|
|
943
|
+
} catch (e12) {
|
|
886
944
|
}
|
|
887
945
|
return null;
|
|
888
946
|
}
|
|
889
947
|
function parseReceipt(response) {
|
|
890
|
-
const headerValue = response.headers.get(HEADER_RESPONSE);
|
|
948
|
+
const headerValue = _nullishCoalesce(response.headers.get(HEADER_RESPONSE), () => ( response.headers.get(HEADER_RESPONSE_V1)));
|
|
891
949
|
if (!headerValue) return null;
|
|
892
950
|
const parsed = fromBase64Json(headerValue);
|
|
893
951
|
return isValidReceipt(parsed) ? parsed : null;
|
|
894
952
|
}
|
|
953
|
+
function parseSettleResponse(response) {
|
|
954
|
+
const headerValue = _nullishCoalesce(response.headers.get(HEADER_RESPONSE), () => ( response.headers.get(HEADER_RESPONSE_V1)));
|
|
955
|
+
if (!headerValue) return null;
|
|
956
|
+
const parsed = fromBase64Json(headerValue);
|
|
957
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.success !== "boolean") return null;
|
|
958
|
+
return {
|
|
959
|
+
success: parsed.success,
|
|
960
|
+
...typeof parsed.transaction === "string" ? { transaction: parsed.transaction } : {},
|
|
961
|
+
...typeof parsed.network === "string" ? { network: parsed.network } : {},
|
|
962
|
+
...typeof parsed.payer === "string" ? { payer: parsed.payer } : {},
|
|
963
|
+
...typeof parsed.errorReason === "string" ? { errorReason: parsed.errorReason } : {}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
895
966
|
function parseSignatureHeader(value) {
|
|
896
967
|
const parsed = fromBase64Json(value);
|
|
897
968
|
if (!parsed || typeof parsed !== "object") return null;
|
|
898
969
|
const v = parsed;
|
|
899
970
|
const accepted = v.accepted;
|
|
900
|
-
const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess',
|
|
971
|
+
const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _6 => _6.scheme]), () => ( v.scheme));
|
|
901
972
|
if (scheme !== "onchain-proof") return null;
|
|
902
973
|
const payload = v.payload;
|
|
903
974
|
if (!payload || typeof payload.txHash !== "string" || typeof payload.nonce !== "string") {
|
|
@@ -910,9 +981,9 @@ function parseExactPaymentHeader(value) {
|
|
|
910
981
|
if (!parsed || typeof parsed !== "object") return null;
|
|
911
982
|
const v = parsed;
|
|
912
983
|
const accepted = _nullishCoalesce(v.accepted, () => ( null));
|
|
913
|
-
const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess',
|
|
984
|
+
const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _7 => _7.scheme]), () => ( v.scheme));
|
|
914
985
|
if (scheme !== "exact") return null;
|
|
915
|
-
const network = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess',
|
|
986
|
+
const network = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _8 => _8.network]), () => ( v.network));
|
|
916
987
|
if (typeof network !== "string") return null;
|
|
917
988
|
const payload = v.payload;
|
|
918
989
|
if (!payload || typeof payload !== "object") return null;
|
|
@@ -969,7 +1040,7 @@ var evmDriver = {
|
|
|
969
1040
|
let resolved;
|
|
970
1041
|
try {
|
|
971
1042
|
resolved = resolveChain(opts.chain, opts.rpcUrl);
|
|
972
|
-
} catch (
|
|
1043
|
+
} catch (e13) {
|
|
973
1044
|
return null;
|
|
974
1045
|
}
|
|
975
1046
|
return makeEvmNetwork(resolved);
|
|
@@ -997,15 +1068,15 @@ function makeEvmNetwork(resolved) {
|
|
|
997
1068
|
const info = resolved.tokens[token.toUpperCase()];
|
|
998
1069
|
if (!info) {
|
|
999
1070
|
const known = Object.keys(resolved.tokens).join(", ") || "(none built in)";
|
|
1000
|
-
throw new (0,
|
|
1071
|
+
throw new (0, _chunkPA6YD3HLcjs.UnknownTokenError)(
|
|
1001
1072
|
`token "${token}" isn't built in for ${resolved.chain.name} (known: ${known}). Pass { address, decimals } instead, or use 'native'.`
|
|
1002
1073
|
);
|
|
1003
1074
|
}
|
|
1004
1075
|
return { asset: info.address, decimals: info.decimals, symbol: info.symbol };
|
|
1005
1076
|
}
|
|
1006
|
-
|
|
1077
|
+
_chunkPA6YD3HLcjs.rejectForeignToken.call(void 0, token, "evm", network);
|
|
1007
1078
|
if (!("address" in token)) {
|
|
1008
|
-
throw new (0,
|
|
1079
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
|
|
1009
1080
|
`chain ${network} is EVM; a custom token must be { address, decimals }.`
|
|
1010
1081
|
);
|
|
1011
1082
|
}
|
|
@@ -1025,7 +1096,7 @@ function makeEvmNetwork(resolved) {
|
|
|
1025
1096
|
let normalized;
|
|
1026
1097
|
try {
|
|
1027
1098
|
normalized = _viem.getAddress.call(void 0, asset);
|
|
1028
|
-
} catch (
|
|
1099
|
+
} catch (e14) {
|
|
1029
1100
|
return null;
|
|
1030
1101
|
}
|
|
1031
1102
|
for (const info of Object.values(resolved.tokens)) {
|
|
@@ -1037,14 +1108,14 @@ function makeEvmNetwork(resolved) {
|
|
|
1037
1108
|
},
|
|
1038
1109
|
assertValidPayTo(payTo) {
|
|
1039
1110
|
if (!_viem.isAddress.call(void 0, payTo)) {
|
|
1040
|
-
throw new (0,
|
|
1111
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
|
|
1041
1112
|
`chain ${network} is EVM, but payTo "${payTo}" is not a valid 0x address.`
|
|
1042
1113
|
);
|
|
1043
1114
|
}
|
|
1044
1115
|
},
|
|
1045
1116
|
bindWallet(wallet) {
|
|
1046
1117
|
if (typeof wallet !== "object" || wallet === null || !("privateKey" in wallet) && !("walletClient" in wallet)) {
|
|
1047
|
-
throw new (0,
|
|
1118
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
|
|
1048
1119
|
`chain ${network} is EVM; wallet must be { privateKey } or { walletClient }.`
|
|
1049
1120
|
);
|
|
1050
1121
|
}
|
|
@@ -1061,12 +1132,12 @@ function makeEvmNetwork(resolved) {
|
|
|
1061
1132
|
});
|
|
1062
1133
|
} catch (err) {
|
|
1063
1134
|
if (isViemInsufficientFunds(err)) {
|
|
1064
|
-
throw new (0,
|
|
1135
|
+
throw new (0, _chunkPA6YD3HLcjs.InsufficientFundsError)(
|
|
1065
1136
|
err instanceof Error ? err.message : "Insufficient funds for payment.",
|
|
1066
1137
|
{ cause: err }
|
|
1067
1138
|
);
|
|
1068
1139
|
}
|
|
1069
|
-
throw _nullishCoalesce(
|
|
1140
|
+
throw _nullishCoalesce(_chunkPA6YD3HLcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
|
|
1070
1141
|
}
|
|
1071
1142
|
},
|
|
1072
1143
|
async confirm(ref, minConfirmations) {
|
|
@@ -1077,7 +1148,7 @@ function makeEvmNetwork(resolved) {
|
|
|
1077
1148
|
});
|
|
1078
1149
|
return { height: receipt.blockNumber.toString() };
|
|
1079
1150
|
} catch (err) {
|
|
1080
|
-
throw new (0,
|
|
1151
|
+
throw new (0, _chunkPA6YD3HLcjs.ConfirmationTimeoutError)(
|
|
1081
1152
|
`EVM tx ${ref} did not reach ${minConfirmations} confirmation(s) in time.`,
|
|
1082
1153
|
{ cause: err }
|
|
1083
1154
|
);
|
|
@@ -1085,19 +1156,28 @@ function makeEvmNetwork(resolved) {
|
|
|
1085
1156
|
},
|
|
1086
1157
|
async estimateCost(accept) {
|
|
1087
1158
|
const { decimals, symbol } = resolved.chain.nativeCurrency;
|
|
1159
|
+
if (accept.scheme === "exact") {
|
|
1160
|
+
return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
|
|
1161
|
+
symbol,
|
|
1162
|
+
decimals,
|
|
1163
|
+
fee: 0n,
|
|
1164
|
+
basis: "estimated",
|
|
1165
|
+
detail: "gasless \u2014 the server/facilitator settles the signed authorization"
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1088
1168
|
const gasLimit = accept.asset === "native" ? 21000n : 65000n;
|
|
1089
1169
|
try {
|
|
1090
1170
|
const gasPrice = await publicClient.getGasPrice();
|
|
1091
|
-
return
|
|
1171
|
+
return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
|
|
1092
1172
|
symbol,
|
|
1093
1173
|
decimals,
|
|
1094
1174
|
fee: gasPrice * gasLimit,
|
|
1095
1175
|
basis: "estimated",
|
|
1096
1176
|
detail: `~${gasLimit} gas @ ${gasPrice} wei/gas`
|
|
1097
1177
|
});
|
|
1098
|
-
} catch (
|
|
1178
|
+
} catch (e15) {
|
|
1099
1179
|
const gasPrice = 5000000000n;
|
|
1100
|
-
return
|
|
1180
|
+
return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
|
|
1101
1181
|
symbol,
|
|
1102
1182
|
decimals,
|
|
1103
1183
|
fee: gasPrice * gasLimit,
|
|
@@ -1118,7 +1198,7 @@ function makeEvmNetwork(resolved) {
|
|
|
1118
1198
|
functionName: "balanceOf",
|
|
1119
1199
|
args: [owner]
|
|
1120
1200
|
});
|
|
1121
|
-
} catch (
|
|
1201
|
+
} catch (e16) {
|
|
1122
1202
|
token = null;
|
|
1123
1203
|
}
|
|
1124
1204
|
return { token, native };
|
|
@@ -1146,6 +1226,21 @@ function makeEvmNetwork(resolved) {
|
|
|
1146
1226
|
minConfirmations: accept.extra.minConfirmations
|
|
1147
1227
|
});
|
|
1148
1228
|
},
|
|
1229
|
+
// Standard x402 `exact` rail (EIP-3009), BUYER side — EVM only. Re-derives the
|
|
1230
|
+
// token's EIP-712 domain on-chain, signs an authorization with the agent's own
|
|
1231
|
+
// key, and returns it for the client to frame into PAYMENT-SIGNATURE. Never
|
|
1232
|
+
// broadcasts. Throws UnsupportedSchemeError for a non-EIP-3009 token / contract signer.
|
|
1233
|
+
async payExact(wallet, accept) {
|
|
1234
|
+
const a = wallet._native;
|
|
1235
|
+
const { payload, payerFrom, nonce } = await payExactEvm({
|
|
1236
|
+
publicClient,
|
|
1237
|
+
walletClient: a.walletClient,
|
|
1238
|
+
account: a.account,
|
|
1239
|
+
chainId: resolved.chainId,
|
|
1240
|
+
accept
|
|
1241
|
+
});
|
|
1242
|
+
return { payload, accepted: accept, payerFrom, nonce };
|
|
1243
|
+
},
|
|
1149
1244
|
// Standard x402 `exact` rail (EIP-3009), seller side — EVM only.
|
|
1150
1245
|
async exactDomain(asset) {
|
|
1151
1246
|
return readExactDomain(publicClient, asset);
|
|
@@ -1176,9 +1271,9 @@ var loaders = {
|
|
|
1176
1271
|
solana: async () => {
|
|
1177
1272
|
let mod;
|
|
1178
1273
|
try {
|
|
1179
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-
|
|
1274
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-MPPE6K24.cjs")));
|
|
1180
1275
|
} catch (cause) {
|
|
1181
|
-
throw new (0,
|
|
1276
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1182
1277
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
1183
1278
|
{ cause }
|
|
1184
1279
|
);
|
|
@@ -1188,9 +1283,9 @@ var loaders = {
|
|
|
1188
1283
|
ton: async () => {
|
|
1189
1284
|
let mod;
|
|
1190
1285
|
try {
|
|
1191
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./ton-
|
|
1286
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./ton-RNEFN25G.cjs")));
|
|
1192
1287
|
} catch (cause) {
|
|
1193
|
-
throw new (0,
|
|
1288
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1194
1289
|
`TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
|
|
1195
1290
|
{ cause }
|
|
1196
1291
|
);
|
|
@@ -1200,9 +1295,9 @@ var loaders = {
|
|
|
1200
1295
|
stellar: async () => {
|
|
1201
1296
|
let mod;
|
|
1202
1297
|
try {
|
|
1203
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./stellar-
|
|
1298
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./stellar-XHLLNHQP.cjs")));
|
|
1204
1299
|
} catch (cause) {
|
|
1205
|
-
throw new (0,
|
|
1300
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1206
1301
|
`Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
|
|
1207
1302
|
{ cause }
|
|
1208
1303
|
);
|
|
@@ -1212,9 +1307,9 @@ var loaders = {
|
|
|
1212
1307
|
xrpl: async () => {
|
|
1213
1308
|
let mod;
|
|
1214
1309
|
try {
|
|
1215
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./xrpl-
|
|
1310
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./xrpl-XN2NBNGI.cjs")));
|
|
1216
1311
|
} catch (cause) {
|
|
1217
|
-
throw new (0,
|
|
1312
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1218
1313
|
`XRPL selected, but its package isn't installed. Run: npm install xrpl`,
|
|
1219
1314
|
{ cause }
|
|
1220
1315
|
);
|
|
@@ -1224,9 +1319,9 @@ var loaders = {
|
|
|
1224
1319
|
tron: async () => {
|
|
1225
1320
|
let mod;
|
|
1226
1321
|
try {
|
|
1227
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./tron-
|
|
1322
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./tron-TKJHNFGM.cjs")));
|
|
1228
1323
|
} catch (cause) {
|
|
1229
|
-
throw new (0,
|
|
1324
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1230
1325
|
`Tron selected, but its package isn't installed. Run: npm install tronweb`,
|
|
1231
1326
|
{ cause }
|
|
1232
1327
|
);
|
|
@@ -1236,9 +1331,9 @@ var loaders = {
|
|
|
1236
1331
|
sui: async () => {
|
|
1237
1332
|
let mod;
|
|
1238
1333
|
try {
|
|
1239
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./sui-
|
|
1334
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./sui-6CVLEXLA.cjs")));
|
|
1240
1335
|
} catch (cause) {
|
|
1241
|
-
throw new (0,
|
|
1336
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1242
1337
|
`Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
|
|
1243
1338
|
{ cause }
|
|
1244
1339
|
);
|
|
@@ -1248,9 +1343,9 @@ var loaders = {
|
|
|
1248
1343
|
near: async () => {
|
|
1249
1344
|
let mod;
|
|
1250
1345
|
try {
|
|
1251
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./near-
|
|
1346
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./near-ZJLZE26R.cjs")));
|
|
1252
1347
|
} catch (cause) {
|
|
1253
|
-
throw new (0,
|
|
1348
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1254
1349
|
`NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
|
|
1255
1350
|
{ cause }
|
|
1256
1351
|
);
|
|
@@ -1260,9 +1355,9 @@ var loaders = {
|
|
|
1260
1355
|
aptos: async () => {
|
|
1261
1356
|
let mod;
|
|
1262
1357
|
try {
|
|
1263
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./aptos-
|
|
1358
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./aptos-GJGIZHNI.cjs")));
|
|
1264
1359
|
} catch (cause) {
|
|
1265
|
-
throw new (0,
|
|
1360
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1266
1361
|
`Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
|
|
1267
1362
|
{ cause }
|
|
1268
1363
|
);
|
|
@@ -1272,9 +1367,9 @@ var loaders = {
|
|
|
1272
1367
|
algorand: async () => {
|
|
1273
1368
|
let mod;
|
|
1274
1369
|
try {
|
|
1275
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./algorand-
|
|
1370
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./algorand-EJ3S2V7E.cjs")));
|
|
1276
1371
|
} catch (cause) {
|
|
1277
|
-
throw new (0,
|
|
1372
|
+
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1278
1373
|
`Algorand selected, but its package isn't installed. Run: npm install algosdk`,
|
|
1279
1374
|
{ cause }
|
|
1280
1375
|
);
|
|
@@ -1391,7 +1486,7 @@ async function searchOpenIndexes(opts = {}) {
|
|
|
1391
1486
|
async function safeSearch(run) {
|
|
1392
1487
|
try {
|
|
1393
1488
|
return await run();
|
|
1394
|
-
} catch (
|
|
1489
|
+
} catch (e17) {
|
|
1395
1490
|
return [];
|
|
1396
1491
|
}
|
|
1397
1492
|
}
|
|
@@ -1497,7 +1592,7 @@ async function register402Index(input) {
|
|
|
1497
1592
|
if (res.ok) {
|
|
1498
1593
|
const body = await res.json().catch(() => ({}));
|
|
1499
1594
|
const msg = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
|
|
1500
|
-
const live = _optionalChain([body, 'access',
|
|
1595
|
+
const live = _optionalChain([body, 'access', _9 => _9.service, 'optionalAccess', _10 => _10.status]) === "active";
|
|
1501
1596
|
return {
|
|
1502
1597
|
source: "402index",
|
|
1503
1598
|
ok: true,
|
|
@@ -1524,7 +1619,7 @@ async function readIndexError(res) {
|
|
|
1524
1619
|
(p) => typeof p === "string" && p.length > 0
|
|
1525
1620
|
);
|
|
1526
1621
|
return parts.length ? [...new Set(parts)].join(" \u2014 ") : void 0;
|
|
1527
|
-
} catch (
|
|
1622
|
+
} catch (e18) {
|
|
1528
1623
|
return void 0;
|
|
1529
1624
|
}
|
|
1530
1625
|
}
|
|
@@ -1634,11 +1729,11 @@ async function readSiwxInfo(res) {
|
|
|
1634
1729
|
try {
|
|
1635
1730
|
const body = await res.json();
|
|
1636
1731
|
const ext = body.extensions;
|
|
1637
|
-
const siwx = _optionalChain([ext, 'optionalAccess',
|
|
1638
|
-
const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess',
|
|
1639
|
-
if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess',
|
|
1732
|
+
const siwx = _optionalChain([ext, 'optionalAccess', _11 => _11["sign-in-with-x"]]);
|
|
1733
|
+
const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _12 => _12.info]), () => ( siwx));
|
|
1734
|
+
if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess', _13 => _13.supportedChains]))) {
|
|
1640
1735
|
const evm = siwx.supportedChains.find(
|
|
1641
|
-
(c) => typeof _optionalChain([c, 'optionalAccess',
|
|
1736
|
+
(c) => typeof _optionalChain([c, 'optionalAccess', _14 => _14.chainId]) === "string" && c.chainId.startsWith("eip155:")
|
|
1642
1737
|
);
|
|
1643
1738
|
if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
|
|
1644
1739
|
}
|
|
@@ -1646,7 +1741,7 @@ async function readSiwxInfo(res) {
|
|
|
1646
1741
|
return info;
|
|
1647
1742
|
}
|
|
1648
1743
|
return null;
|
|
1649
|
-
} catch (
|
|
1744
|
+
} catch (e19) {
|
|
1650
1745
|
return null;
|
|
1651
1746
|
}
|
|
1652
1747
|
}
|
|
@@ -1694,7 +1789,7 @@ function mapRails(accepts) {
|
|
|
1694
1789
|
}
|
|
1695
1790
|
function matchesQuery(r, query) {
|
|
1696
1791
|
const q = query.toLowerCase();
|
|
1697
|
-
return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access',
|
|
1792
|
+
return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _15 => _15.name, 'optionalAccess', _16 => _16.toLowerCase, 'call', _17 => _17(), 'access', _18 => _18.includes, 'call', _19 => _19(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _20 => _20.description, 'optionalAccess', _21 => _21.toLowerCase, 'call', _22 => _22(), 'access', _23 => _23.includes, 'call', _24 => _24(q)]), () => ( false)));
|
|
1698
1793
|
}
|
|
1699
1794
|
function pickString(o, ...keys) {
|
|
1700
1795
|
for (const k of keys) {
|
|
@@ -1727,7 +1822,7 @@ function hostOf(url) {
|
|
|
1727
1822
|
try {
|
|
1728
1823
|
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
|
|
1729
1824
|
return new URL(withScheme).hostname || url;
|
|
1730
|
-
} catch (
|
|
1825
|
+
} catch (e20) {
|
|
1731
1826
|
return url;
|
|
1732
1827
|
}
|
|
1733
1828
|
}
|
|
@@ -1747,7 +1842,18 @@ function encodeBase642(str) {
|
|
|
1747
1842
|
|
|
1748
1843
|
// src/policy.ts
|
|
1749
1844
|
var ALLOW = { allowed: true };
|
|
1750
|
-
var deny = (reason) => ({
|
|
1845
|
+
var deny = (code, reason) => ({
|
|
1846
|
+
allowed: false,
|
|
1847
|
+
reason,
|
|
1848
|
+
code
|
|
1849
|
+
});
|
|
1850
|
+
function resolveDeadline(policy, sessionStart) {
|
|
1851
|
+
const fromTtl = policy.ttlSeconds != null && Number.isSafeInteger(sessionStart + policy.ttlSeconds * 1e3) ? sessionStart + policy.ttlSeconds * 1e3 : null;
|
|
1852
|
+
const fromAbs = policy.expiresAt != null ? policy.expiresAt : null;
|
|
1853
|
+
if (fromTtl == null) return fromAbs;
|
|
1854
|
+
if (fromAbs == null) return fromTtl;
|
|
1855
|
+
return Math.min(fromTtl, fromAbs);
|
|
1856
|
+
}
|
|
1751
1857
|
function hostMatches(host, pattern) {
|
|
1752
1858
|
if (pattern.startsWith("*.")) {
|
|
1753
1859
|
const suffix = pattern.slice(1);
|
|
@@ -1763,18 +1869,26 @@ function chainMatches(intent, allowed) {
|
|
|
1763
1869
|
const id = "id" in allowed ? allowed.id : void 0;
|
|
1764
1870
|
return id !== void 0 && intent.network === `eip155:${id}`;
|
|
1765
1871
|
}
|
|
1766
|
-
function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
1872
|
+
function evaluatePolicy(intent, policy, spentForAssetBase, ctx) {
|
|
1767
1873
|
if (!policy) return ALLOW;
|
|
1874
|
+
if (ctx) {
|
|
1875
|
+
const deadline = resolveDeadline(policy, ctx.sessionStart);
|
|
1876
|
+
if (deadline != null && ctx.now >= deadline) {
|
|
1877
|
+
return deny("SESSION_EXPIRED", "session expired (TTL elapsed) \u2014 refusing to pay.");
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1768
1880
|
if (policy.chains && !policy.chains.some((c) => chainMatches(intent, c))) {
|
|
1769
1881
|
return deny(
|
|
1882
|
+
"CHAIN",
|
|
1770
1883
|
`chain ${intent.network} is not in the allowed set (policy.chains).`
|
|
1771
1884
|
);
|
|
1772
1885
|
}
|
|
1773
1886
|
if (policy.hosts && !policy.hosts.some((h) => hostMatches(intent.host, h))) {
|
|
1774
|
-
return deny(`host ${intent.host} is not in the allowed set (policy.hosts).`);
|
|
1887
|
+
return deny("HOST", `host ${intent.host} is not in the allowed set (policy.hosts).`);
|
|
1775
1888
|
}
|
|
1776
1889
|
if (!intent.recognized && !policy.allowUnknownTokens) {
|
|
1777
1890
|
return deny(
|
|
1891
|
+
"UNKNOWN_TOKEN",
|
|
1778
1892
|
`asset ${intent.asset} on ${intent.network} isn't a token the SDK can price; refusing to pay it on trust. Set policy.allowUnknownTokens to override.`
|
|
1779
1893
|
);
|
|
1780
1894
|
}
|
|
@@ -1787,34 +1901,52 @@ function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
|
1787
1901
|
});
|
|
1788
1902
|
if (!matches) {
|
|
1789
1903
|
return deny(
|
|
1904
|
+
"TOKEN",
|
|
1790
1905
|
`token ${_nullishCoalesce(intent.symbol, () => ( intent.asset))} is not in the allowed set (policy.tokens).`
|
|
1791
1906
|
);
|
|
1792
1907
|
}
|
|
1793
1908
|
}
|
|
1794
1909
|
if (policy.maxAmount !== void 0) {
|
|
1795
|
-
const cap =
|
|
1910
|
+
const cap = _chunkPA6YD3HLcjs.floorUnits.call(void 0, policy.maxAmount, intent.decimals);
|
|
1796
1911
|
if (intent.amountBase > cap) {
|
|
1797
1912
|
return deny(
|
|
1913
|
+
"MAX_AMOUNT",
|
|
1798
1914
|
`payment of ${intent.amountBase} base units exceeds policy.maxAmount ` + `(${policy.maxAmount} ${_nullishCoalesce(intent.symbol, () => ( ""))}).`.trimEnd()
|
|
1799
1915
|
);
|
|
1800
1916
|
}
|
|
1801
1917
|
}
|
|
1802
1918
|
if (policy.maxTotal !== void 0) {
|
|
1803
|
-
const cap =
|
|
1919
|
+
const cap = _chunkPA6YD3HLcjs.floorUnits.call(void 0, policy.maxTotal, intent.decimals);
|
|
1804
1920
|
if (spentForAssetBase + intent.amountBase > cap) {
|
|
1805
1921
|
return deny(
|
|
1922
|
+
"MAX_TOTAL",
|
|
1806
1923
|
`this payment would push spend on ${_nullishCoalesce(intent.symbol, () => ( intent.asset))} past policy.maxTotal (${policy.maxTotal}); already spent ${spentForAssetBase} base units.`
|
|
1807
1924
|
);
|
|
1808
1925
|
}
|
|
1809
1926
|
}
|
|
1927
|
+
if (ctx && policy.windowTotal !== void 0 && policy.windowSeconds !== void 0) {
|
|
1928
|
+
const cap = _chunkPA6YD3HLcjs.floorUnits.call(void 0, policy.windowTotal, intent.decimals);
|
|
1929
|
+
if (ctx.spentInWindowBase + intent.amountBase > cap) {
|
|
1930
|
+
return deny(
|
|
1931
|
+
"WINDOW_TOTAL",
|
|
1932
|
+
`this payment would exceed policy.windowTotal (${policy.windowTotal}) within the last ${policy.windowSeconds}s on ${_nullishCoalesce(intent.symbol, () => ( intent.asset))}.`
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1810
1936
|
return ALLOW;
|
|
1811
1937
|
}
|
|
1812
1938
|
|
|
1813
1939
|
// src/ledger.ts
|
|
1814
1940
|
var keyFor = (network, asset) => `${network}|${asset}`;
|
|
1815
|
-
var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); }
|
|
1941
|
+
var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this); }
|
|
1816
1942
|
__init() {this.records = []}
|
|
1817
1943
|
__init2() {this.buckets = /* @__PURE__ */ new Map()}
|
|
1944
|
+
/**
|
|
1945
|
+
* Session clock origin (epoch-ms) — process/session start = ledger
|
|
1946
|
+
* construction. In-memory; a new process is a new session. The client reads it
|
|
1947
|
+
* to compute the `ttlSeconds` deadline and the rolling-window slice.
|
|
1948
|
+
*/
|
|
1949
|
+
__init3() {this.sessionStart = Date.now()}
|
|
1818
1950
|
/** Record a settled payment. `decimals` is the TRUE token decimals (for the
|
|
1819
1951
|
* per-asset running total used by maxTotal + the formatted summary). */
|
|
1820
1952
|
record(r, decimals) {
|
|
@@ -1838,7 +1970,39 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
|
|
|
1838
1970
|
}
|
|
1839
1971
|
/** Running total (base units) already spent on this (network, asset). */
|
|
1840
1972
|
totalFor(network, asset) {
|
|
1841
|
-
return _nullishCoalesce(_optionalChain([this, 'access',
|
|
1973
|
+
return _nullishCoalesce(_optionalChain([this, 'access', _25 => _25.buckets, 'access', _26 => _26.get, 'call', _27 => _27(keyFor(network, asset)), 'optionalAccess', _28 => _28.total]), () => ( 0n));
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Sum of base-unit amounts for (network, asset) whose record `at` (ISO
|
|
1977
|
+
* timestamp) is at or after `sinceMs` (epoch-ms). Backs the rolling window
|
|
1978
|
+
* (`sinceMs = now - windowSeconds*1000`). A linear scan of `records` —
|
|
1979
|
+
* agent-session cardinality is small (tens), and it only runs when a window
|
|
1980
|
+
* policy is set, so it's negligible against the network round-trip.
|
|
1981
|
+
*/
|
|
1982
|
+
totalSince(network, asset, sinceMs) {
|
|
1983
|
+
let sum = 0n;
|
|
1984
|
+
for (const r of this.records) {
|
|
1985
|
+
if (r.network === network && r.asset === asset && Date.parse(r.at) >= sinceMs) {
|
|
1986
|
+
sum += BigInt(r.amountBase);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return sum;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* The per-(network, asset) buckets, as read-only tuples — `network`, `asset`,
|
|
1993
|
+
* `symbol`, the TRUE `decimals` (frozen from the first record), and the running
|
|
1994
|
+
* `totalBase`. Lets the client compose a budget view WITHOUT coupling the ledger
|
|
1995
|
+
* to the policy (the cap math lives in the client). Decimals only exist for a
|
|
1996
|
+
* pair once it's been spent on — a never-spent pair simply isn't a bucket.
|
|
1997
|
+
*/
|
|
1998
|
+
assetBuckets() {
|
|
1999
|
+
return [...this.buckets.values()].map((b) => ({
|
|
2000
|
+
network: b.network,
|
|
2001
|
+
asset: b.asset,
|
|
2002
|
+
...b.symbol ? { symbol: b.symbol } : {},
|
|
2003
|
+
decimals: b.decimals,
|
|
2004
|
+
totalBase: b.total
|
|
2005
|
+
}));
|
|
1842
2006
|
}
|
|
1843
2007
|
/** An immutable snapshot of all spend so far. */
|
|
1844
2008
|
summary() {
|
|
@@ -1850,7 +2014,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
|
|
|
1850
2014
|
symbol: b.symbol,
|
|
1851
2015
|
decimals: b.decimals,
|
|
1852
2016
|
totalBase: b.total.toString(),
|
|
1853
|
-
totalFormatted:
|
|
2017
|
+
totalFormatted: _chunkPA6YD3HLcjs.formatUnits.call(void 0, b.total, b.decimals),
|
|
1854
2018
|
count: b.count
|
|
1855
2019
|
})),
|
|
1856
2020
|
records: [...this.records]
|
|
@@ -1859,6 +2023,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
|
|
|
1859
2023
|
}, _class);
|
|
1860
2024
|
|
|
1861
2025
|
// src/client.ts
|
|
2026
|
+
var DEFAULT_SCHEMES = ["onchain-proof"];
|
|
1862
2027
|
var RECIPIENT_FIX = {
|
|
1863
2028
|
NO_TRUSTLINE: "the recipient needs a one-time trustline for this asset before it can receive",
|
|
1864
2029
|
NOT_REGISTERED: "the recipient must be storage_deposit-registered on this token (NEP-145, one-time)",
|
|
@@ -1872,22 +2037,53 @@ var PipRailClient = (_class2 = class {
|
|
|
1872
2037
|
|
|
1873
2038
|
// Per-asset tally of everything this client has paid (powers spent() and the
|
|
1874
2039
|
// policy's maxTotal cap).
|
|
1875
|
-
|
|
2040
|
+
__init4() {this.ledger = new SpendLedger()}
|
|
1876
2041
|
// Resolved lazily on first request — this is what lets Solana (and future
|
|
1877
2042
|
// families) auto-mount with no setup call.
|
|
1878
2043
|
|
|
1879
|
-
constructor(opts) {;_class2.prototype.
|
|
2044
|
+
constructor(opts) {;_class2.prototype.__init4.call(this);
|
|
1880
2045
|
this.opts = opts;
|
|
1881
2046
|
this.maxRetries = Math.max(1, _nullishCoalesce(opts.maxPaymentRetries, () => ( 3)));
|
|
1882
2047
|
this.retryTimeoutMs = _nullishCoalesce(opts.retryTimeoutMs, () => ( 3e4));
|
|
1883
2048
|
this.onEvent = _nullishCoalesce(opts.onEvent, () => ( (() => void 0)));
|
|
2049
|
+
this.assertPolicyTimeOptions(opts.policy);
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Fail LOUDLY at construction on a misconfigured time policy — a security
|
|
2053
|
+
* boundary must never silently half-arm. Two invariants (a misconfiguration is
|
|
2054
|
+
* a programmer error → `TypeError`, no new SDK error code):
|
|
2055
|
+
* - the rolling window needs BOTH `windowTotal` and `windowSeconds`, or NEITHER
|
|
2056
|
+
* (one alone is a leash that silently doesn't bite);
|
|
2057
|
+
* - `ttlSeconds` must be a positive, safe integer whose `*1000` deadline stays
|
|
2058
|
+
* within `Number.MAX_SAFE_INTEGER` (else the arithmetic would lose precision).
|
|
2059
|
+
*/
|
|
2060
|
+
assertPolicyTimeOptions(policy) {
|
|
2061
|
+
if (!policy) return;
|
|
2062
|
+
const hasWindowTotal = policy.windowTotal !== void 0;
|
|
2063
|
+
const hasWindowSeconds = policy.windowSeconds !== void 0;
|
|
2064
|
+
if (hasWindowTotal !== hasWindowSeconds) {
|
|
2065
|
+
throw new TypeError(
|
|
2066
|
+
"policy.windowTotal and policy.windowSeconds must be set together \u2014 a rolling-window cap can't be half-armed (set both, or neither)."
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
if (hasWindowSeconds && !(Number.isSafeInteger(policy.windowSeconds) && policy.windowSeconds > 0)) {
|
|
2070
|
+
throw new TypeError("policy.windowSeconds must be a positive integer number of seconds.");
|
|
2071
|
+
}
|
|
2072
|
+
if (policy.ttlSeconds !== void 0) {
|
|
2073
|
+
const ttl = policy.ttlSeconds;
|
|
2074
|
+
if (!Number.isSafeInteger(ttl) || ttl <= 0 || !Number.isSafeInteger(this.ledger.sessionStart + ttl * 1e3)) {
|
|
2075
|
+
throw new TypeError(
|
|
2076
|
+
"policy.ttlSeconds must be a positive integer number of seconds small enough that the resulting deadline stays a safe integer."
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
1884
2080
|
}
|
|
1885
2081
|
/** Emit an observability event, never letting a throwing handler break the
|
|
1886
2082
|
* payment flow (mirrors the server gate's `onPaid` isolation). */
|
|
1887
2083
|
safeEmit(event) {
|
|
1888
2084
|
try {
|
|
1889
2085
|
this.onEvent(event);
|
|
1890
|
-
} catch (
|
|
2086
|
+
} catch (e21) {
|
|
1891
2087
|
}
|
|
1892
2088
|
}
|
|
1893
2089
|
/** Auto-mount the chain's driver, resolve the network, and bind the wallet — once. */
|
|
@@ -1901,6 +2097,11 @@ var PipRailClient = (_class2 = class {
|
|
|
1901
2097
|
return { net, wallet };
|
|
1902
2098
|
})();
|
|
1903
2099
|
}
|
|
2100
|
+
/** Resolve the effective scheme set: a per-call override, else the constructor's
|
|
2101
|
+
* `schemes`, else the `onchain-proof`-only default. */
|
|
2102
|
+
resolveSchemes(perCall) {
|
|
2103
|
+
return _nullishCoalesce(_nullishCoalesce(perCall, () => ( this.opts.schemes)), () => ( DEFAULT_SCHEMES));
|
|
2104
|
+
}
|
|
1904
2105
|
/** GET that auto-handles 402. Pass a full URL to any x402-gated endpoint. */
|
|
1905
2106
|
get(url, init) {
|
|
1906
2107
|
return this.fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: "GET" });
|
|
@@ -1912,7 +2113,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1912
2113
|
* as-is) or a plain object (serialised as JSON).
|
|
1913
2114
|
*/
|
|
1914
2115
|
post(url, body, init) {
|
|
1915
|
-
const headers = new Headers(_optionalChain([init, 'optionalAccess',
|
|
2116
|
+
const headers = new Headers(_optionalChain([init, 'optionalAccess', _29 => _29.headers]));
|
|
1916
2117
|
let payload;
|
|
1917
2118
|
if (body === void 0 || body === null) {
|
|
1918
2119
|
payload = void 0;
|
|
@@ -1943,9 +2144,9 @@ var PipRailClient = (_class2 = class {
|
|
|
1943
2144
|
* "0.05 USDC on Base, within budget → pay it." No funds move.
|
|
1944
2145
|
*/
|
|
1945
2146
|
async quote(url, init) {
|
|
1946
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
2147
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _30 => _30.method]), () => ( "GET")) });
|
|
1947
2148
|
if (res.status !== 402) return null;
|
|
1948
|
-
const { quote } = await this.resolveChallenge(url, res);
|
|
2149
|
+
const { quote } = await this.resolveChallenge(url, res, this.resolveSchemes());
|
|
1949
2150
|
return quote;
|
|
1950
2151
|
}
|
|
1951
2152
|
/**
|
|
@@ -1962,9 +2163,9 @@ var PipRailClient = (_class2 = class {
|
|
|
1962
2163
|
* on Tron, where a USD₮ transfer can cost real TRX.
|
|
1963
2164
|
*/
|
|
1964
2165
|
async estimateCost(url, init) {
|
|
1965
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
2166
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _31 => _31.method]), () => ( "GET")) });
|
|
1966
2167
|
if (res.status !== 402) return null;
|
|
1967
|
-
const { net, accept, quote } = await this.resolveChallenge(url, res);
|
|
2168
|
+
const { net, accept, quote } = await this.resolveChallenge(url, res, this.resolveSchemes());
|
|
1968
2169
|
const cost = await net.estimateCost(accept);
|
|
1969
2170
|
return { quote, cost };
|
|
1970
2171
|
}
|
|
@@ -1973,6 +2174,64 @@ var PipRailClient = (_class2 = class {
|
|
|
1973
2174
|
spent() {
|
|
1974
2175
|
return this.ledger.summary();
|
|
1975
2176
|
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Read-only budget + time leash for a Mode-A (headless) agent — the policy IS
|
|
2179
|
+
* the consent, and this is how the agent SEES what's left of it before paying.
|
|
2180
|
+
* Composes the in-memory ledger with the configured policy; never throws, moves
|
|
2181
|
+
* no funds. PROCESS-SCOPED — every figure resets on restart (see {@link SessionBudget}).
|
|
2182
|
+
*/
|
|
2183
|
+
budget() {
|
|
2184
|
+
const view = this.sessionView();
|
|
2185
|
+
const start = new Date(this.ledger.sessionStart).toISOString();
|
|
2186
|
+
return {
|
|
2187
|
+
session: {
|
|
2188
|
+
start,
|
|
2189
|
+
expiresAt: _optionalChain([view, 'optionalAccess', _32 => _32.expiresAt]) != null ? new Date(view.expiresAt).toISOString() : null,
|
|
2190
|
+
secondsRemaining: _nullishCoalesce(_optionalChain([view, 'optionalAccess', _33 => _33.secondsRemaining]), () => ( null))
|
|
2191
|
+
},
|
|
2192
|
+
byAsset: this.remaining()
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Per-(network, asset) remaining budget — ONE row per pair the ledger already
|
|
2197
|
+
* holds (decimals are known only after the first spend), so a fresh client with
|
|
2198
|
+
* a `maxTotal` set returns `[]` until its first payment. `cap`/`remaining` are
|
|
2199
|
+
* `undefined` when no `maxTotal` is configured (unbounded). Pure + in-memory;
|
|
2200
|
+
* never throws, never sums across tokens (no price oracle). PROCESS-SCOPED.
|
|
2201
|
+
*/
|
|
2202
|
+
remaining() {
|
|
2203
|
+
const maxTotal = _optionalChain([this, 'access', _34 => _34.opts, 'access', _35 => _35.policy, 'optionalAccess', _36 => _36.maxTotal]);
|
|
2204
|
+
return this.ledger.assetBuckets().map((b) => {
|
|
2205
|
+
const base2 = {
|
|
2206
|
+
network: b.network,
|
|
2207
|
+
asset: b.asset,
|
|
2208
|
+
...b.symbol ? { symbol: b.symbol } : {},
|
|
2209
|
+
decimals: b.decimals,
|
|
2210
|
+
spentBase: b.totalBase.toString()
|
|
2211
|
+
};
|
|
2212
|
+
if (maxTotal === void 0) return base2;
|
|
2213
|
+
const capBase = _chunkPA6YD3HLcjs.floorUnits.call(void 0, maxTotal, b.decimals);
|
|
2214
|
+
const remainingBase = capBase > b.totalBase ? capBase - b.totalBase : 0n;
|
|
2215
|
+
return {
|
|
2216
|
+
...base2,
|
|
2217
|
+
capBase: capBase.toString(),
|
|
2218
|
+
remainingBase: remainingBase.toString(),
|
|
2219
|
+
remainingFormatted: _chunkPA6YD3HLcjs.formatUnits.call(void 0, remainingBase, b.decimals)
|
|
2220
|
+
};
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
/** The read-only TIME envelope for the plan/budget surfaces, or `undefined`
|
|
2224
|
+
* when no session deadline (`ttlSeconds`/`expiresAt`) is set. `secondsRemaining`
|
|
2225
|
+
* is clamped ≥ 0 — a best-effort host wall-clock estimate. */
|
|
2226
|
+
sessionView(now = Date.now()) {
|
|
2227
|
+
const policy = this.opts.policy;
|
|
2228
|
+
if (!policy || policy.ttlSeconds == null && policy.expiresAt == null) return void 0;
|
|
2229
|
+
const deadline = resolveDeadline(policy, this.ledger.sessionStart);
|
|
2230
|
+
return {
|
|
2231
|
+
expiresAt: deadline,
|
|
2232
|
+
secondsRemaining: deadline == null ? null : Math.max(0, Math.floor((deadline - now) / 1e3))
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
1976
2235
|
/**
|
|
1977
2236
|
* Plan a payment for a gated URL — WITHOUT paying. The read-only completion of
|
|
1978
2237
|
* the `quote()` → `estimateCost()` → **`planPayment()`** trio: it surveys every
|
|
@@ -1993,14 +2252,14 @@ var PipRailClient = (_class2 = class {
|
|
|
1993
2252
|
* the plan yourself. No funds move.
|
|
1994
2253
|
*/
|
|
1995
2254
|
async planPayment(url, init) {
|
|
1996
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
2255
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _37 => _37.method]), () => ( "GET")) });
|
|
1997
2256
|
if (res.status !== 402) return null;
|
|
1998
2257
|
const challenge = await parseChallenge(res);
|
|
1999
2258
|
if (!challenge) {
|
|
2000
|
-
throw new (0,
|
|
2259
|
+
throw new (0, _chunkPA6YD3HLcjs.InvalidEnvelopeError)("402 response did not include a parseable x402 challenge.");
|
|
2001
2260
|
}
|
|
2002
2261
|
const { net, wallet } = await this.ensure();
|
|
2003
|
-
return this.planFromChallenge(net, wallet, challenge, url);
|
|
2262
|
+
return this.planFromChallenge(net, wallet, challenge, url, this.resolveSchemes());
|
|
2004
2263
|
}
|
|
2005
2264
|
/**
|
|
2006
2265
|
* Convenience over {@link planPayment}: can the wallet settle this URL right now?
|
|
@@ -2028,8 +2287,8 @@ var PipRailClient = (_class2 = class {
|
|
|
2028
2287
|
* - A resource just listed via {@link register} may not appear yet — 402 Index reviews
|
|
2029
2288
|
* before publishing, so retry with a brief backoff if a fresh listing is missing.
|
|
2030
2289
|
* - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
|
|
2031
|
-
*
|
|
2032
|
-
* `
|
|
2290
|
+
* `onchain-proof` rails by default, and standard `exact` rails too once you opt in
|
|
2291
|
+
* with `schemes: ['onchain-proof', 'exact']` (EVM + EIP-3009 — USDC/EURC).
|
|
2033
2292
|
*/
|
|
2034
2293
|
async discover(opts = {}) {
|
|
2035
2294
|
const found = await searchOpenIndexes({
|
|
@@ -2160,29 +2419,33 @@ var PipRailClient = (_class2 = class {
|
|
|
2160
2419
|
* streams throw `NonReplayableBodyError`.
|
|
2161
2420
|
*/
|
|
2162
2421
|
async fetch(url, init) {
|
|
2163
|
-
const body = _optionalChain([init, 'optionalAccess',
|
|
2422
|
+
const body = _optionalChain([init, 'optionalAccess', _38 => _38.body]);
|
|
2164
2423
|
if (body !== void 0 && body !== null && !isReplayableBodyInit(body)) {
|
|
2165
|
-
throw new (0,
|
|
2424
|
+
throw new (0, _chunkPA6YD3HLcjs.NonReplayableBodyError)(
|
|
2166
2425
|
"fetch(): init.body is not replayable. Pass a string, FormData, URLSearchParams, ArrayBuffer, or Blob \u2014 not a ReadableStream."
|
|
2167
2426
|
);
|
|
2168
2427
|
}
|
|
2169
2428
|
const firstResponse = await fetch(url, init);
|
|
2170
2429
|
if (firstResponse.status !== 402) return firstResponse;
|
|
2171
|
-
const
|
|
2430
|
+
const schemes = this.resolveSchemes(_optionalChain([init, 'optionalAccess', _39 => _39.schemes]));
|
|
2431
|
+
const resolved = await this.resolveChallenge(url, firstResponse, schemes);
|
|
2172
2432
|
const { net, wallet, challenge } = resolved;
|
|
2173
2433
|
let accept = resolved.accept;
|
|
2174
2434
|
let quote = resolved.quote;
|
|
2175
|
-
const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
2435
|
+
const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _40 => _40.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
|
|
2176
2436
|
if (autoRoute) {
|
|
2177
|
-
const plan = await this.planFromChallenge(net, wallet, challenge, url);
|
|
2437
|
+
const plan = await this.planFromChallenge(net, wallet, challenge, url, schemes);
|
|
2178
2438
|
if (!plan.best) {
|
|
2179
|
-
throw new (0,
|
|
2439
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentDeclinedError)(_nullishCoalesce(plan.fundingHint, () => ( "No rail is settleable for this payment.")));
|
|
2180
2440
|
}
|
|
2181
2441
|
accept = plan.best.accept;
|
|
2182
2442
|
quote = plan.best.quote;
|
|
2183
2443
|
}
|
|
2184
2444
|
this.safeEmit({ kind: "payment-required", challenge, accept });
|
|
2185
2445
|
await this.authorize(quote);
|
|
2446
|
+
if (accept.scheme === "exact") {
|
|
2447
|
+
return this.payExactRail(net, wallet, accept, url, init, quote);
|
|
2448
|
+
}
|
|
2186
2449
|
const { ref, confirmed } = await this.payAndConfirm(net, wallet, accept);
|
|
2187
2450
|
const response = await this.retryWithProof(url, init, accept, ref, confirmed);
|
|
2188
2451
|
this.recordSpend(quote, ref);
|
|
@@ -2194,19 +2457,37 @@ var PipRailClient = (_class2 = class {
|
|
|
2194
2457
|
* network, pick the accept the client can pay, and build its quote. Shared by
|
|
2195
2458
|
* `quote()` (read-only) and `fetch()` (which then authorises + pays).
|
|
2196
2459
|
*/
|
|
2197
|
-
async resolveChallenge(url, response) {
|
|
2460
|
+
async resolveChallenge(url, response, schemes) {
|
|
2198
2461
|
const challenge = await parseChallenge(response);
|
|
2199
2462
|
if (!challenge) {
|
|
2200
|
-
throw new (0,
|
|
2463
|
+
throw new (0, _chunkPA6YD3HLcjs.InvalidEnvelopeError)(
|
|
2201
2464
|
"402 response did not include a parseable x402 challenge."
|
|
2202
2465
|
);
|
|
2203
2466
|
}
|
|
2204
2467
|
const { net, wallet } = await this.ensure();
|
|
2205
|
-
const candidates = this.gatherCandidates(net, challenge);
|
|
2468
|
+
const candidates = this.gatherCandidates(net, challenge, schemes);
|
|
2206
2469
|
if (candidates.length === 0) {
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2470
|
+
const exactOnNet = challenge.accepts.some(
|
|
2471
|
+
(a) => a.scheme === "exact" && net.supports(a.network)
|
|
2472
|
+
);
|
|
2473
|
+
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
2474
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
2475
|
+
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (EVM + EIP-3009 only), and no 'onchain-proof' rail was offered.`
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
2479
|
+
const payable = challenge.accepts.some(
|
|
2480
|
+
(a) => a.scheme === "exact" && net.supports(a.network) && net.describeAsset(a.asset) != null
|
|
2481
|
+
);
|
|
2482
|
+
if (payable) {
|
|
2483
|
+
throw new (0, _chunkPA6YD3HLcjs.NoCompatibleAcceptError)(
|
|
2484
|
+
`This 402 is payable only via the standard 'exact' rail on ${net.network}, which is OFF by default. Enable it: new PipRailClient({ \u2026, schemes: ['onchain-proof', 'exact'] }) or per call fetch(url, { schemes: ['exact'] }) (MCP: PIPRAIL_SCHEMES=onchain-proof,exact).`
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
const networks = [...new Set(challenge.accepts.map((a) => a.network))].join(", ");
|
|
2489
|
+
throw new (0, _chunkPA6YD3HLcjs.NoCompatibleAcceptError)(
|
|
2490
|
+
`No accepts[] entry payable by this client on ${net.network} (schemes: ${schemes.join(", ")}; challenge offered: ${networks || "none"}).`
|
|
2210
2491
|
);
|
|
2211
2492
|
}
|
|
2212
2493
|
const priced = candidates.map((accept) => ({
|
|
@@ -2216,20 +2497,38 @@ var PipRailClient = (_class2 = class {
|
|
|
2216
2497
|
const chosen = _nullishCoalesce(priced.find((p) => p.quote.withinPolicy), () => ( priced[0]));
|
|
2217
2498
|
return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
|
|
2218
2499
|
}
|
|
2219
|
-
/** The candidate accepts this client could pay
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
*
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
)
|
|
2500
|
+
/** The candidate accepts this client could pay, on the bound network. Always the
|
|
2501
|
+
* backendless `onchain-proof` rails; PLUS standard `exact` rails when `schemes`
|
|
2502
|
+
* enables them AND the driver can settle them (EVM `payExact` + a recognised
|
|
2503
|
+
* EIP-3009 token). `onchain-proof` is gathered FIRST so default selection is
|
|
2504
|
+
* unchanged when `exact` is off. */
|
|
2505
|
+
gatherCandidates(net, challenge, schemes) {
|
|
2506
|
+
const out = [];
|
|
2507
|
+
if (schemes.includes("onchain-proof")) {
|
|
2508
|
+
out.push(
|
|
2509
|
+
...challenge.accepts.filter(
|
|
2510
|
+
(a) => a.scheme === "onchain-proof" && net.supports(a.network)
|
|
2511
|
+
)
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
if (schemes.includes("exact")) {
|
|
2515
|
+
out.push(
|
|
2516
|
+
...challenge.accepts.filter(
|
|
2517
|
+
(a) => a.scheme === "exact" && net.supports(a.network) && typeof net.payExact === "function" && net.describeAsset(a.asset) != null && // a foreign rail's maxTimeoutSeconds must be a usable positive integer, or
|
|
2518
|
+
// signing it would build a NaN/garbage validBefore — drop it silently
|
|
2519
|
+
// (symmetric with an unrecognised token) rather than leak a raw SyntaxError.
|
|
2520
|
+
Number.isInteger(a.maxTimeoutSeconds) && a.maxTimeoutSeconds > 0
|
|
2521
|
+
)
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
return out;
|
|
2227
2525
|
}
|
|
2228
2526
|
/** Build the full {@link PaymentPlan} from an already-parsed challenge + bound
|
|
2229
2527
|
* net/wallet. Shared by `planPayment` (read-only) and `fetch`'s autoRoute. */
|
|
2230
|
-
async planFromChallenge(net, wallet, challenge, url) {
|
|
2528
|
+
async planFromChallenge(net, wallet, challenge, url, schemes) {
|
|
2231
2529
|
const chainLabel = typeof this.opts.chain === "string" ? this.opts.chain : net.network;
|
|
2232
|
-
const
|
|
2530
|
+
const session = this.sessionView();
|
|
2531
|
+
const candidates = this.gatherCandidates(net, challenge, schemes);
|
|
2233
2532
|
if (candidates.length === 0) {
|
|
2234
2533
|
const offered = [...new Set(challenge.accepts.map((a) => a.network))].join(", ") || "none";
|
|
2235
2534
|
return {
|
|
@@ -2239,7 +2538,8 @@ var PipRailClient = (_class2 = class {
|
|
|
2239
2538
|
payable: false,
|
|
2240
2539
|
best: null,
|
|
2241
2540
|
options: [],
|
|
2242
|
-
fundingHint: `This 402 isn't offered on your chain (${chainLabel}); it's payable on: ${offered}
|
|
2541
|
+
fundingHint: `This 402 isn't offered on your chain (${chainLabel}); it's payable on: ${offered}.`,
|
|
2542
|
+
...session ? { session } : {}
|
|
2243
2543
|
};
|
|
2244
2544
|
}
|
|
2245
2545
|
const analysed = await Promise.all(
|
|
@@ -2257,7 +2557,8 @@ var PipRailClient = (_class2 = class {
|
|
|
2257
2557
|
payable: best !== null,
|
|
2258
2558
|
best,
|
|
2259
2559
|
options,
|
|
2260
|
-
fundingHint: best ? null : buildFundingHint(options, chainLabel)
|
|
2560
|
+
fundingHint: best ? null : buildFundingHint(options, chainLabel),
|
|
2561
|
+
...session ? { session } : {}
|
|
2261
2562
|
};
|
|
2262
2563
|
}
|
|
2263
2564
|
/** Analyse ONE rail against the wallet's holdings — quote (existing) + gas
|
|
@@ -2269,29 +2570,39 @@ var PipRailClient = (_class2 = class {
|
|
|
2269
2570
|
const rr = await net.recipientReady(accept.payTo, accept.asset).catch(() => ({ ready: "unknown" }));
|
|
2270
2571
|
const amount = BigInt(accept.amount);
|
|
2271
2572
|
const fee = safeBig(cost.fee);
|
|
2573
|
+
const isExact = accept.scheme === "exact";
|
|
2272
2574
|
const isNative = accept.asset === "native";
|
|
2273
2575
|
const blockers = [];
|
|
2274
2576
|
const warnings = [];
|
|
2275
2577
|
const shortfall = {};
|
|
2276
|
-
if (!quote.withinPolicy)
|
|
2578
|
+
if (!quote.withinPolicy) {
|
|
2579
|
+
blockers.push(
|
|
2580
|
+
quote.policyCode === "SESSION_EXPIRED" || quote.policyCode === "WINDOW_TOTAL" ? "OUTSIDE_WINDOW" : "OUTSIDE_POLICY"
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2277
2583
|
if (quote.symbolMismatch) warnings.push("SYMBOL_MISMATCH");
|
|
2278
|
-
if (cost.basis === "heuristic") warnings.push("GAS_HEURISTIC");
|
|
2584
|
+
if (!isExact && cost.basis === "heuristic") warnings.push("GAS_HEURISTIC");
|
|
2279
2585
|
const tokenKnown = bal.token != null;
|
|
2280
2586
|
const nativeKnown = bal.native != null;
|
|
2281
|
-
if (!tokenKnown || !nativeKnown) warnings.push("BALANCE_UNREADABLE");
|
|
2282
|
-
if (
|
|
2587
|
+
if (isExact ? !tokenKnown : !tokenKnown || !nativeKnown) warnings.push("BALANCE_UNREADABLE");
|
|
2588
|
+
if (isExact) {
|
|
2589
|
+
if (tokenKnown && bal.token < amount) {
|
|
2590
|
+
blockers.push("INSUFFICIENT_TOKEN");
|
|
2591
|
+
shortfall.token = _chunkPA6YD3HLcjs.formatUnits.call(void 0, amount - bal.token, quote.decimals);
|
|
2592
|
+
}
|
|
2593
|
+
} else if (isNative) {
|
|
2283
2594
|
if (nativeKnown && bal.native < amount + fee) {
|
|
2284
2595
|
blockers.push("INSUFFICIENT_TOKEN");
|
|
2285
|
-
shortfall.token =
|
|
2596
|
+
shortfall.token = _chunkPA6YD3HLcjs.formatUnits.call(void 0, amount + fee - bal.native, quote.decimals);
|
|
2286
2597
|
}
|
|
2287
2598
|
} else {
|
|
2288
2599
|
if (tokenKnown && bal.token < amount) {
|
|
2289
2600
|
blockers.push("INSUFFICIENT_TOKEN");
|
|
2290
|
-
shortfall.token =
|
|
2601
|
+
shortfall.token = _chunkPA6YD3HLcjs.formatUnits.call(void 0, amount - bal.token, quote.decimals);
|
|
2291
2602
|
}
|
|
2292
2603
|
if (nativeKnown && bal.native < fee) {
|
|
2293
2604
|
blockers.push("INSUFFICIENT_GAS");
|
|
2294
|
-
shortfall.native =
|
|
2605
|
+
shortfall.native = _chunkPA6YD3HLcjs.formatUnits.call(void 0, fee - bal.native, cost.feeDecimals);
|
|
2295
2606
|
} else if (nativeKnown && fee > 0n && bal.native < fee * 3n / 2n) {
|
|
2296
2607
|
warnings.push("THIN_GAS_MARGIN");
|
|
2297
2608
|
}
|
|
@@ -2306,7 +2617,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2306
2617
|
} else {
|
|
2307
2618
|
recipient = { ready: rr.ready };
|
|
2308
2619
|
}
|
|
2309
|
-
const unreadable = isNative ? !nativeKnown : !tokenKnown || !nativeKnown;
|
|
2620
|
+
const unreadable = isExact ? !tokenKnown : isNative ? !nativeKnown : !tokenKnown || !nativeKnown;
|
|
2310
2621
|
const state = blockers.length ? "blocked" : unreadable || rr.ready === "unknown" ? "unknown" : "payable";
|
|
2311
2622
|
return {
|
|
2312
2623
|
accept,
|
|
@@ -2316,8 +2627,8 @@ var PipRailClient = (_class2 = class {
|
|
|
2316
2627
|
blockers,
|
|
2317
2628
|
warnings,
|
|
2318
2629
|
balance: {
|
|
2319
|
-
token: bal.token != null ?
|
|
2320
|
-
native: bal.native != null ?
|
|
2630
|
+
token: bal.token != null ? _chunkPA6YD3HLcjs.formatUnits.call(void 0, bal.token, quote.decimals) : null,
|
|
2631
|
+
native: bal.native != null ? _chunkPA6YD3HLcjs.formatUnits.call(void 0, bal.native, cost.feeDecimals) : null
|
|
2321
2632
|
},
|
|
2322
2633
|
need: { token: quote.amountFormatted, native: cost.feeFormatted },
|
|
2323
2634
|
...shortfall.token || shortfall.native ? { shortfall } : {},
|
|
@@ -2328,15 +2639,20 @@ var PipRailClient = (_class2 = class {
|
|
|
2328
2639
|
* driver's describeAsset) + the policy verdict + a symbol-mismatch flag. */
|
|
2329
2640
|
buildQuote(net, accept, url, description) {
|
|
2330
2641
|
if (!/^\d+$/.test(accept.amount)) {
|
|
2331
|
-
throw new (0,
|
|
2642
|
+
throw new (0, _chunkPA6YD3HLcjs.InvalidEnvelopeError)(
|
|
2332
2643
|
`challenge amount "${accept.amount}" is not a base-unit integer.`
|
|
2333
2644
|
);
|
|
2334
2645
|
}
|
|
2335
2646
|
const amountBase = BigInt(accept.amount);
|
|
2336
2647
|
const described = net.describeAsset(accept.asset);
|
|
2337
|
-
const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess',
|
|
2338
|
-
|
|
2339
|
-
|
|
2648
|
+
const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _41 => _41.decimals]), () => ( accept.extra.decimals));
|
|
2649
|
+
if (decimals === void 0) {
|
|
2650
|
+
throw new (0, _chunkPA6YD3HLcjs.InvalidEnvelopeError)(
|
|
2651
|
+
`challenge for ${accept.asset} on ${accept.network} states no decimals and the SDK doesn't recognise the token \u2014 refusing to price it.`
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _42 => _42.symbol]), () => ( accept.extra.symbol));
|
|
2655
|
+
const amountFormatted = _chunkPA6YD3HLcjs.formatUnits.call(void 0, amountBase, decimals);
|
|
2340
2656
|
const intent = {
|
|
2341
2657
|
host: hostOf2(url),
|
|
2342
2658
|
chain: this.opts.chain,
|
|
@@ -2347,10 +2663,25 @@ var PipRailClient = (_class2 = class {
|
|
|
2347
2663
|
symbol,
|
|
2348
2664
|
recognized: described != null
|
|
2349
2665
|
};
|
|
2666
|
+
const policy = this.opts.policy;
|
|
2667
|
+
const hasWindow = !!policy && policy.windowTotal != null && policy.windowSeconds != null;
|
|
2668
|
+
const hasTimePolicy = !!policy && (policy.ttlSeconds != null || policy.expiresAt != null || hasWindow);
|
|
2669
|
+
const now = Date.now();
|
|
2670
|
+
const ctx = hasTimePolicy ? {
|
|
2671
|
+
now,
|
|
2672
|
+
sessionStart: this.ledger.sessionStart,
|
|
2673
|
+
// Window slice ONLY when BOTH fields are set — never a `?? 0` width.
|
|
2674
|
+
spentInWindowBase: hasWindow ? this.ledger.totalSince(
|
|
2675
|
+
accept.network,
|
|
2676
|
+
accept.asset,
|
|
2677
|
+
now - policy.windowSeconds * 1e3
|
|
2678
|
+
) : 0n
|
|
2679
|
+
} : void 0;
|
|
2350
2680
|
const decision = evaluatePolicy(
|
|
2351
2681
|
intent,
|
|
2352
2682
|
this.opts.policy,
|
|
2353
|
-
this.ledger.totalFor(accept.network, accept.asset)
|
|
2683
|
+
this.ledger.totalFor(accept.network, accept.asset),
|
|
2684
|
+
ctx
|
|
2354
2685
|
);
|
|
2355
2686
|
const serverSymbol = accept.extra.symbol;
|
|
2356
2687
|
const symbolMismatch = intent.recognized && !!serverSymbol && !!symbol && serverSymbol.toUpperCase() !== symbol.toUpperCase();
|
|
@@ -2369,15 +2700,19 @@ var PipRailClient = (_class2 = class {
|
|
|
2369
2700
|
recognized: intent.recognized,
|
|
2370
2701
|
symbolMismatch,
|
|
2371
2702
|
withinPolicy: decision.allowed,
|
|
2372
|
-
...decision.reason ? { policyReason: decision.reason } : {}
|
|
2703
|
+
...decision.reason ? { policyReason: decision.reason } : {},
|
|
2704
|
+
...decision.code ? { policyCode: decision.code } : {}
|
|
2373
2705
|
};
|
|
2374
2706
|
}
|
|
2375
2707
|
/** Enforce the spend policy and the onBeforePay hook — both refuse by
|
|
2376
|
-
* throwing PaymentDeclinedError, before any funds move.
|
|
2708
|
+
* throwing PaymentDeclinedError, before any funds move. Every refusal carries
|
|
2709
|
+
* a typed `reasonCode` so an agent can branch on the cause (and spot a
|
|
2710
|
+
* TERMINAL expiry/approval decline it must not retry) without parsing prose. */
|
|
2377
2711
|
async authorize(quote) {
|
|
2378
2712
|
if (!quote.withinPolicy) {
|
|
2379
|
-
throw new (0,
|
|
2380
|
-
`Payment refused by policy: ${_nullishCoalesce(quote.policyReason, () => ( "not allowed"))}
|
|
2713
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentDeclinedError)(
|
|
2714
|
+
`Payment refused by policy: ${_nullishCoalesce(quote.policyReason, () => ( "not allowed"))}`,
|
|
2715
|
+
{ reasonCode: reasonCodeForPolicy(quote.policyCode) }
|
|
2381
2716
|
);
|
|
2382
2717
|
}
|
|
2383
2718
|
const hook = this.opts.onBeforePay;
|
|
@@ -2386,13 +2721,15 @@ var PipRailClient = (_class2 = class {
|
|
|
2386
2721
|
try {
|
|
2387
2722
|
approved = await hook(quote);
|
|
2388
2723
|
} catch (err) {
|
|
2389
|
-
throw new (0,
|
|
2390
|
-
cause: err
|
|
2724
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentDeclinedError)("onBeforePay threw \u2014 refusing to pay.", {
|
|
2725
|
+
cause: err,
|
|
2726
|
+
reasonCode: "APPROVAL"
|
|
2391
2727
|
});
|
|
2392
2728
|
}
|
|
2393
2729
|
if (!approved) {
|
|
2394
|
-
throw new (0,
|
|
2395
|
-
`onBeforePay declined ${quote.amountFormatted} ${_nullishCoalesce(quote.symbol, () => ( ""))}`.trimEnd() + ` on ${quote.network}
|
|
2730
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentDeclinedError)(
|
|
2731
|
+
`onBeforePay declined ${quote.amountFormatted} ${_nullishCoalesce(quote.symbol, () => ( ""))}`.trimEnd() + ` on ${quote.network}.`,
|
|
2732
|
+
{ reasonCode: "APPROVAL" }
|
|
2396
2733
|
);
|
|
2397
2734
|
}
|
|
2398
2735
|
}
|
|
@@ -2415,7 +2752,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2415
2752
|
}
|
|
2416
2753
|
async payAndConfirm(net, wallet, accept) {
|
|
2417
2754
|
if (!net.supports(accept.network)) {
|
|
2418
|
-
throw new (0,
|
|
2755
|
+
throw new (0, _chunkPA6YD3HLcjs.WrongChainError)(
|
|
2419
2756
|
`Challenge expects ${accept.network} but client is on ${net.network}.`
|
|
2420
2757
|
);
|
|
2421
2758
|
}
|
|
@@ -2444,7 +2781,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2444
2781
|
accepted: accept,
|
|
2445
2782
|
payload: { nonce: accept.extra.nonce, txHash: ref }
|
|
2446
2783
|
};
|
|
2447
|
-
const headers = new Headers(_optionalChain([originalInit, 'optionalAccess',
|
|
2784
|
+
const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _43 => _43.headers]));
|
|
2448
2785
|
headers.set(HEADER_SIGNATURE, buildSignatureHeader(signature));
|
|
2449
2786
|
let lastResponse = null;
|
|
2450
2787
|
let lastReason = null;
|
|
@@ -2459,7 +2796,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2459
2796
|
() => timeoutController.abort(),
|
|
2460
2797
|
this.retryTimeoutMs
|
|
2461
2798
|
);
|
|
2462
|
-
const signal = _optionalChain([originalInit, 'optionalAccess',
|
|
2799
|
+
const signal = _optionalChain([originalInit, 'optionalAccess', _44 => _44.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
|
|
2463
2800
|
try {
|
|
2464
2801
|
lastResponse = await fetch(url, {
|
|
2465
2802
|
..._nullishCoalesce(originalInit, () => ( {})),
|
|
@@ -2468,7 +2805,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2468
2805
|
});
|
|
2469
2806
|
} catch (err) {
|
|
2470
2807
|
if (timeoutController.signal.aborted) {
|
|
2471
|
-
throw new (0,
|
|
2808
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentTimeoutError)(
|
|
2472
2809
|
`Server did not respond within ${this.retryTimeoutMs}ms after broadcasting payment ${ref}. Re-verify or re-submit ref=${ref} \u2014 do NOT re-pay.`,
|
|
2473
2810
|
{ cause: err, ref }
|
|
2474
2811
|
);
|
|
@@ -2490,16 +2827,111 @@ var PipRailClient = (_class2 = class {
|
|
|
2490
2827
|
kind: "payment-failed",
|
|
2491
2828
|
reason: `server returned 402 after broadcasting payment ${ref}${unconfirmedNote} (${why})`
|
|
2492
2829
|
});
|
|
2493
|
-
throw new (0,
|
|
2830
|
+
throw new (0, _chunkPA6YD3HLcjs.MaxRetriesExceededError)(
|
|
2494
2831
|
`Server still returned 402 after ${attempts} attempt(s) with on-chain proof ref=${ref}${unconfirmedNote}. Last server rejection: ${why}. Re-verify or re-submit ref=${ref} before retrying \u2014 never re-pay (it would double-spend).`,
|
|
2495
2832
|
{ ref }
|
|
2496
2833
|
);
|
|
2497
2834
|
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Pay a standard x402 `exact` rail — a SEPARATE, fundamentally more conservative
|
|
2837
|
+
* path than {@link retryWithProof}. The buyer SIGNS an EIP-3009 authorization ONCE
|
|
2838
|
+
* (the driver's `payExact`) and the server / merchant-chosen facilitator BROADCASTS
|
|
2839
|
+
* it synchronously, so a blind re-POST of a still-in-flight authorization could
|
|
2840
|
+
* double-BROADCAST it. Hence, unlike the onchain-proof loop:
|
|
2841
|
+
*
|
|
2842
|
+
* • sign exactly once — reuse the SAME header on every retry, never re-sign;
|
|
2843
|
+
* • retry ONLY an explicit 402 (a definitive pre-broadcast rejection), bounded
|
|
2844
|
+
* well under `maxTimeoutSeconds` so the loop can't outlive the authorization;
|
|
2845
|
+
* • a post-POST transport error/timeout → {@link PaymentTimeoutError} carrying the
|
|
2846
|
+
* nonce (the facilitator MAY have settled — verify on-chain, NEVER re-pay);
|
|
2847
|
+
* • a 5xx → return as-is (server settle failure; the authorization stays valid +
|
|
2848
|
+
* its nonce unused) — no settled event, no spend;
|
|
2849
|
+
* • a 200 whose SettleResponse says `success:false` → a rejection, NEVER a spend;
|
|
2850
|
+
* • the spend is recorded EXACTLY ONCE, on an affirmative settlement only.
|
|
2851
|
+
*/
|
|
2852
|
+
async payExactRail(net, wallet, accept, url, init, quote) {
|
|
2853
|
+
if (!net.payExact) {
|
|
2854
|
+
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
2855
|
+
`the ${net.family} family can't pay a standard 'exact' rail (EVM + EIP-3009 only).`
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
throwIfAborted(_optionalChain([init, 'optionalAccess', _45 => _45.signal]));
|
|
2859
|
+
const { payload, accepted, payerFrom, nonce } = await net.payExact(wallet, accept);
|
|
2860
|
+
const headers = new Headers(_optionalChain([init, 'optionalAccess', _46 => _46.headers]));
|
|
2861
|
+
headers.set(HEADER_SIGNATURE, buildExactSignatureHeader({ accepted, payload }));
|
|
2862
|
+
const rejectDefinitive = (why2) => {
|
|
2863
|
+
this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})` });
|
|
2864
|
+
throw new (0, _chunkPA6YD3HLcjs.MaxRetriesExceededError)(
|
|
2865
|
+
`exact: the facilitator rejected the payment (${why2}). Fix the cause, then re-present the SAME signed authorization (nonce=${nonce}) \u2014 do NOT re-sign a fresh nonce. ref=${nonce}.`,
|
|
2866
|
+
{ ref: nonce }
|
|
2867
|
+
);
|
|
2868
|
+
};
|
|
2869
|
+
const deadline = Date.now() + Math.max(1, Math.floor(accept.maxTimeoutSeconds / 2)) * 1e3;
|
|
2870
|
+
const maxAttempts = Math.min(this.maxRetries, 3);
|
|
2871
|
+
let lastReason = null;
|
|
2872
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
2873
|
+
if (attempt > 0) {
|
|
2874
|
+
if (Date.now() >= deadline) break;
|
|
2875
|
+
await new Promise((r) => setTimeout(r, Math.min(2e3, 400 * 2 ** (attempt - 1))));
|
|
2876
|
+
}
|
|
2877
|
+
throwIfAborted(_optionalChain([init, 'optionalAccess', _47 => _47.signal]));
|
|
2878
|
+
const budget = Math.min(this.retryTimeoutMs, deadline - Date.now());
|
|
2879
|
+
if (budget <= 0) break;
|
|
2880
|
+
const timeoutController = new AbortController();
|
|
2881
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), budget);
|
|
2882
|
+
const signal = _optionalChain([init, 'optionalAccess', _48 => _48.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, init.signal]) : timeoutController.signal;
|
|
2883
|
+
let response;
|
|
2884
|
+
try {
|
|
2885
|
+
response = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), headers, signal });
|
|
2886
|
+
} catch (err) {
|
|
2887
|
+
throw new (0, _chunkPA6YD3HLcjs.PaymentTimeoutError)(
|
|
2888
|
+
`exact: no response after submitting the authorization (nonce=${nonce}) to ${hostOf2(url)}. The facilitator may have already settled it \u2014 verify on-chain with authorizationState(${payerFrom}, ${nonce}) before re-presenting; do NOT re-pay.`,
|
|
2889
|
+
{ cause: err, ref: nonce }
|
|
2890
|
+
);
|
|
2891
|
+
} finally {
|
|
2892
|
+
clearTimeout(timeoutId);
|
|
2893
|
+
}
|
|
2894
|
+
const settle = parseSettleResponse(response);
|
|
2895
|
+
if (response.status === 402) {
|
|
2896
|
+
if (settle && settle.success === false) rejectDefinitive(_nullishCoalesce(settle.errorReason, () => ( "the facilitator reported success:false")));
|
|
2897
|
+
lastReason = await _asyncNullishCoalesce(await readInvalidReason(response), async () => ( lastReason));
|
|
2898
|
+
continue;
|
|
2899
|
+
}
|
|
2900
|
+
if (response.ok && !(settle && settle.success === false)) {
|
|
2901
|
+
const receipt = parseReceipt(response);
|
|
2902
|
+
this.safeEmit({ kind: "payment-settled", receipt, ...settle ? { settle } : {} });
|
|
2903
|
+
const ref = _optionalChain([settle, 'optionalAccess', _49 => _49.transaction]) || _optionalChain([receipt, 'optionalAccess', _50 => _50.transaction]) || `eip3009-nonce:${nonce}`;
|
|
2904
|
+
this.recordSpend(quote, ref);
|
|
2905
|
+
return response;
|
|
2906
|
+
}
|
|
2907
|
+
if (response.status >= 500) {
|
|
2908
|
+
this.safeEmit({ kind: "payment-failed", reason: `exact: server ${response.status} \u2014 authorization nonce=${nonce} not settled` });
|
|
2909
|
+
return response;
|
|
2910
|
+
}
|
|
2911
|
+
if (settle && settle.success === false) rejectDefinitive(_nullishCoalesce(settle.errorReason, () => ( "the facilitator reported success:false")));
|
|
2912
|
+
this.safeEmit({ kind: "payment-failed", reason: `exact: server ${response.status} \u2014 authorization nonce=${nonce} not settled` });
|
|
2913
|
+
return response;
|
|
2914
|
+
}
|
|
2915
|
+
const why = lastReason ? `${lastReason.error}${lastReason.detail ? ` \u2014 ${lastReason.detail}` : ""}` : "server gave no reason";
|
|
2916
|
+
this.safeEmit({
|
|
2917
|
+
kind: "payment-failed",
|
|
2918
|
+
reason: `exact: 402 after submitting authorization nonce=${nonce} (${why})`
|
|
2919
|
+
});
|
|
2920
|
+
throw new (0, _chunkPA6YD3HLcjs.MaxRetriesExceededError)(
|
|
2921
|
+
`exact: server still returned 402 after submitting the signed authorization (nonce=${nonce}). Last rejection: ${why}. Re-present the SAME authorization \u2014 do NOT re-sign a fresh nonce; verify authorizationState(${payerFrom}, ${nonce}) first. ref=${nonce}.`,
|
|
2922
|
+
{ ref: nonce }
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2498
2925
|
}, _class2);
|
|
2926
|
+
function throwIfAborted(signal) {
|
|
2927
|
+
if (_optionalChain([signal, 'optionalAccess', _51 => _51.aborted])) {
|
|
2928
|
+
throw _nullishCoalesce(signal.reason, () => ( new DOMException("This operation was aborted.", "AbortError")));
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2499
2931
|
function safeBig(s) {
|
|
2500
2932
|
try {
|
|
2501
2933
|
return BigInt(s);
|
|
2502
|
-
} catch (
|
|
2934
|
+
} catch (e22) {
|
|
2503
2935
|
return 0n;
|
|
2504
2936
|
}
|
|
2505
2937
|
}
|
|
@@ -2525,6 +2957,9 @@ function buildFundingHint(options, chainLabel) {
|
|
|
2525
2957
|
if (target.blockers.includes("RECIPIENT_NOT_READY")) {
|
|
2526
2958
|
return `Recipient ${shortAddr(target.accept.payTo)} can't receive on ${chainLabel} yet \u2014 ${_nullishCoalesce(target.recipient.fix, () => ( "recipient not ready"))}.`;
|
|
2527
2959
|
}
|
|
2960
|
+
if (target.blockers.includes("OUTSIDE_WINDOW")) {
|
|
2961
|
+
return target.quote.policyCode === "SESSION_EXPIRED" ? `Session is over on ${chainLabel} \u2014 restart the process or extend the TTL; no retry will succeed.` : `Budget window exhausted on ${chainLabel} \u2014 wait for it to free, or raise policy.windowTotal.`;
|
|
2962
|
+
}
|
|
2528
2963
|
if (target.blockers.includes("OUTSIDE_POLICY")) {
|
|
2529
2964
|
return `Refused by spend policy: ${_nullishCoalesce(target.quote.policyReason, () => ( "not allowed"))}.`;
|
|
2530
2965
|
}
|
|
@@ -2532,10 +2967,10 @@ function buildFundingHint(options, chainLabel) {
|
|
|
2532
2967
|
return `Couldn't fully read your wallet on ${chainLabel} (RPC throttled) \u2014 retry; you may already be able to pay ${target.quote.amountFormatted} ${sym}.`;
|
|
2533
2968
|
}
|
|
2534
2969
|
const parts = [];
|
|
2535
|
-
if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access',
|
|
2970
|
+
if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _52 => _52.shortfall, 'optionalAccess', _53 => _53.token])) {
|
|
2536
2971
|
parts.push(`top up ${target.shortfall.token} ${sym}`);
|
|
2537
2972
|
}
|
|
2538
|
-
if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access',
|
|
2973
|
+
if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _54 => _54.shortfall, 'optionalAccess', _55 => _55.native])) {
|
|
2539
2974
|
parts.push(`add ~${target.shortfall.native} ${target.cost.feeSymbol} for gas`);
|
|
2540
2975
|
}
|
|
2541
2976
|
return parts.length ? `Can't settle on ${chainLabel}: ${parts.join(" and ")} (to pay ${target.quote.amountFormatted} ${sym}).` : `Can't settle on ${chainLabel} for ${target.quote.amountFormatted} ${sym}.`;
|
|
@@ -2549,7 +2984,7 @@ async function planAcross(clients, url, init) {
|
|
|
2549
2984
|
const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
|
|
2550
2985
|
return {
|
|
2551
2986
|
url,
|
|
2552
|
-
network: _nullishCoalesce(_optionalChain([best, 'optionalAccess',
|
|
2987
|
+
network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _56 => _56.accept, 'access', _57 => _57.network]), () => ( live[0].network)),
|
|
2553
2988
|
status,
|
|
2554
2989
|
payable: best !== null,
|
|
2555
2990
|
best,
|
|
@@ -2562,10 +2997,24 @@ function railOnNetwork(rail, matches) {
|
|
|
2562
2997
|
const n = normalizeNetwork(rail.network);
|
|
2563
2998
|
return !n.includes(":") || matches(n);
|
|
2564
2999
|
}
|
|
3000
|
+
function reasonCodeForPolicy(code) {
|
|
3001
|
+
switch (code) {
|
|
3002
|
+
case "SESSION_EXPIRED":
|
|
3003
|
+
return "SESSION_EXPIRED";
|
|
3004
|
+
case "WINDOW_TOTAL":
|
|
3005
|
+
return "OUTSIDE_WINDOW";
|
|
3006
|
+
case "MAX_TOTAL":
|
|
3007
|
+
return "BUDGET";
|
|
3008
|
+
case void 0:
|
|
3009
|
+
return void 0;
|
|
3010
|
+
default:
|
|
3011
|
+
return "POLICY";
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
2565
3014
|
function hostOf2(url) {
|
|
2566
3015
|
try {
|
|
2567
3016
|
return new URL(url).hostname;
|
|
2568
|
-
} catch (
|
|
3017
|
+
} catch (e23) {
|
|
2569
3018
|
return url;
|
|
2570
3019
|
}
|
|
2571
3020
|
}
|
|
@@ -2582,8 +3031,8 @@ function isReplayableBodyInit(value) {
|
|
|
2582
3031
|
async function readInvalidReason(response) {
|
|
2583
3032
|
try {
|
|
2584
3033
|
const body = await response.clone().json();
|
|
2585
|
-
const ext = _optionalChain([body, 'optionalAccess',
|
|
2586
|
-
const piprail = _optionalChain([ext, 'optionalAccess',
|
|
3034
|
+
const ext = _optionalChain([body, 'optionalAccess', _58 => _58.extensions]);
|
|
3035
|
+
const piprail = _optionalChain([ext, 'optionalAccess', _59 => _59.piprail]);
|
|
2587
3036
|
if (piprail && typeof piprail.code === "string") {
|
|
2588
3037
|
return {
|
|
2589
3038
|
error: piprail.code,
|
|
@@ -2596,18 +3045,130 @@ async function readInvalidReason(response) {
|
|
|
2596
3045
|
detail: typeof body.detail === "string" ? body.detail : ""
|
|
2597
3046
|
};
|
|
2598
3047
|
}
|
|
2599
|
-
|
|
3048
|
+
if (body && body.isValid === false && typeof body.invalidReason === "string") {
|
|
3049
|
+
return {
|
|
3050
|
+
error: body.invalidReason,
|
|
3051
|
+
detail: typeof body.invalidMessage === "string" ? body.invalidMessage : ""
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
} catch (e24) {
|
|
2600
3055
|
}
|
|
3056
|
+
const settle = parseSettleResponse(response);
|
|
3057
|
+
if (_optionalChain([settle, 'optionalAccess', _60 => _60.errorReason])) return { error: settle.errorReason, detail: "" };
|
|
2601
3058
|
return null;
|
|
2602
3059
|
}
|
|
2603
3060
|
|
|
3061
|
+
// src/render.ts
|
|
3062
|
+
function summarizePlan(plan) {
|
|
3063
|
+
if (plan == null) return "No payment required \u2014 the URL is not payment-gated.";
|
|
3064
|
+
if (plan.payable && plan.best) {
|
|
3065
|
+
const q = plan.best.quote;
|
|
3066
|
+
const c = plan.best.cost;
|
|
3067
|
+
const otherRails = plan.options.length - 1;
|
|
3068
|
+
const note = otherRails > 0 ? ` ${otherRails} other rail(s) not settleable.` : "";
|
|
3069
|
+
return `Payable: ${q.amountFormatted} ${_nullishCoalesce(q.symbol, () => ( q.asset))} on ${plan.best.accept.network} (gas ~${c.feeFormatted} ${c.feeSymbol}).${note}`;
|
|
3070
|
+
}
|
|
3071
|
+
return `NOT payable: ${_nullishCoalesce(plan.fundingHint, () => ( `no settleable rail on ${plan.network}`))}`;
|
|
3072
|
+
}
|
|
3073
|
+
function explainDecline(err) {
|
|
3074
|
+
if (!(err instanceof _chunkPA6YD3HLcjs.PipRailError)) {
|
|
3075
|
+
return `Payment failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3076
|
+
}
|
|
3077
|
+
switch (err.code) {
|
|
3078
|
+
case "PAYMENT_DECLINED":
|
|
3079
|
+
return err.message;
|
|
3080
|
+
case "PAYMENT_TIMEOUT":
|
|
3081
|
+
case "MAX_RETRIES_EXCEEDED":
|
|
3082
|
+
case "CONFIRMATION_TIMEOUT":
|
|
3083
|
+
return `${err.message} Recover using the proof on .ref (re-verify or re-submit it); never re-pay \u2014 a fresh payment would double-spend.`;
|
|
3084
|
+
case "INSUFFICIENT_FUNDS":
|
|
3085
|
+
return "The wallet cannot cover the payment + gas \u2014 top up the payer (token and/or native gas) and retry.";
|
|
3086
|
+
case "RECIPIENT_NOT_READY":
|
|
3087
|
+
return `${err.message} The fix is on the RECIPIENT (trustline / registration / opt-in / activation), not your balance.`;
|
|
3088
|
+
case "NO_COMPATIBLE_ACCEPT":
|
|
3089
|
+
case "UNSUPPORTED_SCHEME":
|
|
3090
|
+
return err.message;
|
|
3091
|
+
default:
|
|
3092
|
+
return `Payment failed: ${err.message}`;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
function formatSpendReport(summary) {
|
|
3096
|
+
if (summary.count === 0) return "No payments yet.";
|
|
3097
|
+
return summary.byAsset.map(
|
|
3098
|
+
(a) => `${a.totalFormatted} ${_nullishCoalesce(a.symbol, () => ( a.asset))} on ${a.network} (${a.count} payment${a.count === 1 ? "" : "s"})`
|
|
3099
|
+
).join("; ");
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// src/agentGuide.ts
|
|
3103
|
+
var PIPRAIL_AGENT_GUIDE = `# Paying with PipRail \u2014 the agent contract
|
|
3104
|
+
|
|
3105
|
+
You can pay for x402 "402 Payment Required" resources autonomously. Money moves
|
|
3106
|
+
straight from your wallet to the server; PipRail custodies nothing. Follow this.
|
|
3107
|
+
|
|
3108
|
+
## The loop: quote \u2192 plan \u2192 pay
|
|
3109
|
+
1. piprail_quote_payment(url) \u2014 PRICE it. Returns the amount, token, chain, and
|
|
3110
|
+
whether it is within your spend policy. No funds move. Use it to decide if a
|
|
3111
|
+
resource is worth buying.
|
|
3112
|
+
2. piprail_plan_payment(url) \u2014 can I afford it NOW? Reads your balance, native gas,
|
|
3113
|
+
and recipient-readiness across every rail, and returns { payable, best,
|
|
3114
|
+
fundingHint, session? }. If payable is false, do NOT attempt the payment \u2014
|
|
3115
|
+
fundingHint says exactly what to fix.
|
|
3116
|
+
3. piprail_pay_request(url, method?, body?) \u2014 PAY (only if the plan was payable)
|
|
3117
|
+
and return the result.
|
|
3118
|
+
Always plan before you pay so you never commit to a payment you cannot finish.
|
|
3119
|
+
|
|
3120
|
+
## Reading a refusal \u2014 never crash, never double-spend
|
|
3121
|
+
A failed pay returns a STRUCTURED object, never a thrown error you must catch:
|
|
3122
|
+
{ ok:false, code, reason, explain, ref?, reasonCode?, declined? }
|
|
3123
|
+
Branch on \`code\` (always reliable). Key cases:
|
|
3124
|
+
- declined:true with reasonCode:'SESSION_EXPIRED' \u2014 your time budget is over. This
|
|
3125
|
+
is TERMINAL: STOP. Do not retry ANY payment this process; it cannot be undone
|
|
3126
|
+
without a restart / a longer TTL.
|
|
3127
|
+
- declined:true with reasonCode:'APPROVAL' \u2014 a human (or hook) declined this
|
|
3128
|
+
payment. Terminal for this pay: do NOT auto-retry \u2014 they said no, or no one
|
|
3129
|
+
answered.
|
|
3130
|
+
- declined:true with reasonCode:'OUTSIDE_WINDOW' \u2014 your rolling rate-limit is
|
|
3131
|
+
exhausted. Wait for it to free, then retry; do not raise the amount.
|
|
3132
|
+
- declined:true with reasonCode:'POLICY' or 'BUDGET' \u2014 a spend cap or allowlist
|
|
3133
|
+
refused it. Don't retry the same payment; pick a cheaper/allowed one.
|
|
3134
|
+
- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.
|
|
3135
|
+
- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the
|
|
3136
|
+
payment may ALREADY be on-chain. Recover using the proof on \`.ref\` (re-verify
|
|
3137
|
+
or re-submit it); never re-pay \u2014 a fresh payment would double-spend.
|
|
3138
|
+
- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on
|
|
3139
|
+
your chain/scheme; \`explain\` says whether it's the wrong chain or a scheme to enable.
|
|
3140
|
+
|
|
3141
|
+
## Knowing your leash \u2014 call piprail_budget
|
|
3142
|
+
piprail_budget tells you how much budget and time you have left, per
|
|
3143
|
+
(network, asset), plus your spend so far. Read-only; moves no funds. Use it in
|
|
3144
|
+
Mode A to self-check before paying.
|
|
3145
|
+
|
|
3146
|
+
## Two modes
|
|
3147
|
+
- Mode A (headless, default): you run FREE inside a pre-set budget + time
|
|
3148
|
+
envelope. The policy IS the consent \u2014 there is no per-payment prompt. Stay
|
|
3149
|
+
inside it; piprail_budget shows what's left.
|
|
3150
|
+
- Mode B (supervised): the host may ask a human to approve each payment. A
|
|
3151
|
+
decline/cancel/timeout comes back as declined:true (reasonCode:'APPROVAL') \u2014
|
|
3152
|
+
do NOT retry it as if it were a transient error.
|
|
3153
|
+
|
|
3154
|
+
## Hard facts
|
|
3155
|
+
- Spend caps are PER (network, asset). There is no single cross-token dollar cap \u2014
|
|
3156
|
+
budgets aren't summed across tokens (no price oracle).
|
|
3157
|
+
- Spend totals and the time envelope live IN-MEMORY for THIS process; they reset on restart
|
|
3158
|
+
(a convenience, not a durable ledger).
|
|
3159
|
+
`;
|
|
3160
|
+
function agentGuide() {
|
|
3161
|
+
return PIPRAIL_AGENT_GUIDE;
|
|
3162
|
+
}
|
|
3163
|
+
|
|
2604
3164
|
// src/agent.ts
|
|
3165
|
+
var OPEN_OBJECT = { type: "object", additionalProperties: true };
|
|
2605
3166
|
async function readBody(res) {
|
|
2606
3167
|
const text = await res.text();
|
|
2607
3168
|
if (!text) return null;
|
|
2608
3169
|
try {
|
|
2609
3170
|
return JSON.parse(text);
|
|
2610
|
-
} catch (
|
|
3171
|
+
} catch (e25) {
|
|
2611
3172
|
return text;
|
|
2612
3173
|
}
|
|
2613
3174
|
}
|
|
@@ -2674,6 +3235,7 @@ function paymentTools(client) {
|
|
|
2674
3235
|
required: ["url"],
|
|
2675
3236
|
additionalProperties: false
|
|
2676
3237
|
},
|
|
3238
|
+
outputSchema: OPEN_OBJECT,
|
|
2677
3239
|
invoke: async (args) => {
|
|
2678
3240
|
const quote = await client.quote(String(args.url));
|
|
2679
3241
|
return quote ? { gated: true, ...quote } : { gated: false, url: String(args.url) };
|
|
@@ -2697,6 +3259,7 @@ function paymentTools(client) {
|
|
|
2697
3259
|
required: ["url"],
|
|
2698
3260
|
additionalProperties: false
|
|
2699
3261
|
},
|
|
3262
|
+
outputSchema: OPEN_OBJECT,
|
|
2700
3263
|
invoke: async (args) => {
|
|
2701
3264
|
const plan = await client.planPayment(String(args.url));
|
|
2702
3265
|
if (plan == null) return { gated: false, url: String(args.url) };
|
|
@@ -2705,6 +3268,8 @@ function paymentTools(client) {
|
|
|
2705
3268
|
payable: plan.payable,
|
|
2706
3269
|
status: plan.status,
|
|
2707
3270
|
fundingHint: plan.fundingHint,
|
|
3271
|
+
// One model-readable line distilling the whole plan.
|
|
3272
|
+
summary: summarizePlan(plan),
|
|
2708
3273
|
best: plan.best ? {
|
|
2709
3274
|
network: plan.best.accept.network,
|
|
2710
3275
|
symbol: plan.best.quote.symbol,
|
|
@@ -2720,23 +3285,25 @@ function paymentTools(client) {
|
|
|
2720
3285
|
blockers: o.blockers,
|
|
2721
3286
|
warnings: o.warnings,
|
|
2722
3287
|
recipientReady: o.recipient.ready
|
|
2723
|
-
}))
|
|
3288
|
+
})),
|
|
3289
|
+
// The session's time leash, present only when a time policy is configured.
|
|
3290
|
+
...plan.session ? { session: plan.session } : {}
|
|
2724
3291
|
};
|
|
2725
3292
|
}
|
|
2726
3293
|
},
|
|
2727
3294
|
{
|
|
2728
3295
|
name: "piprail_pay_request",
|
|
2729
|
-
description: "Fetch an x402 payment-gated URL, automatically
|
|
3296
|
+
description: "Fetch an x402 payment-gated URL, automatically making the required payment if needed (subject to the spend policy + approval hook). Pays whichever rail the client is configured for \u2014 PipRail's backendless on-chain rail, or, when enabled, the standard `exact` rail (where the buyer signs and the server settles, so no buyer gas). Returns the HTTP status, the response body, and a payment receipt if one settled. If the payment is refused by policy or the approval hook, returns { declined: true, reason } \u2014 no funds moved.",
|
|
2730
3297
|
annotations: {
|
|
2731
3298
|
title: "Pay an x402 request",
|
|
2732
3299
|
readOnlyHint: false,
|
|
2733
3300
|
// this is the one tool that MOVES FUNDS
|
|
2734
3301
|
destructiveHint: true,
|
|
2735
|
-
//
|
|
3302
|
+
// a payment is value-moving and not reversible
|
|
2736
3303
|
idempotentHint: false,
|
|
2737
3304
|
// paying twice = two payments
|
|
2738
3305
|
openWorldHint: true
|
|
2739
|
-
// fetches a URL and settles
|
|
3306
|
+
// fetches a URL and settles a payment
|
|
2740
3307
|
},
|
|
2741
3308
|
parameters: {
|
|
2742
3309
|
type: "object",
|
|
@@ -2778,8 +3345,20 @@ function paymentTools(client) {
|
|
|
2778
3345
|
receipt: parseReceipt(res)
|
|
2779
3346
|
};
|
|
2780
3347
|
} catch (err) {
|
|
2781
|
-
if (err instanceof
|
|
2782
|
-
|
|
3348
|
+
if (err instanceof _chunkPA6YD3HLcjs.PipRailError) {
|
|
3349
|
+
const out = {
|
|
3350
|
+
ok: false,
|
|
3351
|
+
code: err.code,
|
|
3352
|
+
reason: err.message,
|
|
3353
|
+
explain: explainDecline(err)
|
|
3354
|
+
};
|
|
3355
|
+
if (err instanceof _chunkPA6YD3HLcjs.PaymentDeclinedError) {
|
|
3356
|
+
out.declined = true;
|
|
3357
|
+
if (err.reasonCode) out.reasonCode = err.reasonCode;
|
|
3358
|
+
}
|
|
3359
|
+
const ref = err.ref;
|
|
3360
|
+
if (typeof ref === "string") out.ref = ref;
|
|
3361
|
+
return out;
|
|
2783
3362
|
}
|
|
2784
3363
|
throw err;
|
|
2785
3364
|
}
|
|
@@ -2817,10 +3396,57 @@ function paymentTools(client) {
|
|
|
2817
3396
|
const outcomes = await client.register(String(args.url), opts);
|
|
2818
3397
|
return { outcomes };
|
|
2819
3398
|
}
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
name: "piprail_budget",
|
|
3402
|
+
description: "Read how much of your spend budget and time leash is left \u2014 per (network, asset) remaining, the session time envelope, and your spend so far. Use it in Mode A (headless) to self-check BEFORE paying, so you never discover the leash by hitting a decline. Read-only; moves no funds. NOTE: totals and the time envelope are in-memory for THIS process and reset on restart.",
|
|
3403
|
+
annotations: {
|
|
3404
|
+
title: "Check remaining budget",
|
|
3405
|
+
readOnlyHint: true,
|
|
3406
|
+
// reads the in-memory ledger + policy; never pays
|
|
3407
|
+
idempotentHint: true
|
|
3408
|
+
// a pure read
|
|
3409
|
+
},
|
|
3410
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
3411
|
+
outputSchema: OPEN_OBJECT,
|
|
3412
|
+
invoke: async () => {
|
|
3413
|
+
const spent = client.spent();
|
|
3414
|
+
const budget = client.budget();
|
|
3415
|
+
return {
|
|
3416
|
+
spent,
|
|
3417
|
+
remaining: budget.byAsset,
|
|
3418
|
+
session: budget.session,
|
|
3419
|
+
report: formatSpendReport(spent)
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
},
|
|
3423
|
+
{
|
|
3424
|
+
name: "piprail_guide",
|
|
3425
|
+
description: "Read the PipRail agent contract \u2014 the quote \u2192 plan \u2192 pay loop, how to read a refusal (and which declines are TERMINAL), the never-re-pay rule for broadcast-but-unconfirmed payments, and Mode A (headless) vs Mode B (supervised). Read-only; call it once if unsure how to use these tools.",
|
|
3426
|
+
annotations: {
|
|
3427
|
+
title: "How to use PipRail",
|
|
3428
|
+
readOnlyHint: true,
|
|
3429
|
+
idempotentHint: true
|
|
3430
|
+
},
|
|
3431
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
3432
|
+
invoke: async () => ({ guide: PIPRAIL_AGENT_GUIDE })
|
|
2820
3433
|
}
|
|
2821
3434
|
];
|
|
2822
3435
|
}
|
|
2823
3436
|
|
|
3437
|
+
// src/classify.ts
|
|
3438
|
+
function classifyChallenge(challenge, opts) {
|
|
3439
|
+
const accepts = _nullishCoalesce(challenge.accepts, () => ( []));
|
|
3440
|
+
const offeredSchemes = [...new Set(accepts.map((a) => a.scheme))];
|
|
3441
|
+
const offeredNetworks = [...new Set(accepts.map((a) => a.network))];
|
|
3442
|
+
const onClientChain = accepts.some((a) => a.network === opts.network);
|
|
3443
|
+
const payableScheme = accepts.some(
|
|
3444
|
+
(a) => a.network === opts.network && opts.schemes.includes(a.scheme)
|
|
3445
|
+
);
|
|
3446
|
+
const verdict = accepts.length === 0 ? "NO_RAIL" : payableScheme ? "PAYABLE_RAIL" : onClientChain ? "UNPAYABLE_SCHEME" : "WRONG_CHAIN";
|
|
3447
|
+
return { onClientChain, payableScheme, offeredSchemes, offeredNetworks, verdict };
|
|
3448
|
+
}
|
|
3449
|
+
|
|
2824
3450
|
// src/discovery.ts
|
|
2825
3451
|
var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
|
|
2826
3452
|
function buildBazaarExtension(descriptor = {}) {
|
|
@@ -2853,7 +3479,7 @@ function buildBazaarExtension(descriptor = {}) {
|
|
|
2853
3479
|
function pathOf(url) {
|
|
2854
3480
|
try {
|
|
2855
3481
|
return new URL(url).pathname || "/";
|
|
2856
|
-
} catch (
|
|
3482
|
+
} catch (e26) {
|
|
2857
3483
|
return url.startsWith("/") ? url : `/${url}`;
|
|
2858
3484
|
}
|
|
2859
3485
|
}
|
|
@@ -2926,7 +3552,7 @@ async function post(url, body, headers) {
|
|
|
2926
3552
|
let json = null;
|
|
2927
3553
|
try {
|
|
2928
3554
|
json = await res.json();
|
|
2929
|
-
} catch (
|
|
3555
|
+
} catch (e27) {
|
|
2930
3556
|
}
|
|
2931
3557
|
return { status: res.status, json };
|
|
2932
3558
|
}
|
|
@@ -2942,13 +3568,13 @@ async function settleViaFacilitator(input) {
|
|
|
2942
3568
|
try {
|
|
2943
3569
|
verify = await post(`${base2}/verify`, body, auth);
|
|
2944
3570
|
} catch (err) {
|
|
2945
|
-
throw new (0,
|
|
3571
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
2946
3572
|
`exact settle (facilitator ${base2}): /verify request failed (${err instanceof Error ? err.message : String(err)}).`,
|
|
2947
3573
|
{ cause: err }
|
|
2948
3574
|
);
|
|
2949
3575
|
}
|
|
2950
3576
|
if (verify.status !== 200) {
|
|
2951
|
-
throw new (0,
|
|
3577
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
2952
3578
|
`exact settle (facilitator ${base2}): /verify returned HTTP ${verify.status} (transport/auth error).`
|
|
2953
3579
|
);
|
|
2954
3580
|
}
|
|
@@ -2964,13 +3590,13 @@ async function settleViaFacilitator(input) {
|
|
|
2964
3590
|
try {
|
|
2965
3591
|
settle = await post(`${base2}/settle`, body, auth);
|
|
2966
3592
|
} catch (err) {
|
|
2967
|
-
throw new (0,
|
|
3593
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
2968
3594
|
`exact settle (facilitator ${base2}): /settle request failed (${err instanceof Error ? err.message : String(err)}).`,
|
|
2969
3595
|
{ cause: err }
|
|
2970
3596
|
);
|
|
2971
3597
|
}
|
|
2972
3598
|
if (settle.status !== 200) {
|
|
2973
|
-
throw new (0,
|
|
3599
|
+
throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
|
|
2974
3600
|
`exact settle (facilitator ${base2}): /settle returned HTTP ${settle.status} (transport/auth error).`
|
|
2975
3601
|
);
|
|
2976
3602
|
}
|
|
@@ -3036,7 +3662,7 @@ function createPaymentGate(options) {
|
|
|
3036
3662
|
}
|
|
3037
3663
|
net.assertValidPayTo(payTo);
|
|
3038
3664
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
3039
|
-
const amountBase =
|
|
3665
|
+
const amountBase = _chunkPA6YD3HLcjs.parseUnits.call(void 0, a.amount, decimals);
|
|
3040
3666
|
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
3041
3667
|
if (options.exact) spec.exact = await resolveExactRail(net, asset);
|
|
3042
3668
|
return spec;
|
|
@@ -3151,7 +3777,7 @@ function createPaymentGate(options) {
|
|
|
3151
3777
|
const specs = await ready();
|
|
3152
3778
|
const nonce = genNonce();
|
|
3153
3779
|
const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
|
|
3154
|
-
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess',
|
|
3780
|
+
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _61 => _61.extensions]) };
|
|
3155
3781
|
const challenge2 = {
|
|
3156
3782
|
x402Version: 2,
|
|
3157
3783
|
resource: {
|
|
@@ -3159,7 +3785,7 @@ function createPaymentGate(options) {
|
|
|
3159
3785
|
...options.description ? { description: options.description } : {}
|
|
3160
3786
|
},
|
|
3161
3787
|
accepts: buildAccepts(specs, nonce),
|
|
3162
|
-
..._optionalChain([opts, 'optionalAccess',
|
|
3788
|
+
..._optionalChain([opts, 'optionalAccess', _62 => _62.error]) ? { error: opts.error } : {},
|
|
3163
3789
|
...Object.keys(extensions).length > 0 ? { extensions } : {}
|
|
3164
3790
|
};
|
|
3165
3791
|
return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
|
|
@@ -3182,7 +3808,7 @@ function createPaymentGate(options) {
|
|
|
3182
3808
|
if (options.onPaid) {
|
|
3183
3809
|
try {
|
|
3184
3810
|
options.onPaid(receipt);
|
|
3185
|
-
} catch (
|
|
3811
|
+
} catch (e28) {
|
|
3186
3812
|
}
|
|
3187
3813
|
}
|
|
3188
3814
|
}
|
|
@@ -3278,7 +3904,9 @@ function createPaymentGate(options) {
|
|
|
3278
3904
|
amount: accept.amount,
|
|
3279
3905
|
payTo: accept.payTo,
|
|
3280
3906
|
maxTimeoutSeconds: accept.maxTimeoutSeconds,
|
|
3281
|
-
|
|
3907
|
+
// name/version are OPTIONAL on the wire type (a foreign rail may omit them), but the
|
|
3908
|
+
// gate's OWN exact rail always read them on-chain at resolution — so they're present here.
|
|
3909
|
+
extra: { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
|
|
3282
3910
|
},
|
|
3283
3911
|
receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
|
|
3284
3912
|
payerHint: exact.payload.authorization.from
|
|
@@ -3316,7 +3944,7 @@ function requirePayment(options) {
|
|
|
3316
3944
|
try {
|
|
3317
3945
|
result = await gate.verify(_nullishCoalesce(req.headers[HEADER_SIGNATURE], () => ( req.headers[HEADER_SIGNATURE_V1])));
|
|
3318
3946
|
} catch (err) {
|
|
3319
|
-
if (err instanceof
|
|
3947
|
+
if (err instanceof _chunkPA6YD3HLcjs.SettlementError) {
|
|
3320
3948
|
res.status(502);
|
|
3321
3949
|
res.json({ x402Version: 2, error: "settlement_failed", detail: err.message });
|
|
3322
3950
|
return;
|
|
@@ -3410,4 +4038,13 @@ function normaliseHeader(value) {
|
|
|
3410
4038
|
|
|
3411
4039
|
|
|
3412
4040
|
|
|
3413
|
-
|
|
4041
|
+
|
|
4042
|
+
|
|
4043
|
+
|
|
4044
|
+
|
|
4045
|
+
|
|
4046
|
+
|
|
4047
|
+
|
|
4048
|
+
|
|
4049
|
+
|
|
4050
|
+
exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkPA6YD3HLcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkPA6YD3HLcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkPA6YD3HLcjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkPA6YD3HLcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkPA6YD3HLcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkPA6YD3HLcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkPA6YD3HLcjs.NonReplayableBodyError; exports.PIPRAIL_AGENT_GUIDE = PIPRAIL_AGENT_GUIDE; exports.PaymentDeclinedError = _chunkPA6YD3HLcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkPA6YD3HLcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkPA6YD3HLcjs.PipRailError; exports.RecipientNotReadyError = _chunkPA6YD3HLcjs.RecipientNotReadyError; exports.SettlementError = _chunkPA6YD3HLcjs.SettlementError; exports.UnknownTokenError = _chunkPA6YD3HLcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkPA6YD3HLcjs.UnsupportedNetworkError; exports.UnsupportedSchemeError = _chunkPA6YD3HLcjs.UnsupportedSchemeError; exports.WrongChainError = _chunkPA6YD3HLcjs.WrongChainError; exports.WrongFamilyError = _chunkPA6YD3HLcjs.WrongFamilyError; exports.agentGuide = agentGuide; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildExactSignatureHeader = buildExactSignatureHeader; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.classifyChallenge = classifyChallenge; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.explainDecline = explainDecline; exports.formatSpendReport = formatSpendReport; exports.getDirectoryInfo = getDirectoryInfo; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSettleResponse = parseSettleResponse; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.summarizePlan = summarizePlan; exports.toInsufficientFundsError = _chunkPA6YD3HLcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;
|