@piprail/sdk 1.14.0 → 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/CHANGELOG.md +30 -0
- package/ERRORS.md +15 -1
- package/README.md +38 -2
- package/STANDARDS.md +4 -2
- package/dist/{algorand-OIHGJN5S.cjs → algorand-EJ3S2V7E.cjs} +17 -17
- package/dist/{algorand-7EUZYL2Z.js → algorand-F3OYB534.js} +1 -1
- package/dist/{aptos-WDWZOU25.cjs → aptos-GJGIZHNI.cjs} +16 -16
- package/dist/{aptos-CDEYDDM5.js → aptos-SUXOVP7B.js} +1 -1
- package/dist/{chunk-H3A4KWLJ.js → chunk-ILPABTI2.js} +6 -0
- package/dist/{chunk-FTKVCP6K.cjs → chunk-PA6YD3HL.cjs} +17 -11
- package/dist/index.cjs +493 -115
- package/dist/index.d.cts +264 -12
- package/dist/index.d.ts +264 -12
- package/dist/index.js +403 -25
- package/dist/{near-DT6LRIKB.js → near-LM7S3WUD.js} +1 -1
- package/dist/{near-FUH3VAXT.cjs → near-ZJLZE26R.cjs} +19 -19
- package/dist/{solana-QUVXPKBZ.cjs → solana-MPPE6K24.cjs} +14 -14
- package/dist/{solana-3TRYD4QB.js → solana-WDKWWF33.js} +1 -1
- package/dist/{stellar-IK3UML6O.js → stellar-FIJPQZVW.js} +1 -1
- package/dist/{stellar-APZEBFAD.cjs → stellar-XHLLNHQP.cjs} +21 -21
- package/dist/{sui-L7BQNJWO.cjs → sui-6CVLEXLA.cjs} +17 -17
- package/dist/{sui-VSE63WQM.js → sui-B7AVN7NK.js} +1 -1
- package/dist/{ton-QHGQLJX2.js → ton-CHJ26BVA.js} +1 -1
- package/dist/{ton-5DLKKOFE.cjs → ton-RNEFN25G.cjs} +14 -14
- package/dist/{tron-2N2GA62O.js → tron-DD3JDROV.js} +1 -1
- package/dist/{tron-HHIT6WKY.cjs → tron-TKJHNFGM.cjs} +24 -24
- package/dist/{xrpl-2GZMDYW5.js → xrpl-GTUPP6SK.js} +1 -1
- package/dist/{xrpl-USEG4AHX.cjs → xrpl-XN2NBNGI.cjs} +21 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
parseUnits,
|
|
23
23
|
rejectForeignToken,
|
|
24
24
|
toInsufficientFundsError
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-ILPABTI2.js";
|
|
26
26
|
|
|
27
27
|
// src/drivers/registry.ts
|
|
28
28
|
var byFamily = /* @__PURE__ */ new Map();
|
|
@@ -1271,7 +1271,7 @@ var loaders = {
|
|
|
1271
1271
|
solana: async () => {
|
|
1272
1272
|
let mod;
|
|
1273
1273
|
try {
|
|
1274
|
-
mod = await import("./solana-
|
|
1274
|
+
mod = await import("./solana-WDKWWF33.js");
|
|
1275
1275
|
} catch (cause) {
|
|
1276
1276
|
throw new MissingDriverError(
|
|
1277
1277
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
@@ -1283,7 +1283,7 @@ var loaders = {
|
|
|
1283
1283
|
ton: async () => {
|
|
1284
1284
|
let mod;
|
|
1285
1285
|
try {
|
|
1286
|
-
mod = await import("./ton-
|
|
1286
|
+
mod = await import("./ton-CHJ26BVA.js");
|
|
1287
1287
|
} catch (cause) {
|
|
1288
1288
|
throw new MissingDriverError(
|
|
1289
1289
|
`TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
|
|
@@ -1295,7 +1295,7 @@ var loaders = {
|
|
|
1295
1295
|
stellar: async () => {
|
|
1296
1296
|
let mod;
|
|
1297
1297
|
try {
|
|
1298
|
-
mod = await import("./stellar-
|
|
1298
|
+
mod = await import("./stellar-FIJPQZVW.js");
|
|
1299
1299
|
} catch (cause) {
|
|
1300
1300
|
throw new MissingDriverError(
|
|
1301
1301
|
`Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
|
|
@@ -1307,7 +1307,7 @@ var loaders = {
|
|
|
1307
1307
|
xrpl: async () => {
|
|
1308
1308
|
let mod;
|
|
1309
1309
|
try {
|
|
1310
|
-
mod = await import("./xrpl-
|
|
1310
|
+
mod = await import("./xrpl-GTUPP6SK.js");
|
|
1311
1311
|
} catch (cause) {
|
|
1312
1312
|
throw new MissingDriverError(
|
|
1313
1313
|
`XRPL selected, but its package isn't installed. Run: npm install xrpl`,
|
|
@@ -1319,7 +1319,7 @@ var loaders = {
|
|
|
1319
1319
|
tron: async () => {
|
|
1320
1320
|
let mod;
|
|
1321
1321
|
try {
|
|
1322
|
-
mod = await import("./tron-
|
|
1322
|
+
mod = await import("./tron-DD3JDROV.js");
|
|
1323
1323
|
} catch (cause) {
|
|
1324
1324
|
throw new MissingDriverError(
|
|
1325
1325
|
`Tron selected, but its package isn't installed. Run: npm install tronweb`,
|
|
@@ -1331,7 +1331,7 @@ var loaders = {
|
|
|
1331
1331
|
sui: async () => {
|
|
1332
1332
|
let mod;
|
|
1333
1333
|
try {
|
|
1334
|
-
mod = await import("./sui-
|
|
1334
|
+
mod = await import("./sui-B7AVN7NK.js");
|
|
1335
1335
|
} catch (cause) {
|
|
1336
1336
|
throw new MissingDriverError(
|
|
1337
1337
|
`Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
|
|
@@ -1343,7 +1343,7 @@ var loaders = {
|
|
|
1343
1343
|
near: async () => {
|
|
1344
1344
|
let mod;
|
|
1345
1345
|
try {
|
|
1346
|
-
mod = await import("./near-
|
|
1346
|
+
mod = await import("./near-LM7S3WUD.js");
|
|
1347
1347
|
} catch (cause) {
|
|
1348
1348
|
throw new MissingDriverError(
|
|
1349
1349
|
`NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
|
|
@@ -1355,7 +1355,7 @@ var loaders = {
|
|
|
1355
1355
|
aptos: async () => {
|
|
1356
1356
|
let mod;
|
|
1357
1357
|
try {
|
|
1358
|
-
mod = await import("./aptos-
|
|
1358
|
+
mod = await import("./aptos-SUXOVP7B.js");
|
|
1359
1359
|
} catch (cause) {
|
|
1360
1360
|
throw new MissingDriverError(
|
|
1361
1361
|
`Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
|
|
@@ -1367,7 +1367,7 @@ var loaders = {
|
|
|
1367
1367
|
algorand: async () => {
|
|
1368
1368
|
let mod;
|
|
1369
1369
|
try {
|
|
1370
|
-
mod = await import("./algorand-
|
|
1370
|
+
mod = await import("./algorand-F3OYB534.js");
|
|
1371
1371
|
} catch (cause) {
|
|
1372
1372
|
throw new MissingDriverError(
|
|
1373
1373
|
`Algorand selected, but its package isn't installed. Run: npm install algosdk`,
|
|
@@ -1842,7 +1842,18 @@ function encodeBase642(str) {
|
|
|
1842
1842
|
|
|
1843
1843
|
// src/policy.ts
|
|
1844
1844
|
var ALLOW = { allowed: true };
|
|
1845
|
-
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
|
+
}
|
|
1846
1857
|
function hostMatches(host, pattern) {
|
|
1847
1858
|
if (pattern.startsWith("*.")) {
|
|
1848
1859
|
const suffix = pattern.slice(1);
|
|
@@ -1858,18 +1869,26 @@ function chainMatches(intent, allowed) {
|
|
|
1858
1869
|
const id = "id" in allowed ? allowed.id : void 0;
|
|
1859
1870
|
return id !== void 0 && intent.network === `eip155:${id}`;
|
|
1860
1871
|
}
|
|
1861
|
-
function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
1872
|
+
function evaluatePolicy(intent, policy, spentForAssetBase, ctx) {
|
|
1862
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
|
+
}
|
|
1863
1880
|
if (policy.chains && !policy.chains.some((c) => chainMatches(intent, c))) {
|
|
1864
1881
|
return deny(
|
|
1882
|
+
"CHAIN",
|
|
1865
1883
|
`chain ${intent.network} is not in the allowed set (policy.chains).`
|
|
1866
1884
|
);
|
|
1867
1885
|
}
|
|
1868
1886
|
if (policy.hosts && !policy.hosts.some((h) => hostMatches(intent.host, h))) {
|
|
1869
|
-
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).`);
|
|
1870
1888
|
}
|
|
1871
1889
|
if (!intent.recognized && !policy.allowUnknownTokens) {
|
|
1872
1890
|
return deny(
|
|
1891
|
+
"UNKNOWN_TOKEN",
|
|
1873
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.`
|
|
1874
1893
|
);
|
|
1875
1894
|
}
|
|
@@ -1882,6 +1901,7 @@ function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
|
1882
1901
|
});
|
|
1883
1902
|
if (!matches) {
|
|
1884
1903
|
return deny(
|
|
1904
|
+
"TOKEN",
|
|
1885
1905
|
`token ${intent.symbol ?? intent.asset} is not in the allowed set (policy.tokens).`
|
|
1886
1906
|
);
|
|
1887
1907
|
}
|
|
@@ -1890,6 +1910,7 @@ function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
|
1890
1910
|
const cap = floorUnits(policy.maxAmount, intent.decimals);
|
|
1891
1911
|
if (intent.amountBase > cap) {
|
|
1892
1912
|
return deny(
|
|
1913
|
+
"MAX_AMOUNT",
|
|
1893
1914
|
`payment of ${intent.amountBase} base units exceeds policy.maxAmount ` + `(${policy.maxAmount} ${intent.symbol ?? ""}).`.trimEnd()
|
|
1894
1915
|
);
|
|
1895
1916
|
}
|
|
@@ -1898,10 +1919,20 @@ function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
|
1898
1919
|
const cap = floorUnits(policy.maxTotal, intent.decimals);
|
|
1899
1920
|
if (spentForAssetBase + intent.amountBase > cap) {
|
|
1900
1921
|
return deny(
|
|
1922
|
+
"MAX_TOTAL",
|
|
1901
1923
|
`this payment would push spend on ${intent.symbol ?? intent.asset} past policy.maxTotal (${policy.maxTotal}); already spent ${spentForAssetBase} base units.`
|
|
1902
1924
|
);
|
|
1903
1925
|
}
|
|
1904
1926
|
}
|
|
1927
|
+
if (ctx && policy.windowTotal !== void 0 && policy.windowSeconds !== void 0) {
|
|
1928
|
+
const cap = floorUnits(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 ${intent.symbol ?? intent.asset}.`
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1905
1936
|
return ALLOW;
|
|
1906
1937
|
}
|
|
1907
1938
|
|
|
@@ -1910,6 +1941,12 @@ var keyFor = (network, asset) => `${network}|${asset}`;
|
|
|
1910
1941
|
var SpendLedger = class {
|
|
1911
1942
|
records = [];
|
|
1912
1943
|
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
|
+
sessionStart = Date.now();
|
|
1913
1950
|
/** Record a settled payment. `decimals` is the TRUE token decimals (for the
|
|
1914
1951
|
* per-asset running total used by maxTotal + the formatted summary). */
|
|
1915
1952
|
record(r, decimals) {
|
|
@@ -1935,6 +1972,38 @@ var SpendLedger = class {
|
|
|
1935
1972
|
totalFor(network, asset) {
|
|
1936
1973
|
return this.buckets.get(keyFor(network, asset))?.total ?? 0n;
|
|
1937
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
|
+
}));
|
|
2006
|
+
}
|
|
1938
2007
|
/** An immutable snapshot of all spend so far. */
|
|
1939
2008
|
summary() {
|
|
1940
2009
|
return {
|
|
@@ -1977,6 +2046,37 @@ var PipRailClient = class {
|
|
|
1977
2046
|
this.maxRetries = Math.max(1, opts.maxPaymentRetries ?? 3);
|
|
1978
2047
|
this.retryTimeoutMs = opts.retryTimeoutMs ?? 3e4;
|
|
1979
2048
|
this.onEvent = 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
|
+
}
|
|
1980
2080
|
}
|
|
1981
2081
|
/** Emit an observability event, never letting a throwing handler break the
|
|
1982
2082
|
* payment flow (mirrors the server gate's `onPaid` isolation). */
|
|
@@ -2074,6 +2174,64 @@ var PipRailClient = class {
|
|
|
2074
2174
|
spent() {
|
|
2075
2175
|
return this.ledger.summary();
|
|
2076
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: view?.expiresAt != null ? new Date(view.expiresAt).toISOString() : null,
|
|
2190
|
+
secondsRemaining: view?.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 = this.opts.policy?.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 = floorUnits(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: formatUnits(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
|
+
}
|
|
2077
2235
|
/**
|
|
2078
2236
|
* Plan a payment for a gated URL — WITHOUT paying. The read-only completion of
|
|
2079
2237
|
* the `quote()` → `estimateCost()` → **`planPayment()`** trio: it surveys every
|
|
@@ -2369,6 +2527,7 @@ var PipRailClient = class {
|
|
|
2369
2527
|
* net/wallet. Shared by `planPayment` (read-only) and `fetch`'s autoRoute. */
|
|
2370
2528
|
async planFromChallenge(net, wallet, challenge, url, schemes) {
|
|
2371
2529
|
const chainLabel = typeof this.opts.chain === "string" ? this.opts.chain : net.network;
|
|
2530
|
+
const session = this.sessionView();
|
|
2372
2531
|
const candidates = this.gatherCandidates(net, challenge, schemes);
|
|
2373
2532
|
if (candidates.length === 0) {
|
|
2374
2533
|
const offered = [...new Set(challenge.accepts.map((a) => a.network))].join(", ") || "none";
|
|
@@ -2379,7 +2538,8 @@ var PipRailClient = class {
|
|
|
2379
2538
|
payable: false,
|
|
2380
2539
|
best: null,
|
|
2381
2540
|
options: [],
|
|
2382
|
-
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 } : {}
|
|
2383
2543
|
};
|
|
2384
2544
|
}
|
|
2385
2545
|
const analysed = await Promise.all(
|
|
@@ -2397,7 +2557,8 @@ var PipRailClient = class {
|
|
|
2397
2557
|
payable: best !== null,
|
|
2398
2558
|
best,
|
|
2399
2559
|
options,
|
|
2400
|
-
fundingHint: best ? null : buildFundingHint(options, chainLabel)
|
|
2560
|
+
fundingHint: best ? null : buildFundingHint(options, chainLabel),
|
|
2561
|
+
...session ? { session } : {}
|
|
2401
2562
|
};
|
|
2402
2563
|
}
|
|
2403
2564
|
/** Analyse ONE rail against the wallet's holdings — quote (existing) + gas
|
|
@@ -2414,7 +2575,11 @@ var PipRailClient = class {
|
|
|
2414
2575
|
const blockers = [];
|
|
2415
2576
|
const warnings = [];
|
|
2416
2577
|
const shortfall = {};
|
|
2417
|
-
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
|
+
}
|
|
2418
2583
|
if (quote.symbolMismatch) warnings.push("SYMBOL_MISMATCH");
|
|
2419
2584
|
if (!isExact && cost.basis === "heuristic") warnings.push("GAS_HEURISTIC");
|
|
2420
2585
|
const tokenKnown = bal.token != null;
|
|
@@ -2498,10 +2663,25 @@ var PipRailClient = class {
|
|
|
2498
2663
|
symbol,
|
|
2499
2664
|
recognized: described != null
|
|
2500
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;
|
|
2501
2680
|
const decision = evaluatePolicy(
|
|
2502
2681
|
intent,
|
|
2503
2682
|
this.opts.policy,
|
|
2504
|
-
this.ledger.totalFor(accept.network, accept.asset)
|
|
2683
|
+
this.ledger.totalFor(accept.network, accept.asset),
|
|
2684
|
+
ctx
|
|
2505
2685
|
);
|
|
2506
2686
|
const serverSymbol = accept.extra.symbol;
|
|
2507
2687
|
const symbolMismatch = intent.recognized && !!serverSymbol && !!symbol && serverSymbol.toUpperCase() !== symbol.toUpperCase();
|
|
@@ -2520,15 +2700,19 @@ var PipRailClient = class {
|
|
|
2520
2700
|
recognized: intent.recognized,
|
|
2521
2701
|
symbolMismatch,
|
|
2522
2702
|
withinPolicy: decision.allowed,
|
|
2523
|
-
...decision.reason ? { policyReason: decision.reason } : {}
|
|
2703
|
+
...decision.reason ? { policyReason: decision.reason } : {},
|
|
2704
|
+
...decision.code ? { policyCode: decision.code } : {}
|
|
2524
2705
|
};
|
|
2525
2706
|
}
|
|
2526
2707
|
/** Enforce the spend policy and the onBeforePay hook — both refuse by
|
|
2527
|
-
* 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. */
|
|
2528
2711
|
async authorize(quote) {
|
|
2529
2712
|
if (!quote.withinPolicy) {
|
|
2530
2713
|
throw new PaymentDeclinedError(
|
|
2531
|
-
`Payment refused by policy: ${quote.policyReason ?? "not allowed"}
|
|
2714
|
+
`Payment refused by policy: ${quote.policyReason ?? "not allowed"}`,
|
|
2715
|
+
{ reasonCode: reasonCodeForPolicy(quote.policyCode) }
|
|
2532
2716
|
);
|
|
2533
2717
|
}
|
|
2534
2718
|
const hook = this.opts.onBeforePay;
|
|
@@ -2538,12 +2722,14 @@ var PipRailClient = class {
|
|
|
2538
2722
|
approved = await hook(quote);
|
|
2539
2723
|
} catch (err) {
|
|
2540
2724
|
throw new PaymentDeclinedError("onBeforePay threw \u2014 refusing to pay.", {
|
|
2541
|
-
cause: err
|
|
2725
|
+
cause: err,
|
|
2726
|
+
reasonCode: "APPROVAL"
|
|
2542
2727
|
});
|
|
2543
2728
|
}
|
|
2544
2729
|
if (!approved) {
|
|
2545
2730
|
throw new PaymentDeclinedError(
|
|
2546
|
-
`onBeforePay declined ${quote.amountFormatted} ${quote.symbol ?? ""}`.trimEnd() + ` on ${quote.network}
|
|
2731
|
+
`onBeforePay declined ${quote.amountFormatted} ${quote.symbol ?? ""}`.trimEnd() + ` on ${quote.network}.`,
|
|
2732
|
+
{ reasonCode: "APPROVAL" }
|
|
2547
2733
|
);
|
|
2548
2734
|
}
|
|
2549
2735
|
}
|
|
@@ -2771,6 +2957,9 @@ function buildFundingHint(options, chainLabel) {
|
|
|
2771
2957
|
if (target.blockers.includes("RECIPIENT_NOT_READY")) {
|
|
2772
2958
|
return `Recipient ${shortAddr(target.accept.payTo)} can't receive on ${chainLabel} yet \u2014 ${target.recipient.fix ?? "recipient not ready"}.`;
|
|
2773
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
|
+
}
|
|
2774
2963
|
if (target.blockers.includes("OUTSIDE_POLICY")) {
|
|
2775
2964
|
return `Refused by spend policy: ${target.quote.policyReason ?? "not allowed"}.`;
|
|
2776
2965
|
}
|
|
@@ -2808,6 +2997,20 @@ function railOnNetwork(rail, matches) {
|
|
|
2808
2997
|
const n = normalizeNetwork(rail.network);
|
|
2809
2998
|
return !n.includes(":") || matches(n);
|
|
2810
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
|
+
}
|
|
2811
3014
|
function hostOf2(url) {
|
|
2812
3015
|
try {
|
|
2813
3016
|
return new URL(url).hostname;
|
|
@@ -2855,7 +3058,111 @@ async function readInvalidReason(response) {
|
|
|
2855
3058
|
return null;
|
|
2856
3059
|
}
|
|
2857
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} ${q.symbol ?? q.asset} on ${plan.best.accept.network} (gas ~${c.feeFormatted} ${c.feeSymbol}).${note}`;
|
|
3070
|
+
}
|
|
3071
|
+
return `NOT payable: ${plan.fundingHint ?? `no settleable rail on ${plan.network}`}`;
|
|
3072
|
+
}
|
|
3073
|
+
function explainDecline(err) {
|
|
3074
|
+
if (!(err instanceof 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} ${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
|
+
|
|
2858
3164
|
// src/agent.ts
|
|
3165
|
+
var OPEN_OBJECT = { type: "object", additionalProperties: true };
|
|
2859
3166
|
async function readBody(res) {
|
|
2860
3167
|
const text = await res.text();
|
|
2861
3168
|
if (!text) return null;
|
|
@@ -2928,6 +3235,7 @@ function paymentTools(client) {
|
|
|
2928
3235
|
required: ["url"],
|
|
2929
3236
|
additionalProperties: false
|
|
2930
3237
|
},
|
|
3238
|
+
outputSchema: OPEN_OBJECT,
|
|
2931
3239
|
invoke: async (args) => {
|
|
2932
3240
|
const quote = await client.quote(String(args.url));
|
|
2933
3241
|
return quote ? { gated: true, ...quote } : { gated: false, url: String(args.url) };
|
|
@@ -2951,6 +3259,7 @@ function paymentTools(client) {
|
|
|
2951
3259
|
required: ["url"],
|
|
2952
3260
|
additionalProperties: false
|
|
2953
3261
|
},
|
|
3262
|
+
outputSchema: OPEN_OBJECT,
|
|
2954
3263
|
invoke: async (args) => {
|
|
2955
3264
|
const plan = await client.planPayment(String(args.url));
|
|
2956
3265
|
if (plan == null) return { gated: false, url: String(args.url) };
|
|
@@ -2959,6 +3268,8 @@ function paymentTools(client) {
|
|
|
2959
3268
|
payable: plan.payable,
|
|
2960
3269
|
status: plan.status,
|
|
2961
3270
|
fundingHint: plan.fundingHint,
|
|
3271
|
+
// One model-readable line distilling the whole plan.
|
|
3272
|
+
summary: summarizePlan(plan),
|
|
2962
3273
|
best: plan.best ? {
|
|
2963
3274
|
network: plan.best.accept.network,
|
|
2964
3275
|
symbol: plan.best.quote.symbol,
|
|
@@ -2974,7 +3285,9 @@ function paymentTools(client) {
|
|
|
2974
3285
|
blockers: o.blockers,
|
|
2975
3286
|
warnings: o.warnings,
|
|
2976
3287
|
recipientReady: o.recipient.ready
|
|
2977
|
-
}))
|
|
3288
|
+
})),
|
|
3289
|
+
// The session's time leash, present only when a time policy is configured.
|
|
3290
|
+
...plan.session ? { session: plan.session } : {}
|
|
2978
3291
|
};
|
|
2979
3292
|
}
|
|
2980
3293
|
},
|
|
@@ -3032,8 +3345,20 @@ function paymentTools(client) {
|
|
|
3032
3345
|
receipt: parseReceipt(res)
|
|
3033
3346
|
};
|
|
3034
3347
|
} catch (err) {
|
|
3035
|
-
if (err instanceof
|
|
3036
|
-
|
|
3348
|
+
if (err instanceof PipRailError) {
|
|
3349
|
+
const out = {
|
|
3350
|
+
ok: false,
|
|
3351
|
+
code: err.code,
|
|
3352
|
+
reason: err.message,
|
|
3353
|
+
explain: explainDecline(err)
|
|
3354
|
+
};
|
|
3355
|
+
if (err instanceof 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;
|
|
3037
3362
|
}
|
|
3038
3363
|
throw err;
|
|
3039
3364
|
}
|
|
@@ -3071,10 +3396,57 @@ function paymentTools(client) {
|
|
|
3071
3396
|
const outcomes = await client.register(String(args.url), opts);
|
|
3072
3397
|
return { outcomes };
|
|
3073
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 })
|
|
3074
3433
|
}
|
|
3075
3434
|
];
|
|
3076
3435
|
}
|
|
3077
3436
|
|
|
3437
|
+
// src/classify.ts
|
|
3438
|
+
function classifyChallenge(challenge, opts) {
|
|
3439
|
+
const accepts = 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
|
+
|
|
3078
3450
|
// src/discovery.ts
|
|
3079
3451
|
var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
|
|
3080
3452
|
function buildBazaarExtension(descriptor = {}) {
|
|
@@ -3620,6 +3992,7 @@ export {
|
|
|
3620
3992
|
MissingDriverError,
|
|
3621
3993
|
NoCompatibleAcceptError,
|
|
3622
3994
|
NonReplayableBodyError,
|
|
3995
|
+
PIPRAIL_AGENT_GUIDE,
|
|
3623
3996
|
PaymentDeclinedError,
|
|
3624
3997
|
PaymentTimeoutError,
|
|
3625
3998
|
PipRailClient,
|
|
@@ -3631,6 +4004,7 @@ export {
|
|
|
3631
4004
|
UnsupportedSchemeError,
|
|
3632
4005
|
WrongChainError,
|
|
3633
4006
|
WrongFamilyError,
|
|
4007
|
+
agentGuide,
|
|
3634
4008
|
buildBazaarExtension,
|
|
3635
4009
|
buildChallengeHeader,
|
|
3636
4010
|
buildExactAuthorization,
|
|
@@ -3642,11 +4016,14 @@ export {
|
|
|
3642
4016
|
buildX402DnsTxt,
|
|
3643
4017
|
chainIdForExactNetwork,
|
|
3644
4018
|
claim402IndexDomain,
|
|
4019
|
+
classifyChallenge,
|
|
3645
4020
|
createPaymentGate,
|
|
3646
4021
|
decorateOutcome,
|
|
3647
4022
|
eip3009Abi,
|
|
3648
4023
|
encodeXPaymentHeader,
|
|
3649
4024
|
evaluatePolicy,
|
|
4025
|
+
explainDecline,
|
|
4026
|
+
formatSpendReport,
|
|
3650
4027
|
getDirectoryInfo,
|
|
3651
4028
|
normalizeNetwork,
|
|
3652
4029
|
parseChallenge,
|
|
@@ -3666,6 +4043,7 @@ export {
|
|
|
3666
4043
|
resolveChain,
|
|
3667
4044
|
searchOpenIndexes,
|
|
3668
4045
|
settleViaFacilitator,
|
|
4046
|
+
summarizePlan,
|
|
3669
4047
|
toInsufficientFundsError,
|
|
3670
4048
|
toInvalidBody,
|
|
3671
4049
|
verify402IndexDomain
|