mppx 0.13.6 → 0.14.1
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 +20 -0
- package/dist/cli/cli.js +1 -1
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/tempo/server/Methods.d.ts +7 -2
- package/dist/tempo/server/Methods.js +9 -2
- package/dist/tempo/session/precompile/Chain.d.ts +2 -2
- package/dist/tempo/session/precompile/Chain.js +3 -2
- package/dist/tempo/session/server/ChannelStore.d.ts +152 -3
- package/dist/tempo/session/server/ChannelStore.js +243 -3
- package/dist/tempo/session/server/CloseSupervisor.d.ts +110 -0
- package/dist/tempo/session/server/CloseSupervisor.js +310 -0
- package/dist/tempo/session/server/Session.d.ts +41 -2
- package/dist/tempo/session/server/Session.js +43 -4
- package/dist/tempo/session/server/Settlement.d.ts +9 -4
- package/dist/tempo/session/server/Settlement.js +107 -32
- package/dist/tempo/session/server/SponsorshipLimit.d.ts +52 -0
- package/dist/tempo/session/server/SponsorshipLimit.js +107 -0
- package/dist/tempo/session/server/index.d.ts +12 -2
- package/dist/tempo/session/server/index.js +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# mppx
|
|
2
2
|
|
|
3
|
+
## 0.14.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 908af47: `superviseCloses` accepts a `settlementSchedule`, in raw token units, and settles open channels whose spend crossed it, within spend, reporting them `settled`. Servers that charge with `charge()` rather than through the session handler, whose schedule runs only on its own charges, now collect spend without waiting for a close. `resolveSettlementSchedule` and `ResolvedSettlementSchedule` are exported.
|
|
8
|
+
|
|
9
|
+
## 0.14.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 00d3894: Added session accounting primitives to the Tempo session server, so payment servers no longer edit channel records themselves.
|
|
14
|
+
|
|
15
|
+
- `Session.Server.charge()` (also `tempo.session.charge()`) accepted an `idempotencyKey`: a retry with the same key and amount returned the recorded charge without charging again, and the same key with another amount threw `IdempotencyKeyConflictError`. It also accepted a `Store` as well as a channel store.
|
|
16
|
+
- Added `Session.Server.credit()` (also `tempo.session.credit()`), which returned an amount to a channel's `spent` at most once per key. Its first outcome was final: `credited`, `declined` when the channel was closing or finalized or the credit would take `spent` below zero or below what settled or may still settle on-chain, or `unknown` when the store write failed and may have committed.
|
|
17
|
+
- Keyed charge and credit outcomes were recorded on the channel record for `retention` (24 hours by default) and removed by the channel's next keyed charge or credit, so they worked with every channel store.
|
|
18
|
+
- `settle()` and the settlement schedule settled the largest accepted voucher at or below `spent` (`Session.Server.resolveSettleableVoucher()`) instead of the highest voucher, which a credit can leave above `spent`; channels kept up to 16 earlier vouchers for this. A settlement held a credit floor at its amount until its transaction could no longer land. **Breaking:** `settle()` no longer captured a voucher above `spent`, and threw "nothing to settle" when no accepted voucher was within `spent`.
|
|
19
|
+
- Added `tempo.superviseCloses()` (also `Session.Server.superviseCloses()`) for a scheduled job: it read the escrow state of the given channels at one pinned block, answered payer close requests with a payee close capturing `max(spent, settled)` with configurable fee payment, finalized channels whose deposit left the escrow from their `ChannelClosed` event, and reopened channels whose close request was cancelled. It returned the pinned block and its timestamp with per-channel outcomes and counts.
|
|
20
|
+
- Added `sponsorshipLimit` to `tempo.session()`, which limited fee-payer-sponsored opens, and separately top-ups, to five per verified transaction signer per UTC day, counted in a `Store.AtomicStore`.
|
|
21
|
+
- `Chain.getChannelStatesBatch()` accepted a block number.
|
|
22
|
+
|
|
3
23
|
## 0.13.6
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/cli/cli.js
CHANGED
|
@@ -242,7 +242,7 @@ $&`).replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g,`$1$2`).replace(/\
|
|
|
242
242
|
`)){let e=t.trim();if(e===`[[keys]]`){r?.wallet_address&&n.push(r),r={wallet_type:`local`,wallet_address:``,chain_id:0};continue}if(!r)continue;let i=e.match(/^(\w+)\s*=\s*"?([^"]*)"?$/);if(!i)continue;let[,a,o]=i;a===`wallet_type`?r.wallet_type=o:a===`wallet_address`?r.wallet_address=o:a===`chain_id`&&(r.chain_id=Number.parseInt(o,10))}return r?.wallet_address&&n.push(r),n}catch{return[]}}function DP(e){let t=EP();if(t.length===0)return;let n=e.slice(6);if(n==="default"||n===``)return t[0];let r=Number.parseInt(n,10);if(!Number.isNaN(r)&&r>=0&&r<t.length)return t[r]}function OP(){if(IP!==void 0)return IP;try{m.execFileSync(`which`,[`tempo`],{stdio:`ignore`}),IP=!0}catch{IP=!1}return IP}async function kP(e){return new Promise((t,n)=>{m.execFile(`tempo`,[`mpp`,`sign`,`--challenge`,e],(e,r,i)=>{if(e){let t=i?.trim()||e.message;n(Error(`tempo mpp sign failed: ${t}`));return}let a=r.trim();if(!a){n(Error(`tempo mpp sign returned empty output`));return}t(a)})})}function AP(){let e=rk();if(NO(e.get())&&!OP()&&b.platform()===`darwin`)try{let t=m.execFileSync(`security`,[`dump-keychain`],{encoding:`utf-8`}),n=[];for(let e of t.split(`keychain:`)){let t=e.match(/"svce"<blob>="([^"]*)"/),r=e.match(/"acct"<blob>="([^"]*)"/);t?.[1]===jP&&r?.[1]&&n.push(r[1])}if(n.length>0)return e.set(n[0]),n[0]}catch{}}var jP,MP,NP,PP,FP,IP,LP=I((()=>{Ox(),yT(),Fx(),rC(),rP(),GE(),Uj(),dk(),ZO(),uA(),jP=`mppx`,MP=p.union([p.boolean(),p.literal(`true`).transform(()=>!0),p.literal(`false`).transform(()=>!1)]),NP=p.object({allowCustomEscrow:p.optional(MP),autoSwap:p.optional(MP),channel:p.optional(p.coerce.string()),deposit:p.optional(p.union([p.string(),p.number()])),payWith:p.optional(p.string()),slippage:p.optional(p.coerce.number()),tokenIn:p.optional(p.string())}),PP=[`allowCustomEscrow`,`autoSwap`,`channel`,`deposit`,`payWith`,`slippage`,`tokenIn`],FP=[`payable`,`unknownBalance`,`insufficientBalance`,`unsupported`]}));OS(),dk(),uA();const RP=[...Object.entries(_k),...Object.entries(vk),...Object.entries(yk),...Object.entries(bk)];function zP(){return lA({method:`evm`,supports:BP,async setup({challenge:e,options:t}){let n=e.request,r=n.network?VP(Dk(n.network)):void 0,i=n.network&&n.asset?RP.find(([,e])=>Tk(e,n.asset,n.network)):void 0,a=n.extra?.name,o=n.extra?.version,s=n.asset&&n.network&&typeof a==`string`&&typeof o==`string`?hk({address:n.asset,decimals:i?.[1].decimals??6,network:n.network,transfer:{name:a,type:`eip3009`,version:o}}):void 0,c=await ck(t.account);return{explorerUrl:r?.blockExplorers?.default?.url,methods:[...cA({account:c,...s&&{currencies:[s]}})],tokenDecimals:i?.[1].decimals??6,tokenSymbol:i?.[0]??``}}})}function BP(e){if(!ES(e))return!1;let t=e.request,n=t.extra,r=n?.assetTransferMethod;return t.scheme===`exact`&&typeof t.asset==`string`&&typeof t.network==`string`&&typeof n?.name==`string`&&typeof n?.version==`string`&&(r===void 0||r===`eip3009`)}function VP(e){return Object.values(Be).find(t=>t.id===e)}_S(),JS(),LP();const HP=[sP(),bA(),dA(),zP()];async function UP(e,t,n){let r=n;for(let n of t??[]){let t=await n.preparePayment?.({challenge:e,...r===void 0?{}:{credentialContext:r}});t&&`credentialContext`in t&&(r=t.credentialContext)}return r}function WP(e,t){let n=t?.plugins?.find(t=>JP(t,e));if(n)return{plugin:n};let r=qP(t)?.filter(t=>t.name===e.method&&t.intent===e.intent),i=r?.find(t=>t.canHandleChallenge?.({challenge:e})??!0);if(i)return{method:i};if(r?.length)return{};let a=HP.find(t=>JP(t,e));return a?{plugin:a}:{}}function GP(e,t){let n=qP(t);if(n?.length){let r=NS(e,n,AS(n,t?.paymentPreferences).entries);if(r){let e=t?.plugins?.find(e=>JP(e,r.challenge));return{challenge:r.challenge,...e?{plugin:e}:{method:r.method}}}return}for(let n of e){let e=WP(n,t);if(e.plugin||e.method)return{challenge:n,...e}}}function KP(e){let t=qP(e);if(t?.length)return AS(t,e?.paymentPreferences).header}function qP(e){return Array.isArray(e?.methods)?e.methods.flat():void 0}function JP(e,t){return e.supports?e.supports(t):e.method===t.method}async function YP(e){let t=XP(e);if(!t)return;let n=await import(t);return{config:n.default??n,path:t}}function XP(e){if(e){let n=c.resolve(e);return t.existsSync(n)?n:void 0}let n=process.env.MPPX_CONFIG?.trim();if(n){let e=c.resolve(n);return t.existsSync(e)?e:void 0}}function ZP(e,t){function n(t){let n={...t.request};if(t.method===`tempo`&&t.intent===`session`){let e={...n.methodDetails};delete e.sessionSnapshot,Object.keys(e).length?n.methodDetails=e:delete n.methodDetails}return $x({...t,request:n,id:e.id,expires:e.expires})}if(n(t)!==n(e))throw Error(`Payment request changed on retry. Run the command again to approve the new payment.`)}LP(),_S(),iM(),hN(),TN(),_O();function QP(e){return e??globalThis.fetch.bind(globalThis)}function $P(e,t){let n=e.request.methodDetails?.chainId;if(n!==void 0&&n!==t.chainId)throw Error(`Close challenge changed the session chain.`);if(typeof e.request.recipient!=`string`||e.request.recipient.toLowerCase()!==t.descriptor.payee.toLowerCase())throw Error(`Close challenge changed the session payee.`);if(typeof e.request.currency!=`string`||e.request.currency.toLowerCase()!==t.descriptor.token.toLowerCase())throw Error(`Close challenge changed the session token.`);if(Zj(e,t.escrow).toLowerCase()!==t.escrow.toLowerCase())throw Error(`Close challenge changed the session escrow.`);let r=LD(e);if(r&&r.channelId.toLowerCase()!==t.channelId.toLowerCase())throw Error(`Close challenge changed the session channel.`)}async function eF(e){$P(e.challenge,e.channel);let t=QP(e.fetch),n,r=async(r,i)=>{n&&=(await e.onChallenge?.(n),void 0);let a=await t(r,i);if(a.status!==402)return a;let o=cS(a).find(FD);if(!o)throw Error(`Refreshed close response did not include tempo/session.`);return $P(o,e.channel),n=o,a},i=wN({...e.manager,bootstrap:!1,escrow:e.channel.escrow,fetch:r});pN(i).rehydrate(e);let a=await i.close();if(!a)throw Error(`Session close response did not include a payment receipt.`);return{manager:i,receipt:a}}_S(),iM(),_M(),_O(),Tj(),Zx();var tF=class extends Error{name=`SessionStateError`;code=`SESSION_STATE_INVALID`;file;constructor(e,t={}){super(e,t.cause===void 0?void 0:{cause:t.cause}),this.file=t.file}},nF=class extends Error{name=`SessionBusyError`;code=`SESSION_BUSY`;exitCode=75;scope;owner;constructor(e,t){super(`Session scope is busy in process ${t.pid} on ${t.hostname}.`),this.scope=e,this.owner=t}};const rF=J.object({channelId:qx(),cumulativeAmount:J.string(),deposit:J.string(),descriptor:J.object({payer:Kx(),payee:Kx(),operator:Kx(),token:Kx(),salt:qx(),authorizedSigner:Kx(),expiringNonceHash:qx()}),escrow:Kx(),chainId:J.number(),opened:J.boolean()}),iF=J.object({name:J.optional(J.string()),address:Kx()}),aF=J.object({method:J.literal(`tempo`),intent:J.literal(`session`),status:J.literal(`success`),timestamp:J.string(),reference:J.string(),challengeId:J.string(),channelId:qx(),acceptedCumulative:J.string(),spent:J.string(),units:J.optional(J.number()),txHash:J.optional(qx())}),oF=J.object({version:J.literal(1),method:J.literal(`tempo`),intent:J.literal(`session`),status:J.enum([`opening`,`open`,`closing`,`stale`]),channel:rF,account:iF,endpoint:J.string(),challenge:gS,receipt:J.optional(aF),spent:J.string(),units:J.number(),createdAt:J.string(),updatedAt:J.string()}),sF=J.object({version:J.literal(1),channelId:qx()}),cF=J.object({version:J.literal(1),scope:J.string(),hostname:J.string(),pid:J.number(),token:J.string(),createdAt:J.string()});function lF(e,t,n,r){let i=e.safeParse(t);if(!i.success)throw Z(n,r,i.error);return i.data}function uF(e){let t=bF(e);return[t.payer,t.payee,t.token,t.escrow,t.chainId].join(`:`)}function dF(e){return{payer:e.descriptor.payer,payee:e.descriptor.payee,token:e.descriptor.token,escrow:e.escrow,chainId:e.chainId}}function fF(e={}){let t=e.stateRoot??mF(),n={root:t,channels:c.join(t,`channels`),locks:c.join(t,`locks`),preferred:c.join(t,`preferred`)},r=e.hostname??b.hostname(),i=e.pid??process.pid,a=e.now??(()=>new Date),o=e.isProcessAlive??KF;async function s(){for(let e of[n.root,n.channels,n.locks,n.preferred])await C.mkdir(e,{recursive:!0,mode:448}),await C.chmod(e,448)}async function l(e){let t=vF(e),r=hF(n,t),i=await LF(r);if(i===void 0)return;let a=OF(i,r);if(a.channel.channelId.toLowerCase()!==t)throw Z(r,`Session filename does not match its channel ID.`);return kF(a)}async function u(){let e;try{e=await C.readdir(n.channels,{withFileTypes:!0})}catch(e){if(JF(e,`ENOENT`))return[];throw Z(n.channels,`Unable to list managed sessions.`,e)}let t=[];for(let n of e){if(!n.isFile()||n.name.includes(`.tmp-`)||!n.name.endsWith(`.json`))continue;let e=n.name.slice(0,-5);vF(e);let r=await l(e);r&&t.push(r)}return t.sort((e,t)=>e.createdAt.localeCompare(t.createdAt))}async function d(e){await s();let t=wF(e.channel),r=vF(t.channelId),i=hF(n,r),o=await LF(i),c=o===void 0?void 0:OF(o,i);c&&AF(c,e,i);let l=EF(e.challenge,i);MF(l,t,i);let u=e.receipt?DF(e.receipt,t.channelId,i):void 0,d=c?lM(c.channel):void 0,f=IF(t.cumulativeAmount,d?.cumulativeAmount??0n),p=IF(e.spent??0n,u?BigInt(u.spent):0n,c?BigInt(c.spent):0n);if(p>f)throw Z(i,`Session spend exceeds the locally authorized cumulative amount.`);let m=TF(cM({...t,cumulativeAmount:f}),i),h=FF(c?.updatedAt,a(),i),g=PF(c?.receipt,u),_=CF(e.account,i),v=OF({version:1,method:`tempo`,intent:`session`,status:e.status,channel:m,account:{..._.name===void 0?c?.account.name===void 0?{}:{name:c.account.name}:{name:_.name},address:_.address},endpoint:SF(e.endpoint,i),challenge:l,...g!==void 0&&{receipt:g},spent:p.toString(),units:Math.max(c?.units??0,e.units??0,u?.units??0),createdAt:c?.createdAt??h,updatedAt:h},i);return await RF(i,v),kF(v)}async function f(e){let t=_F(n,e),r=await LF(t);if(r===void 0)return;let{channelId:i}=lF(sF,r,t,`Preferred session is invalid.`),a=await l(i);if(!a)throw Z(t,`Preferred session ${i} does not exist.`);return NF(a.channel,e,t),i}async function p(e,t){let r=vF(t),i=await l(r),a=_F(n,e);if(!i)throw Z(a,`Session ${r} does not exist.`);NF(i.channel,e,a),await RF(a,{version:1,channelId:r})}async function m(e,t){let r=_F(n,e),i=t===void 0?void 0:vF(t),a=await LF(r);if(a===void 0)return;let o=lF(sF,a,r,`Preferred session is invalid.`).channelId;i&&o.toLowerCase()!==i||await WF(r,`Unable to clear preferred session.`)}async function h(e){let t=vF(e),r=await l(t);r&&(await m(dF(r.channel),t),await WF(hF(n,t),`Unable to remove session.`))}async function g(e){await s();let t=gF(n,e);for(let n=0;n<3;n++){let n={version:1,scope:e,hostname:r,pid:i,token:_(),createdAt:a().toISOString()};try{if(await zF(t,n),await HF(VF(t))){await UF(t,n);continue}return{async release(){await UF(t,n)}}}catch(e){if(!JF(e,`EEXIST`))throw e}let s=await LF(t);if(s===void 0)continue;let c=lF(cF,s,t,`Session lock is invalid.`);if(c.scope!==e)throw Z(t,`Session lock scope is invalid.`);if(c.hostname!==r||o(c.pid))throw new nF(e,c);await BF(t,c)}let c=await LF(t);throw c===void 0?Z(t,`Unable to acquire session lock.`):new nF(e,lF(cF,c,t,`Session lock is invalid.`))}async function v(e){return g(uF(e))}return{root:t,get:l,list:u,upsert:d,remove:h,getPreferred:f,setPreferred:p,clearPreferred:m,acquire:v}}function pF(e,t){let n=xF(t.scope),r=t.selection===`auto`||t.selection===`new`?void 0:vF(t.selection);function i(e){if(e.toLowerCase()!==n)throw new tF(`Session manager requested an unexpected payment scope.`)}async function a(n=!0){if(t.selection===`new`&&!r)return;let i=r??await e.getPreferred(t.scope);if(!i)return;let a=await e.get(i);if(!a)throw new tF(`Session ${i} does not exist.`);if(NF(a.channel,t.scope),!(n&&(a.status!==`open`||!a.channel.opened)))return r=i,a}return{async get(e){return i(e),(await a())?.channel},async set(n){if(i(oM(n)),NF(n,t.scope),r&&r.toLowerCase()!==n.channelId.toLowerCase())throw new tF(`Session manager selected ${r}, but attempted to store ${n.channelId}.`);r||t.onNewChannel?.(n);let a=await e.upsert({...t.context(),channel:n});r=a.channel.channelId,await e.setPreferred(t.scope,a.channel.channelId)},async delete(n){i(n);let o=await a(!1);if(!o)return;let s=t.context();await e.upsert({...o,...s,status:s.status===`closing`?`closing`:`stale`,channel:o.channel,spent:s.spent??o.spent,units:s.units??o.units}),s.status!==`closing`&&await e.clearPreferred(t.scope,o.channel.channelId),r=void 0}}}function mF(){let e=process.env.XDG_STATE_HOME||c.join(b.homedir(),`.local`,`state`);return c.join(e,`mppx`,`sessions`,`v1`)}function hF(e,t){return c.join(e.channels,`${t}.json`)}function gF(e,t){let n=g(`sha256`).update(t).digest(`hex`);return c.join(e.locks,`${n}.lock`)}function _F(e,t){let n=g(`sha256`).update(uF(t)).digest(`hex`);return c.join(e.preferred,`${n}.json`)}function vF(e){return lF(qx(),e,void 0,`Invalid session channel ID: ${e}.`).toLowerCase()}function yF(e,t,n){return lF(Kx(),e,n,`Invalid ${t}.`).toLowerCase()}function bF(e){if(!Number.isSafeInteger(e.chainId)||e.chainId<0)throw new tF(`Invalid session chain ID.`);return{payer:yF(e.payer,`session payer`),payee:yF(e.payee,`session payee`),token:yF(e.token,`session token`),escrow:yF(e.escrow,`session escrow`),chainId:e.chainId}}function xF(e){let t=bF(e);return[t.payee,t.token,t.escrow,t.chainId].join(`:`)}function SF(e,t){if(typeof e!=`string`)throw Z(t,`Session endpoint is invalid.`);let n;try{n=new URL(e)}catch(e){throw Z(t,`Session endpoint is invalid.`,e)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw Z(t,`Session endpoint must use HTTP or HTTPS.`);if(n.username||n.password)throw Z(t,`Session endpoint must not contain credentials.`);let r=e.indexOf(`#`);return r===-1?e:e.slice(0,r)}function CF(e,t){return lF(iF,e,t,`Session account is invalid.`)}function wF(e){return lM(TF(cM(e)))}function TF(e,t){return lF(rF,e,t,`Stored session channel is invalid.`)}function EF(e,t){let n=gS.safeParse(e);if(!n.success||!FD(n.data))throw Z(t,`Stored session challenge is invalid.`);return n.data}function DF(e,t,n){let r=lF(aF,e,n,`Stored session receipt is invalid.`);if(r.channelId!==t.toLowerCase())throw Z(n,`Stored session receipt has a different channel ID.`);if(r.reference.toLowerCase()!==t.toLowerCase())throw Z(n,`Stored session receipt has a different reference.`);if(BigInt(r.spent)>BigInt(r.acceptedCumulative))throw Z(n,`Stored session receipt spend exceeds its accepted amount.`);return r}function OF(e,t){let n=lF(oF,e,t,`Stored session is invalid.`),{receipt:r,...i}=n,a=n.channel,o=n.account;if(o.address.toLowerCase()!==a.descriptor.payer.toLowerCase())throw Z(t,`Stored session account does not match the channel payer.`);let s=r?DF(r,a.channelId,t):void 0,c=n.spent,l=BigInt(a.cumulativeAmount);if(BigInt(c)>l)throw Z(t,`Stored session spend exceeds its cumulative authorization.`);if(s&&BigInt(s.acceptedCumulative)>l)throw Z(t,`Stored receipt exceeds the cumulative authorization.`);if(Date.parse(n.updatedAt)<Date.parse(n.createdAt))throw Z(t,`Stored session timestamps are not monotonic.`);let u=SF(n.endpoint,t);if(u!==n.endpoint)throw Z(t,`Stored session endpoint contains a fragment.`);let d=EF(n.challenge,t);return MF(d,lM(a),t),{...i,account:o,channel:a,endpoint:u,challenge:d,...s&&{receipt:s}}}function kF(e){return{version:1,status:e.status,channel:lM(e.channel),account:e.account,endpoint:e.endpoint,challenge:e.challenge,...e.receipt&&{receipt:e.receipt},spent:BigInt(e.spent),units:e.units,createdAt:e.createdAt,updatedAt:e.updatedAt}}function AF(e,t,n){let r=wF(t.channel),i=CF(t.account,n),a=lM(e.channel);if(JSON.stringify(jF(r))!==JSON.stringify(jF(a)))throw Z(n,`Session update changed immutable channel identity.`);if(i.address.toLowerCase()!==e.account.address.toLowerCase())throw Z(n,`Session update changed the payer account.`)}function jF(e){return{channelId:e.channelId.toLowerCase(),descriptor:Object.fromEntries(Object.entries(e.descriptor).map(([e,t])=>[e,typeof t==`string`?t.toLowerCase():t])),escrow:e.escrow.toLowerCase(),chainId:e.chainId}}function MF(e,t,n){if(xj({...t.descriptor,chainId:t.chainId,escrow:t.escrow}).toLowerCase()!==t.channelId.toLowerCase())throw Z(n,`Session channel ID does not match its descriptor.`);let r=yF(e.request.recipient,`challenge recipient`,n),i=yF(e.request.currency,`challenge currency`,n);if(r!==t.descriptor.payee.toLowerCase())throw Z(n,`Session challenge payee does not match the channel.`);if(i!==t.descriptor.token.toLowerCase())throw Z(n,`Session challenge token does not match the channel.`);if(Zj(e,t.escrow).toLowerCase()!==t.escrow.toLowerCase())throw Z(n,`Session challenge escrow does not match the channel.`);if(qF(e.request.methodDetails)){let r=e.request.methodDetails;if(r.chainId!==void 0&&r.chainId!==t.chainId)throw Z(n,`Session challenge chain does not match the channel.`)}}function NF(e,t,n){let r=bF(t);if(e.descriptor.payer.toLowerCase()!==r.payer||e.descriptor.payee.toLowerCase()!==r.payee||e.descriptor.token.toLowerCase()!==r.token||e.escrow.toLowerCase()!==r.escrow||e.chainId!==r.chainId)throw Z(n,`Session channel does not match the selected payment scope.`)}function PF(e,t){return e?t&&Date.parse(t.timestamp)>=Date.parse(e.timestamp)?t:e:t}function FF(e,t,n){if(!Number.isFinite(t.getTime()))throw Z(n,`Session clock is invalid.`);let r=t.toISOString();return!e||Date.parse(r)>=Date.parse(e)?r:e}function IF(...e){return e.reduce((e,t)=>t>e?t:e,0n)}async function LF(e){let t;try{t=await C.readFile(e,`utf8`)}catch(t){if(JF(t,`ENOENT`))return;throw Z(e,`Unable to read session state.`,t)}try{return JSON.parse(t)}catch(t){throw Z(e,`Session state contains invalid JSON.`,t)}}async function RF(e,t){await C.mkdir(c.dirname(e),{recursive:!0,mode:448}),await C.chmod(c.dirname(e),448);let n=`${e}.tmp-${process.pid}-${_()}`,r;try{r=await C.open(n,`wx`,384),await r.writeFile(`${JSON.stringify(t,null,2)}\n`,`utf8`),await r.sync(),await r.close(),r=void 0,await C.rename(n,e),await C.chmod(e,384),await GF(c.dirname(e))}catch(t){throw await r?.close().catch(()=>void 0),await C.unlink(n).catch(()=>void 0),t instanceof tF?t:Z(e,`Unable to write session state.`,t)}}async function zF(e,t){let n;try{n=await C.open(e,`wx`,384),await n.writeFile(`${JSON.stringify(t)}\n`,`utf8`),await n.sync(),await n.close(),n=void 0,await GF(c.dirname(e))}catch(t){throw await n?.close().catch(()=>void 0),n&&await C.unlink(e).catch(()=>void 0),t}}async function BF(e,t){let n=VF(e);try{await C.link(e,n)}catch(t){if(JF(t,`EEXIST`)||JF(t,`ENOENT`))return;throw Z(e,`Unable to claim dead lock.`,t)}try{let r=await LF(n);if(r===void 0||lF(cF,r,n,`Session lock is invalid.`).token!==t.token)return;await WF(e,`Unable to reclaim dead lock.`)}finally{await WF(n,`Unable to release dead lock claim.`)}}function VF(e){return`${e}.reclaim`}async function HF(e){try{return await C.access(e),!0}catch(t){if(JF(t,`ENOENT`))return!1;throw Z(e,`Unable to inspect session lock.`,t)}}async function UF(e,t){let n=await LF(e);n!==void 0&&lF(cF,n,e,`Session lock is invalid.`).token===t.token&&await WF(e,`Unable to release session lock.`)}async function WF(e,t){try{await C.unlink(e),await GF(c.dirname(e))}catch(n){if(!JF(n,`ENOENT`))throw Z(e,t,n)}}async function GF(e){let t=await C.open(e,`r`);try{await t.sync()}finally{await t.close()}}function KF(e){try{return process.kill(e,0),!0}catch(e){if(JF(e,`ESRCH`))return!1;if(JF(e,`EPERM`))return!0;throw e}}function qF(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function JF(e,t){return e instanceof Error&&`code`in e&&e.code===t}function Z(e,t,n){return new tF(t,{...n!==void 0&&{cause:n},file:e})}Ox(),yT(),aN(),SM(),Tj(),dk(),ZO();const YF=p.object({status:p.enum([`opening`,`open`,`closing`,`stale`]),channelId:p.string(),account:p.string().optional(),payer:p.string(),payee:p.string(),authorizedSigner:p.string(),token:p.string(),escrow:p.string(),chainId:p.number(),cumulativeAmount:p.string(),confirmedSpend:p.string(),deposit:p.string(),units:p.number(),resourceUrl:p.string(),createdAt:p.string(),updatedAt:p.string()}),XF=p.object({channelId:p.string(),status:p.enum([`closed`,`already-closed`]),spent:p.string(),txHash:p.string().optional()}),ZF=p.object({closed:p.array(XF),failed:p.array(p.object({channelId:p.string(),message:p.string()}))});function QF(e){return e===`mainnet`?ve.id:ye.id}function $F(e){if(e===ve.id)return`mainnet`;if(e===ye.id)return`testnet`}function eI(e){let t={};for(let n of e??[]){let e=n.indexOf(`:`);if(e===-1)throw new L({code:`INVALID_HEADER`,message:`Invalid header format: ${n}`,exitCode:2});t[n.slice(0,e).trim()]=n.slice(e+1).trim()}return t}function tI(e){return{status:e.status,channelId:e.channel.channelId,...e.account.name&&{account:e.account.name},payer:e.channel.descriptor.payer,payee:e.channel.descriptor.payee,authorizedSigner:e.channel.descriptor.authorizedSigner,token:e.channel.descriptor.token,escrow:e.channel.escrow,chainId:e.channel.chainId,cumulativeAmount:e.channel.cumulativeAmount.toString(),confirmedSpend:e.spent.toString(),deposit:e.channel.deposit.toString(),units:e.units,resourceUrl:e.endpoint,createdAt:e.createdAt,updatedAt:e.updatedAt}}function nI(e,t){throw e instanceof L?e:e instanceof nF?new L({code:e.code,message:e.message,exitCode:e.exitCode,cause:e}):e instanceof tF?new L({code:e.code,message:e.message,exitCode:65,cause:e}):new L({code:t,message:e instanceof Error?e.message:String(e),exitCode:75,...e instanceof Error&&{cause:e}})}async function rI(e,t){let n=await sk(t??e.account.name);if(!UM(n.account,e.channel.descriptor))throw new L({code:`SESSION_ACCOUNT_MISMATCH`,message:`Account ${n.account.address} cannot sign for session ${e.channel.channelId}.`,exitCode:2});return n}async function iI(e,t){if(t.network&&QF(t.network)!==e)throw new L({code:`CHAIN_MISMATCH`,message:`Session uses chainId ${e}, not ${t.network}.`,exitCode:2});let n=t.network??$F(e),r=LO(t.rpcUrl,{network:n}),i=await zO({network:n,rpcUrl:r});if(i.id!==e)throw new L({code:`CHAIN_MISMATCH`,message:`Session uses chainId ${e}, but RPC is chainId ${i.id}.`,exitCode:2});return M({chain:i,transport:le(r)})}const aI=cb(`sessions`,{description:`Manage persistent payment sessions (list, view, close)`}).command(`list`,{description:`List persistent payment sessions`,options:p.object({account:p.string().optional().describe(`Filter by account name`),network:p.enum([`mainnet`,`testnet`]).optional().describe(`Filter by Tempo network`)}),output:p.object({sessions:p.array(YF)}),alias:{account:`a`},async run(e){try{let t=(await fF().list()).filter(t=>!(e.options.account&&t.account.name!==e.options.account||e.options.network&&t.channel.chainId!==QF(e.options.network))).map(tI),n={sessions:t};if(e.format===`json`&&e.formatExplicit)return e.ok(n);t.length===0&&console.log(`No sessions.`);for(let[e,n]of t.entries())e>0&&console.log(``),oI(n);return}catch(e){return nI(e,`SESSION_LIST_FAILED`)}}}).command(`view`,{description:`View a persistent payment session`,args:p.object({channelId:p.string().describe(`Full session channel ID`)}),output:YF,async run(e){try{let t=await fF().get(e.args.channelId);if(!t)throw new L({code:`SESSION_NOT_FOUND`,message:`Session ${e.args.channelId} was not found.`,exitCode:2});let n=tI(t);if(e.format===`json`&&e.formatExplicit)return e.ok(n);oI(n);return}catch(e){return nI(e,`SESSION_VIEW_FAILED`)}}}).command(`close`,{description:`Cooperatively close persistent payment sessions`,usage:[{suffix:`<channel-id> [options]`},{suffix:`--all --yes [options]`}],args:p.object({channelId:p.string().optional().describe(`Full session channel ID`)}),options:p.object({account:p.string().optional().describe(`Account name`),all:p.boolean().optional().default(!1).describe(`Close every matching session`),header:p.array(p.string()).optional().describe(`Add close request header (repeatable)`),network:p.enum([`mainnet`,`testnet`]).optional().describe(`Tempo network`),rpcUrl:p.string().optional().describe(`RPC endpoint (env: MPPX_RPC_URL)`),url:p.string().optional().describe(`Override the stored resource URL`),yes:p.boolean().optional().default(!1).describe(`Confirm closing every session`)}),output:p.union([XF,ZF]),alias:{account:`a`,header:`H`,rpcUrl:`r`},async run(e){if(e.options.all&&e.args.channelId)return e.error({code:`INVALID_SESSION_CLOSE`,message:`Specify a channel ID or --all, not both.`,exitCode:2});if(!e.options.all&&!e.args.channelId)return e.error({code:`INVALID_SESSION_CLOSE`,message:`Specify a channel ID or --all.`,exitCode:2});if(e.options.all&&!e.options.yes)return e.error({code:`CONFIRMATION_REQUIRED`,message:`Closing all sessions requires --yes.`,exitCode:2});if(e.options.all&&(e.options.header||e.options.rpcUrl||e.options.url))return e.error({code:`INVALID_SESSION_CLOSE`,message:`--header, --rpc-url, and --url only apply to a single session.`,exitCode:2});let t=fF();async function n(e,n={}){let r=await t.get(e);if(!r)throw new L({code:`SESSION_NOT_FOUND`,message:`Session ${e} was not found.`,exitCode:2});let i=dF(r.channel),a=await t.acquire(i);try{let r=await t.get(e);if(!r)throw new L({code:`SESSION_NOT_FOUND`,message:`Session ${e} was not found.`,exitCode:2});let a=await rI(r,n.account),o=await iI(r.channel.chainId,n);if(xj({...r.channel.descriptor,escrow:r.channel.escrow,chainId:r.channel.chainId}).toLowerCase()!==r.channel.channelId.toLowerCase())throw new L({code:`SESSION_STATE_INVALID`,message:`Stored descriptor does not derive the session channel ID.`,exitCode:65});if((await yM(o,r.channel.channelId,r.channel.escrow)).deposit===0n)return await t.remove(r.channel.channelId),{channelId:r.channel.channelId,status:`already-closed`,spent:r.spent.toString()};let s=SF(n.resourceUrl??r.endpoint),c={...a.source===`keychain`&&{name:a.accountName},address:a.account.address},l=(e=r.challenge)=>({status:`closing`,account:c,endpoint:s,challenge:e,...r.receipt&&{receipt:r.receipt},spent:r.spent,units:r.units}),u=await t.upsert({...l(),channel:r.channel}),d=eI(n.headers),f=u.challenge,p=await eF({channel:u.channel,challenge:u.challenge,fetch:async(e,t)=>globalThis.fetch(e,{...t,headers:{...d,...Mw(t?.headers)}}),input:s,spent:u.spent,async onChallenge(e){f=e,await t.upsert({...l(e),channel:u.channel})},manager:{account:a.account,client:o,channelStore:pF(t,{scope:i,selection:u.channel.channelId,context:()=>l(f)})}});return await t.remove(u.channel.channelId),{channelId:u.channel.channelId,status:`closed`,spent:p.receipt.spent,txHash:p.receipt.txHash}}finally{await a.release()}}try{if(e.options.all){let r=(await t.list()).filter(t=>(!e.options.account||t.account.name===e.options.account)&&(!e.options.network||t.channel.chainId===QF(e.options.network))),i=[],a=[];for(let t of r)try{i.push(await n(t.channel.channelId,{account:e.options.account,network:e.options.network}))}catch(e){a.push({channelId:t.channel.channelId,message:e instanceof Error?e.message:String(e)})}let o={closed:i,failed:a};if(a.length>0){for(let e of a)process.stderr.write(`${e.channelId}: ${e.message}\n`);process.exitCode=1}if(e.format===`json`&&e.formatExplicit)return e.ok(o);for(let e of i)console.log(`${e.channelId} ${e.status} ${e.spent}`);for(let e of a)console.log(`${e.channelId} failed ${e.message}`);return}let r=await n(e.args.channelId,{account:e.options.account,headers:e.options.header,network:e.options.network,resourceUrl:e.options.url,rpcUrl:e.options.rpcUrl});if(e.format===`json`&&e.formatExplicit)return e.ok(r);console.log(`${r.channelId} ${r.status} ${r.spent}`),r.txHash&&console.log(` transaction ${r.txHash}`);return}catch(e){return nI(e,`SESSION_CLOSE_FAILED`)}}});function oI(e){console.log(e.channelId),console.log(` status ${e.status}`),console.log(` cumulative amount ${e.cumulativeAmount}`),console.log(` confirmed spend ${e.confirmedSpend}`),console.log(` deposit ${e.deposit}`),console.log(` chain ${e.chainId}`),console.log(` payer ${e.payer}`),console.log(` payee ${e.payee}`),console.log(` token ${e.token}`),console.log(` resource ${e.resourceUrl}`)}Ox(),aN(),hN(),TN(),GE(),dk(),ZO();function sI(e,t){if(t&&e!==`auto`&&t.toLowerCase()!==e.toLowerCase())throw new L({code:`SESSION_SELECTION_CONFLICT`,message:`--session and -M channel= select different sessions.`,exitCode:2});let n=t??e;if(n===`auto`||n===`new`)return n;if(/^0x[0-9a-fA-F]{64}$/.test(n))return n.toLowerCase();throw new L({code:`INVALID_SESSION`,message:`Session must be auto, new, or a 32-byte channel ID.`,exitCode:2})}function cI(e){let t=e.allowCustomEscrow;if(t!==void 0){if(t===`true`)return!0;if(t===`false`)return!1;throw new L({code:`INVALID_ALLOW_CUSTOM_ESCROW`,message:`allowCustomEscrow must be true or false.`,exitCode:2})}}function lI(e,t,n,r){if(t.deposit!==void 0)return t.deposit;let i=e.request.suggestedDeposit;return typeof i==`string`?se(BigInt(i),r):n?`10`:void 0}function uI(e,t){if(!e)return;if(e.challengeId!==t.challenge.id)throw Error(`Session receipt challenge does not match the paid request.`);if(e.channelId.toLowerCase()!==t.channelId.toLowerCase())throw Error(`Session receipt channel does not match the selected channel.`);let n=BigInt(e.acceptedCumulative),r=BigInt(e.spent);if(n>t.cumulative)throw Error(`Session receipt exceeds the local cumulative authorization.`);if(r>n)throw Error(`Session receipt spend exceeds its accepted authorization.`);return e}function dI(e,t){return e.channel.channelId.toLowerCase()===t.toLowerCase()}function fI(e){if(e.trim()!==`[DONE]`){if(e.length===0){process.stdout.write(`
|
|
243
243
|
`);return}try{let t=JSON.parse(e);process.stdout.write(t.token??t.choices?.[0]?.delta?.content??e)}catch{process.stdout.write(e)}}}async function pI(e){let{options:t}=e,n=await sk(t.account),r=LO(t.rpcUrl,{network:t.network}),i=await zO({network:t.network,rpcUrl:r}),a=M({chain:i,transport:le(r)}),o=cI(e.methodOptions),s=await LM({allowCustomEscrow:o,challenge:e.challenge,getClient:async()=>a});if(s.chainId!==i.id)throw new L({code:`CHAIN_MISMATCH`,message:`Challenge requires chainId ${s.chainId}, but RPC is chainId ${i.id}.`,exitCode:2});let{decimals:c}=await UO(a,s.token,n.account.address,{requireDecimals:!0}),l={payer:n.account.address,payee:s.payee,token:s.token,escrow:s.escrow,chainId:s.chainId},u=sI(t.session,e.methodOptions.channel),d=e.registry??fF(),f=await d.acquire(l).catch(e=>{throw e instanceof nF?new L({code:e.code,message:e.message,exitCode:e.exitCode,cause:e}):e});try{let r=u===`auto`?await d.getPreferred(l):u===`new`?void 0:u,s=r?await d.get(r):void 0;if(r&&!s)throw new L({code:`SESSION_NOT_FOUND`,message:`Session ${r} was not found.`,exitCode:2});if(s&&u!==`auto`&&(s.status!==`open`||!s.channel.opened||uF(dF(s.channel))!==uF(l)||!UM(n.account,s.channel.descriptor)))throw new L({code:`SESSION_MISMATCH`,message:`Session ${s.channel.channelId} cannot be used for this account and challenge.`,exitCode:2});let f=s?.status===`open`&&s.channel.opened?s:void 0,p=f?`open`:`opening`,m=e.challenge,h=f?.receipt,g=f?.spent??0n,_=f?.units??0,v=SF(e.endpoint),y={...n.source===`keychain`&&{name:n.accountName},address:n.account.address},b=pF(d,{scope:l,selection:u,context:()=>({status:p,account:y,endpoint:v,challenge:m,...h&&{receipt:h},spent:g,units:_}),onNewChannel(){p=`opening`,h=void 0,g=0n,_=0}}),{signal:x,...S}=e.init,C={...S,...x&&{signal:x},onReceipt(e){h=e,g=g>BigInt(e.spent)?g:BigInt(e.spent),_=Math.max(_,e.units??0)}};if(e.challengeResponse.status!==402||e.challengeResponse.bodyUsed)throw Error(`Session manager requires an unconsumed 402 challenge response.`);let w=!0,T=wN({account:n.account,allowCustomEscrow:o,bootstrap:!1,client:a,channelStore:b,credentialContext:e.credentialContext,decimals:c,maxDeposit:lI(e.challenge,e.methodOptions,HO(i),c),fetch:async(t,n)=>w?(w=!1,e.challengeResponse):e.fetch(t,n)});f&&pN(T).rehydrate({channel:f.channel,challenge:e.challenge,input:e.fetchInput,spent:f.spent});let{onReceipt:ee,...E}=C,D=await T.fetch(e.fetchInput,E);if(t.fail&&D.status>=400)throw new L({code:`HTTP_ERROR`,message:`HTTP error ${D.status}`,exitCode:22});if(D.status===402)throw new L({code:`PAYMENT_REJECTED`,message:`Payment rejected.`,exitCode:75});EO(D,{include:!1,verbose:t.include?Math.max(t.verbose,2):t.verbose,silent:t.silent}),m=D.challenge??e.challenge;let O=T.channelId;if(!O)throw Error(`Session manager did not select a channel.`);if(h=uI(D.receipt,{challenge:m,channelId:O,cumulative:T.cumulative})??h,FE(D)){let t=pN(T).consumeSseResponse(e.fetchInput,D,{onReceipt:ee,signal:e.init.signal??void 0});for await(let e of t)fI(e)}else process.stdout.write(Buffer.from(await D.arrayBuffer()));let k=await d.get(O);if(!k||!dI(k,O))throw Error(`Session ${O} was not persisted.`);if(h){let e=uI(h,{challenge:m,channelId:O,cumulative:T.cumulative});if(!e)throw Error(`Session receipt validation failed.`);h=e,g=g>BigInt(e.spent)?g:BigInt(e.spent),_=Math.max(_,e.units??0),p=`open`}await d.upsert({status:p,channel:k.channel,account:y,endpoint:v,challenge:m,...h&&{receipt:h},spent:g,units:_}),t.silent||(e.info(`Session retained ${O}\n`),e.info(`Close with: mppx sessions close ${O}\n`))}finally{await f.release()}}ZO();function Q(e,t){return t?{label:e,detail:t,severity:`pass`}:{label:e,severity:`pass`}}function $(e,t,n){let r={label:e,severity:`fail`};return t&&(r.detail=t),n&&(r.hint=n),r}function mI(e,t,n){let r={label:e,severity:`warn`};return t&&(r.detail=t),n&&(r.hint=n),r}function hI(e,t,n){let r=t?{label:e,detail:t,severity:`skip`}:{label:e,severity:`skip`};return n&&(r.hint=n),r}const gI={pass:X.green(`✓`),fail:X.red(`✗`),warn:X.yellow(`⚠`),suggested:X.cyan(`◇`),skip:X.dim(`○`)};function _I(e){let t=gI[e.severity],n=e.detail?`${e.label} (${e.detail})`:e.label;console.log(` ${t} ${n}`),e.hint&&e.severity!==`pass`&&console.log(X.dim(` → ${e.hint}`))}function vI(e){console.log(`\n${X.bold(e)}`)}function yI(e,t){for(let n of e)_I(n),n.severity===`pass`?t.passed++:n.severity===`fail`?t.failed++:n.severity===`warn`?t.warnings++:n.severity===`suggested`?t.suggested++:n.severity===`skip`&&t.skipped++}async function bI(e,t,n=15e3){let r=new AbortController,i=setTimeout(()=>r.abort(),n);try{return await fetch(e,{...t,redirect:`error`,signal:r.signal})}finally{clearTimeout(i)}}function xI(e){return e<1024?`${e}B`:e<1048576?`${(e/1024).toFixed(1)}KB`:`${(e/1048576).toFixed(1)}MB`}const SI=new Set([`get`,`post`,`put`,`patch`,`delete`,`head`,`options`,`trace`]);function CI(e){return typeof e==`string`&&/^0x[0-9a-fA-F]{40}$/.test(e)}function wI(e){return typeof e==`string`&&/^(0|[1-9][0-9]*)$/.test(e)}function TI(e){let t=e.indexOf(`:`);if(t<1)return null;let n=e.slice(0,t).toLowerCase();if(!SI.has(n))return null;let r=e.slice(t+1);if(!r)return null;let i=r.startsWith(`/`)?r:`/`+r;return{method:n.toUpperCase(),path:i}}function EI(e,t){if(e){try{let n=JSON.parse(e);if(n&&typeof n==`object`&&!Array.isArray(n)){let e=Object.keys(n);if(e.length>0&&e.every(e=>e.startsWith(`/`))){let e=n[t];return e===void 0?void 0:typeof e==`string`?e:JSON.stringify(e)}}}catch{}return e}}function DI(e){if(!e)return{};let t={};for(let n of e){let e=n.indexOf(`:`);e>0&&(t[n.slice(0,e).trim().toLowerCase()]=n.slice(e+1).trim())}return t}async function OI(e){let t=new URL(`openapi.json`,e.replace(/\/?$/,`/`)).href;try{let e=await bI(t,{});if(!e.ok)return{error:`HTTP ${e.status}`};let n=await e.text();return{doc:JSON.parse(n),raw:n}}catch(e){return e instanceof DOMException&&e.name===`AbortError`?{error:`Request timed out after 15s`}:e instanceof SyntaxError?{error:`Invalid JSON`}:{error:e.message}}}async function kI(e){let t=new URL(`/llms.txt`,e).href,n=`llms.txt found and nonempty`;try{let e=await bI(t,{});if(!e.ok)throw Error(`HTTP ${e.status}`);let r=(await e.text()).trim();if(!r)throw Error(`Empty document`);let i=e.headers.get(`content-type`)?.split(`;`)[0]?.trim().toLowerCase();if(i&&!i.startsWith(`text/`)||i===`text/html`||/^<(?:!doctype html|html|head|body)\b/i.test(r))throw Error(`Expected text documentation, received non-text or HTML content`);return Q(n,t)}catch(e){return{label:n,detail:`${t}: ${e.message}`,severity:`suggested`,hint:`Serve nonempty text documentation at /llms.txt.`}}}function AI(e){let t=[],n=[],r=e.paths;if(!r)return[];for(let[e,i]of Object.entries(r)){let r=NI(i.parameters);for(let[a,o]of Object.entries(i)){if(!SI.has(a)||!o||typeof o!=`object`||Array.isArray(o))continue;let i=o,s=PI(r,NI(i.parameters)),c=s.length>0?s:void 0;if(i[`x-payment-info`]){let n=i[`x-payment-info`];t.push({method:a.toUpperCase(),path:e,amount:n.amount,parameters:c})}else{let t=i.responses;t&&`402`in t&&n.push({method:a.toUpperCase(),path:e,parameters:c})}}}return t.length>0?t:n}function jI(e,t){let n=e.paths;if(!n)return;let r=n[t.path];if(!r)return;let i=r[t.method.toLowerCase()];if(!i?.requestBody)return;let a=i.requestBody.content?.[`application/json`];if(!a)return;if(a.example)return JSON.stringify(a.example);if(a.examples&&typeof a.examples==`object`){let e=Object.values(a.examples)[0];if(e?.value)return JSON.stringify(e.value)}let o=a.schema,s=new Set;if(o=II(o,e,s),!o||o.type!==`object`)return;let c=LI(o,e,s);if(c&&typeof c==`object`&&Object.keys(c).length>0)return JSON.stringify(c)}function MI(e,t,n){if(!t.path.startsWith(`/`)||t.path.startsWith(`//`)||t.path.includes(`\\`))throw Error(`OpenAPI path must be an origin-relative path: ${t.path}`);let r=t.path;t.parameters&&(r=FI(r,t.parameters));let i=r.startsWith(`/`)?r.slice(1):r,a=new URL(e.replace(/\/?$/,`/`)),o=new URL(i,a);if(o.origin!==a.origin)throw Error(`OpenAPI path must be an origin-relative path: ${t.path}`);let s=o.href;if(n){let e=new URL(s);for(let t of n){let[n,...r]=t.split(`=`);n&&e.searchParams.set(n,r.join(`=`))}s=e.href}return s}function NI(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(!n||typeof n!=`object`)continue;let e=n;e.in===`path`&&typeof e.name==`string`&&t.push(e.schema&&typeof e.schema==`object`?{name:e.name,in:`path`,schema:e.schema,example:e.example}:{name:e.name,in:`path`,example:e.example})}return t}function PI(e,t){let n=[...e];for(let e of t){let t=n.findIndex(t=>t.name===e.name);t>=0?n[t]=e:n.push(e)}return n}function FI(e,t){return e.replace(/\{([^}]+)\}/g,(e,n)=>{let r=t.find(e=>e.name===n&&e.in===`path`);if(!r)return e;let i=r.example??r.schema?.example??r.schema?.default;return i===void 0?e:encodeURIComponent(String(i))})}function II(e,t,n){if(!e||typeof e.$ref!=`string`)return e;if(n?.has(e.$ref))return;let r=e.$ref.replace(/^#\//,``).split(`/`),i=t;for(let e of r)if(i&&typeof i==`object`)i=i[e];else return;return i}function LI(e,t,n){let r=II(e,t,n)??e;if(r.const!==void 0)return r.const;if(r.example!==void 0)return r.example;if(r.default!==void 0)return r.default;switch(r.type){case`string`:return r.enum&&Array.isArray(r.enum)?r.enum[0]:r.format===`email`?`test@example.com`:r.format===`uuid`?`00000000-0000-0000-0000-000000000000`:r.format===`uri`||r.format===`url`?`https://example.com`:r.format===`date`?`2026-01-01`:r.pattern===`^\\d{5}(?:-\\d{4})?$`?`10001`:r.pattern===`^[A-Z]{2}$`?`US`:`test`;case`number`:case`integer`:return r.minimum??1;case`boolean`:return!0;case`array`:return[];case`object`:{let e=r.properties;if(!e)return{};let i=r.required||[],a={};for(let r of i){let i=e[r];i&&(a[r]=LI(i,t,n))}return a}default:return null}}_S(),Fx(),aw(),ZO();const RI=Object.values(Be);function zI(e){let t=e.request.methodDetails;return typeof t?.chainId==`number`&&RI.find(e=>e.id===t.chainId)?.testnet===!0}async function BI(e,t,n,r){let i=[],a;try{a=MI(e,t,r?.query)}catch(e){return i.push($(`Request URL valid`,e.message)),{results:i}}let o=DI(r?.extraHeaders),s;r?.body&&!r.bodyIsAutogenerated&&(s=r.body,o[`content-type`]||=`application/json`);let c;try{let e={method:t.method};Object.keys(o).length>0&&(e.headers=o),s&&(e.body=s),c=await bI(a,e)}catch(e){return i.push($(`Request failed`,e.message)),{results:i}}if(!s&&(c.status===400||c.status===422)){let e=r?.body??(r?.discoveryDoc?jI(r.discoveryDoc,t):void 0);if(e){n&&console.log(X.dim(` Retrying with body: ${e}`)),s=e,o[`content-type`]=`application/json`;try{c=await bI(a,{method:t.method,headers:o,body:s})}catch(e){return i.push($(`Request failed`,e.message)),{results:i}}}}if(c.status!==402)return c.status===200?i.push(hI(`Returns 402 without credentials`,`Got 200 (endpoint may not require payment in all cases)`)):c.status===401||c.status===403?i.push(hI(`Returns 402 without credentials`,`Got ${c.status} (endpoint requires auth before payment gate)`)):i.push($(`Returns 402 without credentials`,`Got ${c.status} instead`,c.status===400||c.status===422?`Server requires a valid request body before returning 402. Add a requestBody schema with examples to your OpenAPI doc, or use --body.`:`Endpoints that require payment must return HTTP 402 with a WWW-Authenticate: Payment header when no valid credential is provided.`)),{results:i};i.push(Q(`Returns 402 without credentials`));let l=c.headers.get(q.wwwAuthenticate),u=ZI(c);if(!l)return u.length>0?(i.push(hI(`Not an MPP endpoint`,`No WWW-Authenticate header — x402 protocol detected`)),i.push(...u)):i.push(hI(`Not an MPP endpoint`,`No WWW-Authenticate header (may be x402 or other protocol)`)),{results:i};if(u.length>0&&i.push(...u),!l.startsWith(`${Ax.payment} `))return i.push(hI(`Not an MPP endpoint`,`WWW-Authenticate scheme is not Payment`)),{results:i};i.push(Q(`WWW-Authenticate header present`,`Payment scheme`));let d;try{d=cS(c)}catch(e){let t=e.message;i.push($(`Challenge parseable`,t,`The Payment challenge must include a request="<base64url>" parameter containing JSON-encoded payment details.`));let n=l.slice(l.indexOf(` `)+1);return i.push(mI(`Received`,n.length>200?n.slice(0,200)+`...`:n)),{results:i}}if(d.length===0)return i.push($(`Challenge parseable`,`No Payment challenges found in header`)),{results:i};let f=d.map(e=>`${e.method}/${e.intent}`).join(`, `);i.push(Q(`Challenge parseable`,d.length===1?f:`${d.length} methods: ${f}`));let p=new URL(e).hostname;VI(d,i),HI(d,i),UI(d,i),WI(d,p,i);let m=d.length>1;for(let e of d){let t=e.request;GI(e,t,i,m)}if(n)for(let e of d)console.log(X.dim(` Challenge (${e.method}/${e.intent}): ${JSON.stringify(e,null,2)}`));return{results:i,resolvedBody:s,challenges:d}}function VI(e,t){let n=e.filter(e=>!e.id);n.length===0?t.push(Q(`Challenge has id`)):t.push($(`Challenge has id`,`${n.length} missing`,`Every challenge must include a unique id field.`))}function HI(e,t){let n=e.filter(e=>!e.realm);n.length===0?t.push(Q(`Challenge has realm`)):t.push($(`Challenge has realm`,`${n.length} missing`,`Set realm to your server's hostname.`))}function UI(e,t){let n=new Date;for(let r of e){if(!r.expires){t.push(mI(`Challenge has expiration`,`missing expires field`,`Add an expires field (ISO 8601) to prevent replay attacks.`));return}if(new Date(r.expires)<=n){t.push($(`Challenge expires in the future`,`Expired at ${r.expires}`,`The expires timestamp must be in the future.`));return}}let r=Math.min(...e.map(e=>new Date(e.expires).getTime()-n.getTime()));t.push(Q(`Challenge expires in the future`,`${Math.round(r/6e4)}m from now`))}function WI(e,t,n){let r=e=>e.toLowerCase().replace(/\.$/,``),i=r(t),a=[...new Set(e.map(e=>e.realm??``))].filter(e=>{let t=r(e);return!t||t!==i&&!i.endsWith(`.${t}`)});a.length>0?n.push($(`Realm matches server hostname`,`realm="${a[0]}" vs host="${t}"`,`Set the realm to your production hostname (or base domain) in the challenge.`)):n.push(Q(`Realm matches server hostname`))}function GI(e,t,n,r){let i=r?`[${e.method}] `:``;e.method===jx.tempo?KI(t,i,n,e):e.method===jx.stripe?qI(t,i,n):e.method===jx.evm&&JI(e,t,i,n)}function KI(e,t,n,r){let i=e.methodDetails,a=Array.isArray(i?.splits)&&i.splits.length>0;if(CI(e.recipient)?n.push(Q(`${t}Valid recipient address`)):e.recipient===void 0&&a?n.push(Q(`${t}Uses splits (no single recipient)`)):e.recipient===void 0?n.push($(`${t}Valid recipient address`,`Missing recipient (and no splits defined)`,`Set request.recipient to a valid 0x address, or use methodDetails.splits for multiple recipients.`)):n.push($(`${t}Valid recipient address`,`Got: ${String(e.recipient)}`,`Set request.recipient to a valid 0x-prefixed 40-hex-char address.`)),CI(e.currency)){let e=zI(r)?`testnet`:`mainnet`;n.push(Q(`${t}Valid currency address`,e))}else n.push($(`${t}Valid currency address`,`Got: ${String(e.currency)}`,`Set request.currency to a valid token address.`));YI(e,t,n,`token's smallest unit (e.g. "10000" = $0.01 for 6-decimal tokens)`)}function qI(e,t,n){YI(e,t,n,`currency's smallest unit (e.g. "100" = $1.00 for USD)`);let r=e.currency;typeof r==`string`&&/^[a-z]{3}$/i.test(r)?n.push(Q(`${t}Valid currency code`,r.toUpperCase())):n.push($(`${t}Valid currency code`,r?`Got: ${r}`:`missing`,`Must be a three-letter ISO currency code (e.g. "usd").`));let i=e.methodDetails,a=i?.networkId;a?n.push(Q(`${t}Has networkId`,a.slice(0,20))):n.push($(`${t}Has networkId`,`Missing methodDetails.networkId`,`Set methodDetails.networkId to your Stripe Business Network profile ID.`));let o=i?.paymentMethodTypes;Array.isArray(o)&&o.length>0?n.push(Q(`${t}Has paymentMethodTypes`,o.join(`, `))):n.push($(`${t}Has paymentMethodTypes`,`Missing or empty`,`Set methodDetails.paymentMethodTypes to an array (e.g. ["card"]).`))}function JI(e,t,n,r){if(CI(t.recipient)?r.push(Q(`${n}Valid recipient address`)):r.push($(`${n}Valid recipient address`,`Got: ${String(t.recipient)}`,`Set request.recipient to a valid 0x-prefixed Ethereum address.`)),CI(t.currency)){let t=zI(e)?`testnet`:`mainnet`;r.push(Q(`${n}Valid currency address`,t))}else r.push($(`${n}Valid currency address`,`Got: ${String(t.currency)}`,`Set request.currency to a valid ERC-20 token contract address.`));YI(t,n,r,`token's smallest unit`);let i=t.methodDetails?.chainId;typeof i==`number`&&i>0?r.push(Q(`${n}Has chainId`,String(i))):r.push($(`${n}Has chainId`,`Missing methodDetails.chainId`,`Set methodDetails.chainId to the EVM chain ID.`))}function YI(e,t,n,r){wI(e.amount)?n.push(Q(`${t}Amount is valid integer string`)):e.amount===void 0||e.amount===null?n.push($(`${t}Amount is valid integer string`,`Missing amount`,`Set request.amount to a string of digits in the ${r}.`)):n.push($(`${t}Amount is valid integer string`,`Got: ${String(e.amount)}`,`request.amount must be a string of digits (no decimals, no prefix).`))}async function XI(e,t,n){let r=[],i=MI(e,t,n?.query),a={[q.authorization]:`${Ax.payment} dGhpcyBpcyBnYXJiYWdl`},o;n?.body&&(o=n.body,a[`content-type`]=`application/json`);try{let e=await bI(i,{method:t.method,headers:a,body:o??null});e.status===402?(r.push(Q(`Malformed credential returns 402`,`not 500`)),e.headers.get(q.wwwAuthenticate)?.startsWith(`${Ax.payment} `)?r.push(Q(`Error response includes fresh challenge`)):r.push(mI(`Error response includes fresh challenge`,`No WWW-Authenticate header`,`When rejecting an invalid credential, respond with 402 and include a fresh WWW-Authenticate: Payment challenge so the client can retry.`))):e.status>=500?r.push($(`Malformed credential returns 402`,`Got ${e.status} (server error)`,`When the Authorization header contains an invalid credential, respond with 402 (not 500). Catch credential validation errors and return a fresh challenge.`)):r.push(mI(`Malformed credential returns 402`,`Got ${e.status}`,`When the Authorization header contains an invalid Payment credential, respond with 402 and a fresh WWW-Authenticate challenge. Returning ${e.status} prevents the client from retrying with a valid payment.`))}catch(e){r.push($(`Error handling test`,e.message))}return r}function ZI(e){let t=[],n=e.headers.get(`payment-required`)??e.headers.get(`x-payment-required`);if(!n)return t;let r=e.headers.get(`payment-required`)?`PAYMENT-REQUIRED`:`X-Payment-Required`;try{let e=QC(n);t.push(Q(`x402 payment challenge`,`${r} with valid x402 v${e.x402Version} payload`))}catch{t.push(mI(`x402 payment challenge`,`${r} header present but failed schema validation`,`Ensure the PAYMENT-REQUIRED header contains a valid base64-encoded JSON payload matching the x402 schema.`))}return t}const QI=[`No discovery document found, endpoints could not be automatically found.`,`MPP servers must serve an OpenAPI document at /openapi.json with x-payment-info extensions.`,`The MPP SDKs can automatically expose this endpoint. See https://mpp.dev/sdk.`,``,`To test a specific endpoint: mppx validate <url> --endpoint POST:/your/path`].join(`
|
|
244
244
|
`);Zx();const $I={fundingCurrency:J.optional(J.string()),method:J.string(),reference:J.string(),externalId:J.optional(J.string()),subscriptionId:J.optional(J.string()),status:J.literal(`success`),timestamp:Hx()};J.object($I);const eL=J.looseObject($I);function tL(e){let t=k.toString(e);return nL(JSON.parse(t))}function nL(e){return eL.parse(e)}_S(),Fx(),rP(),CO(),_O(),dk(),ZO();async function rL(e,t,n){try{n||console.log(X.dim(` Provisioning testnet wallet and funding via faucet...`));let t=he(),r=_e(t),i=M({chain:ye,transport:le()});if(FD(e)){let{chainId:t,operator:n}=e.request.methodDetails??{};if(t===ye.id&&typeof n==`string`&&ue(n)&&n!==me){let e=await Fe.faucet.fund(i,{account:n});await Promise.all(e.map(e=>Me(i,{hash:e})))}}let a=await Fe.faucet.fund(i,{account:r});await Promise.all(a.map(e=>Me(i,{hash:e}))),n||console.log(X.dim(` Using wallet: ${r.address}`));let o=e.request.currency;if(typeof o==`string`)for(let e=0;e<10;e++){let e=await Fe.token.getBalance(i,{account:r.address,token:o}).catch(()=>void 0);if(e&&e.amount>0n)break;await new Promise(e=>setTimeout(e,500))}return{methods:[...eP({account:r})]}}catch(e){t&&console.log(X.dim(` Provisioning failed: ${e.message}`));return}}async function iL(){let e=ik(),{isTempoAccount:t}=await Promise.resolve().then(()=>(ZO(),wO)),{resolveTempoAccount:n}=await Promise.resolve().then(()=>(LP(),iP));if(t(e)){let t=n(e);if(t)return t.wallet_address}try{return(await ck()).address}catch{return}}function aL(e){try{let{execSync:t}=m,n=t(`stripe config --list`,{encoding:`utf8`,timeout:5e3}).match(/test_mode_api_key\s*=\s*'([^']+)'/);if(n?.[1])return e&&console.log(X.dim(` Using Stripe test key from stripe CLI`)),n[1]}catch{}}async function oL(e,t,n){let r=M({chain:e,transport:le()}),[i,a,o]=await Promise.all([n?De(r,{address:t,abi:oe,functionName:`balanceOf`,args:[n]}):0n,De(r,{address:t,abi:oe,functionName:`decimals`}),De(r,{address:t,abi:oe,functionName:`symbol`}).catch(()=>void 0)]);return{balance:i,decimals:a,symbol:o??void 0}}function sL(e){return Object.values(Be).find(t=>t.id===e)}function cL(e,t){return`$${(Number(e)/10**t).toFixed(2)}`}function lL(e){switch(e.method){case jx.evm:return`no EVM payment plugin yet`;case`solana`:return`no Solana payment plugin yet`;default:return`no plugin for ${e.method}/${e.intent}`}}async function uL(e,t,n,r){let i=[],a=MI(e,t,r.query),o=DI(r.extraHeaders),s;r.body&&(s=r.body,o[`content-type`]=`application/json`);let c;try{c=await bI(a,{method:t.method,headers:o,body:s??null})}catch(e){return i.push($(`Payment: fetch challenge`,e.message)),i}if(c.status!==402)return i.push(hI(`Payment: skipped`,`Endpoint returned ${c.status}`)),i;let l;try{l=cS(c)}catch(e){return i.push($(`Payment: parse challenge`,e.message)),i}if(l.length===0)return i.push($(`Payment: parse challenge`,`No Payment challenges in response`)),i;let u=[];WI(l,new URL(a).hostname,u);let d=u.filter(e=>e.severity===`fail`);if(d.length>0)return i.push(...d),r.onResults?.(d),i;let f=await YP().catch(()=>void 0),p=l.find(e=>{if(e.method!==jx.tempo||qP(f?.config)?.some(t=>t.name===e.method&&t.intent===e.intent))return!1;let t=e.request.methodDetails;return typeof t?.chainId==`number`&&t.chainId!==vO.mainnet});if(p){let e=await rL(p,n,r.silent);if(e)try{await hL(i,await wT({methods:e.methods,polyfill:!1,async onChallenge(e,{createCredential:t}){ZP(p,e);let n=await t(await UP(e,f?.config.extensions));return i.push(Q(`Payment: submitted`,`ephemeral testnet wallet`)),n},fetch:(e,t)=>bI(e,t??{},3e4)}).fetch(a,{method:t.method,headers:o,body:s??null}),n,ye)}catch(e){i.push($(`Payment: create credential`,e.message))}else i.push($(`Payment: auto-provision wallet`,`Failed to create and fund testnet wallet`));r.onResults?.(i)}let m=r.interactive,h=process.env.MPPX_STRIPE_SECRET_KEY??aL(n),g=h?.startsWith(`sk_test_`)||h?.startsWith(`rk_test_`),_=new Set([jx.tempo,jx.evm,jx.stripe]);for(let e of l){if(e===p||!_.has(e.method))continue;r.silent||console.log(``);let c=i.length,l=()=>{r.onResults&&i.length>c&&r.onResults(i.slice(c))},u=`Payment [${e.method}]`;try{switch(e.method){case jx.tempo:case jx.evm:await dL(e,u,{results:i,url:a,endpoint:t,fetchHeaders:o,fetchBody:s,verbose:n,loaded:f,isInteractive:m,options:r});break;case jx.stripe:await fL(e,u,{results:i,url:a,endpoint:t,fetchHeaders:o,fetchBody:s,verbose:n,loaded:f,isInteractive:m,isStripeTestKey:g??!1,stripeKey:h,options:r})}}finally{l()}}return i}async function dL(e,t,n){let{results:r,url:i,endpoint:a,fetchHeaders:o,fetchBody:s,verbose:c,loaded:l,options:u}=n,d=e.request,f=d.methodDetails,p=wI(d.amount)?BigInt(d.amount):void 0,m=f?.decimals??d.decimals??6,h=d.currency,{plugin:g,method:_}=WP(e,l?.config);if(!g&&!_){r.push(hI(t,lL(e)));return}let v;if(!_){try{v=await iL()}catch{}if(!v){r.push(hI(t,`no wallet configured. Run "mppx account create" to create one.`));return}}let y;if(e.method===jx.tempo){let e=f?.chainId??(_?void 0:ve.id);e!==void 0&&(y=e===ye.id?ye:sL(e))}else if(e.method===jx.evm){let e=f?.chainId;e&&(y=sL(e))}if(p&&h&&!y){r.push(hI(t,`unable to resolve payment chain and verify token decimals`));return}let b;if(p&&h&&y)try{let n;if(e.method===jx.tempo){let e=await UO(M({chain:y,transport:le()}),h,v??me,{requireDecimals:!0});n=e.balance,m=e.decimals,b=e.symbol}else{let e=await oL(y,h,v);n=e.balance,m=e.decimals,b=e.symbol}if(v&&n<p){let e=cL(p,m),i=cL(n,m),a=b??`tokens`;r.push(hI(t,`insufficient balance (have ${i}, need ${e} ${a} on ${y.name})`,`Fund wallet ${v} with at least ${e} ${a} on ${y.name}.`));return}}catch(e){c&&console.log(X.dim(` Balance check skipped: ${e.message}`)),r.push(hI(t,`unable to verify token balance and decimals from the payment chain`));return}let x=p?cL(p,m):`unknown amount`,S=b??(h?.length===3?h.toUpperCase():``),C=y?.name,w=`${x}${S?` ${S}`:``}${C?` on ${C}`:``}`;if(u.silent||console.log(X.dim(v?` Attempting payment with wallet ${v}`:` Attempting payment with configured method`)),!_&&y?.testnet)u.silent||console.log(X.dim(` Auto-approved: ${w} (testnet)`));else if(!u.yes&&!n.isInteractive){r.push(hI(t,`non-interactive mode, use --yes to approve`));return}else if(u.yes)u.silent||console.log(X.dim(` Auto-approved: ${w}`));else if(!await FO(` ${X.yellow(`Pay`)} ${w}. Continue?`,!1)){r.push(hI(t,`declined`));return}let T,ee,E;if(g)try{let t=await g.setup({challenge:e,options:{network:`mainnet`},methodOpts:{}});T=t.methods,ee=t.createCredential,E=t.credentialContext}catch(e){r.push(hI(t,e.message));return}else T=[_];if(_&&FD(e)){let n=!0,u=wT({methods:[_],polyfill:!1,async onChallenge(n,{createCredential:i}){let a=await UP(n,l?.config.extensions);ZP(e,n);let o=await i(a);return r.push(Q(`${t}: submitted`)),o},fetch:async(t,r)=>n?(n=!1,new Response(null,{status:402,headers:{[q.wwwAuthenticate]:$x(e)}})):bI(t,r??{},3e4)});try{await hL(r,await u.fetch(i,{method:a.method,headers:o,body:s??null}),c,y)}catch(e){r.push($(t,e.message))}return}let D=await pL(e,T,ee,t,r,l?.config.extensions,E);D&&(g?.prepareCredentialRequest?.({challenge:e,credential:D,headers:o}),await mL(r,i,a,D,o,s,c,y,fS(e)))}async function fL(e,t,n){let{results:r,url:i,endpoint:a,fetchHeaders:o,fetchBody:s,verbose:c,loaded:l,options:u,stripeKey:d}=n,{plugin:f,method:p}=WP(e,l?.config);if(!f&&!p){r.push(hI(t,`no Stripe payment method available`));return}let m=!!(f&&!l?.config.plugins?.includes(f)&&n.isStripeTestKey),h=e.request,g=wI(h.amount)?BigInt(h.amount):void 0,_=h.decimals??2,v=h.currency,y=g?cL(g,_):`unknown amount`,b=v?.length===3?v.toUpperCase():``,x=`${y}${b?` ${b}`:``} via Stripe${m?` (testmode)`:``}`;if(m)u.silent||console.log(X.dim(` Attempting: ${x}`));else if(!u.yes&&!n.isInteractive){r.push(hI(t,`non-interactive mode, use --yes to approve`));return}else if(u.yes)u.silent||console.log(X.dim(` Auto-approved: ${x}`));else if(!await FO(` ${X.yellow(`Pay`)} ${x}. Continue?`,!1)){r.push(hI(t,`declined`));return}let S,C,w;if(f)try{let t={paymentMethod:`pm_card_visa`};d&&(t.secretKey=d);let n=await f.setup({challenge:e,options:{},methodOpts:t});S=n.methods,C=n.createCredential,w=n.credentialContext}catch(e){r.push(hI(t,e.message));return}else S=[p];let T=await pL(e,S,C,t,r,l?.config.extensions,w);if(T){if(f?.prepareCredentialRequest?.({challenge:e,credential:T,headers:o}),m){let n=await bI(i,{method:a.method,headers:{...o,[fS(e)]:T},body:s??null},3e4);n.status>=200&&n.status<300?r.push(Q(`${t}: successful`,`HTTP ${n.status}`)):r.push(hI(`${t}: server is in livemode`,void 0,`Run your server with a Stripe test key to automatically validate Stripe payments in testmode.`));return}await mL(r,i,a,T,o,s,c,void 0,fS(e))}}async function pL(e,t,n,r,i,a,o){let s=new Response(null,{status:402,headers:{[q.wwwAuthenticate]:$x(e)}});try{let c=await UP(e,a,o),l;return l=n?await n(s,c):await wT({methods:t,polyfill:!1}).createCredential(s,c),i.push(Q(`${r}: submitted`)),l}catch(e){let t=e.message;t.toLowerCase().includes(`insufficient`)?i.push(hI(r,`insufficient balance: ${t}`)):i.push($(r,t));return}}async function mL(e,t,n,r,i,a,o,s,c=q.authorization){let l;try{l=await bI(t,{method:n.method,headers:{...i,[c]:r},body:a??null},3e4)}catch(t){return e.push($(`Payment: send credential`,t.message)),e}return hL(e,l,o,s)}async function hL(e,t,n,r){if(t.status===402){let n=await t.text().catch(()=>``),r=`Payment rejected`;try{let e=JSON.parse(n);r=e.detail??e.title??r}catch{}return e.push($(`Payment: accepted`,r,`The server rejected a valid credential. Check that your payment verification logic accepts the credential format and that the payment was processed on-chain.`)),e}if(t.status>=400&&t.status<500)e.push(mI(`Payment: post-payment response`,`Got ${t.status}`,`Payment succeeded but the endpoint returned a client error. The endpoint likely requires request body parameters. Use --body to provide them.`));else if(t.status>=500)return e.push($(`Payment: server response`,`Got ${t.status}`,`Payment was accepted but the server errored while generating the response. Check server logs for the underlying error.`)),e;else e.push(Q(`Payment: successful`,`HTTP ${t.status}`));let i=t.headers.get(q.paymentReceipt);if(!i)e.push($(`Payment-Receipt header present`,void 0,`After accepting payment, include a Payment-Receipt header with a base64url-encoded JSON object containing: method, reference, status ("success"), and timestamp (ISO 8601).`));else{e.push(Q(`Payment-Receipt header present`));try{let t=tL(i);if(e.push(Q(`Receipt parseable`)),t.status===`success`?e.push(Q(`Receipt status is "success"`)):e.push($(`Receipt status is "success"`,`Got: ${t.status}`)),t.reference){let n=/^0x[0-9a-fA-F]{64}$/.test(t.reference),r=t.reference.startsWith(`pi_`);n||r?e.push(Q(`Receipt reference valid`,t.reference.slice(0,20)+`...`)):e.push(mI(`Receipt reference format`,t.reference.slice(0,40)))}else e.push(mI(`Receipt has reference`,`No reference field`));if(t.timestamp){let n=new Date(t.timestamp),r=Date.now()-n.getTime();r<6e4?e.push(Q(`Receipt timestamp recent`,`${Math.round(r/1e3)}s ago`)):e.push(mI(`Receipt timestamp recent`,`${Math.round(r/6e4)}m ago`))}n&&console.log(X.dim(` Receipt: ${JSON.stringify(t,null,2)}`))}catch(t){e.push($(`Receipt parseable`,t.message))}}let a=t.headers.get(`content-type`)??``,o=await t.text().catch(()=>``);if(o.length>0)e.push(Q(`Response body non-empty`,`${a.split(`;`)[0]}, ${xI(o.length)}`));else{let n=[...t.headers.entries()].filter(([e])=>!e.startsWith(`x-`)&&![`content-type`,`content-length`,`date`,`server`,`connection`,`keep-alive`,`cache-control`,`vary`,`access-control-allow-origin`,`payment-receipt`,`payment-session`,`payment-session-snapshot`].includes(e.toLowerCase()));n.length>0?e.push(mI(`Response body empty -- data may be in headers only`,n.map(([e])=>e).join(`, `))):e.push(mI(`Response body empty`))}if(a?e.push(Q(`Content-Type header set`,a.split(`;`)[0])):e.push(mI(`Content-Type header set`)),i)try{let t=tL(i);if(t.reference&&/^0x[0-9a-fA-F]{64}$/.test(t.reference)){let n=r?.blockExplorers?.default?.url;if(n){let r=n.includes(`tempo`)?`receipt`:`tx`;e.push(Q(`On-chain transaction`,`${n}/${r}/${t.reference}`))}}}catch{}return e}async function*gL(e){let t=e.url.replace(/\/$/,``).replace(/\/openapi\.json$/i,``),n=e.verbose??!1,r=await xL(t,e);yield{phase:`discovery`,discovery:r,results:r.checks};for(let i of r.endpoints){yield{phase:`endpoint`,endpoint:i};let a,o=!1;e.endpoint?a=e.body:(a=EI(e.body,i.path),!a&&r.doc&&(a=jI(r.doc,i),o=!!a));let{results:s,resolvedBody:c,challenges:l}=await BI(t,i,n,{body:a,bodyIsAutogenerated:o,query:e.query,extraHeaders:e.headers,discoveryDoc:r.doc??void 0}),u=c??a,d=s.some(e=>e.severity===`pass`&&e.label===`Challenge parseable`),f=!s.some(e=>e.severity===`fail`),p=(l??[]).filter(e=>e.method!==`stripe`).map(e=>e.request.methodDetails?.chainId).filter(e=>typeof e==`number`),m=p.some(bL),h=p.some(e=>!bL(e)),g=!d&&s.some(e=>e.label===`Not an MPP endpoint`),_=s.some(e=>e.severity===`pass`&&e.label===`WWW-Authenticate header present`),v=s.some(e=>e.severity===`fail`&&e.label===`Challenge parseable`);if(yield{phase:`challenge`,endpoint:i,results:s,isMpp:d,isTestnet:m,isMainnet:h,isNonMppPayment:g,isMalformedChallenge:!d&&_&&v,methods:l?.map(e=>e.method)??[]},d&&(yield{phase:`errorHandling`,endpoint:i,results:await XI(t,i,{body:u,query:e.query})},!e.skipPayment&&f)){let r=e.onPaymentResults,a=await uL(t,i,n,{body:u,query:e.query,extraHeaders:e.headers,yes:e.yes,silent:!r,interactive:e.interactive,...r&&{onResults:r}});yield{phase:`payment`,endpoint:i,results:a,succeeded:a.some(e=>e.severity===`pass`&&e.label===`Payment: successful`)}}}}async function _L(e){let t=e.url.replace(/\/$/,``).replace(/\/openapi\.json$/i,``),n,r=[],i,a=!1,o=!1;for await(let t of gL(e))switch(t.phase){case`discovery`:n=t.discovery;break;case`endpoint`:i={method:t.endpoint.method,path:t.endpoint.path,challenge:[],errorHandling:[],payment:[]},r.push(i);break;case`challenge`:i&&(i.challenge=t.results),t.isMpp&&(t.isTestnet&&(a=!0),t.isMainnet&&(o=!0));break;case`errorHandling`:i&&(i.errorHandling=t.results);break;case`payment`:i&&(i.payment=t.results)}let s={passed:0,failed:0,warnings:0,suggested:0,skipped:0};if(n)for(let e of n.checks)e.severity===`pass`?s.passed++:e.severity===`fail`?s.failed++:e.severity===`warn`?s.warnings++:e.severity===`suggested`?s.suggested++:e.severity===`skip`&&s.skipped++;for(let e of r)for(let t of[...e.challenge,...e.errorHandling,...e.payment])t.severity===`pass`?s.passed++:t.severity===`fail`?s.failed++:t.severity===`warn`?s.warnings++:t.severity===`suggested`?s.suggested++:t.severity===`skip`&&s.skipped++;if(!n)throw Error(`Discovery phase did not complete`);let c=vL(r,n,{sawTestnet:a,sawMainnet:o});return{url:t,discovery:n,endpoints:r,summary:s,suggestions:c}}function vL(e,t,n){let r=[],i=e.flatMap(e=>[...e.challenge,...e.payment]);t.found||r.push(QI);let a=n.sawMainnet,o=n.sawTestnet,s=i.some(e=>e.label.includes(`[stripe]`)&&e.label.includes(`livemode`));return a&&!o?r.push(`Validate your testnet server to test without real funds (Tempo wallets are automatically provisioned).`):o&&!a&&r.push(`Validate your mainnet server to confirm real payments work end-to-end.`),s&&r.push(`Validate your testmode Stripe server to test card payments end-to-end (auto-pays with test card).`),r}const yL=Object.values(Be);function bL(e){return yL.find(t=>t.id===e)?.testnet===!0}async function xL(e,t){let n=[],r=[],i=null,a=!1,o=!1;n.push(await kI(e));let s=new URL(e).origin,c;t.discoveryPath?c=[new URL(t.discoveryPath,e+`/`).href.replace(/\/openapi\.json$/i,``)]:(c=[e,s,`${s}/api`],c=[...new Set(c)]);let l,u=[];for(let e of c)if(l=await OI(e),`error`in l)u.push(`${e}/openapi.json: ${l.error}`);else break;if(!l||`error`in l)n.push({label:`Document found`,detail:u.join(`; `),hint:`MPP servers must serve an OpenAPI document at /openapi.json with x-payment-info extensions.`,severity:`fail`});else{a=!0,n.push({label:`Document found and parseable`,severity:`pass`});let e=rE(l.doc),t=e.filter(e=>e.severity===`error`),r=e.filter(e=>e.severity===`warning`);if(t.length>0){n.push({label:`Valid OpenAPI structure`,detail:`${t.length} error(s)`,severity:`fail`});for(let e of t)n.push({label:e.message,detail:e.path,severity:`fail`})}else o=!0,n.push({label:`Valid OpenAPI structure`,severity:`pass`});for(let e of r)n.push({label:e.message,detail:e.path,severity:`warn`});i=l.doc}if(t.endpoint){let e=TI(t.endpoint);e&&r.push(e)}else if(i){r=AI(i);let e=BigInt(`999999999999999999`);r.sort((t,n)=>{let r=wI(t.amount)?BigInt(t.amount):e,i=wI(n.amount)?BigInt(n.amount):e;return r<i?-1:+(r>i)}),r.length>0?n.push({label:`Paid endpoints found`,detail:`${r.length} endpoint(s)`,severity:`pass`}):n.push({label:`Paid endpoints found`,detail:`No endpoints with x-payment-info`,severity:`warn`})}return{found:a,valid:o,endpoints:r,checks:n,doc:i}}Ox(),ZO();const SL=cb(`validate`,{description:`Validate an MPP server implementation end-to-end`,args:p.object({url:p.string().describe(`Base URL of the MPP server to validate`)}),options:p.object({endpoint:p.string().optional().describe(`Endpoint to test (METHOD:path). Skips discovery.`),body:p.string().optional().describe(`Request body. With --endpoint, used directly. In discovery mode, JSON with / keys is a per-path mapping.`),query:p.array(p.string()).optional().describe(`Query parameter (key=value, repeatable)`),header:p.array(p.string()).optional().describe(`Request header (key:value, repeatable)`),verbose:p.number().default(0).meta({count:!0}).describe(`Verbosity level`),yes:p.boolean().default(!1).describe(`Auto-approve mainnet payments`),outputJson:p.boolean().default(!1).describe(`Output results as JSON (auto-enabled in known agent environments)`)}),alias:{endpoint:`e`,header:`H`,verbose:`v`,yes:`y`,outputJson:`j`},async run(e){if(e.options.outputJson||MO()){let t=await _L({url:e.args.url,endpoint:e.options.endpoint,body:e.options.body,query:e.options.query,headers:e.options.header,verbose:e.options.verbose>0,yes:e.options.yes,interactive:!1});console.log(JSON.stringify(t,null,2));let n=t.endpoints.length===0&&!e.options.endpoint;(t.summary.failed>0||!t.discovery.found||n)&&process.exit(1);return}let t=e.args.url.replace(/\/$/,``).replace(/\/openapi\.json$/i,``);console.log(`\n${X.bold(`mppx validate`)} ${X.dim(t)}\n`);let n={passed:0,failed:0,warnings:0,suggested:0,skipped:0},r=!1,i=!1,a=!1,o=!1,s=!1,c=!1,l=!1,u=0;for await(let t of gL({url:e.args.url,endpoint:e.options.endpoint,body:e.options.body,query:e.options.query,headers:e.options.header,verbose:e.options.verbose>0,yes:e.options.yes,interactive:!!process.stdin.isTTY,onPaymentResults:e=>yI(e,n)}))switch(t.phase){case`discovery`:if(vI(`Discovery (/openapi.json)`),yI(t.results,n),l=t.discovery.found,!l&&!e.options.endpoint){console.log(``);let[e,...t]=QI.split(`
|
|
245
|
-
`);console.log(X.yellow(` ${e}`));for(let e of t)console.log(e?X.dim(` ${e}`):``);console.log(``),process.exit(1)}t.discovery.endpoints.length===0&&!e.options.endpoint&&l&&(console.log(X.dim(` Use --endpoint to specify endpoints manually.`)),console.log(``),process.exit(1));break;case`endpoint`:u++,vI(`${t.endpoint.method} ${t.endpoint.path}`);break;case`challenge`:console.log(X.dim(` Challenge`)),yI(t.results,n),t.isMpp&&(r=!0,t.isTestnet&&(o=!0),t.isMainnet&&(s=!0)),t.isNonMppPayment&&(i=!0),t.isMalformedChallenge&&(a=!0);break;case`errorHandling`:console.log(X.dim(` Error Handling`)),yI(t.results,n);break;case`payment`:t.succeeded&&(c=!0)}CL(n,{sawMppEndpoint:r,sawNonMppPaymentEndpoint:i,sawMalformedChallenge:a,sawTestnet:o,sawMainnet:s,paymentSucceeded:c},u)}});function CL(e,t,n){!t.sawMppEndpoint&&n>0&&(console.log(``),t.sawMalformedChallenge?(console.log(X.yellow(` Payment scheme detected but challenge format is invalid on ${n} endpoint(s).`)),console.log(X.dim(` The server uses WWW-Authenticate: Payment but the challenge parameters do not conform to MPP.`)),console.log(X.dim(` Fix: encode payment details as base64url JSON in a request="..." parameter. See errors above.`))):t.sawNonMppPaymentEndpoint?(console.log(X.yellow(` No MPP endpoints found. Tested ${n} endpoint(s) but none use WWW-Authenticate: Payment.`)),console.log(X.dim(` This server may use x402 or another payment protocol.`))):e.skipped>0&&e.failed===0?(console.log(X.yellow(` Could not reach payment gate on any endpoint (all returned 401/403/200).`)),console.log(X.dim(` The server may require authentication before payment. Try providing auth or use --endpoint with a public path.`))):(console.log(X.yellow(` No MPP endpoints found. Tested ${n} endpoint(s) but none use WWW-Authenticate: Payment.`)),console.log(X.dim(` This server may use x402 or another payment protocol.`))),console.log(``),process.exit(1)),console.log(``);let r=[];e.passed>0&&r.push(X.green(`${e.passed} passed`)),e.failed>0&&r.push(X.red(`${e.failed} failed`)),e.warnings>0&&r.push(X.yellow(`${e.warnings} warning(s)`)),e.suggested>0&&r.push(X.cyan(`${e.suggested} suggested`)),e.skipped>0&&r.push(X.yellow(`${e.skipped} skipped`)),console.log(`${X.bold(`Summary:`)} ${r.join(`, `)}`),t.paymentSucceeded&&t.sawTestnet&&!t.sawMainnet?(console.log(``),console.log(X.dim(` Tip: also validate your mainnet server to confirm real payments work end-to-end.`))):t.sawMainnet&&!t.sawTestnet&&(console.log(``),console.log(X.dim(` Tip: also validate your testnet server for free. This CLI automatically provisions and funds a testnet wallet for testing.`))),console.log(``),e.failed>0&&process.exit(1)}function wL(e){return EL(e)?e.offers:[e]}function TL(e){let{_canonicalRequest:t,intent:n,name:r}=e,i=t.methodDetails??{},a=DL(t.amount)??DL(i.amount)??null,o=DL(t.currency)??DL(i.currency),s=DL(e.description)??DL(t.description),c={amount:a,...o?{currency:o}:{},...s?{description:s}:{},intent:n,method:r},l=new Set([`amount`,`currency`,`description`,`methodDetails`]);for(let[e,n]of Object.entries(t))!l.has(e)&&n!==void 0&&(c[e]=n);return c}function EL(e){return`offers`in e&&!(`_canonicalRequest`in e)}function DL(e){return typeof e==`string`?e:void 0}var OL=I((()=>{})),kL=Ze({generate:()=>AL,generateProxy:()=>jL});function AL(e,t){let n=e.methods,r=new Map,i={};for(let e of n)r.set(`${e.name}/${e.intent}`,e),i[e.intent]=(i[e.intent]??0)+1;for(let e of n)i[e.intent]===1&&r.set(e.intent,e);let a=t.routes.map(e=>NL(e,r));return ML({info:{title:t.info?.title??e.realm,version:t.info?.version??`1.0.0`},routes:a,serviceInfo:t.serviceInfo})}function jL(e){let t=e.routes.map(t=>({...t,path:PL(e.basePath,t.path)}));return ML({info:{title:e.info?.title??`API Proxy`,version:e.info?.version??`1.0.0`},routes:t,serviceInfo:e.serviceInfo})}function ML(e){let t={};for(let n of e.routes){let e=n.method.toLowerCase(),r={responses:{...n.payment?{402:{description:`Payment Required`}}:{},200:{description:`Successful response`}}};n.payment&&(r[`x-payment-info`]=XT.parse(n.payment)),n.summary&&(r.summary=n.summary),n.requestBody&&(r.requestBody=n.requestBody),t[n.path]||(t[n.path]={}),t[n.path][e]=r}let n={info:e.info,openapi:`3.1.0`,paths:t};return e.serviceInfo&&(n[`x-service-info`]=e.serviceInfo),n}function NL(e,t){if(`handler`in e){let t=e.handler._internal;if(!t)throw Error(`Route ${e.method.toUpperCase()} ${e.path} is missing discovery metadata`);return{method:e.method,path:e.path,payment:{offers:wL(t).map(TL)},...e.requestBody?{requestBody:e.requestBody}:{},...e.summary?{summary:e.summary}:{}}}let n=t.get(e.intent);if(!n)throw Error(`Unknown intent "${e.intent}" for route ${e.method.toUpperCase()} ${e.path}. Available: ${[...t.keys()].join(`, `)}`);let{description:r,...i}=e.options,a={_canonicalRequest:Lx(n,i),...typeof r==`string`?{description:r}:{},intent:n.intent,name:n.name};return{method:e.method,path:e.path,payment:TL(a),...e.requestBody?{requestBody:e.requestBody}:{},...e.summary?{summary:e.summary}:{}}}function PL(e,t){if(!e)return t;let n=e.startsWith(`/`)?e:`/${e}`;return`${n.endsWith(`/`)?n.slice(0,-1):n}${t}`}var FL=I((()=>{zx(),nE(),OL()}));Ox(),_S(),yT(),JC(),sw(),uw(),Fx(),_O(),aw(),OS(),AC(),dk(),ZO();const IL=`0.13.6`,LL=p.object({address:p.string(),isDefault:p.boolean().optional(),name:p.string(),source:p.string().optional()}),RL=p.object({address:p.string(),balances:p.array(p.string()).optional(),name:p.string(),type:p.string().optional()}),zL=p.object({message:p.string(),path:p.string(),severity:p.string()}),BL=p.object({description:p.string().optional(),id:p.string(),name:p.string().optional(),paidEndpoints:p.number(),status:p.string().optional(),url:p.string().optional()}),VL=p.object({description:p.string().optional(),method:p.string(),path:p.string(),payment:p.unknown().optional()});function HL(e){return e.format===`json`&&e.formatExplicit}function UL(e,t,n){if(HL(e))return e.ok(t);n()}async function WL(t){if(!t.body)return;let n=t.body.getReader();try{for(;;){let{done:t,value:r}=await n.read();if(t)break;process.stdout.write(Buffer.from(r))||await e(process.stdout,`drain`)}}finally{n.releaseLock()}}function GL(){return process.stdin.isTTY===!1&&process.stdin.listenerCount(`data`)===0&&process.stdin.listenerCount(`readable`)===0}function KL(e){return typeof e==`string`?e:void 0}function qL(e){return Array.isArray(e.endpoints)?e.endpoints.filter(e=>typeof e==`object`&&!!e):[]}function JL(e){let t=qL(e);return{...KL(e.description)?{description:KL(e.description)}:{},id:KL(e.id)??KL(e.name)??`unknown`,...KL(e.name)?{name:KL(e.name)}:{},paidEndpoints:t.filter(e=>e.payment).length,...KL(e.status)?{status:KL(e.status)}:{},...KL(e.serviceUrl)||KL(e.url)?{url:KL(e.serviceUrl)??KL(e.url)}:{}}}function YL(e){return{...KL(e.description)?{description:KL(e.description)}:{},method:KL(e.method)??`GET`,path:KL(e.path)??`/`,...e.payment===void 0?{}:{payment:e.payment}}}function XL(e){if(!e)return`free`;if(typeof e!=`object`)return String(e);let t=e,n=KL(t.amount),r=KL(t.currency),i=KL(t.method),a=KL(t.intent);return[n,r,i&&a?`${i}/${a}`:i??a].filter(Boolean).join(` `)}function ZL(e){return e.request.currency??e.request.asset}function QL(e,t){if(!t)return[...e];let n=t.toLowerCase();return e.filter(e=>{let t=ZL(e);return typeof t==`string`&&t.toLowerCase()===n})}async function $L(e,t,n={}){let r=()=>qC().getChallenges(e),i=()=>ow().getChallenges(n.ignoreX402ResponseUrl?new Response(null,{headers:e.headers,status:e.status}):e);if(t===`mpp`)return r();if(t===`x402`)return i();let a=[],o;try{a=await r()}catch(e){o=e}let s=[],c;try{s=await i()}catch(e){c=e}if(a.length||s.length)return[...a,...s];if(o)throw o;if(c)throw c;return[]}function eR(e){return cw({name:`selected-challenge`,isPaymentRequired:e=>e.status===402,getChallenges:()=>[e],setCredential:e=>e})}function tR(e,t){if(!ES(e))return new Response(null,{status:402,headers:{[q.wwwAuthenticate]:$x(e)}});let n=e.request;return n.resource?new Response(null,{status:402,headers:{[fC]:nw({accepts:[sC(n)],...n.extensions&&{extensions:n.extensions},resource:n.resource,x402Version:2})}}):t}function nR(e){let{host:t,hostname:n}=new URL(e);return!n.endsWith(`.localhost`)||n===`localhost`?{url:e}:{host:t,url:e.replace(n,`127.0.0.1`)}}async function rR(e,t,n,r){let i=t,a=nR(i),o={...n,redirect:`manual`,headers:Mw(n.headers)};a.host&&oR(o.headers,a.host);for(let t=0;t<=20;t++){let n=await e(a.url,o),s=n.headers.get(`location`);if(!r||!s||![301,302,303,307,308].includes(n.status))return{init:o,logicalUrl:i,response:n,target:a};if(t===20)throw TypeError(`Too many redirects`);await n.body?.cancel().catch(()=>{});let c=new URL(s,i),l=new URL(i),u=Mw(o.headers);if(c.origin!==l.origin)for(let e of[`authorization`,`cookie`,`host`,`proxy-authorization`])for(let t of Object.keys(u))t.toLowerCase()===e&&delete u[t];let d=(o.method??(o.body?`POST`:`GET`)).toUpperCase(),f=n.status===303&&d!==`GET`&&d!==`HEAD`||(n.status===301||n.status===302)&&d===`POST`;if(f)for(let e of[`content-encoding`,`content-language`,`content-length`,`content-location`,`content-type`])for(let t of Object.keys(u))t.toLowerCase()===e&&delete u[t];i=c.href,a=nR(i),a.host&&oR(u,a.host);let p={...o};f&&(delete p.body,p.method=`GET`),o={...p,headers:u,redirect:`manual`}}throw TypeError(`Too many redirects`)}function iR(e,t,n){let r=new URL(e),i=new URL(n||e);return i.origin!==r.origin&&aR(i),nR(i.href)}function aR(e){throw new L({code:`UNSAFE_PAYMENT_REDIRECT`,message:`Refusing to send a payment credential to redirected origin ${e.origin}.`,exitCode:1})}function oR(e,t){for(let t of Object.keys(e))t.toLowerCase()===`host`&&delete e[t];t&&(e.Host=t)}function sR(e){let t=e.request.resource;return typeof t?.url==`string`?t.url:void 0}async function cR(){let e=process.env.MPPX_SERVICES_URL??`https://mpp.dev/api/services`,t=await globalThis.fetch(e);if(!t.ok)throw new L({code:`SERVICES_FETCH_FAILED`,message:`Failed to fetch services registry: HTTP ${t.status}`,exitCode:1});let n=await t.json();if(!n||typeof n!=`object`||!Array.isArray(n.services))throw new L({code:`SERVICES_INVALID`,message:`Services registry response did not contain a services array.`,exitCode:1});return n.services}function lR(e,t){let n=t.toLowerCase();return e.find(e=>{let t=KL(e.id)?.toLowerCase(),r=KL(e.name)?.toLowerCase();return t===n||r===n})}const uR=cb(`mppx`,{mcp:{tools:{discovery:`direct`}},version:IL,description:`Make HTTP requests with automatic payment handling`,usage:[{suffix:`<url> [options]`}],args:p.object({url:p.string().describe(`URL to make request to`)}),options:p.object({account:p.string().optional().describe(`Account name (env: MPPX_ACCOUNT)`),autoSwap:p.boolean().optional().describe(`Auto-swap source tokens into payment currency`),config:p.string().optional().describe(`Path to config file`),confirm:p.boolean().optional().default(!1).describe(`Show confirmation prompts`),currency:p.string().optional().describe(`Payment currency/token address to select`),data:p.string().optional().describe(`Send request body (implies POST unless -X is set)`),fail:p.boolean().optional().describe(`Fail silently on HTTP errors (exit 22)`),header:p.array(p.string()).optional().describe(`Add header (repeatable)`),include:p.boolean().optional().describe(`Include response headers in output`),insecure:p.boolean().optional().describe(`Skip TLS certificate verification (true for localhost/.local)`),jsonBody:p.string().optional().describe(`Send JSON body (sets Content-Type and Accept, implies POST)`),location:p.boolean().optional().describe(`Follow redirects`),method:p.string().optional().describe(`HTTP method`),methodOpt:p.array(p.string()).optional().describe(`Method-specific option (key=value, repeatable)`),network:p.enum([`mainnet`,`testnet`]).optional().describe(`Tempo network`),payWith:p.string().optional().describe(`Source token for Tempo auto-swap`),protocol:p.enum([`auto`,`mpp`,`x402`]).default(`auto`).describe(`Payment protocol to use`),rpcUrl:p.string().optional().describe(`RPC endpoint, defaults to public RPC for chain (env: MPPX_RPC_URL)`),session:p.string().optional().default(`auto`).refine(e=>e===`auto`||e===`new`||/^0x[0-9a-fA-F]{64}$/.test(e),`Expected auto, new, or a 32-byte channel ID`).describe(`Session selection: auto, new, or channel ID`),silent:p.boolean().default(!1).describe(`Silent mode (suppress progress and info)`),slippage:p.number().optional().describe(`Tempo auto-swap max slippage percentage`),userAgent:p.string().optional().describe(`Set User-Agent header`),verbose:p.number().default(0).meta({count:!0}).describe(`Verbosity (-v details, -vv headers)`)}),alias:{account:`a`,config:`c`,data:`d`,fail:`f`,header:`H`,include:`i`,insecure:`k`,jsonBody:`J`,location:`L`,method:`X`,methodOpt:`M`,rpcUrl:`r`,silent:`s`,userAgent:`A`,verbose:`v`},examples:[{args:{url:`mpp.dev/api/ping/paid`},description:`Make a payment request`}],async run(e){let t=e.options.silent?e=>{}:e=>process.stderr.write(e),n=await YP(e.options.config);n&&e.options.verbose>=1&&t(`${X.dim(`Using config`)} ${X.blue(c.relative(process.cwd(),n.path))}\n`);let r=pk(),i={"User-Agent":e.options.userAgent??`mppx/${IL}${r?` AIAgent/${r}`:``}`};if(e.options.header)for(let t of e.options.header){let n=t.indexOf(`:`);if(n===-1)return e.error({code:`INVALID_HEADER`,message:`Invalid header format: ${t}`,exitCode:2});i[t.slice(0,n).trim()]=t.slice(n+1).trim()}let a=KP(n?.config);a&&!Object.keys(i).some(e=>e.toLowerCase()===`accept-payment`)&&(i[`Accept-Payment`]=a);let o=(()=>{let t=/^https?:\/\//.test(e.args.url),n=/^(localhost|.*\.localhost|127\.0\.0\.1|\[::1\])(:\d+)?/.test(e.args.url);return t?e.args.url:`${n?`http`:`https`}://${e.args.url}`})(),{hostname:s}=new URL(o),l=e.options.insecure||s===`localhost`||s.endsWith(`.localhost`)||s.endsWith(`.local`)?async(e,t)=>{let n=process.env.NODE_TLS_REJECT_UNAUTHORIZED;process.env.NODE_TLS_REJECT_UNAUTHORIZED=`0`;try{return await globalThis.fetch(e,t)}finally{n===void 0?delete process.env.NODE_TLS_REJECT_UNAUTHORIZED:process.env.NODE_TLS_REJECT_UNAUTHORIZED=n}}:globalThis.fetch,u=nR(o),d=u.url;u.host&&(i.Host=u.host);try{let r=jO(e.options.methodOpt),a=fF(),s={redirect:e.options.location?`follow`:`manual`};e.options.jsonBody?(s.body=e.options.jsonBody,i[`Content-Type`]??=`application/json`,i.Accept??=`application/json`):e.options.data&&(s.body=e.options.data),e.options.method?s.method=e.options.method.toUpperCase():s.body&&(s.method=`POST`),Object.keys(i).length>0&&(s.headers=i);let c={include:e.options.include??!1,verbose:e.options.verbose,silent:e.options.silent};e.options.verbose>=2&&TO(o,s,t);let f=await rR(l,o,s,e.options.location??!1),p=f.response;if(p.status!==402){if(e.options.session!==`auto`)return e.error({code:`UNSUPPORTED_SESSION`,message:`--session requires a tempo/session payment challenge.`,exitCode:2});if(e.options.fail&&p.status>=400)return e.error({code:`HTTP_ERROR`,message:`HTTP error ${p.status}`,exitCode:22});EO(p,c),await WL(p);return}let m=p.url?new URL(p.url):void 0,h=u.host!==void 0&&m?.hostname===`127.0.0.1`,g=await $L(p,e.options.protocol,{ignoreX402ResponseUrl:h});if(g.length===0)return e.error({code:`UNSUPPORTED_PROTOCOL`,message:e.options.protocol===`auto`?`Server did not offer a payment challenge.`:`Server did not offer an ${e.options.protocol} payment challenge.`,exitCode:2});let _=QL(g,e.options.currency);if(e.options.currency&&_.length===0){let t=g.map(ZL).filter(e=>typeof e==`string`).join(`, `);return e.error({code:`UNSUPPORTED_CURRENCY`,message:`Server did not offer payment currency ${e.options.currency}.${t?` Offered currencies: ${t}.`:``}`,exitCode:2})}let v=qP(n?.config)?.length?_:await oP(_,{options:{account:e.options.account,network:e.options.network,rpcUrl:e.options.rpcUrl}}),y=GP(v,n?.config);if(!y){let t=v.map(e=>`${e.method}/${e.intent}`).join(`, `);return e.error({code:`UNSUPPORTED_METHOD`,message:`Unsupported payment method. Server offers: ${t}. Add it to mppx.config.ts using defineConfig().`,exitCode:2})}let{challenge:b,plugin:x,method:S}=y,C=ES(b),w=C?void 0:iR(o,u,f.logicalUrl),T=tR(b,p),ee=b.request.currency??``,E=b.request.decimals??6,D,O;x&&(O=await x.setup({challenge:b,options:{account:e.options.account,autoSwap:e.options.autoSwap,network:e.options.network,payWith:e.options.payWith,rpcUrl:e.options.rpcUrl,slippage:e.options.slippage},methodOpts:r}),ee=O.tokenSymbol,E=O.tokenDecimals,D=O.explorerUrl);let A=!e.options.silent&&e.options.confirm,te=new Set;{EO(p,b.method===`tempo`&&b.intent===`session`&&e.options.include?{...c,include:!1,verbose:Math.max(c.verbose,2)}:c);let n=(()=>{let e=new Set([`id`,`request`]),t=[];for(let[n,r]of Object.entries(b))e.has(n)||r===void 0||t.push([n,kO(n,r)]);return t.sort(([e],[t])=>e.localeCompare(t))})(),r={tokenSymbol:ee,tokenDecimals:E,explorerUrl:D},i=(()=>{let e=new Set([`decimals`,`currency`,`methodDetails`]),t=[];for(let[n,i]of Object.entries(b.request))e.has(n)||i===void 0||t.push([n,DO(n,i,r)]);return t.sort(([e],[t])=>e.localeCompare(t))})(),a=(()=>{let e=b.request.methodDetails;if(!e)return[];let t=[];for(let[n,i]of Object.entries(e))if(i!==void 0){if(n===`memo`&&typeof i==`string`){let e=OO(i);t.push([n,e?`${e}\n${X.dim(i)}`:i])}else t.push([n,DO(n,i,r)])}return t.sort(([e],[t])=>e.localeCompare(t))})(),o=[[`Challenge`,n],[`Request`,i],...a.length?[[`Details`,a]]:[]];for(let[,e]of o)for(let[t]of e)te.add(t);let s=Math.max(...o.flatMap(([,e])=>e.map(([e])=>e.length))),l=` ${``.padEnd(s)} `;if(e.options.verbose>=1||A){t(`${X.bold(X.yellow(`Payment Required`))}\n`);for(let[e,n]of o){t(`${X.bold(e)}\n`);for(let[e,r]of n){let[n,...i]=r.split(`
|
|
245
|
+
`);console.log(X.yellow(` ${e}`));for(let e of t)console.log(e?X.dim(` ${e}`):``);console.log(``),process.exit(1)}t.discovery.endpoints.length===0&&!e.options.endpoint&&l&&(console.log(X.dim(` Use --endpoint to specify endpoints manually.`)),console.log(``),process.exit(1));break;case`endpoint`:u++,vI(`${t.endpoint.method} ${t.endpoint.path}`);break;case`challenge`:console.log(X.dim(` Challenge`)),yI(t.results,n),t.isMpp&&(r=!0,t.isTestnet&&(o=!0),t.isMainnet&&(s=!0)),t.isNonMppPayment&&(i=!0),t.isMalformedChallenge&&(a=!0);break;case`errorHandling`:console.log(X.dim(` Error Handling`)),yI(t.results,n);break;case`payment`:t.succeeded&&(c=!0)}CL(n,{sawMppEndpoint:r,sawNonMppPaymentEndpoint:i,sawMalformedChallenge:a,sawTestnet:o,sawMainnet:s,paymentSucceeded:c},u)}});function CL(e,t,n){!t.sawMppEndpoint&&n>0&&(console.log(``),t.sawMalformedChallenge?(console.log(X.yellow(` Payment scheme detected but challenge format is invalid on ${n} endpoint(s).`)),console.log(X.dim(` The server uses WWW-Authenticate: Payment but the challenge parameters do not conform to MPP.`)),console.log(X.dim(` Fix: encode payment details as base64url JSON in a request="..." parameter. See errors above.`))):t.sawNonMppPaymentEndpoint?(console.log(X.yellow(` No MPP endpoints found. Tested ${n} endpoint(s) but none use WWW-Authenticate: Payment.`)),console.log(X.dim(` This server may use x402 or another payment protocol.`))):e.skipped>0&&e.failed===0?(console.log(X.yellow(` Could not reach payment gate on any endpoint (all returned 401/403/200).`)),console.log(X.dim(` The server may require authentication before payment. Try providing auth or use --endpoint with a public path.`))):(console.log(X.yellow(` No MPP endpoints found. Tested ${n} endpoint(s) but none use WWW-Authenticate: Payment.`)),console.log(X.dim(` This server may use x402 or another payment protocol.`))),console.log(``),process.exit(1)),console.log(``);let r=[];e.passed>0&&r.push(X.green(`${e.passed} passed`)),e.failed>0&&r.push(X.red(`${e.failed} failed`)),e.warnings>0&&r.push(X.yellow(`${e.warnings} warning(s)`)),e.suggested>0&&r.push(X.cyan(`${e.suggested} suggested`)),e.skipped>0&&r.push(X.yellow(`${e.skipped} skipped`)),console.log(`${X.bold(`Summary:`)} ${r.join(`, `)}`),t.paymentSucceeded&&t.sawTestnet&&!t.sawMainnet?(console.log(``),console.log(X.dim(` Tip: also validate your mainnet server to confirm real payments work end-to-end.`))):t.sawMainnet&&!t.sawTestnet&&(console.log(``),console.log(X.dim(` Tip: also validate your testnet server for free. This CLI automatically provisions and funds a testnet wallet for testing.`))),console.log(``),e.failed>0&&process.exit(1)}function wL(e){return EL(e)?e.offers:[e]}function TL(e){let{_canonicalRequest:t,intent:n,name:r}=e,i=t.methodDetails??{},a=DL(t.amount)??DL(i.amount)??null,o=DL(t.currency)??DL(i.currency),s=DL(e.description)??DL(t.description),c={amount:a,...o?{currency:o}:{},...s?{description:s}:{},intent:n,method:r},l=new Set([`amount`,`currency`,`description`,`methodDetails`]);for(let[e,n]of Object.entries(t))!l.has(e)&&n!==void 0&&(c[e]=n);return c}function EL(e){return`offers`in e&&!(`_canonicalRequest`in e)}function DL(e){return typeof e==`string`?e:void 0}var OL=I((()=>{})),kL=Ze({generate:()=>AL,generateProxy:()=>jL});function AL(e,t){let n=e.methods,r=new Map,i={};for(let e of n)r.set(`${e.name}/${e.intent}`,e),i[e.intent]=(i[e.intent]??0)+1;for(let e of n)i[e.intent]===1&&r.set(e.intent,e);let a=t.routes.map(e=>NL(e,r));return ML({info:{title:t.info?.title??e.realm,version:t.info?.version??`1.0.0`},routes:a,serviceInfo:t.serviceInfo})}function jL(e){let t=e.routes.map(t=>({...t,path:PL(e.basePath,t.path)}));return ML({info:{title:e.info?.title??`API Proxy`,version:e.info?.version??`1.0.0`},routes:t,serviceInfo:e.serviceInfo})}function ML(e){let t={};for(let n of e.routes){let e=n.method.toLowerCase(),r={responses:{...n.payment?{402:{description:`Payment Required`}}:{},200:{description:`Successful response`}}};n.payment&&(r[`x-payment-info`]=XT.parse(n.payment)),n.summary&&(r.summary=n.summary),n.requestBody&&(r.requestBody=n.requestBody),t[n.path]||(t[n.path]={}),t[n.path][e]=r}let n={info:e.info,openapi:`3.1.0`,paths:t};return e.serviceInfo&&(n[`x-service-info`]=e.serviceInfo),n}function NL(e,t){if(`handler`in e){let t=e.handler._internal;if(!t)throw Error(`Route ${e.method.toUpperCase()} ${e.path} is missing discovery metadata`);return{method:e.method,path:e.path,payment:{offers:wL(t).map(TL)},...e.requestBody?{requestBody:e.requestBody}:{},...e.summary?{summary:e.summary}:{}}}let n=t.get(e.intent);if(!n)throw Error(`Unknown intent "${e.intent}" for route ${e.method.toUpperCase()} ${e.path}. Available: ${[...t.keys()].join(`, `)}`);let{description:r,...i}=e.options,a={_canonicalRequest:Lx(n,i),...typeof r==`string`?{description:r}:{},intent:n.intent,name:n.name};return{method:e.method,path:e.path,payment:TL(a),...e.requestBody?{requestBody:e.requestBody}:{},...e.summary?{summary:e.summary}:{}}}function PL(e,t){if(!e)return t;let n=e.startsWith(`/`)?e:`/${e}`;return`${n.endsWith(`/`)?n.slice(0,-1):n}${t}`}var FL=I((()=>{zx(),nE(),OL()}));Ox(),_S(),yT(),JC(),sw(),uw(),Fx(),_O(),aw(),OS(),AC(),dk(),ZO();const IL=`0.14.1`,LL=p.object({address:p.string(),isDefault:p.boolean().optional(),name:p.string(),source:p.string().optional()}),RL=p.object({address:p.string(),balances:p.array(p.string()).optional(),name:p.string(),type:p.string().optional()}),zL=p.object({message:p.string(),path:p.string(),severity:p.string()}),BL=p.object({description:p.string().optional(),id:p.string(),name:p.string().optional(),paidEndpoints:p.number(),status:p.string().optional(),url:p.string().optional()}),VL=p.object({description:p.string().optional(),method:p.string(),path:p.string(),payment:p.unknown().optional()});function HL(e){return e.format===`json`&&e.formatExplicit}function UL(e,t,n){if(HL(e))return e.ok(t);n()}async function WL(t){if(!t.body)return;let n=t.body.getReader();try{for(;;){let{done:t,value:r}=await n.read();if(t)break;process.stdout.write(Buffer.from(r))||await e(process.stdout,`drain`)}}finally{n.releaseLock()}}function GL(){return process.stdin.isTTY===!1&&process.stdin.listenerCount(`data`)===0&&process.stdin.listenerCount(`readable`)===0}function KL(e){return typeof e==`string`?e:void 0}function qL(e){return Array.isArray(e.endpoints)?e.endpoints.filter(e=>typeof e==`object`&&!!e):[]}function JL(e){let t=qL(e);return{...KL(e.description)?{description:KL(e.description)}:{},id:KL(e.id)??KL(e.name)??`unknown`,...KL(e.name)?{name:KL(e.name)}:{},paidEndpoints:t.filter(e=>e.payment).length,...KL(e.status)?{status:KL(e.status)}:{},...KL(e.serviceUrl)||KL(e.url)?{url:KL(e.serviceUrl)??KL(e.url)}:{}}}function YL(e){return{...KL(e.description)?{description:KL(e.description)}:{},method:KL(e.method)??`GET`,path:KL(e.path)??`/`,...e.payment===void 0?{}:{payment:e.payment}}}function XL(e){if(!e)return`free`;if(typeof e!=`object`)return String(e);let t=e,n=KL(t.amount),r=KL(t.currency),i=KL(t.method),a=KL(t.intent);return[n,r,i&&a?`${i}/${a}`:i??a].filter(Boolean).join(` `)}function ZL(e){return e.request.currency??e.request.asset}function QL(e,t){if(!t)return[...e];let n=t.toLowerCase();return e.filter(e=>{let t=ZL(e);return typeof t==`string`&&t.toLowerCase()===n})}async function $L(e,t,n={}){let r=()=>qC().getChallenges(e),i=()=>ow().getChallenges(n.ignoreX402ResponseUrl?new Response(null,{headers:e.headers,status:e.status}):e);if(t===`mpp`)return r();if(t===`x402`)return i();let a=[],o;try{a=await r()}catch(e){o=e}let s=[],c;try{s=await i()}catch(e){c=e}if(a.length||s.length)return[...a,...s];if(o)throw o;if(c)throw c;return[]}function eR(e){return cw({name:`selected-challenge`,isPaymentRequired:e=>e.status===402,getChallenges:()=>[e],setCredential:e=>e})}function tR(e,t){if(!ES(e))return new Response(null,{status:402,headers:{[q.wwwAuthenticate]:$x(e)}});let n=e.request;return n.resource?new Response(null,{status:402,headers:{[fC]:nw({accepts:[sC(n)],...n.extensions&&{extensions:n.extensions},resource:n.resource,x402Version:2})}}):t}function nR(e){let{host:t,hostname:n}=new URL(e);return!n.endsWith(`.localhost`)||n===`localhost`?{url:e}:{host:t,url:e.replace(n,`127.0.0.1`)}}async function rR(e,t,n,r){let i=t,a=nR(i),o={...n,redirect:`manual`,headers:Mw(n.headers)};a.host&&oR(o.headers,a.host);for(let t=0;t<=20;t++){let n=await e(a.url,o),s=n.headers.get(`location`);if(!r||!s||![301,302,303,307,308].includes(n.status))return{init:o,logicalUrl:i,response:n,target:a};if(t===20)throw TypeError(`Too many redirects`);await n.body?.cancel().catch(()=>{});let c=new URL(s,i),l=new URL(i),u=Mw(o.headers);if(c.origin!==l.origin)for(let e of[`authorization`,`cookie`,`host`,`proxy-authorization`])for(let t of Object.keys(u))t.toLowerCase()===e&&delete u[t];let d=(o.method??(o.body?`POST`:`GET`)).toUpperCase(),f=n.status===303&&d!==`GET`&&d!==`HEAD`||(n.status===301||n.status===302)&&d===`POST`;if(f)for(let e of[`content-encoding`,`content-language`,`content-length`,`content-location`,`content-type`])for(let t of Object.keys(u))t.toLowerCase()===e&&delete u[t];i=c.href,a=nR(i),a.host&&oR(u,a.host);let p={...o};f&&(delete p.body,p.method=`GET`),o={...p,headers:u,redirect:`manual`}}throw TypeError(`Too many redirects`)}function iR(e,t,n){let r=new URL(e),i=new URL(n||e);return i.origin!==r.origin&&aR(i),nR(i.href)}function aR(e){throw new L({code:`UNSAFE_PAYMENT_REDIRECT`,message:`Refusing to send a payment credential to redirected origin ${e.origin}.`,exitCode:1})}function oR(e,t){for(let t of Object.keys(e))t.toLowerCase()===`host`&&delete e[t];t&&(e.Host=t)}function sR(e){let t=e.request.resource;return typeof t?.url==`string`?t.url:void 0}async function cR(){let e=process.env.MPPX_SERVICES_URL??`https://mpp.dev/api/services`,t=await globalThis.fetch(e);if(!t.ok)throw new L({code:`SERVICES_FETCH_FAILED`,message:`Failed to fetch services registry: HTTP ${t.status}`,exitCode:1});let n=await t.json();if(!n||typeof n!=`object`||!Array.isArray(n.services))throw new L({code:`SERVICES_INVALID`,message:`Services registry response did not contain a services array.`,exitCode:1});return n.services}function lR(e,t){let n=t.toLowerCase();return e.find(e=>{let t=KL(e.id)?.toLowerCase(),r=KL(e.name)?.toLowerCase();return t===n||r===n})}const uR=cb(`mppx`,{mcp:{tools:{discovery:`direct`}},version:IL,description:`Make HTTP requests with automatic payment handling`,usage:[{suffix:`<url> [options]`}],args:p.object({url:p.string().describe(`URL to make request to`)}),options:p.object({account:p.string().optional().describe(`Account name (env: MPPX_ACCOUNT)`),autoSwap:p.boolean().optional().describe(`Auto-swap source tokens into payment currency`),config:p.string().optional().describe(`Path to config file`),confirm:p.boolean().optional().default(!1).describe(`Show confirmation prompts`),currency:p.string().optional().describe(`Payment currency/token address to select`),data:p.string().optional().describe(`Send request body (implies POST unless -X is set)`),fail:p.boolean().optional().describe(`Fail silently on HTTP errors (exit 22)`),header:p.array(p.string()).optional().describe(`Add header (repeatable)`),include:p.boolean().optional().describe(`Include response headers in output`),insecure:p.boolean().optional().describe(`Skip TLS certificate verification (true for localhost/.local)`),jsonBody:p.string().optional().describe(`Send JSON body (sets Content-Type and Accept, implies POST)`),location:p.boolean().optional().describe(`Follow redirects`),method:p.string().optional().describe(`HTTP method`),methodOpt:p.array(p.string()).optional().describe(`Method-specific option (key=value, repeatable)`),network:p.enum([`mainnet`,`testnet`]).optional().describe(`Tempo network`),payWith:p.string().optional().describe(`Source token for Tempo auto-swap`),protocol:p.enum([`auto`,`mpp`,`x402`]).default(`auto`).describe(`Payment protocol to use`),rpcUrl:p.string().optional().describe(`RPC endpoint, defaults to public RPC for chain (env: MPPX_RPC_URL)`),session:p.string().optional().default(`auto`).refine(e=>e===`auto`||e===`new`||/^0x[0-9a-fA-F]{64}$/.test(e),`Expected auto, new, or a 32-byte channel ID`).describe(`Session selection: auto, new, or channel ID`),silent:p.boolean().default(!1).describe(`Silent mode (suppress progress and info)`),slippage:p.number().optional().describe(`Tempo auto-swap max slippage percentage`),userAgent:p.string().optional().describe(`Set User-Agent header`),verbose:p.number().default(0).meta({count:!0}).describe(`Verbosity (-v details, -vv headers)`)}),alias:{account:`a`,config:`c`,data:`d`,fail:`f`,header:`H`,include:`i`,insecure:`k`,jsonBody:`J`,location:`L`,method:`X`,methodOpt:`M`,rpcUrl:`r`,silent:`s`,userAgent:`A`,verbose:`v`},examples:[{args:{url:`mpp.dev/api/ping/paid`},description:`Make a payment request`}],async run(e){let t=e.options.silent?e=>{}:e=>process.stderr.write(e),n=await YP(e.options.config);n&&e.options.verbose>=1&&t(`${X.dim(`Using config`)} ${X.blue(c.relative(process.cwd(),n.path))}\n`);let r=pk(),i={"User-Agent":e.options.userAgent??`mppx/${IL}${r?` AIAgent/${r}`:``}`};if(e.options.header)for(let t of e.options.header){let n=t.indexOf(`:`);if(n===-1)return e.error({code:`INVALID_HEADER`,message:`Invalid header format: ${t}`,exitCode:2});i[t.slice(0,n).trim()]=t.slice(n+1).trim()}let a=KP(n?.config);a&&!Object.keys(i).some(e=>e.toLowerCase()===`accept-payment`)&&(i[`Accept-Payment`]=a);let o=(()=>{let t=/^https?:\/\//.test(e.args.url),n=/^(localhost|.*\.localhost|127\.0\.0\.1|\[::1\])(:\d+)?/.test(e.args.url);return t?e.args.url:`${n?`http`:`https`}://${e.args.url}`})(),{hostname:s}=new URL(o),l=e.options.insecure||s===`localhost`||s.endsWith(`.localhost`)||s.endsWith(`.local`)?async(e,t)=>{let n=process.env.NODE_TLS_REJECT_UNAUTHORIZED;process.env.NODE_TLS_REJECT_UNAUTHORIZED=`0`;try{return await globalThis.fetch(e,t)}finally{n===void 0?delete process.env.NODE_TLS_REJECT_UNAUTHORIZED:process.env.NODE_TLS_REJECT_UNAUTHORIZED=n}}:globalThis.fetch,u=nR(o),d=u.url;u.host&&(i.Host=u.host);try{let r=jO(e.options.methodOpt),a=fF(),s={redirect:e.options.location?`follow`:`manual`};e.options.jsonBody?(s.body=e.options.jsonBody,i[`Content-Type`]??=`application/json`,i.Accept??=`application/json`):e.options.data&&(s.body=e.options.data),e.options.method?s.method=e.options.method.toUpperCase():s.body&&(s.method=`POST`),Object.keys(i).length>0&&(s.headers=i);let c={include:e.options.include??!1,verbose:e.options.verbose,silent:e.options.silent};e.options.verbose>=2&&TO(o,s,t);let f=await rR(l,o,s,e.options.location??!1),p=f.response;if(p.status!==402){if(e.options.session!==`auto`)return e.error({code:`UNSUPPORTED_SESSION`,message:`--session requires a tempo/session payment challenge.`,exitCode:2});if(e.options.fail&&p.status>=400)return e.error({code:`HTTP_ERROR`,message:`HTTP error ${p.status}`,exitCode:22});EO(p,c),await WL(p);return}let m=p.url?new URL(p.url):void 0,h=u.host!==void 0&&m?.hostname===`127.0.0.1`,g=await $L(p,e.options.protocol,{ignoreX402ResponseUrl:h});if(g.length===0)return e.error({code:`UNSUPPORTED_PROTOCOL`,message:e.options.protocol===`auto`?`Server did not offer a payment challenge.`:`Server did not offer an ${e.options.protocol} payment challenge.`,exitCode:2});let _=QL(g,e.options.currency);if(e.options.currency&&_.length===0){let t=g.map(ZL).filter(e=>typeof e==`string`).join(`, `);return e.error({code:`UNSUPPORTED_CURRENCY`,message:`Server did not offer payment currency ${e.options.currency}.${t?` Offered currencies: ${t}.`:``}`,exitCode:2})}let v=qP(n?.config)?.length?_:await oP(_,{options:{account:e.options.account,network:e.options.network,rpcUrl:e.options.rpcUrl}}),y=GP(v,n?.config);if(!y){let t=v.map(e=>`${e.method}/${e.intent}`).join(`, `);return e.error({code:`UNSUPPORTED_METHOD`,message:`Unsupported payment method. Server offers: ${t}. Add it to mppx.config.ts using defineConfig().`,exitCode:2})}let{challenge:b,plugin:x,method:S}=y,C=ES(b),w=C?void 0:iR(o,u,f.logicalUrl),T=tR(b,p),ee=b.request.currency??``,E=b.request.decimals??6,D,O;x&&(O=await x.setup({challenge:b,options:{account:e.options.account,autoSwap:e.options.autoSwap,network:e.options.network,payWith:e.options.payWith,rpcUrl:e.options.rpcUrl,slippage:e.options.slippage},methodOpts:r}),ee=O.tokenSymbol,E=O.tokenDecimals,D=O.explorerUrl);let A=!e.options.silent&&e.options.confirm,te=new Set;{EO(p,b.method===`tempo`&&b.intent===`session`&&e.options.include?{...c,include:!1,verbose:Math.max(c.verbose,2)}:c);let n=(()=>{let e=new Set([`id`,`request`]),t=[];for(let[n,r]of Object.entries(b))e.has(n)||r===void 0||t.push([n,kO(n,r)]);return t.sort(([e],[t])=>e.localeCompare(t))})(),r={tokenSymbol:ee,tokenDecimals:E,explorerUrl:D},i=(()=>{let e=new Set([`decimals`,`currency`,`methodDetails`]),t=[];for(let[n,i]of Object.entries(b.request))e.has(n)||i===void 0||t.push([n,DO(n,i,r)]);return t.sort(([e],[t])=>e.localeCompare(t))})(),a=(()=>{let e=b.request.methodDetails;if(!e)return[];let t=[];for(let[n,i]of Object.entries(e))if(i!==void 0){if(n===`memo`&&typeof i==`string`){let e=OO(i);t.push([n,e?`${e}\n${X.dim(i)}`:i])}else t.push([n,DO(n,i,r)])}return t.sort(([e],[t])=>e.localeCompare(t))})(),o=[[`Challenge`,n],[`Request`,i],...a.length?[[`Details`,a]]:[]];for(let[,e]of o)for(let[t]of e)te.add(t);let s=Math.max(...o.flatMap(([,e])=>e.map(([e])=>e.length))),l=` ${``.padEnd(s)} `;if(e.options.verbose>=1||A){t(`${X.bold(X.yellow(`Payment Required`))}\n`);for(let[e,n]of o){t(`${X.bold(e)}\n`);for(let[e,r]of n){let[n,...i]=r.split(`
|
|
246
246
|
`);t(` ${X.dim(e.padEnd(s))} ${n}\n`);for(let e of i)t(`${l}${e}\n`)}}}if(A&&(t(`
|
|
247
247
|
`),!await FO(`Proceed with ${b.intent}?`,!0))){t(`Aborted.
|
|
248
248
|
`);return}}let ne=process.env.MPPX_PRIVATE_KEY?.trim()||!NO(ik(e.options.account));if(FD(b)&&ne&&!S){try{await pI({challenge:b,challengeResponse:T,credentialContext:await UP(b,n?.config.extensions,O?.credentialContext),endpoint:f.logicalUrl,fetch:l,fetchInput:w.url,init:{...f.init,redirect:`manual`},info:t,methodOptions:r,options:{account:e.options.account,fail:e.options.fail,include:e.options.include,network:e.options.network,rpcUrl:e.options.rpcUrl,session:e.options.session,silent:e.options.silent,verbose:e.options.verbose},registry:a})}catch(e){return nI(e,`SESSION_REQUEST_FAILED`)}return}if(e.options.session!==`auto`)return e.error({code:`UNSUPPORTED_SESSION`,message:S&&FD(b)?`--session cannot override a configured session method. Configure its channelStore instead.`:`--session requires a tempo/session payment challenge.`,exitCode:2});let j=S&&FD(b)?void 0:await UP(b,n?.config.extensions,O?.credentialContext),M,re;if(S&&FD(b)){let e=T;M=await wT({methods:[S],polyfill:!1,async onChallenge(e,{createCredential:t}){let r=await UP(e,n?.config.extensions,O?.credentialContext);return ZP(b,e),t(r)},fetch:async(t,n)=>{if(e){let t=e;return e=void 0,t}return l(t,n)}}).fetch(w.url,{...f.init,context:j,redirect:`manual`})}else{if(O?.createCredential)re=await O.createCredential(T,j);else if(O)re=await wT({methods:O.methods,polyfill:!1,transport:eR(b)}).createCredential(T,j);else if(S)re=await wT({methods:[S],polyfill:!1,transport:eR(b)}).createCredential(T,j);else throw Error(`unreachable`);let n=C?pC:fS(b),r={...Mw(f.init.headers),[n]:re},i=C?nR((sR(b)??p.url)||o):w;C&&oR(r,i.host),x?.prepareCredentialRequest?.({challenge:b,credential:re,headers:r});let a={...f.init,redirect:`manual`,headers:r};e.options.verbose>=2&&TO(i.url,a,t),M=await l(i.url,a)}if(e.options.fail&&M.status>=400)return e.error({code:`HTTP_ERROR`,message:`HTTP error ${M.status}`,exitCode:22});if(M.status===402){let n=await M.text();t(`${X.bold(X.red(`Payment Rejected`))}\n`);try{let e=JSON.parse(n),r=[];for(let[t,n]of Object.entries(e))n!==void 0&&r.push([t,String(n)]);r.sort(([e],[t])=>e.localeCompare(t));let i=Math.max(...r.map(([e])=>e.length));for(let[e,n]of r)t(` ${X.dim(e.padEnd(i))} ${n}\n`)}catch{n&&t(` ${n}\n`)}return e.error({code:`PAYMENT_REJECTED`,message:`Payment rejected`,exitCode:75})}if(EO(M,c),!(re&&await x?.handleResponse?.({challenge:b,credential:re,response:M,fetchUrl:d,fetchInit:s,silent:e.options.silent,verbose:e.options.verbose,confirmEnabled:A,confirm:FO,tokenSymbol:ee,tokenDecimals:E,explorerUrl:D,shownKeys:te}))){let n=M.headers.get(q.paymentReceipt);if(n&&e.options.verbose>=1)try{let e=JSON.parse(k.toString(n));t(`\n${X.bold(X.green(`Payment Receipt`))}\n`);let r=[],i=e.channelId,a=e.reference,o=i&&a&&i===a,s=new Set([`acceptedCumulative`,`spent`]);for(let[t,n]of Object.entries(e)){if(n===void 0||te.has(t)||t===`reference`&&o)continue;let e=x?.formatReceiptField?.(t,n);e===void 0?s.has(t)&&typeof n==`string`?r.push([t,`${n} ${X.dim(`(${IO(BigInt(n),ee,E)})`)}`]):(t===`reference`||t===`txHash`)&&typeof n==`string`&&D?r.push([t,AO(`${D}/tx/${n}`,n)]):r.push([t,String(n)]):r.push([t,e])}r.sort(([e],[t])=>e.localeCompare(t));let c=Math.max(...r.map(([e])=>e.length));for(let[e,n]of r)t(` ${X.dim(e.padEnd(c))} ${n}\n`);t(`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Current mppx package version. */
|
|
2
|
-
export declare const version = "0.
|
|
2
|
+
export declare const version = "0.14.1";
|
|
3
3
|
/** Canonical SDK identifier for this mppx release. */
|
|
4
|
-
export declare const sdkIdentifier = "mppx/0.
|
|
4
|
+
export declare const sdkIdentifier = "mppx/0.14.1";
|
|
5
5
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/internal/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/sync-version.ts.
|
|
2
2
|
/** Current mppx package version. */
|
|
3
|
-
export const version = '0.
|
|
3
|
+
export const version = '0.14.1';
|
|
4
4
|
/** Canonical SDK identifier for this mppx release. */
|
|
5
5
|
export const sdkIdentifier = `mppx/${version}`;
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { NoExtraKeys } from '../../internal/types.js';
|
|
2
2
|
import * as Store from '../../Store.js';
|
|
3
3
|
import * as Currencies from '../internal/currencies.js';
|
|
4
|
-
import {
|
|
4
|
+
import { superviseCloses as superviseCloses_ } from '../session/server/CloseSupervisor.js';
|
|
5
|
+
import { charge as sessionCharge_, credit as sessionCredit_, session as session_, settle as settle_, settleBatch as settleBatch_ } from '../session/server/Session.js';
|
|
5
6
|
import * as Settlement from '../session/server/Settlement.js';
|
|
6
7
|
import type { SessionController as SessionController_ } from '../session/server/Sse.js';
|
|
7
8
|
import * as Ws_ from '../session/server/Ws.js';
|
|
@@ -2415,17 +2416,21 @@ export declare namespace tempo {
|
|
|
2415
2416
|
/** Creates ordered TIP-1034 session offers with shared storage and settlement helpers. */
|
|
2416
2417
|
const session: typeof sessionOffers & {
|
|
2417
2418
|
charge: typeof sessionCharge_;
|
|
2419
|
+
credit: typeof sessionCredit_;
|
|
2418
2420
|
settle: typeof settle_;
|
|
2419
2421
|
settleBatch: typeof settleBatch_;
|
|
2422
|
+
superviseCloses: typeof superviseCloses_;
|
|
2420
2423
|
};
|
|
2421
2424
|
/** Creates ordered subscription offers. Existing subscriptions retain their authorized currency. */
|
|
2422
2425
|
const subscription: typeof subscriptionOffers;
|
|
2423
2426
|
/** Renews an overdue Tempo subscription outside of the HTTP request path. */
|
|
2424
2427
|
const renewSubscription: typeof renewSubscription_;
|
|
2425
|
-
/** One-shot settle: reads
|
|
2428
|
+
/** One-shot settle: reads the largest voucher within spend from storage and submits it on-chain. */
|
|
2426
2429
|
const settle: typeof settle_;
|
|
2427
2430
|
/** Batch-settle precompile-backed session channels. */
|
|
2428
2431
|
const settleBatch: typeof settleBatch_;
|
|
2432
|
+
/** Answers payer close requests on supervised session channels; call it from a scheduled job. */
|
|
2433
|
+
const superviseCloses: typeof superviseCloses_;
|
|
2429
2434
|
/** Raised by `settle` when its transaction confirmed but the channel store could not record it. */
|
|
2430
2435
|
const SettlementCheckpointError: typeof Settlement.SettlementCheckpointError;
|
|
2431
2436
|
/** Raised by `settle` when its transaction confirmed but the channel store could not record it. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as Store from '../../Store.js';
|
|
2
2
|
import * as Currencies from '../internal/currencies.js';
|
|
3
3
|
import * as ChannelStore from '../session/server/ChannelStore.js';
|
|
4
|
-
import {
|
|
4
|
+
import { superviseCloses as superviseCloses_ } from '../session/server/CloseSupervisor.js';
|
|
5
|
+
import { charge as sessionCharge_, credit as sessionCredit_, session as session_, settle as settle_, settleBatch as settleBatch_, verifyStream as verifyStream_, } from '../session/server/Session.js';
|
|
5
6
|
import * as Settlement from '../session/server/Settlement.js';
|
|
6
7
|
import * as Ws_ from '../session/server/Ws.js';
|
|
7
8
|
import { charge as charge_ } from './Charge.js';
|
|
@@ -9,8 +10,10 @@ import { fallbackStore } from './internal/fallback-store.js';
|
|
|
9
10
|
import { renew as renewSubscription_, subscription as subscription_ } from './Subscription.js';
|
|
10
11
|
const sessionServer = Object.assign(session_, {
|
|
11
12
|
charge: sessionCharge_,
|
|
13
|
+
credit: sessionCredit_,
|
|
12
14
|
settle: settle_,
|
|
13
15
|
settleBatch: settleBatch_,
|
|
16
|
+
superviseCloses: superviseCloses_,
|
|
14
17
|
});
|
|
15
18
|
function createChargeMethod(parameters) {
|
|
16
19
|
// `tempo()` accepts the intersection of charge/session parameters, then
|
|
@@ -78,17 +81,21 @@ export function tempo(parameters) {
|
|
|
78
81
|
/** Creates ordered TIP-1034 session offers with shared storage and settlement helpers. */
|
|
79
82
|
tempo.session = Object.assign(sessionOffers, {
|
|
80
83
|
charge: sessionCharge_,
|
|
84
|
+
credit: sessionCredit_,
|
|
81
85
|
settle: settle_,
|
|
82
86
|
settleBatch: settleBatch_,
|
|
87
|
+
superviseCloses: superviseCloses_,
|
|
83
88
|
});
|
|
84
89
|
/** Creates ordered subscription offers. Existing subscriptions retain their authorized currency. */
|
|
85
90
|
tempo.subscription = subscriptionOffers;
|
|
86
91
|
/** Renews an overdue Tempo subscription outside of the HTTP request path. */
|
|
87
92
|
tempo.renewSubscription = renewSubscription_;
|
|
88
|
-
/** One-shot settle: reads
|
|
93
|
+
/** One-shot settle: reads the largest voucher within spend from storage and submits it on-chain. */
|
|
89
94
|
tempo.settle = settle_;
|
|
90
95
|
/** Batch-settle precompile-backed session channels. */
|
|
91
96
|
tempo.settleBatch = settleBatch_;
|
|
97
|
+
/** Answers payer close requests on supervised session channels; call it from a scheduled job. */
|
|
98
|
+
tempo.superviseCloses = superviseCloses_;
|
|
92
99
|
/** Raised by `settle` when its transaction confirmed but the channel store could not record it. */
|
|
93
100
|
tempo.SettlementCheckpointError = Settlement.SettlementCheckpointError;
|
|
94
101
|
/** Experimental websocket helpers for Tempo sessions. */
|
|
@@ -146,9 +146,9 @@ export declare function getChannel(client: Client, descriptor: ChannelDescriptor
|
|
|
146
146
|
*/
|
|
147
147
|
export declare function getChannelState(client: Client, channelId: Hex, escrow?: Address, blockNumber?: bigint): Promise<ChannelState>;
|
|
148
148
|
/**
|
|
149
|
-
* Read channel states from the TIP20EscrowChannel precompile.
|
|
149
|
+
* Read channel states from the TIP20EscrowChannel precompile, optionally at one block.
|
|
150
150
|
*/
|
|
151
|
-
export declare function getChannelStatesBatch(client: Client, channelIds: readonly Hex[], escrow?: Address): Promise<ChannelState[]>;
|
|
151
|
+
export declare function getChannelStatesBatch(client: Client, channelIds: readonly Hex[], escrow?: Address, blockNumber?: bigint): Promise<ChannelState[]>;
|
|
152
152
|
/** Tuning for {@link readbackWithRetry}. */
|
|
153
153
|
export type ReadbackRetryOptions = {
|
|
154
154
|
/** Additional attempts after the first, before giving up. @default 5 */
|
|
@@ -141,14 +141,15 @@ export async function getChannelState(client, channelId, escrow = tip20ChannelEs
|
|
|
141
141
|
return stateFromTuple(state);
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* Read channel states from the TIP20EscrowChannel precompile.
|
|
144
|
+
* Read channel states from the TIP20EscrowChannel precompile, optionally at one block.
|
|
145
145
|
*/
|
|
146
|
-
export async function getChannelStatesBatch(client, channelIds, escrow = tip20ChannelEscrow) {
|
|
146
|
+
export async function getChannelStatesBatch(client, channelIds, escrow = tip20ChannelEscrow, blockNumber) {
|
|
147
147
|
const states = await readContract(client, {
|
|
148
148
|
address: escrow,
|
|
149
149
|
abi: escrowAbi,
|
|
150
150
|
functionName: 'getChannelStatesBatch',
|
|
151
151
|
args: [channelIds],
|
|
152
|
+
...(blockNumber !== undefined ? { blockNumber } : {}),
|
|
152
153
|
});
|
|
153
154
|
return states.map(stateFromTuple);
|
|
154
155
|
}
|
|
@@ -24,10 +24,11 @@ import type { SessionMethodDetails } from './RequestState.js';
|
|
|
24
24
|
* - `deposit` reflects the latest on-chain value
|
|
25
25
|
*/
|
|
26
26
|
export type State = BaseState & BackendState;
|
|
27
|
-
/** Result of an atomic channel deduction attempt. */
|
|
27
|
+
/** Result of an atomic channel deduction attempt. `replayed` marks a keyed charge recorded earlier. */
|
|
28
28
|
export type DeductResult = {
|
|
29
29
|
ok: true;
|
|
30
30
|
channel: State;
|
|
31
|
+
replayed?: boolean | undefined;
|
|
31
32
|
} | {
|
|
32
33
|
ok: false;
|
|
33
34
|
channel: State;
|
|
@@ -282,12 +283,58 @@ export interface BaseState {
|
|
|
282
283
|
lastSettlementSpent?: bigint | undefined;
|
|
283
284
|
/** Charge operation count when the last server-scheduled settlement ran. */
|
|
284
285
|
lastSettlementUnits?: number | undefined;
|
|
286
|
+
/**
|
|
287
|
+
* Accepted vouchers below `highestVoucher`, ascending, that a settlement may capture once
|
|
288
|
+
* `spent` covers them. See {@link resolveSettleableVoucher}.
|
|
289
|
+
*/
|
|
290
|
+
retainedVouchers?: readonly RetainedVoucher[] | undefined;
|
|
291
|
+
/** Settlement transaction that may still land; credits keep `spent` at or above its amount. */
|
|
292
|
+
pendingSettlement?: PendingSettlement | undefined;
|
|
293
|
+
/** Outcomes of keyed charges and credits, by `charge:<key>` and `credit:<key>`, until they expire. */
|
|
294
|
+
idempotencyMarkers?: Record<string, IdempotencyMarker> | undefined;
|
|
295
|
+
/** First block at which the close supervisor read this channel open on-chain. */
|
|
296
|
+
closeSupervisedFromBlock?: bigint | undefined;
|
|
285
297
|
}
|
|
286
298
|
/** Expiring ownership claim for one server-scheduled settlement attempt. */
|
|
287
299
|
export type ScheduledSettlementLease = {
|
|
288
300
|
expiresAt: number;
|
|
289
301
|
owner: string;
|
|
290
302
|
};
|
|
303
|
+
/** An accepted voucher kept below the highest one. */
|
|
304
|
+
export type RetainedVoucher = {
|
|
305
|
+
/** Cumulative amount the voucher authorizes. */
|
|
306
|
+
cumulativeAmount: bigint;
|
|
307
|
+
/** Voucher signature. */
|
|
308
|
+
signature: Hex;
|
|
309
|
+
};
|
|
310
|
+
/** A settlement transaction that may land until `expiresAt`. */
|
|
311
|
+
export type PendingSettlement = {
|
|
312
|
+
/** Cumulative amount the transaction captures. */
|
|
313
|
+
amount: bigint;
|
|
314
|
+
/** Unix milliseconds after which the transaction can no longer be included. */
|
|
315
|
+
expiresAt: number;
|
|
316
|
+
};
|
|
317
|
+
/** Why a credit was not applied. */
|
|
318
|
+
export type CreditDeclineReason =
|
|
319
|
+
/** Crediting would take `spent` below what settled, or may still settle, on-chain. */
|
|
320
|
+
'belowSettled'
|
|
321
|
+
/** A close is pending, so its capture is already fixed. */
|
|
322
|
+
| 'closing'
|
|
323
|
+
/** The amount is larger than `spent`. */
|
|
324
|
+
| 'exceedsSpent'
|
|
325
|
+
/** The channel is closed. */
|
|
326
|
+
| 'finalized';
|
|
327
|
+
/** Recorded outcome of one keyed charge or credit. */
|
|
328
|
+
export type IdempotencyMarker = {
|
|
329
|
+
/** Amount the operation charged or credited. */
|
|
330
|
+
amount: bigint;
|
|
331
|
+
/** Unix milliseconds until which a retry with the same key replays this outcome. */
|
|
332
|
+
expiresAt: number;
|
|
333
|
+
/** `charged`, `credited`, or why the credit was declined. */
|
|
334
|
+
outcome: 'charged' | 'credited' | CreditDeclineReason;
|
|
335
|
+
/** Charge operations a credit returned, when any. */
|
|
336
|
+
units?: number | undefined;
|
|
337
|
+
};
|
|
291
338
|
/** Returns whether a channel is backed by the TIP20EscrowChannel precompile. */
|
|
292
339
|
export declare function isPrecompileState(state: State): state is BaseState & PrecompileBackendState;
|
|
293
340
|
/** Returns the greater bigint value without decreasing persisted monotonic state. */
|
|
@@ -304,6 +351,30 @@ export declare function resolvePrecompileChannelIdentity(parameters: ResolvePrec
|
|
|
304
351
|
export declare function openChannelState(parameters: OpenChannelStateParameters): State;
|
|
305
352
|
/** Merges top-up on-chain state into persisted channel state without decreasing monotonic fields. */
|
|
306
353
|
export declare function topUpChannelState(parameters: TopUpStateParameters): State | null;
|
|
354
|
+
/**
|
|
355
|
+
* Number of accepted vouchers kept below the highest one. Clients sign every voucher above their
|
|
356
|
+
* last one, so once a credit lowers `spent` the highest voucher stays above it for good and only
|
|
357
|
+
* earlier vouchers can be settled; those fall at or below `spent` as later charges accumulate.
|
|
358
|
+
* Each costs about 200 bytes of the channel record. In a simulated 20,000-request channel crediting
|
|
359
|
+
* up to 80% of each request back, which left the highest voucher 80,000 units above `spent`,
|
|
360
|
+
* settling every 50 requests left on average 1,290 units unsettled with 16 retained vouchers and
|
|
361
|
+
* 784 with 32.
|
|
362
|
+
*/
|
|
363
|
+
export declare const maxRetainedVouchers = 16;
|
|
364
|
+
/**
|
|
365
|
+
* Returns the vouchers to keep once `highestVoucherAmount` replaces the highest voucher: the
|
|
366
|
+
* retained ones and the superseded highest voucher, above `settledOnChain`. Beyond
|
|
367
|
+
* {@link maxRetainedVouchers} it repeatedly drops the voucher whose neighbours are closest, so the
|
|
368
|
+
* kept ones stay spread over the unsettled range and some keep falling at or below `spent` however
|
|
369
|
+
* far credits have moved it below the highest voucher.
|
|
370
|
+
*/
|
|
371
|
+
export declare function retainVouchers(current: Pick<State, 'highestVoucher' | 'retainedVouchers' | 'settledOnChain'>, highestVoucherAmount: bigint, settledOnChain?: bigint): RetainedVoucher[];
|
|
372
|
+
/**
|
|
373
|
+
* Returns the largest accepted voucher a settlement may capture. The escrow's `settle` captures a
|
|
374
|
+
* voucher's whole cumulative amount, and a credit may leave the highest voucher above `spent`, so
|
|
375
|
+
* the voucher must be at most `spent`; one at or below `settledOnChain` captures nothing.
|
|
376
|
+
*/
|
|
377
|
+
export declare function resolveSettleableVoucher(state: Pick<State, 'channelId' | 'highestVoucher' | 'retainedVouchers' | 'settledOnChain' | 'spent'>): SessionSignedVoucher | null;
|
|
307
378
|
/** Merges an accepted voucher into persisted channel state after signature and on-chain checks pass. */
|
|
308
379
|
export declare function acceptVoucherStateUpdate(parameters: AcceptVoucherStateUpdateParameters): State;
|
|
309
380
|
/**
|
|
@@ -313,6 +384,8 @@ export declare function acceptVoucherStateUpdate(parameters: AcceptVoucherStateU
|
|
|
313
384
|
export declare function resolveCloseCaptureAmount(parameters: ResolveCloseCaptureAmountParameters): bigint;
|
|
314
385
|
/** Seconds a server close transaction stays valid after its pending-close marker is written. */
|
|
315
386
|
export declare const pendingCloseValiditySeconds = 25;
|
|
387
|
+
/** Extra seconds allowed for clock differences between the server and the chain. */
|
|
388
|
+
export declare const pendingCloseClockSkewSeconds = 30;
|
|
316
389
|
/** Clears a server pending-close marker whose close transaction expired without closing the channel. */
|
|
317
390
|
export declare function reconcileExpiredPendingClose(parameters: {
|
|
318
391
|
current: State | null;
|
|
@@ -405,14 +478,90 @@ export type ChannelStore = {
|
|
|
405
478
|
export declare function normalizeChannelId(channelId: string): Hex;
|
|
406
479
|
/** Wraps a generic mppx store in the shared session channel-store interface. */
|
|
407
480
|
export declare function fromStore(store: Store.Store | Store.AtomicStore): ChannelStore;
|
|
481
|
+
/**
|
|
482
|
+
* Default time a keyed charge or credit outcome is kept: a day, longer than any retry within one
|
|
483
|
+
* request or durable job step. Callers whose retries span longer pass their own `retention`.
|
|
484
|
+
*/
|
|
485
|
+
export declare const defaultIdempotencyRetention: number;
|
|
486
|
+
/** Options for charging a channel at most once per key. */
|
|
487
|
+
export type ChargeOptions = {
|
|
488
|
+
/**
|
|
489
|
+
* Key of the operation being charged. A retry with the same key and amount returns the recorded
|
|
490
|
+
* charge without charging again; the same key with a different amount throws
|
|
491
|
+
* {@link IdempotencyKeyConflictError}. A refused charge records nothing, so it may succeed later.
|
|
492
|
+
*/
|
|
493
|
+
idempotencyKey?: string | undefined;
|
|
494
|
+
/**
|
|
495
|
+
* Milliseconds the charge stays recorded, which must cover every retry. The channel record keeps
|
|
496
|
+
* one marker per keyed charge and credit of this window; expired markers are removed by the next
|
|
497
|
+
* keyed charge or credit of the channel. @default 86_400_000
|
|
498
|
+
*/
|
|
499
|
+
retention?: number | undefined;
|
|
500
|
+
};
|
|
501
|
+
/** Options for returning an amount to a channel at most once per key. */
|
|
502
|
+
export type CreditOptions = {
|
|
503
|
+
/**
|
|
504
|
+
* Key of the credit. Its first outcome, credited or declined, is final: a retry with the same key
|
|
505
|
+
* and terms returns it without crediting again, and the same key with other terms throws
|
|
506
|
+
* {@link IdempotencyKeyConflictError}.
|
|
507
|
+
*/
|
|
508
|
+
idempotencyKey: string;
|
|
509
|
+
/** Milliseconds the outcome stays recorded; see {@link ChargeOptions.retention}. @default 86_400_000 */
|
|
510
|
+
retention?: number | undefined;
|
|
511
|
+
/** Charge operations to take back from `units`, e.g. `1` when refunding a whole request. @default 0 */
|
|
512
|
+
units?: number | undefined;
|
|
513
|
+
};
|
|
514
|
+
/**
|
|
515
|
+
* Outcome of a credit. `declined` leaves `spent` unchanged, so the caller refunds the amount
|
|
516
|
+
* another way; a finalized channel whose `settledOnChain` is below `spent` was withdrawn before
|
|
517
|
+
* its spend was captured. `unknown` means the store write failed and may have committed: retry
|
|
518
|
+
* with the same key to learn the outcome.
|
|
519
|
+
*/
|
|
520
|
+
export type CreditResult = {
|
|
521
|
+
status: 'credited';
|
|
522
|
+
channel: State;
|
|
523
|
+
replayed: boolean;
|
|
524
|
+
} | {
|
|
525
|
+
status: 'declined';
|
|
526
|
+
reason: CreditDeclineReason;
|
|
527
|
+
channel: State;
|
|
528
|
+
replayed: boolean;
|
|
529
|
+
} | {
|
|
530
|
+
status: 'unknown';
|
|
531
|
+
error: unknown;
|
|
532
|
+
};
|
|
533
|
+
/** Raised when an idempotency key is reused with terms other than those it recorded. */
|
|
534
|
+
export declare class IdempotencyKeyConflictError extends Error {
|
|
535
|
+
readonly name = "IdempotencyKeyConflictError";
|
|
536
|
+
/** The reused key. */
|
|
537
|
+
readonly idempotencyKey: string;
|
|
538
|
+
constructor(options: {
|
|
539
|
+
idempotencyKey: string;
|
|
540
|
+
operation: 'charge' | 'credit';
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** Validates the idempotency options of a keyed charge or credit. */
|
|
544
|
+
export declare function assertIdempotencyOptions(options: {
|
|
545
|
+
idempotencyKey?: string | undefined;
|
|
546
|
+
retention?: number | undefined;
|
|
547
|
+
}): void;
|
|
408
548
|
/**
|
|
409
549
|
* Atomically deducts `amount` from a channel's available voucher balance.
|
|
410
550
|
*
|
|
411
551
|
* Returns `{ ok: true, channel }` with updated spend/unit counters when the
|
|
412
552
|
* deduction succeeds. Returns `{ ok: false, channel }` without mutating state
|
|
413
|
-
* when the channel exists but cannot currently be charged.
|
|
553
|
+
* when the channel exists but cannot currently be charged. With an
|
|
554
|
+
* `idempotencyKey`, a charge recorded under the key returns
|
|
555
|
+
* `{ ok: true, channel, replayed: true }` without charging again.
|
|
556
|
+
*/
|
|
557
|
+
export declare function deductFromChannel(store: ChannelStore, channelId: State['channelId'], amount: bigint, options?: ChargeOptions): Promise<DeductResult>;
|
|
558
|
+
/**
|
|
559
|
+
* Returns `amount` to a channel's `spent` at most once per idempotency key. The credit is declined
|
|
560
|
+
* when the channel is closing or finalized, or when it would take `spent` below zero or below
|
|
561
|
+
* what settled, or may still settle, on-chain, because a close captures `max(spent, settled)`.
|
|
562
|
+
* The first outcome for a key is final, so a declined credit is never applied later.
|
|
414
563
|
*/
|
|
415
|
-
export declare function
|
|
564
|
+
export declare function creditChannel(store: ChannelStore, channelId: State['channelId'], amount: bigint, options: CreditOptions): Promise<CreditResult>;
|
|
416
565
|
/** Returns unexpired stream reservations that ordinary deductions must respect. */
|
|
417
566
|
export declare function reservedStreamAmount(state: Pick<State, 'streamReservations'>): bigint;
|
|
418
567
|
//# sourceMappingURL=ChannelStore.d.ts.map
|