@piprail/sdk 3.0.0 → 3.1.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 +105 -0
- package/dist/{algorand-W776EEM2.cjs → algorand-4FTTEV7X.cjs} +7 -1
- package/dist/{algorand-HL57PQHE.js → algorand-KJ5XHSMT.js} +7 -1
- package/dist/index.cjs +389 -230
- package/dist/index.d.cts +54 -8
- package/dist/index.d.ts +54 -8
- package/dist/index.js +199 -40
- package/dist/{ledger-Crc1bZox.d.cts → ledger-DkHUORUe.d.cts} +7 -0
- package/dist/{ledger-Crc1bZox.d.ts → ledger-DkHUORUe.d.ts} +7 -0
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/{solana-EBV6PUCU.cjs → solana-TIEJV742.cjs} +7 -1
- package/dist/{solana-O6Q6QILH.js → solana-WRXL54MR.js} +7 -1
- package/dist/{stellar-5C7FQLPS.cjs → stellar-5GMZJBTA.cjs} +9 -1
- package/dist/{stellar-YF5LOJEM.js → stellar-YQLOIFDD.js} +9 -1
- package/dist/{xrpl-GOVHMYYK.js → xrpl-5K444RGX.js} +16 -1
- package/dist/{xrpl-YS3IXPLV.cjs → xrpl-G2FKFXRI.cjs} +16 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -401,6 +401,11 @@ function resolveChain(input, rpcUrlOverride) {
|
|
|
401
401
|
}
|
|
402
402
|
return { chain: input, chainId: input.id, rpcUrl: rpcUrl2, tokens: knownTokensForId(input.id) };
|
|
403
403
|
}
|
|
404
|
+
if (!Number.isSafeInteger(input.id) || input.id <= 0) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`resolveChain: chain id must be a positive safe integer (EIP-155), got ${String(input.id)}.`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
404
409
|
const rpcUrl = rpcUrlOverride ?? input.rpcUrl;
|
|
405
410
|
if (!rpcUrl) {
|
|
406
411
|
throw new Error(`resolveChain: chain ${input.id} needs an rpcUrl.`);
|
|
@@ -574,8 +579,25 @@ async function quoteEvmSwap(p) {
|
|
|
574
579
|
} catch {
|
|
575
580
|
return null;
|
|
576
581
|
}
|
|
577
|
-
const
|
|
578
|
-
|
|
582
|
+
const MAX_REFINEMENTS = 3;
|
|
583
|
+
let real = await route(needIn);
|
|
584
|
+
let summary = real?.data?.routeSummary;
|
|
585
|
+
for (let attempt = 0; attempt < MAX_REFINEMENTS; attempt++) {
|
|
586
|
+
if (!summary?.amountOut) break;
|
|
587
|
+
let out;
|
|
588
|
+
try {
|
|
589
|
+
out = BigInt(summary.amountOut);
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
if (out >= p.wantAmount) break;
|
|
594
|
+
if (out <= 0n) return null;
|
|
595
|
+
const next = applySlippage((needIn * p.wantAmount + out - 1n) / out, p.slippageBps);
|
|
596
|
+
if (next <= needIn) break;
|
|
597
|
+
needIn = next;
|
|
598
|
+
real = await route(needIn);
|
|
599
|
+
summary = real?.data?.routeSummary;
|
|
600
|
+
}
|
|
579
601
|
if (!summary?.amountOut || !real?.data?.routerAddress) return null;
|
|
580
602
|
try {
|
|
581
603
|
if (BigInt(summary.amountOut) < p.wantAmount) return null;
|
|
@@ -2440,7 +2462,7 @@ var loaders = {
|
|
|
2440
2462
|
solana: async () => {
|
|
2441
2463
|
let mod;
|
|
2442
2464
|
try {
|
|
2443
|
-
mod = await import("./solana-
|
|
2465
|
+
mod = await import("./solana-WRXL54MR.js");
|
|
2444
2466
|
} catch (cause) {
|
|
2445
2467
|
throw new MissingDriverError(
|
|
2446
2468
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
@@ -2464,7 +2486,7 @@ var loaders = {
|
|
|
2464
2486
|
stellar: async () => {
|
|
2465
2487
|
let mod;
|
|
2466
2488
|
try {
|
|
2467
|
-
mod = await import("./stellar-
|
|
2489
|
+
mod = await import("./stellar-YQLOIFDD.js");
|
|
2468
2490
|
} catch (cause) {
|
|
2469
2491
|
throw new MissingDriverError(
|
|
2470
2492
|
`Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
|
|
@@ -2476,7 +2498,7 @@ var loaders = {
|
|
|
2476
2498
|
xrpl: async () => {
|
|
2477
2499
|
let mod;
|
|
2478
2500
|
try {
|
|
2479
|
-
mod = await import("./xrpl-
|
|
2501
|
+
mod = await import("./xrpl-5K444RGX.js");
|
|
2480
2502
|
} catch (cause) {
|
|
2481
2503
|
throw new MissingDriverError(
|
|
2482
2504
|
`XRPL selected, but its package isn't installed. Run: npm install xrpl`,
|
|
@@ -2536,7 +2558,7 @@ var loaders = {
|
|
|
2536
2558
|
algorand: async () => {
|
|
2537
2559
|
let mod;
|
|
2538
2560
|
try {
|
|
2539
|
-
mod = await import("./algorand-
|
|
2561
|
+
mod = await import("./algorand-KJ5XHSMT.js");
|
|
2540
2562
|
} catch (cause) {
|
|
2541
2563
|
throw new MissingDriverError(
|
|
2542
2564
|
`Algorand selected, but its package isn't installed. Run: npm install algosdk`,
|
|
@@ -2846,8 +2868,39 @@ var SpendLedger = class {
|
|
|
2846
2868
|
}
|
|
2847
2869
|
}
|
|
2848
2870
|
/** Running total (base units) already spent on this (network, asset). */
|
|
2871
|
+
/*
|
|
2872
|
+
* ── IN-FLIGHT RESERVATIONS ─────────────────────────────────────────────────────────
|
|
2873
|
+
*
|
|
2874
|
+
* A cap is read when a quote is priced and written when the payment settles, and a whole
|
|
2875
|
+
* network round trip sits between the two. Without a reservation, N concurrent payments all
|
|
2876
|
+
* price against the same "spent so far", all pass, and all settle: an agent with a 2.50 cap
|
|
2877
|
+
* spends 4.00 and every individual check was correct. It is the same read-await-write shape
|
|
2878
|
+
* that let one proof be redeemed N times, on the other side of the wire.
|
|
2879
|
+
*
|
|
2880
|
+
* A reservation is taken SYNCHRONOUSLY by the client before it pays, counts toward every
|
|
2881
|
+
* total below while it is outstanding, and is released the moment the payment either settles
|
|
2882
|
+
* (the real record replaces it) or fails (so a refused payment never consumes the leash).
|
|
2883
|
+
*/
|
|
2884
|
+
pending = /* @__PURE__ */ new Map();
|
|
2885
|
+
pendingSeq = 0;
|
|
2886
|
+
/** Reserve budget for a payment about to be attempted. Returns the token to settle it with. */
|
|
2887
|
+
reserve(network, asset, amountBase, decimals, denom) {
|
|
2888
|
+
const token = `r${++this.pendingSeq}`;
|
|
2889
|
+
const scaled = denom ? scaleToDenom(amountBase, decimals) ?? 0n : 0n;
|
|
2890
|
+
this.pending.set(token, { network, asset, amountBase, denom: denom?.toUpperCase(), scaled, at: Date.now() });
|
|
2891
|
+
return token;
|
|
2892
|
+
}
|
|
2893
|
+
/** Drop a reservation: the payment settled (its real record now counts) or it failed. */
|
|
2894
|
+
release(token) {
|
|
2895
|
+
if (token) this.pending.delete(token);
|
|
2896
|
+
}
|
|
2897
|
+
pendingFor(network, asset) {
|
|
2898
|
+
let sum = 0n;
|
|
2899
|
+
for (const p of this.pending.values()) if (p.network === network && p.asset === asset) sum += p.amountBase;
|
|
2900
|
+
return sum;
|
|
2901
|
+
}
|
|
2849
2902
|
totalFor(network, asset) {
|
|
2850
|
-
return this.buckets.get(keyFor(network, asset))?.total ?? 0n;
|
|
2903
|
+
return (this.buckets.get(keyFor(network, asset))?.total ?? 0n) + this.pendingFor(network, asset);
|
|
2851
2904
|
}
|
|
2852
2905
|
/**
|
|
2853
2906
|
* Running grand total for a DENOMINATION, scaled to {@link DENOM_PRECISION} (so
|
|
@@ -2856,11 +2909,14 @@ var SpendLedger = class {
|
|
|
2856
2909
|
* `0n` for a denomination never spent on. Case-insensitive.
|
|
2857
2910
|
*/
|
|
2858
2911
|
totalForDenom(denom) {
|
|
2859
|
-
|
|
2912
|
+
const key = denom.toUpperCase();
|
|
2913
|
+
let pending = 0n;
|
|
2914
|
+
for (const p of this.pending.values()) if (p.denom === key) pending += p.scaled;
|
|
2915
|
+
return (this.denomTotals.get(key) ?? 0n) + pending;
|
|
2860
2916
|
}
|
|
2861
2917
|
/** Total number of settled payments (across every chain + token). Powers `maxPayments`. */
|
|
2862
2918
|
count() {
|
|
2863
|
-
return this.records.length;
|
|
2919
|
+
return this.records.length + this.pending.size;
|
|
2864
2920
|
}
|
|
2865
2921
|
/** Mark a `warnAtFraction` threshold key as fired; returns `true` the FIRST time (so the
|
|
2866
2922
|
* caller emits the `budget-threshold` event once) and `false` thereafter. Shared across
|
|
@@ -2878,6 +2934,7 @@ var SpendLedger = class {
|
|
|
2878
2934
|
*/
|
|
2879
2935
|
countSince(sinceMs) {
|
|
2880
2936
|
let n = 0;
|
|
2937
|
+
for (const p of this.pending.values()) if (p.at >= sinceMs) n += 1;
|
|
2881
2938
|
for (const r of this.records) {
|
|
2882
2939
|
const t = Date.parse(r.at);
|
|
2883
2940
|
if (Number.isNaN(t) || t >= sinceMs) n += 1;
|
|
@@ -2893,6 +2950,9 @@ var SpendLedger = class {
|
|
|
2893
2950
|
*/
|
|
2894
2951
|
totalSince(network, asset, sinceMs) {
|
|
2895
2952
|
let sum = 0n;
|
|
2953
|
+
for (const p of this.pending.values()) {
|
|
2954
|
+
if (p.network === network && p.asset === asset && p.at >= sinceMs) sum += p.amountBase;
|
|
2955
|
+
}
|
|
2896
2956
|
for (const r of this.records) {
|
|
2897
2957
|
if (r.network !== network || r.asset !== asset) continue;
|
|
2898
2958
|
const t = Date.parse(r.at);
|
|
@@ -3303,6 +3363,32 @@ var PipRailClient = class {
|
|
|
3303
3363
|
this.assertPolicyTimeOptions(opts.policy);
|
|
3304
3364
|
this.assertPolicySpendControls(opts.policy);
|
|
3305
3365
|
this.assertModeIsHonest(opts);
|
|
3366
|
+
this.sealAuthority();
|
|
3367
|
+
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Pin the three authority accessors to THIS instance, non-writable and non-configurable.
|
|
3370
|
+
*
|
|
3371
|
+
* `paymentTools()` decides which tools a model is handed by calling `canAgentSell()` /
|
|
3372
|
+
* `canAgentSwap()`, which read `mode()`. Those were plain prototype methods, so any code
|
|
3373
|
+
* holding the client could reassign one — `client.mode = () => 'sovereign'` turned a
|
|
3374
|
+
* budgeted client's eight tools into sovereign's fourteen.
|
|
3375
|
+
*
|
|
3376
|
+
* A MODEL could never do that (it sends JSON tool arguments; it does not hold the object),
|
|
3377
|
+
* so this is not a path a model can walk. It is defence in depth for the case where a
|
|
3378
|
+
* client passes through code that is not the operator's own: an agent framework, a plugin,
|
|
3379
|
+
* some middleware that wraps or proxies objects. Authority is set once, by whoever
|
|
3380
|
+
* provisioned the key, and nothing downstream gets to revise it.
|
|
3381
|
+
*/
|
|
3382
|
+
sealAuthority() {
|
|
3383
|
+
const mode = this.opts.mode ?? DEFAULT_AGENT_MODE;
|
|
3384
|
+
const sovereign = mode === "sovereign";
|
|
3385
|
+
for (const [name, fn] of [
|
|
3386
|
+
["mode", () => mode],
|
|
3387
|
+
["canAgentSell", () => sovereign],
|
|
3388
|
+
["canAgentSwap", () => sovereign]
|
|
3389
|
+
]) {
|
|
3390
|
+
Object.defineProperty(this, name, { value: fn, writable: false, configurable: false, enumerable: false });
|
|
3391
|
+
}
|
|
3306
3392
|
}
|
|
3307
3393
|
/**
|
|
3308
3394
|
* Fail LOUDLY at construction on a malformed amount cap — a security boundary
|
|
@@ -4245,22 +4331,35 @@ var PipRailClient = class {
|
|
|
4245
4331
|
quote = plan.best.quote;
|
|
4246
4332
|
}
|
|
4247
4333
|
this.safeEmit({ kind: "payment-required", challenge, accept });
|
|
4248
|
-
await this.authorize(quote);
|
|
4334
|
+
const reservation = await this.authorize(quote);
|
|
4249
4335
|
if (accept.scheme === "upto") {
|
|
4250
|
-
|
|
4336
|
+
try {
|
|
4337
|
+
return await this.payUptoRail(net, wallet, accept, url, init, quote, reservation);
|
|
4338
|
+
} finally {
|
|
4339
|
+
this.ledger.release(reservation);
|
|
4340
|
+
}
|
|
4251
4341
|
}
|
|
4252
4342
|
if (accept.scheme === "exact") {
|
|
4253
|
-
|
|
4343
|
+
try {
|
|
4344
|
+
return await this.payExactRail(net, wallet, accept, url, init, quote, challenge.x402Version, reservation);
|
|
4345
|
+
} finally {
|
|
4346
|
+
this.ledger.release(reservation);
|
|
4347
|
+
}
|
|
4254
4348
|
}
|
|
4255
4349
|
if (accept.scheme !== "onchain-proof") {
|
|
4350
|
+
this.ledger.release(reservation);
|
|
4256
4351
|
throw new UnsupportedSchemeError(
|
|
4257
4352
|
`internal: unrouted accept scheme '${accept.scheme}' reached the onchain-proof pay path.`
|
|
4258
4353
|
);
|
|
4259
4354
|
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4355
|
+
try {
|
|
4356
|
+
const { ref, confirmed } = await this.payAndConfirm(net, wallet, accept);
|
|
4357
|
+
const response = await this.retryWithProof(url, init, accept, ref, confirmed);
|
|
4358
|
+
this.recordSpend(quote, ref, void 0, reservation);
|
|
4359
|
+
return response;
|
|
4360
|
+
} finally {
|
|
4361
|
+
this.ledger.release(reservation);
|
|
4362
|
+
}
|
|
4264
4363
|
}
|
|
4265
4364
|
/* ------------------------- internals ------------------------- */
|
|
4266
4365
|
/**
|
|
@@ -4449,9 +4548,11 @@ var PipRailClient = class {
|
|
|
4449
4548
|
shortfall.token = formatUnits(amount - bal.token, quote.decimals);
|
|
4450
4549
|
}
|
|
4451
4550
|
} else if (isNative) {
|
|
4452
|
-
|
|
4551
|
+
const spendable = bal.token ?? bal.native;
|
|
4552
|
+
const spendableKnown = spendable != null;
|
|
4553
|
+
if (spendableKnown && spendable < amount + fee) {
|
|
4453
4554
|
blockers.push("INSUFFICIENT_TOKEN");
|
|
4454
|
-
shortfall.token = formatUnits(amount + fee -
|
|
4555
|
+
shortfall.token = formatUnits(amount + fee - spendable, quote.decimals);
|
|
4455
4556
|
}
|
|
4456
4557
|
} else {
|
|
4457
4558
|
if (tokenKnown && bal.token < amount) {
|
|
@@ -4603,18 +4704,28 @@ var PipRailClient = class {
|
|
|
4603
4704
|
quote
|
|
4604
4705
|
});
|
|
4605
4706
|
}
|
|
4707
|
+
const reservation = this.ledger.reserve(
|
|
4708
|
+
quote.network,
|
|
4709
|
+
quote.asset,
|
|
4710
|
+
BigInt(quote.amount),
|
|
4711
|
+
quote.decimals,
|
|
4712
|
+
denomOf(quote.symbol, quote.asset, this.opts.policy)
|
|
4713
|
+
);
|
|
4606
4714
|
const hook = this.opts.onBeforePay;
|
|
4607
|
-
if (!hook) return;
|
|
4715
|
+
if (!hook) return reservation;
|
|
4608
4716
|
let approved;
|
|
4609
4717
|
try {
|
|
4610
4718
|
approved = await hook(quote);
|
|
4611
4719
|
} catch (err) {
|
|
4720
|
+
this.ledger.release(reservation);
|
|
4612
4721
|
this.refuse("onBeforePay threw \u2014 refusing to pay.", { reasonCode: "APPROVAL", quote, cause: err });
|
|
4613
4722
|
}
|
|
4614
4723
|
if (!approved) {
|
|
4724
|
+
this.ledger.release(reservation);
|
|
4615
4725
|
const reason = `onBeforePay declined ${quote.amountFormatted} ${quote.symbol ?? ""}`.trimEnd() + ` on ${quote.network}.`;
|
|
4616
4726
|
this.refuse(reason, { reasonCode: "APPROVAL", quote });
|
|
4617
4727
|
}
|
|
4728
|
+
return reservation;
|
|
4618
4729
|
}
|
|
4619
4730
|
/**
|
|
4620
4731
|
* Refuse a payment BEFORE any send: emit BOTH the legacy `payment-failed` (so existing
|
|
@@ -4653,7 +4764,7 @@ var PipRailClient = class {
|
|
|
4653
4764
|
* spend (POL-1). So the cap-bearing `amountBase` is the MAX; the clamped actual is surfaced
|
|
4654
4765
|
* separately on `settledBase`/`settledFormatted` for transparency (it equals the receipt's
|
|
4655
4766
|
* amount). When absent (onchain-proof/exact) this is byte-identical to before. */
|
|
4656
|
-
recordSpend(quote, ref, settledAmountBase) {
|
|
4767
|
+
recordSpend(quote, ref, settledAmountBase, reservation) {
|
|
4657
4768
|
const denom = denomOf(quote.symbol, quote.asset, this.opts.policy);
|
|
4658
4769
|
const amountBase = quote.amount;
|
|
4659
4770
|
const amountFormatted = quote.amountFormatted;
|
|
@@ -4684,6 +4795,7 @@ var PipRailClient = class {
|
|
|
4684
4795
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4685
4796
|
};
|
|
4686
4797
|
this.ledger.record(record, quote.decimals, denom);
|
|
4798
|
+
this.ledger.release(reservation);
|
|
4687
4799
|
const budget = this.budget();
|
|
4688
4800
|
if (this.opts.onSpend) {
|
|
4689
4801
|
try {
|
|
@@ -4855,7 +4967,7 @@ var PipRailClient = class {
|
|
|
4855
4967
|
* • a 200 whose SettleResponse says `success:false` → a rejection, NEVER a spend;
|
|
4856
4968
|
* • the spend is recorded EXACTLY ONCE, on an affirmative settlement only.
|
|
4857
4969
|
*/
|
|
4858
|
-
async payExactRail(net, wallet, accept, url, init, quote, x402Version = 2) {
|
|
4970
|
+
async payExactRail(net, wallet, accept, url, init, quote, x402Version = 2, reservation) {
|
|
4859
4971
|
if (!net.payExact) {
|
|
4860
4972
|
throw new UnsupportedSchemeError(
|
|
4861
4973
|
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM, Solana, Algorand, Aptos + NEAR today).`
|
|
@@ -4919,7 +5031,7 @@ var PipRailClient = class {
|
|
|
4919
5031
|
this.captureReceipt(response, url);
|
|
4920
5032
|
this.safeEmit({ kind: "payment-settled", receipt, ...settle ? { settle } : {} });
|
|
4921
5033
|
const ref = settle?.transaction || receipt?.transaction || `${net.family === "evm" ? "eip3009" : net.family}-nonce:${nonce}`;
|
|
4922
|
-
this.recordSpend(quote, ref);
|
|
5034
|
+
this.recordSpend(quote, ref, void 0, reservation);
|
|
4923
5035
|
return response;
|
|
4924
5036
|
}
|
|
4925
5037
|
if (response.status >= 500) {
|
|
@@ -4950,7 +5062,7 @@ var PipRailClient = class {
|
|
|
4950
5062
|
* `settle.amount` FAILS SAFE to the MAX (over-counts, never under-counts). The buyer SIGNS, the
|
|
4951
5063
|
* merchant self-settles — the buyer never broadcasts.
|
|
4952
5064
|
*/
|
|
4953
|
-
async payUptoRail(net, wallet, accept, url, init, quote) {
|
|
5065
|
+
async payUptoRail(net, wallet, accept, url, init, quote, reservation) {
|
|
4954
5066
|
if (!net.payUpto) {
|
|
4955
5067
|
throw new UnsupportedSchemeError(
|
|
4956
5068
|
`the ${net.family} family can't pay a standard 'upto' rail (EVM-Permit2 only today).`
|
|
@@ -5004,7 +5116,7 @@ var PipRailClient = class {
|
|
|
5004
5116
|
this.safeEmit({ kind: "payment-settled", receipt, ...settle ? { settle } : {} });
|
|
5005
5117
|
const ref = settle?.transaction || receipt?.transaction || `upto-nonce:${nonce}`;
|
|
5006
5118
|
const settledAmount = settle?.amount ?? receipt?.amount;
|
|
5007
|
-
this.recordSpend(quote, ref, settledAmount);
|
|
5119
|
+
this.recordSpend(quote, ref, settledAmount, reservation);
|
|
5008
5120
|
return response;
|
|
5009
5121
|
}
|
|
5010
5122
|
if (response.status >= 500) {
|
|
@@ -6018,7 +6130,7 @@ function renderLandingPage(sd) {
|
|
|
6018
6130
|
}
|
|
6019
6131
|
|
|
6020
6132
|
// src/facilitator.ts
|
|
6021
|
-
async function fetchFacilitatorFeePayer(url, network, timeoutMs =
|
|
6133
|
+
async function fetchFacilitatorFeePayer(url, network, timeoutMs = 15e3) {
|
|
6022
6134
|
const base2 = url.replace(/\/+$/, "");
|
|
6023
6135
|
const ctrl = new AbortController();
|
|
6024
6136
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
@@ -6058,7 +6170,7 @@ function parseFacilitatorSupported(body) {
|
|
|
6058
6170
|
}
|
|
6059
6171
|
return out;
|
|
6060
6172
|
}
|
|
6061
|
-
async function facilitatorCoverage(url, timeoutMs =
|
|
6173
|
+
async function facilitatorCoverage(url, timeoutMs = 15e3) {
|
|
6062
6174
|
const base2 = url.replace(/\/+$/, "");
|
|
6063
6175
|
const ctrl = new AbortController();
|
|
6064
6176
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
@@ -6580,6 +6692,11 @@ function createPaymentGate(options) {
|
|
|
6580
6692
|
net.assertValidPayTo(payTo);
|
|
6581
6693
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
6582
6694
|
const amountBase = parseUnits(a.amount, decimals);
|
|
6695
|
+
if (amountBase <= 0n) {
|
|
6696
|
+
throw new InvalidConfigError(
|
|
6697
|
+
`requirePayment: amount must be greater than zero, got "${a.amount}" on ${net.network}. A gate that charges nothing gates nothing; omit the gate instead.`
|
|
6698
|
+
);
|
|
6699
|
+
}
|
|
6583
6700
|
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
6584
6701
|
if (exactOption) {
|
|
6585
6702
|
const outcome = await resolveExactRail(net, asset);
|
|
@@ -6698,6 +6815,11 @@ function createPaymentGate(options) {
|
|
|
6698
6815
|
"requirePayment/createPaymentGate: `isUsed` and `markUsed` must be provided TOGETHER \u2014 a custom replay store needs both a read and a write. Supplying only " + (hasIsUsed ? "`isUsed`" : "`markUsed`") + " silently disables replay protection (double-spend). Provide both, or neither (the built-in in-memory store)."
|
|
6699
6816
|
);
|
|
6700
6817
|
}
|
|
6818
|
+
if (typeof options.releaseUsed === "function" && !(hasIsUsed && hasMarkUsed)) {
|
|
6819
|
+
throw new Error(
|
|
6820
|
+
"requirePayment/createPaymentGate: `releaseUsed` needs `isUsed` + `markUsed` too \u2014 it releases a reservation a CUSTOM store made, so without one it would never fire. Provide all three, or none (the built-in store already releases on failure)."
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6701
6823
|
const hasCustomStore = hasIsUsed && hasMarkUsed;
|
|
6702
6824
|
const localUsed = /* @__PURE__ */ new Map();
|
|
6703
6825
|
const replayWindowMs = maxTimeoutSeconds * 1e3;
|
|
@@ -6707,23 +6829,35 @@ function createPaymentGate(options) {
|
|
|
6707
6829
|
localUsed.delete(key);
|
|
6708
6830
|
}
|
|
6709
6831
|
}
|
|
6832
|
+
const localKey = (ref) => ref.startsWith("pid:") ? ref : ref.toLowerCase();
|
|
6710
6833
|
async function claimTx(ref) {
|
|
6711
|
-
|
|
6712
|
-
return options.isUsed ? Boolean(await options.isUsed(ref)) : false;
|
|
6713
|
-
}
|
|
6714
|
-
const key = ref.startsWith("pid:") ? ref : ref.toLowerCase();
|
|
6834
|
+
const key = localKey(ref);
|
|
6715
6835
|
const now = Date.now();
|
|
6716
6836
|
pruneUsed(now);
|
|
6717
6837
|
if (localUsed.has(key)) return true;
|
|
6718
6838
|
localUsed.set(key, now + replayWindowMs);
|
|
6839
|
+
if (hasCustomStore) {
|
|
6840
|
+
try {
|
|
6841
|
+
if (options.isUsed && await options.isUsed(ref)) return true;
|
|
6842
|
+
} catch (err) {
|
|
6843
|
+
localUsed.delete(key);
|
|
6844
|
+
throw err;
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6719
6847
|
return false;
|
|
6720
6848
|
}
|
|
6721
6849
|
async function settleTx(ref, ok) {
|
|
6722
|
-
if (
|
|
6723
|
-
|
|
6850
|
+
if (!ok) {
|
|
6851
|
+
localUsed.delete(localKey(ref));
|
|
6852
|
+
if (hasCustomStore && options.releaseUsed) {
|
|
6853
|
+
try {
|
|
6854
|
+
await options.releaseUsed(ref);
|
|
6855
|
+
} catch {
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6724
6858
|
return;
|
|
6725
6859
|
}
|
|
6726
|
-
if (
|
|
6860
|
+
if (hasCustomStore && options.markUsed) await options.markUsed(ref);
|
|
6727
6861
|
}
|
|
6728
6862
|
function buildAccept(s, nonce) {
|
|
6729
6863
|
return {
|
|
@@ -7413,6 +7547,12 @@ function nonceIn(payload) {
|
|
|
7413
7547
|
const fromAccept = typeof accepted?.extra?.nonce === "string" ? accepted.extra.nonce : void 0;
|
|
7414
7548
|
return fromPayload ?? fromAccept;
|
|
7415
7549
|
}
|
|
7550
|
+
function refIn(payload) {
|
|
7551
|
+
if (typeof payload !== "object" || payload === null) return void 0;
|
|
7552
|
+
const inner = payload.payload;
|
|
7553
|
+
const ref = inner && typeof inner.txHash === "string" ? inner.txHash.trim() : void 0;
|
|
7554
|
+
return ref ? ref.toLowerCase() : void 0;
|
|
7555
|
+
}
|
|
7416
7556
|
function toToolError(err) {
|
|
7417
7557
|
if (!(err instanceof PipRailError)) throw err;
|
|
7418
7558
|
const out = {
|
|
@@ -7953,16 +8093,12 @@ function paymentTools(client) {
|
|
|
7953
8093
|
const railSchemes = (t) => [
|
|
7954
8094
|
...new Set((t.rails ?? []).flatMap((r) => [...r.schemes ?? []]))
|
|
7955
8095
|
];
|
|
7956
|
-
|
|
7957
|
-
isUsed: (ref) => spentProofs.has(ref),
|
|
7958
|
-
markUsed: (ref) => void spentProofs.add(ref)
|
|
7959
|
-
};
|
|
7960
|
-
let gate = createPaymentGate({ ...base3, ...shared, exact: true });
|
|
8096
|
+
let gate = createPaymentGate({ ...base3, exact: true });
|
|
7961
8097
|
let check = await gate.selfTest();
|
|
7962
8098
|
const warnings = [];
|
|
7963
8099
|
if (!check.ok || !railSchemes(check).includes("exact")) {
|
|
7964
8100
|
const why = check.error ?? "it did not resolve on this RPC";
|
|
7965
|
-
gate = createPaymentGate({ ...base3
|
|
8101
|
+
gate = createPaymentGate({ ...base3 });
|
|
7966
8102
|
check = await gate.selfTest();
|
|
7967
8103
|
warnings.push(
|
|
7968
8104
|
`This offer carries onchain-proof ONLY, so a standard x402 agent-buyer cannot pay it. A PipRail buyer (piprail_pay_request) and a human still can. The gasless exact rail was dropped because ${why} Two things cause this: the token or family has no exact scheme (a native coin never does, so price in USDC), or the token domain read failed on a busy public RPC, which is transient. Retrying on a dedicated rpcUrl is worth one attempt before you settle for this.`
|
|
@@ -8059,7 +8195,30 @@ function paymentTools(client) {
|
|
|
8059
8195
|
next: "Do NOT deliver. Ask the buyer to pay THIS offer's challenge."
|
|
8060
8196
|
};
|
|
8061
8197
|
}
|
|
8062
|
-
const
|
|
8198
|
+
const proofRef = refIn(asObject ?? decodeBase64Json(raw));
|
|
8199
|
+
let reservedHere;
|
|
8200
|
+
if (proofRef) {
|
|
8201
|
+
if (spentProofs.has(proofRef)) {
|
|
8202
|
+
return {
|
|
8203
|
+
ok: true,
|
|
8204
|
+
paid: false,
|
|
8205
|
+
offerId: offer.id,
|
|
8206
|
+
reason: "this settlement was already collected \u2014 one payment settles exactly one offer.",
|
|
8207
|
+
code: "tx_already_used",
|
|
8208
|
+
next: "Do NOT deliver. Ask the buyer to pay THIS offer's challenge."
|
|
8209
|
+
};
|
|
8210
|
+
}
|
|
8211
|
+
spentProofs.add(proofRef);
|
|
8212
|
+
reservedHere = proofRef;
|
|
8213
|
+
}
|
|
8214
|
+
let result;
|
|
8215
|
+
try {
|
|
8216
|
+
result = asObject !== void 0 ? await offer.gate.verifyObject(asObject) : await offer.gate.verify(raw);
|
|
8217
|
+
} catch (err) {
|
|
8218
|
+
if (reservedHere) spentProofs.delete(reservedHere);
|
|
8219
|
+
throw err;
|
|
8220
|
+
}
|
|
8221
|
+
if (result.kind !== "paid" && reservedHere) spentProofs.delete(reservedHere);
|
|
8063
8222
|
if (result.kind === "paid") {
|
|
8064
8223
|
const r = result.receipt;
|
|
8065
8224
|
const entry = {
|
|
@@ -1056,6 +1056,13 @@ declare class SpendLedger {
|
|
|
1056
1056
|
* {@link SpendStore} when one is configured (a failed append never throws). */
|
|
1057
1057
|
record(r: SpendRecord, decimals: number, denom?: string): void;
|
|
1058
1058
|
/** Running total (base units) already spent on this (network, asset). */
|
|
1059
|
+
private readonly pending;
|
|
1060
|
+
private pendingSeq;
|
|
1061
|
+
/** Reserve budget for a payment about to be attempted. Returns the token to settle it with. */
|
|
1062
|
+
reserve(network: string, asset: string, amountBase: bigint, decimals: number, denom?: string): string;
|
|
1063
|
+
/** Drop a reservation: the payment settled (its real record now counts) or it failed. */
|
|
1064
|
+
release(token: string | undefined): void;
|
|
1065
|
+
private pendingFor;
|
|
1059
1066
|
totalFor(network: string, asset: string): bigint;
|
|
1060
1067
|
/**
|
|
1061
1068
|
* Running grand total for a DENOMINATION, scaled to {@link DENOM_PRECISION} (so
|
|
@@ -1056,6 +1056,13 @@ declare class SpendLedger {
|
|
|
1056
1056
|
* {@link SpendStore} when one is configured (a failed append never throws). */
|
|
1057
1057
|
record(r: SpendRecord, decimals: number, denom?: string): void;
|
|
1058
1058
|
/** Running total (base units) already spent on this (network, asset). */
|
|
1059
|
+
private readonly pending;
|
|
1060
|
+
private pendingSeq;
|
|
1061
|
+
/** Reserve budget for a payment about to be attempted. Returns the token to settle it with. */
|
|
1062
|
+
reserve(network: string, asset: string, amountBase: bigint, decimals: number, denom?: string): string;
|
|
1063
|
+
/** Drop a reservation: the payment settled (its real record now counts) or it failed. */
|
|
1064
|
+
release(token: string | undefined): void;
|
|
1065
|
+
private pendingFor;
|
|
1059
1066
|
totalFor(network: string, asset: string): bigint;
|
|
1060
1067
|
/**
|
|
1061
1068
|
* Running grand total for a DENOMINATION, scaled to {@link DENOM_PRECISION} (so
|
package/dist/node.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { v as SpendStore } from './ledger-
|
|
2
|
-
export { _ as memorySpendStore } from './ledger-
|
|
1
|
+
import { v as SpendStore } from './ledger-DkHUORUe.cjs';
|
|
2
|
+
export { _ as memorySpendStore } from './ledger-DkHUORUe.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A durable {@link SpendStore} backed by a local JSONL file (one settled payment per
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { v as SpendStore } from './ledger-
|
|
2
|
-
export { _ as memorySpendStore } from './ledger-
|
|
1
|
+
import { v as SpendStore } from './ledger-DkHUORUe.js';
|
|
2
|
+
export { _ as memorySpendStore } from './ledger-DkHUORUe.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A durable {@link SpendStore} backed by a local JSONL file (one settled payment per
|
|
@@ -763,6 +763,7 @@ function toKeypair(wallet, network) {
|
|
|
763
763
|
}
|
|
764
764
|
|
|
765
765
|
// src/drivers/solana/index.ts
|
|
766
|
+
var RENT_EXEMPT_MIN_LAMPORTS = 890880n;
|
|
766
767
|
var solanaDriver = {
|
|
767
768
|
family: "solana",
|
|
768
769
|
resolve(opts) {
|
|
@@ -890,7 +891,12 @@ function makeSolanaNetwork(preset, rpcUrl) {
|
|
|
890
891
|
async balanceOf(wallet, asset) {
|
|
891
892
|
const owner = wallet._native.publicKey;
|
|
892
893
|
const native = await connection.getBalance(owner).then((n) => BigInt(n)).catch(() => null);
|
|
893
|
-
if (asset === "native")
|
|
894
|
+
if (asset === "native") {
|
|
895
|
+
if (native === null) return { token: null, native };
|
|
896
|
+
const reserve = await connection.getMinimumBalanceForRentExemption(0).then((n) => BigInt(n)).catch(() => RENT_EXEMPT_MIN_LAMPORTS);
|
|
897
|
+
const spendable = native > reserve ? native - reserve : 0n;
|
|
898
|
+
return { token: spendable, native };
|
|
899
|
+
}
|
|
894
900
|
let token;
|
|
895
901
|
try {
|
|
896
902
|
const mint = new (0, _web3js.PublicKey)(asset);
|
|
@@ -763,6 +763,7 @@ function toKeypair(wallet, network) {
|
|
|
763
763
|
}
|
|
764
764
|
|
|
765
765
|
// src/drivers/solana/index.ts
|
|
766
|
+
var RENT_EXEMPT_MIN_LAMPORTS = 890880n;
|
|
766
767
|
var solanaDriver = {
|
|
767
768
|
family: "solana",
|
|
768
769
|
resolve(opts) {
|
|
@@ -890,7 +891,12 @@ function makeSolanaNetwork(preset, rpcUrl) {
|
|
|
890
891
|
async balanceOf(wallet, asset) {
|
|
891
892
|
const owner = wallet._native.publicKey;
|
|
892
893
|
const native = await connection.getBalance(owner).then((n) => BigInt(n)).catch(() => null);
|
|
893
|
-
if (asset === "native")
|
|
894
|
+
if (asset === "native") {
|
|
895
|
+
if (native === null) return { token: null, native };
|
|
896
|
+
const reserve = await connection.getMinimumBalanceForRentExemption(0).then((n) => BigInt(n)).catch(() => RENT_EXEMPT_MIN_LAMPORTS);
|
|
897
|
+
const spendable = native > reserve ? native - reserve : 0n;
|
|
898
|
+
return { token: spendable, native };
|
|
899
|
+
}
|
|
894
900
|
let token;
|
|
895
901
|
try {
|
|
896
902
|
const mint = new PublicKey3(asset);
|
|
@@ -419,6 +419,7 @@ function isStellarNotFound(e) {
|
|
|
419
419
|
const x = e;
|
|
420
420
|
return _optionalChain([x, 'optionalAccess', _14 => _14.response, 'optionalAccess', _15 => _15.status]) === 404 || _optionalChain([x, 'optionalAccess', _16 => _16.name]) === "NotFoundError";
|
|
421
421
|
}
|
|
422
|
+
var STELLAR_BASE_RESERVE = 5000000n;
|
|
422
423
|
var stellarDriver = {
|
|
423
424
|
family: "stellar",
|
|
424
425
|
resolve(opts) {
|
|
@@ -550,9 +551,11 @@ function makeStellarNetwork(preset, rpcUrl) {
|
|
|
550
551
|
return { token: null, native: null };
|
|
551
552
|
}
|
|
552
553
|
let lines;
|
|
554
|
+
let subentryCount;
|
|
553
555
|
try {
|
|
554
556
|
const account = await server.loadAccount(owner);
|
|
555
557
|
lines = account.balances;
|
|
558
|
+
subentryCount = account.subentry_count;
|
|
556
559
|
} catch (e) {
|
|
557
560
|
return isStellarNotFound(e) ? { token: 0n, native: 0n } : { token: null, native: null };
|
|
558
561
|
}
|
|
@@ -564,7 +567,12 @@ function makeStellarNetwork(preset, rpcUrl) {
|
|
|
564
567
|
}
|
|
565
568
|
};
|
|
566
569
|
const native = toBase(_optionalChain([lines, 'access', _17 => _17.find, 'call', _18 => _18((b) => b.asset_type === "native"), 'optionalAccess', _19 => _19.balance]));
|
|
567
|
-
if (asset === "native")
|
|
570
|
+
if (asset === "native") {
|
|
571
|
+
if (native === null) return { token: null, native };
|
|
572
|
+
const subentries = BigInt(_nullishCoalesce(subentryCount, () => ( 0)));
|
|
573
|
+
const min = (2n + subentries) * STELLAR_BASE_RESERVE;
|
|
574
|
+
return { token: native > min ? native - min : 0n, native };
|
|
575
|
+
}
|
|
568
576
|
const parts = parseStellarAssetId(asset);
|
|
569
577
|
const line = parts ? lines.find(
|
|
570
578
|
(b) => (b.asset_type === "credit_alphanum4" || b.asset_type === "credit_alphanum12") && b.asset_code === parts.code && b.asset_issuer === parts.issuer
|
|
@@ -419,6 +419,7 @@ function isStellarNotFound(e) {
|
|
|
419
419
|
const x = e;
|
|
420
420
|
return x?.response?.status === 404 || x?.name === "NotFoundError";
|
|
421
421
|
}
|
|
422
|
+
var STELLAR_BASE_RESERVE = 5000000n;
|
|
422
423
|
var stellarDriver = {
|
|
423
424
|
family: "stellar",
|
|
424
425
|
resolve(opts) {
|
|
@@ -550,9 +551,11 @@ function makeStellarNetwork(preset, rpcUrl) {
|
|
|
550
551
|
return { token: null, native: null };
|
|
551
552
|
}
|
|
552
553
|
let lines;
|
|
554
|
+
let subentryCount;
|
|
553
555
|
try {
|
|
554
556
|
const account = await server.loadAccount(owner);
|
|
555
557
|
lines = account.balances;
|
|
558
|
+
subentryCount = account.subentry_count;
|
|
556
559
|
} catch (e) {
|
|
557
560
|
return isStellarNotFound(e) ? { token: 0n, native: 0n } : { token: null, native: null };
|
|
558
561
|
}
|
|
@@ -564,7 +567,12 @@ function makeStellarNetwork(preset, rpcUrl) {
|
|
|
564
567
|
}
|
|
565
568
|
};
|
|
566
569
|
const native = toBase(lines.find((b) => b.asset_type === "native")?.balance);
|
|
567
|
-
if (asset === "native")
|
|
570
|
+
if (asset === "native") {
|
|
571
|
+
if (native === null) return { token: null, native };
|
|
572
|
+
const subentries = BigInt(subentryCount ?? 0);
|
|
573
|
+
const min = (2n + subentries) * STELLAR_BASE_RESERVE;
|
|
574
|
+
return { token: native > min ? native - min : 0n, native };
|
|
575
|
+
}
|
|
568
576
|
const parts = parseStellarAssetId(asset);
|
|
569
577
|
const line = parts ? lines.find(
|
|
570
578
|
(b) => (b.asset_type === "credit_alphanum4" || b.asset_type === "credit_alphanum12") && b.asset_code === parts.code && b.asset_issuer === parts.issuer
|