@dvmkit/dvmctl 0.2.0-rc.2 → 0.2.1-rc.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/README.md CHANGED
@@ -6,7 +6,7 @@ Install this exact release:
6
6
 
7
7
  <!-- x-release-please-start-version -->
8
8
  ```bash
9
- npm install -g @dvmkit/dvmctl@0.2.0-rc.2
9
+ npm install -g @dvmkit/dvmctl@0.2.1-rc.1
10
10
  ```
11
11
  <!-- x-release-please-end -->
12
12
 
@@ -14,7 +14,7 @@ Install the complete bundled skill directory so its references remain available:
14
14
 
15
15
  <!-- x-release-please-start-version -->
16
16
  ```bash
17
- npm install --ignore-scripts --prefix "$HOME/.dvmkit" @dvmkit/dvmctl@0.2.0-rc.2
17
+ npm install --ignore-scripts --prefix "$HOME/.dvmkit" @dvmkit/dvmctl@0.2.1-rc.1
18
18
  skill_dir="$(dirname "$(NODE_PATH="$HOME/.dvmkit/node_modules" node -p "require.resolve('@dvmkit/dvmctl/skill')")")"
19
19
  ```
20
20
  <!-- x-release-please-end -->
package/dist/dvmctl.js CHANGED
@@ -2136,7 +2136,7 @@ Opening browser to authenticate...`);console.log(`
2136
2136
  ${authUrl}
2137
2137
  `);console.log(`Session code: ${fingerprint}\u2026 (must match the code shown in your browser)
2138
2138
  `);try{const{exec}=await import("child_process");const cmd=process.platform==="darwin"?`open "${authUrl}"`:process.platform==="win32"?`start "${authUrl}"`:`xdg-open "${authUrl}"`;exec(cmd)}catch{console.log("Could not open browser automatically. Please open the URL above.")}console.log("Waiting for authentication...");const pollUrl=`${PLATFORM_URL}/api/v1/cli-auth/${sessionId}`;for(let i=0;i<CLI_AUTH_MAX_POLL_ATTEMPTS;i++){const jitter=Math.floor(Math.random()*CLI_AUTH_POLL_JITTER_MS);await sleep(CLI_AUTH_POLL_INTERVAL_MS+jitter);let res;try{res=await fetch(pollUrl,{headers:{Authorization:`Bearer ${verifier}`}})}catch{continue}if(res.status===404)continue;if(!res.ok)continue;const data=await res.json().catch(()=>null);if(!data)continue;if(data.cancelled){if(human){console.log("\nSign-in cancelled.")}else{console.log(JSON.stringify({authenticated:false,cancelled:true}))}process.exit(1)}if(data.token&&data.account){saveAuthToken({token:data.token,account:data.account});if(human){console.log(`
2139
- Authenticated as ${data.account}`)}else{console.log(JSON.stringify({authenticated:true,account:data.account}))}return}}console.error("\nAuthentication timed out. Try again or use DVMKIT_API_KEY env var.");process.exit(1)}function sleep(ms){return new Promise(resolve8=>setTimeout(resolve8,ms))}init_output();import{DvmError as DvmError10}from"@dvmkit/sdk/internal";import{DvmError as DvmError9,providerProseSources as providerProseSources3}from"@dvmkit/sdk/internal";import{ADMIN_STATE_UNAVAILABLE,buildAdminAuthHeader,DvmError as DvmError3}from"@dvmkit/sdk/internal";var AdminCashuClient=class{endpoint;dvmId;privkeyHex;fetchImpl;now;constructor(opts){this.endpoint=opts.endpoint.replace(/\/+$/,"");this.dvmId=opts.dvmId;this.privkeyHex=opts.privkeyHex;this.fetchImpl=opts.fetchImpl??globalThis.fetch.bind(globalThis);this.now=opts.now??Date.now}async getPendingBatches(){return this.requestJson("GET","/admin/cashu/pending-batches",void 0)}async markMelted(body){return this.requestJson("POST","/admin/cashu/mark-melted",body)}async rotateLockPubkey(body){return this.requestJson("POST","/admin/cashu/rotate-lock-pubkey",body)}async getPendingDrains(){return this.requestJson("GET","/admin/credit/pending-drains",void 0)}async getBitcoinLiability(){return this.requestJson("GET","/admin/credit/bitcoin-liability",void 0)}async parkDrain(body){return this.requestJson("POST","/admin/credit/park-drain",body)}async markDrainSent(body){return this.requestJson("POST","/admin/credit/mark-drain-sent",body)}async getBlockedInvoices(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/blocked-invoices",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileInvoice(body){return this.requestJson("POST","/admin/credit/reconcile-invoice",body)}async writeOffInvoice(body){return this.requestJson("POST","/admin/credit/write-off-invoice",body)}async getX402Settlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/x402-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402Settlement(body){return this.requestJson("POST","/admin/credit/reconcile-x402-settlement",body)}async writeOffX402Settlement(body){return this.requestJson("POST","/admin/credit/write-off-x402-settlement",body)}async getX402ExactSettlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/x402-exact-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402ExactSettlement(settlementIntentId){return this.requestJson("POST","/admin/credit/reconcile-x402-exact-settlement",{settlement_intent_id:settlementIntentId})}async getTempoDrains(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/tempo-drains",void 0,params.size===0?void 0:`?${params.toString()}`)}async getTempoCreditLosses(opts){const query=opts?.limit===void 0?void 0:`?limit=${String(opts.limit)}`;return this.requestJson("GET","/admin/credit/tempo-losses",void 0,query)}async reconcileTempoDrain(body){return this.requestJson("POST","/admin/credit/reconcile-tempo-drain",body)}async writeOffTempoDrain(body){return this.requestJson("POST","/admin/credit/write-off-tempo-drain",body)}async requestJson(method,path,body,query){const{header}=buildAdminAuthHeader({method,path,dvmId:this.dvmId,privkeyHex:this.privkeyHex,now:this.now});const init2={method,headers:{Authorization:header,...body===void 0?{}:{"Content-Type":"application/json"}},...body===void 0?{}:{body:JSON.stringify(body)}};let res;try{res=await this.fetchImpl(`${this.endpoint}${path}${query??""}`,init2)}catch(err){throw new DvmError3("admin_unreachable",`Failed to reach DVM admin endpoint ${this.endpoint}${path}: ${err instanceof Error?err.message:String(err)}`,"The DVM may be down or the endpoint URL is wrong. Verify with `dvmctl info <handle>`.")}const text=await res.text();let parsed;try{parsed=text.length===0?{}:JSON.parse(text)}catch{throw new DvmError3("admin_invalid_json",`DVM admin endpoint ${path} returned non-JSON (HTTP ${res.status}): ${text.slice(0,200)}`,"The DVM may not be running the tracked change SDK. Redeploy with the latest SDK.",void 0,void 0,{message:"provider"})}if(!res.ok){const body2=parsed;const apiError=new AdminApiError(res.status,{error:typeof body2.error==="string"?body2.error:"admin_error",reason:typeof body2.reason==="string"?body2.reason:void 0,message:typeof body2.message==="string"?body2.message:void 0,hint:typeof body2.hint==="string"?body2.hint:void 0,display:typeof body2.display==="string"?body2.display:void 0,raw:parsed});if(res.status===503&&apiError.body.error===ADMIN_STATE_UNAVAILABLE){throw new DvmError3(ADMIN_STATE_UNAVAILABLE,"DVM admin authentication state is temporarily unavailable; the requested DVM operation did not run.","Retry the same command. If this keeps happening, inspect the DVM's database health and restart the DVM. Do not rotate or replace the lock key.")}throw apiError}return parsed}};var AdminApiError=class extends Error{constructor(status2,body){super(`Admin endpoint returned HTTP ${status2}: ${body.error}${body.reason?` (${body.reason})`:""}`);this.status=status2;this.body=body;this.name="AdminApiError"}status;body;get isBadSignature(){return this.status===401&&this.body.reason==="bad_signature"}};function rethrowIfBadSignature(err){if(!(err instanceof AdminApiError)||!err.isBadSignature)return;throw new DvmError3("admin_bad_signature","DVM admin endpoint rejected our signed challenge (bad_signature).","The DVM's DVMKIT_CASHU_LOCK_PUBKEY does not match the privkey derived from this mnemonic. Re-deploy with the matching pubkey or switch mnemonics.")}function rethrowIfAdminStateUnavailable(err){if(err instanceof DvmError3&&err.code===ADMIN_STATE_UNAVAILABLE)throw err}init_builder_lock_identity();import{DvmError as DvmError8}from"@dvmkit/sdk/internal";async function resolveDvm(slug,opts={}){const{token,platformUrl}=requireAuth();const{dvms,org}=await listDvms(platformUrl,token,{org:resolveOrgScope(opts)});const dvm=dvms.find(candidate=>candidate.slug===slug);if(!dvm){throw new DvmError8("not_found",`DVM "${slug}" not found in ${scopeLabel(org)}.`,"Run `dvmctl list` to see the DVMs available in this context.")}return{dvmId:dvm.id,dvmkitUrl:dvm.dvmkitUrl,platformUrl,token}}async function connectAdmin(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);return{client:new AdminCashuClient({endpoint:await resolveAdminEndpoint(handle2,opts.endpoint,selected),dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey}),dvmId:canonicalDvmId}}async function adminCall(fn){try{return await fn()}catch(err){rethrowIfBadSignature(err);if(err instanceof AdminApiError){throw new DvmError9(err.body.error,err.body.message??err.message,err.body.hint,{http_status:err.status,...err.body.reason?{reason:err.body.reason}:{}},err.body.display,providerProseSources3(err.body,{message:err.message}))}throw err}}async function resolveAdminEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError9("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}var MAX_LIMIT=200;async function blockedInvoices(handle2,opts){const limit=resolveLimit(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getBlockedInvoices({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{}}));const unresolved=res.invoices.filter(i=>i.status==="blocked");if(opts.human){if(res.invoices.length===0){info("No blocked Lightning funding invoices. Nothing to reconcile.");return}for(const invoice of res.invoices){raw(`${invoice.payment_hash} ${formatAmount(invoice)} ${invoice.status} ${invoice.blocked_reason??"-"} credit=${invoice.credit_id} fund=${invoice.fund_id}`)}info(`${unresolved.length} awaiting a decision. Credit one with \`dvmctl credit reconcile ${handle2} <payment-hash> --credit-id <id>\`.`);if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_blocked",handle:handle2,dvmId,slug:handle2,invoices:res.invoices,blocked_count:unresolved.length,...res.next_after?{next_after:res.next_after}:{},display:unresolved.length===0?"No Lightning payments are waiting on an operator decision.":`${unresolved.length} Lightning payment(s) landed in this DVM's wallet with no caller balance to put them on.`,hint:"Credit one onto a credit the caller holds with `dvmctl credit reconcile <handle> <payment-hash> --credit-id <id>`, or close it with `dvmctl credit write-off`."})}async function reconcileInvoice(handle2,paymentHash,opts){const creditId=opts.creditId;if(!creditId){throw new DvmError10("missing_credit_id","No target credit named.","Pass `--credit-id <id>`. Which credit this payment should land on is not derivable from the failed row \u2014 `dvmctl credit blocked` shows the one it was minted for, and `dvm credit list` (as the caller) shows what they hold.")}const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileInvoice({payment_hash:paymentHash,credit_id:creditId}));if(opts.human){info(res.replayed?`Already credited to ${res.credit_id} \u2014 nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}.`);return}json({op:"credit_reconcile",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,credit_id:res.credit_id,fund_id:res.fund_id,amount_micro:res.amount_micro,currency:res.currency,replayed:res.replayed,...res.credit?{credit:res.credit}:{},display:res.replayed?`This payment was already credited to ${res.credit_id}. Nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}. The caller can spend it now.`,hint:"The deposit is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function writeOffInvoice(handle2,paymentHash,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffInvoice({payment_hash:paymentHash,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (${res.payment_hash})${res.note?` \u2014 note on record: "${res.note}"`:""}. Nothing changed.`:`Wrote off ${formatMicro(res.amount_micro,res.currency)} (${res.payment_hash}). No balance was created.`);return}json({op:"credit_write_off",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,amount_micro:res.amount_micro,currency:res.currency,note:res.note,replayed:res.replayed,display:res.replayed?`This payment was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:`Closed this payment without crediting it. The ${formatMicro(res.amount_micro,res.currency)} stays in your wallet and no caller balance was created for it.`,hint:"Reversible: `dvmctl credit reconcile` still accepts a written-off invoice if you change your mind."})}function resolveLimit(limit){if(limit===void 0)return 50;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT){throw new DvmError10("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT} (got ${limit}).`,"Leave it unset for the default page of 50, then follow `next_after` for the rest.")}return limit}function formatAmount(invoice){return`${formatMicro(invoice.amount_micro,invoice.currency)} (${invoice.amount_msats} msats)`}function formatMicro(amountMicro,currency){return`${(amountMicro/1e6).toFixed(6)} ${currency.toUpperCase()}`}import{lstatSync,mkdirSync as mkdirSync4,writeFileSync as writeFileSync4}from"fs";import{basename,join as join5,resolve as resolve2}from"path";import{createInterface}from"readline";import{DvmError as DvmError11}from"@dvmkit/sdk/internal";import{existsSync as existsSync4,readFileSync as readFileSync4}from"fs";import{dirname,join as join4}from"path";import{fileURLToPath}from"url";var CLI_VERSION=readVersion();function readVersion(){let dir=dirname(fileURLToPath(import.meta.url));for(;;){const candidate=join4(dir,"package.json");if(existsSync4(candidate)){const{version:version4}=JSON.parse(readFileSync4(candidate,"utf8"));return version4}const parent=dirname(dir);if(parent===dir){throw new Error(`tracked change: no package.json found walking up from ${import.meta.url}`)}dir=parent}}function packageJsonTemplate(opts){const pkg={name:opts.dirName,version:"0.1.0",private:true,type:"module",engines:{node:"^22.12.0 || ^24.0.0 || >=26.0.0"},scripts:{test:"vitest run",typecheck:"tsc --noEmit",dev:"dvmctl dev handler.ts",deploy:"dvmctl deploy"},dependencies:{"@dvmkit/sdk":"0.1.4-rc.7",pg:"^8"},devDependencies:{"@types/node":"^22",vitest:"5.0.1",typescript:"^5"}};return JSON.stringify(pkg,null,2)+"\n"}function tsconfigTemplate(){const config2={compilerOptions:{target:"ES2024",module:"ES2022",moduleResolution:"bundler",isolatedModules:true,strict:true,skipLibCheck:true,outDir:"dist"},include:["*.ts"],exclude:["*.test.ts"]};return JSON.stringify(config2,null,2)+"\n"}function envExampleTemplate(){return`# Local settings for \`dvmctl dev\`. Copy this file to \`.env\`.
2139
+ Authenticated as ${data.account}`)}else{console.log(JSON.stringify({authenticated:true,account:data.account}))}return}}console.error("\nAuthentication timed out. Try again or use DVMKIT_API_KEY env var.");process.exit(1)}function sleep(ms){return new Promise(resolve8=>setTimeout(resolve8,ms))}init_output();import{DvmError as DvmError10}from"@dvmkit/sdk/internal";import{DvmError as DvmError9,providerProseSources as providerProseSources3}from"@dvmkit/sdk/internal";import{ADMIN_STATE_UNAVAILABLE,buildAdminAuthHeader,DvmError as DvmError3}from"@dvmkit/sdk/internal";var AdminCashuClient=class{endpoint;dvmId;privkeyHex;fetchImpl;now;constructor(opts){this.endpoint=opts.endpoint.replace(/\/+$/,"");this.dvmId=opts.dvmId;this.privkeyHex=opts.privkeyHex;this.fetchImpl=opts.fetchImpl??globalThis.fetch.bind(globalThis);this.now=opts.now??Date.now}async getPendingBatches(){return this.requestJson("GET","/admin/cashu/pending-batches",void 0)}async markMelted(body){return this.requestJson("POST","/admin/cashu/mark-melted",body)}async rotateLockPubkey(body){return this.requestJson("POST","/admin/cashu/rotate-lock-pubkey",body)}async getPendingDrains(){return this.requestJson("GET","/admin/credit/pending-drains",void 0)}async getBitcoinLiability(){return this.requestJson("GET","/admin/credit/bitcoin-liability",void 0)}async parkDrain(body){return this.requestJson("POST","/admin/credit/park-drain",body)}async markDrainSent(body){return this.requestJson("POST","/admin/credit/mark-drain-sent",body)}async getBlockedInvoices(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/blocked-invoices",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileInvoice(body){return this.requestJson("POST","/admin/credit/reconcile-invoice",body)}async writeOffInvoice(body){return this.requestJson("POST","/admin/credit/write-off-invoice",body)}async getX402Settlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/x402-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402Settlement(body){return this.requestJson("POST","/admin/credit/reconcile-x402-settlement",body)}async writeOffX402Settlement(body){return this.requestJson("POST","/admin/credit/write-off-x402-settlement",body)}async getX402ExactSettlements(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.includeResolved)params.set("include_resolved","true");return this.requestJson("GET","/admin/credit/x402-exact-settlements",void 0,params.size===0?void 0:`?${params.toString()}`)}async reconcileX402ExactSettlement(settlementIntentId){return this.requestJson("POST","/admin/credit/reconcile-x402-exact-settlement",{settlement_intent_id:settlementIntentId})}async getTempoDrains(opts){const params=new URLSearchParams;if(opts?.limit!==void 0)params.set("limit",String(opts.limit));if(opts?.after!==void 0)params.set("after",opts.after);if(opts?.includeResolved)params.set("include_resolved","true");if(opts?.minAgeMs!==void 0)params.set("min_age_ms",String(opts.minAgeMs));if(opts?.evidence===false)params.set("evidence","false");return this.requestJson("GET","/admin/credit/tempo-drains",void 0,params.size===0?void 0:`?${params.toString()}`)}async getTempoCreditLosses(opts){const query=opts?.limit===void 0?void 0:`?limit=${String(opts.limit)}`;return this.requestJson("GET","/admin/credit/tempo-losses",void 0,query)}async reconcileTempoDrain(body){return this.requestJson("POST","/admin/credit/reconcile-tempo-drain",body)}async writeOffTempoDrain(body){return this.requestJson("POST","/admin/credit/write-off-tempo-drain",body)}async requestJson(method,path,body,query){const{header}=buildAdminAuthHeader({method,path,dvmId:this.dvmId,privkeyHex:this.privkeyHex,now:this.now});const init2={method,headers:{Authorization:header,...body===void 0?{}:{"Content-Type":"application/json"}},...body===void 0?{}:{body:JSON.stringify(body)}};let res;try{res=await this.fetchImpl(`${this.endpoint}${path}${query??""}`,init2)}catch(err){throw new DvmError3("admin_unreachable",`Failed to reach DVM admin endpoint ${this.endpoint}${path}: ${err instanceof Error?err.message:String(err)}`,"The DVM may be down or the endpoint URL is wrong. Verify with `dvmctl info <handle>`.")}const text=await res.text();let parsed;try{parsed=text.length===0?{}:JSON.parse(text)}catch{throw new DvmError3("admin_invalid_json",`DVM admin endpoint ${path} returned non-JSON (HTTP ${res.status}): ${text.slice(0,200)}`,"The DVM may not be running the tracked change SDK. Redeploy with the latest SDK.",void 0,void 0,{message:"provider"})}if(!res.ok){const body2=parsed;const apiError=new AdminApiError(res.status,{error:typeof body2.error==="string"?body2.error:"admin_error",reason:typeof body2.reason==="string"?body2.reason:void 0,message:typeof body2.message==="string"?body2.message:void 0,hint:typeof body2.hint==="string"?body2.hint:void 0,display:typeof body2.display==="string"?body2.display:void 0,raw:parsed});if(res.status===503&&apiError.body.error===ADMIN_STATE_UNAVAILABLE){throw new DvmError3(ADMIN_STATE_UNAVAILABLE,"DVM admin authentication state is temporarily unavailable; the requested DVM operation did not run.","Retry the same command. If this keeps happening, inspect the DVM's database health and restart the DVM. Do not rotate or replace the lock key.")}throw apiError}return parsed}};var AdminApiError=class extends Error{constructor(status2,body){super(`Admin endpoint returned HTTP ${status2}: ${body.error}${body.reason?` (${body.reason})`:""}`);this.status=status2;this.body=body;this.name="AdminApiError"}status;body;get isBadSignature(){return this.status===401&&this.body.reason==="bad_signature"}};function rethrowIfBadSignature(err){if(!(err instanceof AdminApiError)||!err.isBadSignature)return;throw new DvmError3("admin_bad_signature","DVM admin endpoint rejected our signed challenge (bad_signature).","The DVM's DVMKIT_CASHU_LOCK_PUBKEY does not match the privkey derived from this mnemonic. Re-deploy with the matching pubkey or switch mnemonics.")}function rethrowIfAdminStateUnavailable(err){if(err instanceof DvmError3&&err.code===ADMIN_STATE_UNAVAILABLE)throw err}init_builder_lock_identity();import{DvmError as DvmError8}from"@dvmkit/sdk/internal";async function resolveDvm(slug,opts={}){const{token,platformUrl}=requireAuth();const{dvms,org}=await listDvms(platformUrl,token,{org:resolveOrgScope(opts)});const dvm=dvms.find(candidate=>candidate.slug===slug);if(!dvm){throw new DvmError8("not_found",`DVM "${slug}" not found in ${scopeLabel(org)}.`,"Run `dvmctl list` to see the DVMs available in this context.")}return{dvmId:dvm.id,dvmkitUrl:dvm.dvmkitUrl,platformUrl,token}}async function connectAdmin(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);return{client:new AdminCashuClient({endpoint:await resolveAdminEndpoint(handle2,opts.endpoint,selected),dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey}),dvmId:canonicalDvmId}}async function adminCall(fn){try{return await fn()}catch(err){rethrowIfBadSignature(err);if(err instanceof AdminApiError){throw new DvmError9(err.body.error,err.body.message??err.message,err.body.hint,{http_status:err.status,...err.body.reason?{reason:err.body.reason}:{}},err.body.display,providerProseSources3(err.body,{message:err.message}))}throw err}}async function resolveAdminEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError9("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}var MAX_LIMIT=200;async function blockedInvoices(handle2,opts){const limit=resolveLimit(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getBlockedInvoices({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{}}));const unresolved=res.invoices.filter(i=>i.status==="blocked");if(opts.human){if(res.invoices.length===0){info("No blocked Lightning funding invoices. Nothing to reconcile.");return}for(const invoice of res.invoices){raw(`${invoice.payment_hash} ${formatAmount(invoice)} ${invoice.status} ${invoice.blocked_reason??"-"} credit=${invoice.credit_id} fund=${invoice.fund_id}`)}info(`${unresolved.length} awaiting a decision. Credit one with \`dvmctl credit reconcile ${handle2} <payment-hash> --credit-id <id>\`.`);if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_blocked",handle:handle2,dvmId,slug:handle2,invoices:res.invoices,blocked_count:unresolved.length,...res.next_after?{next_after:res.next_after}:{},display:unresolved.length===0?"No Lightning payments are waiting on an operator decision.":`${unresolved.length} Lightning payment(s) landed in this DVM's wallet with no caller balance to put them on.`,hint:"Credit one onto a credit the caller holds with `dvmctl credit reconcile <handle> <payment-hash> --credit-id <id>`, or close it with `dvmctl credit write-off`."})}async function reconcileInvoice(handle2,paymentHash,opts){const creditId=opts.creditId;if(!creditId){throw new DvmError10("missing_credit_id","No target credit named.","Pass `--credit-id <id>`. Which credit this payment should land on is not derivable from the failed row \u2014 `dvmctl credit blocked` shows the one it was minted for, and `dvm credit list` (as the caller) shows what they hold.")}const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileInvoice({payment_hash:paymentHash,credit_id:creditId}));if(opts.human){info(res.replayed?`Already credited to ${res.credit_id} \u2014 nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}.`);return}json({op:"credit_reconcile",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,credit_id:res.credit_id,fund_id:res.fund_id,amount_micro:res.amount_micro,currency:res.currency,replayed:res.replayed,...res.credit?{credit:res.credit}:{},display:res.replayed?`This payment was already credited to ${res.credit_id}. Nothing moved.`:`Credited ${formatMicro(res.amount_micro,res.currency)} to ${res.credit_id}. The caller can spend it now.`,hint:"The deposit is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function writeOffInvoice(handle2,paymentHash,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffInvoice({payment_hash:paymentHash,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (${res.payment_hash})${res.note?` \u2014 note on record: "${res.note}"`:""}. Nothing changed.`:`Wrote off ${formatMicro(res.amount_micro,res.currency)} (${res.payment_hash}). No balance was created.`);return}json({op:"credit_write_off",handle:handle2,dvmId,slug:handle2,payment_hash:res.payment_hash,amount_micro:res.amount_micro,currency:res.currency,note:res.note,replayed:res.replayed,display:res.replayed?`This payment was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:`Closed this payment without crediting it. The ${formatMicro(res.amount_micro,res.currency)} stays in your wallet and no caller balance was created for it.`,hint:"Reversible: `dvmctl credit reconcile` still accepts a written-off invoice if you change your mind."})}function resolveLimit(limit){if(limit===void 0)return 50;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT){throw new DvmError10("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT} (got ${limit}).`,"Leave it unset for the default page of 50, then follow `next_after` for the rest.")}return limit}function formatAmount(invoice){return`${formatMicro(invoice.amount_micro,invoice.currency)} (${invoice.amount_msats} msats)`}function formatMicro(amountMicro,currency){return`${(amountMicro/1e6).toFixed(6)} ${currency.toUpperCase()}`}import{lstatSync,mkdirSync as mkdirSync4,writeFileSync as writeFileSync4}from"fs";import{basename,join as join5,resolve as resolve2}from"path";import{createInterface}from"readline";import{DvmError as DvmError11}from"@dvmkit/sdk/internal";import{existsSync as existsSync4,readFileSync as readFileSync4}from"fs";import{dirname,join as join4}from"path";import{fileURLToPath}from"url";var CLI_VERSION=readVersion();function readVersion(){let dir=dirname(fileURLToPath(import.meta.url));for(;;){const candidate=join4(dir,"package.json");if(existsSync4(candidate)){const{version:version4}=JSON.parse(readFileSync4(candidate,"utf8"));return version4}const parent=dirname(dir);if(parent===dir){throw new Error(`tracked change: no package.json found walking up from ${import.meta.url}`)}dir=parent}}function packageJsonTemplate(opts){const pkg={name:opts.dirName,version:"0.1.0",private:true,type:"module",engines:{node:"^22.12.0 || ^24.0.0 || >=26.0.0"},scripts:{test:"vitest run",typecheck:"tsc --noEmit",dev:"dvmctl dev handler.ts",deploy:"dvmctl deploy"},dependencies:{"@dvmkit/sdk":"0.1.5-rc.8",pg:"^8"},devDependencies:{"@types/node":"^22",vitest:"5.0.1",typescript:"^5"}};return JSON.stringify(pkg,null,2)+"\n"}function tsconfigTemplate(){const config2={compilerOptions:{target:"ES2024",module:"ES2022",moduleResolution:"bundler",isolatedModules:true,strict:true,skipLibCheck:true,outDir:"dist"},include:["*.ts"],exclude:["*.test.ts"]};return JSON.stringify(config2,null,2)+"\n"}function envExampleTemplate(){return`# Local settings for \`dvmctl dev\`. Copy this file to \`.env\`.
2140
2140
  # On deploy, set production secrets on the platform \u2014 they are not read from this file.
2141
2141
  # OPENAI_API_KEY=sk-...
2142
2142
 
@@ -2156,8 +2156,8 @@ Authenticated as ${data.account}`)}else{console.log(JSON.stringify({authenticate
2156
2156
  # DVMKIT_TEMPO_RECIPIENT=0xYourTempoRecipientAddress
2157
2157
  # DVMKIT_TEMPO_SECRET_KEY=replace-with-a-random-secret
2158
2158
  # DVMKIT_TEMPO_RPC_URL=https://your-tempo-rpc.example
2159
- # The SDK has no local Tempo test-network switch. Use a funded testnet account
2160
- # only when your deployment and its caller are configured for that network.
2159
+ # For fake-money local testing on Moderato (chain 42431):
2160
+ # DVMKIT_TEMPO_TESTNET=true
2161
2161
  `}function agentsTemplate(opts){return`# Agent instructions
2162
2162
 
2163
2163
  Before changing this DVM, read the installed \`build-dvm\` skill from
@@ -2428,7 +2428,7 @@ ${r}`)}
2428
2428
  ${e19}]`:`[${t4.join(",")}]`;return r=e19,i3}let t3=c,n3=o??Object.keys(t3),i2=[];for(let e20 of n3){let n4=s(t3,e20);n4!==void 0&&i2.push(`${He(e20)}${r?": ":":"}${n4}`)}let a2=i2.length===0?"{}":r?`{
2429
2429
  ${r}${i2.join(`,
2430
2430
  ${r}`)}
2431
- ${e19}}`:`{${i2.join(",")}}`;return r=e19,a2}finally{a.delete(c)}}default:return}};return s({"":e17},"")}function Ke(e17){return e17.trim().replace(/[\t\n\f\r ]+/g,"").replace(/={1,2}$/,"")}function qe(e17){return base643.encode(e17)}function Je(e17){return base64urlnopad.encode(e17)}function Ye(e17){return base64url3.encode(e17)}function Xe(e17){try{return base64urlnopad.decode(Ke(e17))}catch(e18){throw new b("Invalid base64url string",{cause:e18})}}function Ze(e17){try{return base64nopad.decode(Ke(e17))}catch(e18){throw new b("Invalid base64 string",{cause:e18})}}function Qe(e17){try{return Xe(e17)}catch(t2){try{return Ze(e17)}catch{throw t2}}}function et(e17){if(typeof e17!="string"||e17.length===0)return false;let t2=Ke(e17);if(t2.length===0)return false;let n=/[-_]/.test(t2)?base64urlnopad:base64nopad;try{return n.encode(n.decode(t2))===t2}catch{return false}}function st(e17){let t2=[];return ct(e17,t2),new Uint8Array(t2)}function ct(e17,t2){if(e17===null)t2.push(246);else if(e17===void 0)t2.push(247);else if(typeof e17=="boolean")t2.push(e17?245:244);else if(typeof e17=="number")mt(e17,t2);else if(typeof e17=="bigint")ut(e17,t2);else if(typeof e17=="string")gt(e17,t2);else if(Array.isArray(e17))_t(e17,t2);else if(e17 instanceof Uint8Array)ht(e17,t2);else if(typeof e17=="object"&&e17&&!Array.isArray(e17))vt(e17,t2);else throw new b("Unsupported type")}function lt(e17,t2){e17<24?t2.push(e17):e17<256?t2.push(24,e17):e17<65536?t2.push(25,e17>>>8&255,e17&255):e17<4294967296?t2.push(26,e17>>>24&255,e17>>>16&255,e17>>>8&255,e17&255):ut(BigInt(e17),t2)}function ut(e17,t2){e17>=0n?dt(0,e17,t2):dt(1,-1n-e17,t2)}function dt(e17,t2,n){let r=e17<<5;if(t2<24n)n.push(r|Number(t2));else if(t2<256n)n.push(r|24,Number(t2));else if(t2<65536n){let e18=Number(t2);n.push(r|25,e18>>>8&255,e18&255)}else if(t2<4294967296n){let e18=Number(t2);n.push(r|26,e18>>>24&255,e18>>>16&255,e18>>>8&255,e18&255)}else if(t2<18446744073709551616n){let e18=Number(t2>>32n),i=Number(t2&4294967295n);n.push(r|27,e18>>>24&255,e18>>>16&255,e18>>>8&255,e18&255,i>>>24&255,i>>>16&255,i>>>8&255,i&255)}else throw new b("BigInt value out of uint64 range")}function ft(e17,t2){let n=-1-e17;n<24?t2.push(32|n):n<256?t2.push(56,n&255):n<65536?t2.push(57,n>>>8&255,n&255):n<4294967296?t2.push(58,n>>>24&255,n>>>16&255,n>>>8&255,n&255):ut(BigInt(e17),t2)}function pt(e17,t2){let n=new DataView(new ArrayBuffer(8));n.setFloat64(0,e17,false),t2.push(251);for(let e18=0;e18<8;e18++)t2.push(n.getUint8(e18))}function mt(e17,t2){Number.isInteger(e17)?e17>=0?lt(e17,t2):ft(e17,t2):pt(e17,t2)}function ht(e17,t2){let n=e17.length;if(n<24)t2.push(64+n);else if(n<256)t2.push(88,n);else if(n<65536)t2.push(89,n>>8&255,n&255);else if(n<4294967296)t2.push(90,n>>>24&255,n>>>16&255,n>>>8&255,n&255);else throw new b("Byte string too long to encode");for(let n2=0;n2<e17.length;n2++)t2.push(e17[n2])}function gt(e17,t2){let n=new TextEncoder().encode(e17),r=n.length;if(r<24)t2.push(96+r);else if(r<256)t2.push(120,r);else if(r<65536)t2.push(121,r>>>8&255,r&255);else if(r<4294967296)t2.push(122,r>>>24&255,r>>>16&255,r>>>8&255,r&255);else throw new b("String too long to encode");for(let e18=0;e18<n.length;e18++)t2.push(n[e18])}function _t(e17,t2){let n=e17.length;if(n<24)t2.push(128|n);else if(n<256)t2.push(152,n);else if(n<65536)t2.push(153,n>>>8&255,n&255);else throw new b("Unsupported array length");for(let n2 of e17)ct(n2,t2)}function vt(e17,t2){let n=Object.keys(e17),r=n.length;if(r>=4294967296)throw new b("Object has too many keys to encode");r<24?t2.push(160|r):r<256?t2.push(184,r):r<65536?t2.push(185,r>>8&255,r&255):t2.push(186,r>>24&255,r>>16&255,r>>8&255,r&255);for(let r2 of n)gt(r2,t2),ct(e17[r2],t2)}var jt=utf8ToBytes3("Secp256k1_HashToCurve_Cashu_");function Mt(r){let i=sha2565(concatBytes(jt,r)),a=0,o=new Uint8Array(4),s=new DataView(o.buffer),c=2**16;for(let r2=0;r2<c;r2++){s.setUint32(0,a,true);let r3=sha2565(concatBytes(i,o));try{return P(bytesToHex2(concatBytes(new Uint8Array([2]),r3)))}catch{a++}}throw new b("No valid point found")}function Nt(t2){let n=t2.map(e17=>e17.toHex(false)).join("");return sha2565(new TextEncoder().encode(n))}function P(e17){return secp256k1.Point.fromHex(e17)}var Lt=e17=>{let t2,n=e18=>bytesToNumberBE(Qe(e18))%BigInt(2**31-1);if(e17.length===12&&et(e17))t2=n(e17);else if(V(e17))t2=wi(e17)%BigInt(2**31-1);else if(et(e17))t2=n(e17);else throw new b("Invalid keyset id: neither hex nor base64");return t2};function Rt(){return secp256k1.utils.randomSecretKey()}function Vt(e17,t2){let n=Mt(e17);if(t2===void 0)t2=secp256k1.Point.Fn.fromBytes(Rt());else if(t2===0n)throw new b("Blinding factor r must be non-zero");let r=secp256k1.Point.BASE.multiply(t2);return{B_:n.add(r),r:t2,secret:e17}}function Ht(e17,t2,n){return e17.subtract(n.multiply(t2))}function Ut(e17,t2,n,r){let i=Ht(e17.C_,t2,r);return{id:e17.id,secret:n,C:i}}var gn=utf8ToBytes3("Cashu_P2BK_v1");var _n=gn.slice();function vn(e17,n,r=true){let i=+!r,a=e17.length+i;if(a>11)throw new b(`Too many pubkeys, ${a} slots provided, maximum allowed is 11 in total`);if(!e17.length)return{blinded:[],Ehex:""};n=n??secp256k1.utils.randomSecretKey();let o=secp256k1.Point.Fn.fromBytes(n),s=secp256k1.getPublicKey(n,true);return{blinded:e17.map((e18,t2)=>{let n2=P(e18),r2=xn(n2,o,t2+i),a2=n2.add(secp256k1.Point.BASE.multiply(r2));if(a2.equals(secp256k1.Point.ZERO))throw new b("Blinded key at infinity");return a2.toHex(true)}),Ehex:bytesToHex2(s)}}function xn(t2,r,i){if(!Number.isInteger(i)||i<0||i>255)throw new b("P2BK: slot index must be an integer in [0, 255]");let a=t2.multiply(r).toBytes(true).slice(1),o=new Uint8Array([i&255]),s=bytesToNumberBE(sha2565(concatBytes(gn,a,o)));if((s===0n||s>=secp256k1.Point.CURVE().n)&&(s=bytesToNumberBE(sha2565(concatBytes(gn,a,o,new Uint8Array([255])))),s===0n||s>=secp256k1.Point.CURVE().n))throw new b("P2BK: tweak derivation failed");return s}var Sn={SIG_INPUTS:"SIG_INPUTS",SIG_ALL:"SIG_ALL"};var Cn=new Set(Object.values(Sn));function Dn(e17){let t2=e17.toLowerCase();if(t2.length===66&&(t2.startsWith("02")||t2.startsWith("03")))return t2;if(t2.length===64)return`02${t2}`;throw new b(`Invalid pubkey, expected 33 byte compressed or 32 byte x only, got length ${t2.length}`)}function L(e17){let t2=new Set,n=[];for(let r of e17){let e18=Dn(r),i=e18.slice(-64);t2.has(i)||(t2.add(i),n.push(e18))}return n}function On(e17){let t2=L(Array.isArray(e17.pubkey)?e17.pubkey:[e17.pubkey]),n=L(e17.refundKeys??[]),r=typeof e17.hashlock=="string"&&e17.hashlock.length>0;if(t2.length===0&&!r)throw new b("P2PK requires at least one pubkey");let i=+!!r+t2.length+n.length;if(i>11)throw new b(`Too many pubkeys, ${i} slots provided, maximum allowed is 11 in total`);e17.sigFlag!==void 0&&Wn(e17.sigFlag);let a=t2.length>0?e17.requiredSignatures??1:e17.requiredSignatures,o=e17.requiredRefundSignatures;return Kn({mainKeyCount:t2.length,refundKeyCount:n.length,nSigs:a,nSigsRefund:o,hasLocktime:e17.locktime!==void 0}),{pubkey:t2.length===1?t2[0]:t2,...e17.locktime===void 0?{}:{locktime:e17.locktime},...n.length>0?{refundKeys:n}:{},...a!==void 0&&a>1?{requiredSignatures:a}:{},...o!==void 0&&o>1?{requiredRefundSignatures:o}:{},...e17.additionalTags?.length?{additionalTags:e17.additionalTags}:{},...e17.blindKeys?{blindKeys:true}:{},...e17.sigFlag===void 0?{}:{sigFlag:e17.sigFlag},...e17.hashlock?{hashlock:e17.hashlock}:{}}}function Wn(e17){if(!Cn.has(e17))throw new b(`Invalid sigflag "${e17}": must be "SIG_INPUTS" or "SIG_ALL"`)}function Gn(e17,t2){if(!Number.isInteger(e17)||e17<1)throw new b(`${t2} must be a positive integer, got ${e17}`);return e17}function Kn(e17){let{mainKeyCount:t2,refundKeyCount:n,nSigs:r,nSigsRefund:i,hasLocktime:a}=e17;if(r!==void 0&&(Gn(r,"requiredSignatures (n_sigs)"),r>t2))throw new b(`requiredSignatures (n_sigs) (${r}) exceeds available pubkeys (${t2})`);if(i!==void 0){if(Gn(i,"requiredRefundSignatures (n_sigs_refund)"),n===0)throw new b("requiredRefundSignatures (n_sigs_refund) requires refund keys");if(i>n)throw new b(`requiredRefundSignatures (n_sigs_refund) (${i}) exceeds available refund keys (${n})`)}if(n>0&&!a)throw new b("refund keys require a locktime")}var $n=utf8ToBytes3("Cashu_DLEQ_R_v1");var tr=(e17,t2,n,r)=>{let i=secp256k1.Point.Fn.fromBytes(e17.s),a=secp256k1.Point.Fn.fromBytes(e17.e),o=secp256k1.Point.BASE.multiply(i),s=r.multiply(a),c=t2.multiply(i),l=n.multiply(a);return equalBytes2(Nt([o.subtract(s),c.subtract(l),r,n]),e17.e)};var nr=(e17,t2,n,r)=>{if(t2.r===void 0)throw new b("verifyDLEQProof_reblind: Undefined blinding factor");let i=Mt(e17),a=n.add(r.multiply(t2.r)),o=secp256k1.Point.BASE.multiply(t2.r);return tr(t2,i.add(o),a,r)};var ir="m/129372'/0'";var ar=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");var or=(function(e17){return e17[e17.SECRET=0]="SECRET",e17[e17.BLINDING_FACTOR=1]="BLINDING_FACTOR",e17})(or||{});var sr=(function(e17){return e17[e17.DEPRECATED_BIP32=0]="DEPRECATED_BIP32",e17[e17.HMAC_SHA256=1]="HMAC_SHA256",e17})(sr||{});function ur(e17,t2,n){return dr(e17,t2)(n)}function dr(e17,t2){switch(pr(e17),fr(t2)){case sr.DEPRECATED_BIP32:{let n=Lt(t2),r=HDKey.fromMasterSeed(e17).derive(`${ir}/${n}'`);return e18=>mr(r,e18)}case sr.HMAC_SHA256:return n=>hr(e17,t2,n)}}function fr(e17){if(e17.length===12&&et(e17))return sr.DEPRECATED_BIP32;let t2=/^[a-fA-F0-9]+$/.test(e17),n=e17.startsWith("01");if(t2&&n&&e17.length%2!=0)throw new b("Invalid hex string: odd length.");if(!t2&&et(e17)||t2&&e17.startsWith("00"))return sr.DEPRECATED_BIP32;if(t2&&n)return sr.HMAC_SHA256;throw new b(`Unrecognized keyset ID version ${e17.slice(0,2)}`)}function pr(e17){if(!(e17 instanceof Uint8Array)||e17.length<16||e17.length>64)throw new b("seed must be a 16 to 64 byte Uint8Array")}function mr(e17,t2){if(!Number.isInteger(t2)||t2<0||t2>=2147483648)throw new b("Counter must be an integer in the range 0 <= counter < 2^31");let n=e17.deriveChild(HARDENED_OFFSET+t2),r=n.deriveChild(0).privateKey,i=n.deriveChild(1).privateKey;if(r===null||i===null)throw new b("Could not derive private key");return{secret:r,blindingFactor:i}}function hr(e17,t2,n){if(!Number.isSafeInteger(n)||n<0)throw new b("Counter must be an integer in the range 0 <= counter <= 2^53 - 1");return{secret:gr(e17,t2,n,or.SECRET),blindingFactor:gr(e17,t2,n,or.BLINDING_FACTOR)}}function gr(t2,i,o,s){let c=concatBytes(utf8ToBytes3("Cashu_KDF_HMAC_SHA256"),hexToBytes2(i),numberToBytesBE(o,8));switch(s){case or.SECRET:c=concatBytes(c,hexToBytes2("00"));break;case or.BLINDING_FACTOR:c=concatBytes(c,hexToBytes2("01"))}let l=hmac2(sha2565,t2,c);if(s===or.BLINDING_FACTOR){let e17=bytesToNumberBE(l),t3=e17>=ar?e17-ar:e17;if(t3===0n)throw new b("Derived invalid blinding scalar r == 0");return numberToBytesBE(t3,32)}return l}var wr=utf8ToBytes3("Cashu_MintQuoteSig_v1");function B(e17,t2,n,r){let i=Ci(e17,"splitAmount.value",true),a=n?.map(e18=>Ci(e18,"splitAmount.split",true));if(a){if(a.length>8192)throw new b(`Cannot split amount: split would exceed ${ae} outputs`);let e18=A.sum(a);if(i.isZero()&&e18.isZero())return a;let n2=a.filter(e19=>!e19.isZero()),r2=A.sum(n2);if(r2.greaterThan(i))throw new b(`Split is greater than total amount: ${r2.toString()} > ${i.toString()}`);if(n2.some(e19=>!Si(e19,t2)))throw new b("Provided amount preferences do not match the amounts of the mint keyset.");if(r2.equals(i))return n2;a=n2,i=i.subtract(r2)}else a=[];let o=xi(t2,"desc");if(o.length===0)throw new b("Cannot split amount, keyset is inactive or contains no keys");for(let e18 of o){if(e18.isZero())continue;let t3=i.divideBy(e18),n2=ae-a.length;if(n2<=0||t3.greaterThan(n2))throw new b(`Cannot split amount: fill would exceed ${ae} outputs`);let r2=t3.toNumber();for(let t4=0;t4<r2;t4++)a.push(e18);if(i=i.subtract(e18.multiplyBy(t3)),i.isZero())break}if(!i.isZero())throw new b(`Unable to split remaining amount: ${i.toString()}`);return r&&(a=a.sort((e18,t3)=>r==="desc"?t3.compareTo(e18):e18.compareTo(t3))),a}function xi(e17,t2){let n=Object.keys(e17).map(e18=>A.from(e18));return n.sort((e18,n2)=>t2==="desc"?n2.compareTo(e18):e18.compareTo(n2)),n}function Si(e17,t2){let n=Ci(e17,"hasCorrespondingKey.amount",true).toString();return Object.prototype.hasOwnProperty.call(t2,n)}function Ci(e17,t2,n=false){let r=A.from(e17);if(!n&&r.isZero())throw new b(`Amount must be positive: ${r.toString()}, op: ${t2}`);return r}function wi(e17){return e17?BigInt(`0x${e17}`):0n}function Ti(e17){return e17.toString(16).padStart(64,"0")}function V(e17){return typeof e17=="string"&&e17.length%2==0&&/^[a-f0-9]+$/i.test(e17)}function Ei(e17){return Array.isArray(e17)?e17.some(e18=>!V(e18.id)):!V(e17.id)}function Oi(e17){return e17.map(e18=>{let t2={...e18};return t2.id=t2.id.slice(0,16),t2})}function ki(e17,t2){let n=q(e17.proofs);if(Ei(n))throw new b("Proofs contain a legacy keyset ID and cannot be encoded. Swap them at the mint first.");return Ai({...e17,proofs:n},t2?.removeDleq)}function Ai(e17,t2){let n=e17.proofs;if(t2&&(n=Ki(n)),Ei(n))throw new b("can not encode to v4 token if proofs contain non-hex keyset id");return n=Oi(n),"cashuB"+Je(st(Mi({...e17,proofs:n})))}function ji(e17){if(e17.r==null)throw new b("Missing blinding factor in included DLEQ proof");return{e:hexToBytes2(e17.e),s:hexToBytes2(e17.s),r:hexToBytes2(e17.r)}}function Mi(e17){let t2=Object.create(null),n=e17.mint;for(let n2=0;n2<e17.proofs.length;n2++){let r=e17.proofs[n2];t2[r.id]?t2[r.id].push(r):t2[r.id]=[r]}let i={m:n,u:e17.unit||"sat",t:Object.keys(t2).map(e18=>({i:hexToBytes2(e18),p:t2[e18].map(e19=>({a:e19.amount.toBigInt(),s:e19.secret,c:hexToBytes2(e19.C),...e19.dleq&&{d:ji(e19.dleq)},...e19.p2pk_e&&{pe:hexToBytes2(e19.p2pk_e)},...e19.witness&&{w:JSON.stringify(e19.witness)}}))}))};return e17.memo&&(i.d=e17.memo),i}function zi(n,i){for(let e17 of Object.keys(n))if(e17.length>20)throw new b("Invalid keyset denomination: exceeds 20 digits");let o=i?.unit??"sat",s=i?.expiry,c=i?.versionByte??1,l=i?.input_fee_ppk;if(i?.isDeprecatedBase64??false)return qe(sha2565(utf8ToBytes3(Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([,e17])=>e17).reduce((e17,t2)=>e17+t2,"")))).slice(0,12);switch(c){case 0:return"00"+bytesToHex2(sha2565(Bi(...Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([,e17])=>hexToBytes2(e17))))).slice(0,14);case 1:{if(!o)throw new b("Cannot compute keyset ID version 01: unit is required.");let r=Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([e17,t2])=>`${e17}:${t2}`).join(",");return r+=`|unit:${o}`,l&&(r+=`|input_fee_ppk:${l}`),s&&(r+=`|final_expiry:${s}`),"01"+bytesToHex2(sha2565(utf8ToBytes3(r)))}default:throw new b(`Unrecognized keyset ID version: ${c}`)}}function Bi(...e17){let t2=e17.reduce((e18,t3)=>e18+t3.length,0),n=new Uint8Array(t2),r=0;for(let t3 of e17)n.set(t3,r),r+=t3.length;return n}function H(e17){return typeof e17=="object"&&!!e17}function U(e17){return H(e17)&&!Array.isArray(e17)}function W(e17,...t2){for(let n of t2)e17[n]===void 0&&(e17[n]=null)}function G(...e17){return e17.map(e18=>e18.replace(/(^\/+|\/+$)/g,"")).join("/")}function Hi(e17){let t2;try{t2=new URL(e17)}catch(t3){throw new b(`Invalid mint URL: ${e17}`,{cause:t3})}if(t2.protocol!=="http:"&&t2.protocol!=="https:")throw new b(`Invalid mint URL scheme: ${t2.protocol}`);if(t2.username||t2.password)throw new b("Mint URL must not contain credentials");if(t2.search||t2.href.includes("?"))throw new b("Mint URL must not contain query parameters");if(t2.hash||t2.href.includes("#"))throw new b("Mint URL must not contain a fragment");if(/%[0-9a-f]{2}/i.test(t2.pathname))throw new b("Mint URL path must not contain percent-encoded characters");return t2.href.replace(/\/+$/,"")}function q(e17){return e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}function Ki(e17){return e17.map(e18=>{let{dleq:t2,...n}=e18;return n})}function Ji(e17,t2,n){let i=n?.require??true;if(!Si(e17.amount,t2.keys))throw new b(Object.keys(t2.keys).length===0?`No keys loaded for keyset ${t2.id}`:`Undefined key for amount ${e17.amount.toString()} in keyset ${t2.id}`);if(e17?.dleq==null)return!i;if(e17.dleq.r==null)return false;let a=t2.keys[e17.amount.toString()];try{let t3={e:hexToBytes2(e17.dleq.e),s:hexToBytes2(e17.dleq.s),r:wi(e17.dleq.r)};return nr(new TextEncoder().encode(e17.secret),t3,P(e17.C),P(a))}catch{return false}}function J(e17,t2,n){if(e17==null){if(arguments.length>=3)return n;throw new b(`Invalid ${t2}: missing value`)}try{return A.from(e17).toNumber()}catch(e18){throw new b(`Invalid ${t2}: ${e18 instanceof Error?e18.message:String(e18)}`,{cause:e18})}}function ra(e17){return{...e17,input_fee_ppk:J(e17.input_fee_ppk,"keyset.input_fee_ppk",void 0),final_expiry:J(e17.final_expiry,"keyset.final_expiry",void 0)}}function ia(e17){return{...e17,input_fee_ppk:J(e17.input_fee_ppk,"keys.input_fee_ppk",void 0),final_expiry:J(e17.final_expiry,"keys.final_expiry",void 0)}}function aa(e17,t2){let n;try{n=new URL(e17).protocol}catch{throw new b(`OIDCAuth: ${t2} is not a valid URL`)}if(n!=="https:"&&n!=="http:")throw new b(`OIDCAuth: ${t2} must be an http(s) URL`)}var oa=class t{static fromMintInfo(e17,n){let r=e17?.nuts?.["21"];if(!r?.openid_discovery)throw new b("OIDCAuth: mint does not advertise NUT-21 openid_discovery");let i=n?.clientId??r.client_id??"cashu-client";return new t(r.openid_discovery,{...n,clientId:i})}constructor(e17,t2){this.tokenListeners=[],this.inflightRefreshes=new Map,this.discoveryUrl=e17,this.logger=t2?.logger??E,this.clientId=t2?.clientId??"cashu-client",this.scope=t2?.scope??"openid",this.onTokens=t2?.onTokens}setClient(e17){this.clientId=e17}setScope(e17){this.scope=e17??"openid"}addTokenListener(e17){this.tokenListeners=[...this.tokenListeners,e17]}removeTokenListener(e17){this.tokenListeners=this.tokenListeners.filter(t2=>t2!==e17)}async loadConfig(){if(this.config)return this.config;let e17=await fetch(this.discoveryUrl,{method:"GET",headers:{Accept:"application/json"}}),t2=await e17.text(),n,r;try{n=t2?JSON.parse(t2):void 0}catch(e18){r=e18,this.logger.warn("OIDCAuth: bad discovery JSON",{err:e18})}if(!e17.ok||!n)throw new b("OIDCAuth: invalid discovery document",{cause:r});let i=n;if(typeof i.token_endpoint!="string"||i.token_endpoint.length===0)throw new b("OIDCAuth: invalid discovery document, missing token_endpoint");return this.config=i,i}generatePKCE(){let t2=Je(randomBytes3(48));return{verifier:t2,challenge:Je(sha2565(utf8ToBytes3(t2)))}}async buildAuthCodeUrl(e17){let t2=await this.loadConfig(),n=e17.scope??this.scope,r=e17.codeChallengeMethod;r!==void 0&&r!=="S256"&&this.logger.warn(`OIDCAuth: codeChallengeMethod '${typeof r=="string"?r:typeof r}' is not supported by cashu-ts v5, which accepts S256 only.`);let i=new URLSearchParams({response_type:"code",client_id:this.clientId,redirect_uri:e17.redirectUri,scope:n,code_challenge_method:e17.codeChallengeMethod??"S256",code_challenge:e17.codeChallenge});if(e17.state&&i.set("state",e17.state),!t2.authorization_endpoint)throw new b("OIDCAuth: discovery lacks authorization_endpoint");return aa(t2.authorization_endpoint,"authorization_endpoint"),`${t2.authorization_endpoint}?${i.toString()}`}async exchangeAuthCode(e17){let t2=await this.loadConfig(),n=this.toForm({grant_type:"authorization_code",code:e17.code,redirect_uri:e17.redirectUri,client_id:this.clientId,code_verifier:e17.codeVerifier}),r=await this.postFormStrict(t2.token_endpoint,n);return this.handleTokens(r,"signin"),r}async deviceStart(){let e17=(await this.loadConfig()).device_authorization_endpoint;if(!e17)throw new b("OIDCAuth: provider lacks device_authorization_endpoint");let t2=this.toForm({client_id:this.clientId,scope:this.scope}),n=await this.postFormStrict(e17,t2);return aa(n.verification_uri,"verification_uri"),n.verification_uri_complete!==void 0&&aa(n.verification_uri_complete,"verification_uri_complete"),n}async devicePoll(e17,t2=5){let n=await this.loadConfig(),r=Number(t2),i=Number.isFinite(r)?Math.max(1,r):5;for(;;){await this.sleep(i*1e3);let t3=this.toForm({grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:e17,client_id:this.clientId}),r2=await this.postFormLoose(n.token_endpoint,t3);if(r2.access_token)return this.handleTokens(r2,"signin",this.tokenListeners),r2;let a=(r2.error??"").toString();if(a!=="authorization_pending"){if(a==="slow_down"){i=Math.max(i+5,i*2);continue}throw new b(`OIDCAuth: ${r2.error_description||a||"device authorization failed"}`)}}}async startDeviceAuth(e17=5){let t2=await this.deviceStart(),n=Number(t2.interval),r=Number.isFinite(n)&&n>0?n:1,i=Number(e17),a=Math.max(r,Number.isFinite(i)?i:5),o=new AbortController,s,c=new Promise(e18=>{s=e18}),l=new Set,u=()=>{if(o.signal.aborted)throw new b("OIDCAuth: device polling cancelled")},d=async()=>{let e18=await this.loadConfig(),n2=Math.max(1,a);for(;;){u();let r2;await this.sleep(n2*1e3,e19=>{r2=e19,l.add(e19)}),l.delete(r2),u();let i2=this.toForm({grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:t2.device_code,client_id:this.clientId}),a2=await this.postFormLoose(e18.token_endpoint,i2,o.signal);if(u(),a2.access_token)return this.handleTokens(a2,"signin"),a2;let s2=(a2.error??"").toString();if(s2!=="authorization_pending"){if(s2==="slow_down"){n2=Math.max(n2+5,n2*2);continue}throw new b(`OIDCAuth: ${a2.error_description||s2||"device authorization failed"}`)}}},f=()=>{o.abort(),s();for(let e18 of l)e18();l.clear()};return{...t2,poll:()=>Promise.race([d(),c.then(()=>{throw new b("OIDCAuth: device polling cancelled")})]),cancel:f}}async refresh(e17){let t2=this.clientId,n=this.tokenListeners,r=this.inflightRefreshes.get(e17);if(r?.listeners===n&&r.clientId===t2)return r.promise;let i=(async()=>{let r2=await this.loadConfig(),i2=this.toForm({grant_type:"refresh_token",refresh_token:e17,client_id:t2}),a=await this.postFormStrict(r2.token_endpoint,i2);return a.refresh_token||(a.refresh_token=e17),this.handleTokens(a,"refresh",n),a})();this.inflightRefreshes.set(e17,{clientId:t2,listeners:n,promise:i});try{return await i}finally{this.inflightRefreshes.get(e17)?.promise===i&&this.inflightRefreshes.delete(e17)}}async passwordGrant(e17,t2){let n=await this.loadConfig(),r=this.toForm({grant_type:"password",client_id:this.clientId,username:e17,password:t2,scope:this.scope}),i=await this.postFormStrict(n.token_endpoint,r);return this.handleTokens(i,"signin"),i}handleTokens(e17,t2,n=this.tokenListeners){if(!e17.access_token)throw new b(`OIDCAuth: ${e17.error_description||e17.error||"token response missing access_token"}`);queueMicrotask(()=>O(e18=>this.onTokens?.(e18,t2),e17,this.logger,{where:"OIDCAuth.handleTokens"}));let r=new Set(this.tokenListeners);for(let i of n)r.has(i)&&queueMicrotask(()=>O(e18=>i(e18,t2),e17,this.logger,{where:"OIDCAuth.handleTokens.listener"}))}toForm(e17){let t2=e18=>encodeURIComponent(e18).replace(/%20/g,"+");return Object.entries(e17).map(([e18,n])=>`${t2(e18)}=${t2(n)}`).join("&")}async postFormStrict(e17,t2){try{let n=await fetch(e17,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t2,redirect:"error"}),r=await n.text(),i,a;try{i=r?JSON.parse(r):void 0}catch(e18){a=e18,this.logger.warn("OIDCAuth: bad JSON (strict)",{err:e18})}if(!n.ok){let e18=i??{};throw new b(`OIDCAuth: ${e18.error_description||e18.error||`HTTP ${n.status}`}`,{cause:a})}return this.logger.debug("OIDCAuth Response",{status:n.status}),i??{}}catch(e18){throw this.logger.error("OIDCAuth: postFormStrict failed",{err:e18}),e18}}async postFormLoose(e17,t2,n){try{let r=await fetch(e17,{method:"POST",signal:n,headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t2,redirect:"error"}),i=await r.text(),a;try{a=i?JSON.parse(i):void 0}catch(e18){this.logger.warn("OIDCAuth: bad JSON (loose)",{err:e18})}return this.logger.debug("OIDCAuth Response",{status:r.status}),a??{}}catch(e18){return this.logger.error("OIDCAuth: postFormLoose network error",{err:e18}),{error:"network_error",error_description:String(e18)}}}sleep(e17,t2){let n=Math.min(e17,ue);return new Promise(e18=>{let r=setTimeout(e18,n);t2?.(()=>{clearTimeout(r),e18()})})}};var Y=class e10{constructor(t2,n){let r=n??E;this._mintInfo=e10.normalizeInfo(t2,r),this._maxArrayLength=e10.resolveMaxArrayLength(t2.max_array_length,r);let i=this.toEndpoints(this._mintInfo?.nuts?.[22]?.protected_endpoints,r);this._protected22=this.buildIndex(i);let a=this.toEndpoints(this._mintInfo?.nuts?.[21]?.protected_endpoints,r);this._protected21=this.buildIndex(a)}static normalizeInfo(t2,n=E){if(!U(t2)||!U(t2.nuts))throw n.error("MintInfo: malformed info response",{op:"normalizeInfo"}),new b("Invalid response from mint");return{...t2,...Array.isArray(t2.contact)?{contact:e10.capList(t2.contact,"contact",n)}:{},...Array.isArray(t2.urls)?{urls:e10.capList(t2.urls,"urls",n)}:{},nuts:{...t2.nuts,...t2.nuts[4]?{4:e10.normalizeSwapSection(t2.nuts[4],"nuts.4",n)}:{},...t2.nuts[5]?{5:e10.normalizeSwapSection(t2.nuts[5],"nuts.5",n)}:{},...t2.nuts[15]?{15:e10.normalizeNut15(t2.nuts[15],n)}:{},...t2.nuts[17]?{17:e10.normalizeNut17(t2.nuts[17],n)}:{},...t2.nuts[19]?{19:e10.normalizeNut19(t2.nuts[19],n)}:{},...t2.nuts[21]?{21:e10.normalizeNut21(t2.nuts[21],n)}:{},...t2.nuts[22]?{22:e10.normalizeNut22(t2.nuts[22],n)}:{},...t2.nuts[29]?{29:e10.normalizeNut29(t2.nuts[29],n)}:{}}}}static dropSection(e17,t2){t2.warn(`MintInfo: ${e17} is malformed and was omitted`)}static normalizeSwapSection(t2,n,r){if(!U(t2))throw r.error("MintInfo: malformed info response",{op:n}),new b("Invalid response from mint");return{...t2,methods:e10.normalizeSwapMethods(t2.methods,r)}}static normalizeSwapMethods(t2,n){return Array.isArray(t2)?e10.capList(t2,"nuts.4/5.methods",n).filter(e17=>U(e17)).map(e17=>{let t3={...e17};return W(t3,"min_amount","max_amount"),t3}):(n.warn("MintInfo: nuts.4/5.methods is malformed and was omitted"),[])}static resolveMaxArrayLength(e17,t2){if(e17==null)return 500;let n;try{n=J(e17,"max_array_length")}catch{return t2.warn("MintInfo: max_array_length is malformed, defaulting to internal default",{value:e17}),500}if(n<1||n>1e4){let e18=Math.min(Math.max(n,1),oe);return t2.warn("MintInfo: max_array_length is out of range and was clamped",{advertised:n,clampedTo:e18}),e18}return n}static normalizeNut15(t2,n){return U(t2)?Array.isArray(t2.methods)?{...t2,methods:e10.capList(t2.methods,"nuts.15.methods",n)}:t2:e10.dropSection("nuts.15",n)}static normalizeNut17(t2,n){return U(t2)?Array.isArray(t2.supported)?{...t2,supported:e10.capList(t2.supported,"nuts.17.supported",n)}:t2:e10.dropSection("nuts.17",n)}static normalizeNut19(t2,n){return U(t2)?{...t2,...Array.isArray(t2.cached_endpoints)?{cached_endpoints:e10.capList(t2.cached_endpoints,"nuts.19.cached_endpoints",n)}:{},ttl:J(t2.ttl,"nuts.19.ttl",null)}:e10.dropSection("nuts.19",n)}static normalizeNut21(t2,n){return U(t2)?Array.isArray(t2.protected_endpoints)?{...t2,protected_endpoints:e10.capList(t2.protected_endpoints,"nuts.21.protected_endpoints",n)}:t2:e10.dropSection("nuts.21",n)}static normalizeNut22(t2,n){if(!U(t2))return e10.dropSection("nuts.22",n);let r=100;try{r=J(t2.bat_max_mint,"nuts.22.bat_max_mint",100)}catch{n.warn("MintInfo: nuts.22.bat_max_mint is malformed, defaulting to internal cap",{value:t2.bat_max_mint})}return r>100&&(n.warn("MintInfo: nuts.22.bat_max_mint exceeds internal cap and was clamped",{advertised:r,clampedTo:100}),r=100),{...t2,...Array.isArray(t2.protected_endpoints)?{protected_endpoints:e10.capList(t2.protected_endpoints,"nuts.22.protected_endpoints",n)}:{},bat_max_mint:r}}static normalizeNut29(t2,n){if(!U(t2))return e10.dropSection("nuts.29",n);let r=100;try{r=J(t2.max_batch_size,"nuts.29.max_batch_size",100)}catch{n.warn("MintInfo: nuts.29.max_batch_size is malformed, defaulting to internal cap",{value:t2.max_batch_size})}return r>100&&(n.warn("MintInfo: nuts.29.max_batch_size exceeds internal cap and was clamped",{advertised:r,clampedTo:100}),r=100),{methods:Array.isArray(t2.methods)?e10.capList(t2.methods,"nuts.29.methods",n):t2.methods,max_batch_size:r}}isSupported(e17){switch(e17){case 4:case 5:return this.checkMintMelt(e17);case 7:case 8:case 9:case 10:case 11:case 12:case 14:case 20:return this.checkGenericNut(e17);case 17:return this.checkNut17();case 15:return this.checkNut15();case 19:return this.checkNut19();case 29:return this.checkNut29();default:throw new b("nut is not supported by cashu-ts")}}requiresBlindAuthToken(e17,t2){return this.matchesProtected(this._protected22,e17,t2)}requiresClearAuthToken(e17,t2){return this.matchesProtected(this._protected21,e17,t2)}matchesProtected(e17,t2,n){if(!e17)return false;let r=e17.exact[t2],i=e17.prefix[t2];if(!r||!i)return false;if(e17.exact[t2].has(n))return true;for(let r2 of e17.prefix[t2])if(n.startsWith(r2))return true;return false}checkGenericNut(e17){return this._mintInfo.nuts[e17]?.supported?{supported:true}:{supported:false}}checkMintMelt(t2){let n=this._mintInfo.nuts[t2];return n&&n.methods.length>0&&!n.disabled?{disabled:false,params:e10.snapshot(n.methods)}:{disabled:true,params:e10.snapshot(n?.methods??[])}}checkNut17(){return this._mintInfo.nuts[17]&&this._mintInfo.nuts[17].supported.length>0?{supported:true,params:e10.snapshot(this._mintInfo.nuts[17].supported)}:{supported:false}}checkNut15(){return this._mintInfo.nuts[15]&&this._mintInfo.nuts[15].methods.length>0?{supported:true,params:e10.snapshot(this._mintInfo.nuts[15].methods)}:{supported:false}}checkNut19(){let t2=this._mintInfo.nuts?.[19];if(t2&&(t2?.cached_endpoints?.length||0)>0){let n=J(t2.ttl,"nuts.19.ttl",null);return{supported:true,params:{ttl:n===null?Infinity:Math.max(n,0)*1e3,cached_endpoints:e10.snapshot(t2.cached_endpoints)}}}return{supported:false}}checkNut29(){let t2=this._mintInfo.nuts?.[29];return t2?{supported:true,params:e10.snapshot(t2)}:{supported:false}}static capList(e17,t2,n){return e17.length<=1024?e17:(n.warn(`MintInfo: ${t2} exceeds internal cap and was truncated`,{advertised:e17.length,cap:se}),e17.slice(0,se))}toEndpoints(t2,n){if(!Array.isArray(t2))return[];let r=e10.capList(t2,"nuts.21/22.protected_endpoints",n),i=[];for(let e17 of r)if(e17&&typeof e17=="object"){let t3=e17,n2=t3.method,r2=t3.path;if(typeof n2=="string"&&typeof r2=="string"){let e18=n2.toUpperCase();(e18==="GET"||e18==="POST")&&i.push({method:e18,path:r2})}}return i}buildIndex(e17){if(!e17?.length)return;let t2={GET:new Set,POST:new Set},n={GET:[],POST:[]};for(let r of e17){let e18=r.path;if(e18.startsWith("^")&&(e18=e18.slice(1)),e18.endsWith("$")&&(e18=e18.slice(0,-1)),e18.endsWith(".*")){n[r.method].push(e18.slice(0,-2));continue}if(e18.endsWith("*")){n[r.method].push(e18.slice(0,-1));continue}t2[r.method].add(e18)}return n.GET.sort((e18,t3)=>t3.length-e18.length),n.POST.sort((e18,t3)=>t3.length-e18.length),{exact:t2,prefix:n}}static snapshot(t2,n=0){if(typeof t2!="object"||!t2||t2 instanceof A)return t2;if(n>64)throw new b("Mint info nesting exceeds 64 levels");if(Array.isArray(t2))return t2.map(t3=>e10.snapshot(t3,n+1));let r={};for(let[i,a]of Object.entries(t2))r[i]=e10.snapshot(a,n+1);return r}get cache(){return e10.snapshot(this._mintInfo)}get contact(){return e10.snapshot(this._mintInfo.contact)}get description(){return this._mintInfo.description}get description_long(){return this._mintInfo.description_long}get name(){return this._mintInfo.name}get pubkey(){return this._mintInfo.pubkey}get nuts(){return e10.snapshot(this._mintInfo.nuts)}get version(){return this._mintInfo.version}get motd(){return this._mintInfo.motd}get icon_url(){return this._mintInfo.icon_url}get urls(){return e10.snapshot(this._mintInfo.urls)}get time(){return this._mintInfo.time}get tos_url(){return this._mintInfo.tos_url}get maxArrayLength(){return this._maxArrayLength}supportsNut04Description(e17,t2){return this._mintInfo.nuts[4]?.methods.some(n=>n.method===e17&&(t2?n.unit===t2:true)&&(n.options?.description===true||n.description===true))}supportsMintMeltMethod(e17,t2,n){let{disabled:r,params:i}=this.isSupported(e17==="mint"?4:5);return r?false:i.some(e18=>e18.method===t2&&e18.unit===n)}supportsAmountless(e17="bolt11",t2="sat"){let n=this._mintInfo?.nuts?.[5]?.methods??[];return Array.isArray(n)?n.some(n2=>n2.method===e17&&n2.unit===t2&&n2.options?.amountless===true):false}};var sa={UNPAID:"UNPAID",PAID:"PAID",ISSUED:"ISSUED"};var ca={UNPAID:"UNPAID",PENDING:"PENDING",PAID:"PAID"};function ua(e17){return e17.window!==void 0&&e17.window.document!==void 0?true:e17.WorkerGlobalScope!==void 0&&e17.self!==void 0&&e17.self instanceof e17.WorkerGlobalScope}var da=ua(globalThis);function fa(e17,t2,n=da){return Pa({Accept:"application/json, text/plain, */*",...e17?{"Content-Type":"application/json"}:void 0,...n?void 0:{"User-Agent":"Mozilla/5.0"}},t2)}function pa(e17,t2){return e17 instanceof Error?e17.message:t2}function ma(e17,t2,n,r){if(t2?.signal.aborted)return new Ea(`Request timed out after ${n}ms`,{cause:e17});if(r?.aborted)return new X(pa(e17,"Request aborted by caller"))}async function ha(e17,t2){if(t2?.aborted)throw new b("response body read aborted");let n=e17.text();if(!t2)return n;n.catch(()=>void 0);let r,i=new Promise((e18,n2)=>{r=()=>n2(new b("response body read aborted")),t2.aborted?r():t2.addEventListener("abort",r,{once:true})});i.catch(()=>void 0);try{return await Promise.race([n,i])}finally{r&&t2.removeEventListener("abort",r)}}function ga(e17){if(e17===null)return;let t2=e17.trim();if(t2!==""){if(/^\d+$/.test(t2))return Math.max(Number(t2)*1e3,0);if(/[a-zA-Z]/.test(t2)){let e18=new Date(t2).getTime();if(!Number.isNaN(e18))return Math.max(e18-Date.now(),0)}}}var _a3={endpoint:true,requestBody:true,logger:true,ttl:true,cached_endpoints:true,requestTimeout:true,onResponseMeta:true};var va={};var ya=E;function xa(e17){ya=e17}var Sa=9;var Ca=1e3;var wa=100;var Ta=["blind-auth","clear-auth"];var X=class e11 extends S{constructor(t2){super(t2),this.name="CallerAbortError",Object.setPrototypeOf(this,e11.prototype)}};var Ea=class e12 extends S{constructor(t2,n){super(t2,n),this.name="UncancellableReadError",Object.setPrototypeOf(this,e12.prototype)}};function Da(e17){return e17 instanceof X||e17 instanceof Ea?false:e17 instanceof S?true:e17 instanceof x&&e17.status>=500}function Oa(e17,t2){return t2?new Promise((n,r)=>{if(t2.aborted){r(new X("Request aborted by caller"));return}let i=()=>{clearTimeout(a),t2.removeEventListener("abort",i),r(new X("Request aborted by caller"))};t2.addEventListener("abort",i,{once:true});let a=setTimeout(()=>{t2.removeEventListener("abort",i),n()},e17)}):new Promise(t3=>setTimeout(t3,e17))}function ka(e17){try{return new URL(e17).pathname}catch{return e17.startsWith("/")?e17.split(/[?#]/,1)[0]:void 0}}function Aa(e17,t2){return e17===t2?true:e17.endsWith(t2)}async function ja(e17){let{ttl:t2,cached_endpoints:n,endpoint:r}=e17,i=e17.logger??ya;if(Object.keys(e17.headers??{}).some(e18=>e18.toLowerCase()==="blind-auth"))return await Ma(e17);let a=ka(r),o=e17.method?.toUpperCase()??"GET";if(!(a!==void 0&&n?.some(e18=>Aa(a,e18.path)&&e18.method===o)&&t2))return await Ma(e17);let s=0,c=Date.now(),l=async()=>{try{return await Ma(e17)}catch(n2){if(Da(n2)){let r2=Date.now()-c;if(s<Sa&&(!t2||r2<t2)){let a2=Math.min(2**s*wa,Ca),o2=Math.random()*a2;if(r2+o2>t2)throw i.error(`Network Error: request abandoned after ${s} retries`,{e:n2,retries:s}),n2;return s++,i.info(`Network Error: attempting retry ${s} in ${o2}ms`,{e:n2,retries:s,delay:o2}),await Oa(o2,e17.signal),l()}}throw i.error("Request failed and could not be retried",{e:n2}),n2}};return l()}async function Ma(e17){let{endpoint:t2,requestBody:n,headers:r,requestTimeout:i,onResponseMeta:a,cached_endpoints:o,ttl:s,logger:c,...l}=e17,u=c??ya,d=n?j.stringify(n):void 0,f=fa(d,r),p=Object.keys(f).some(e18=>Ta.includes(e18.toLowerCase())),m=e17.signal??void 0;if(m?.aborted)throw new X("Request aborted by caller");let h=i===void 0?void 0:new AbortController,g=m,_,v;if(h)if(_=setTimeout(()=>h.abort(),i),!m)g=h.signal;else{let e18=new AbortController,t3=()=>e18.abort();m.addEventListener("abort",t3,{once:true}),h.signal.addEventListener("abort",t3,{once:true}),v=()=>{m.removeEventListener("abort",t3),h.signal.removeEventListener("abort",t3)},g=e18.signal}try{let e18;try{e18=await fetch(t2,{body:d,headers:f,cache:"no-store",credentials:"omit",referrer:"",referrerPolicy:"no-referrer",...d===void 0?void 0:{redirect:"error"},...l,...p?{redirect:"error"}:void 0,signal:g})}catch(e19){let t3=!!h?.signal.aborted,n3=!!m?.aborted;throw t3?new S(`Request timed out after ${i}ms`,{cause:e19}):n3?new X(pa(e19,"Request aborted by caller")):e19 instanceof Error&&(e19.name==="AbortError"||e19.name==="TimeoutError")?new S(e19.message,{cause:e19}):new S(pa(e19,"Network request failed"),{cause:e19})}let n2=ga(e18.headers.get("Retry-After"));if(a&&e18.headers&&O(a,{endpoint:t2,status:e18.status,retryAfterMs:n2,rateLimit:e18.headers.get("RateLimit")??void 0,rateLimitPolicy:e18.headers.get("RateLimit-Policy")??void 0,headers:e18.headers},u,{op:"request.onResponseMeta",status:e18.status,endpoint:t2}),!e18.ok){if(e18.status===429){let t4=e18.body;throw t4&&typeof t4.cancel=="function"&&t4.cancel().catch(()=>void 0),new T("429 Too Many Requests",n2)}let t3,r3;try{t3=Na(await ha(e18,g))}catch(e19){let n3=ma(e19,h,i,m);if(n3)throw n3;r3=e19,t3={error:"bad response"}}if(e18.status===400&&"code"in t3&&typeof t3.code=="number"&&"detail"in t3&&typeof t3.detail=="string")throw new te(t3.code,t3.detail);let a2="HTTP request failed";throw"error"in t3&&typeof t3.error=="string"?a2=t3.error:"detail"in t3&&typeof t3.detail=="string"&&(a2=t3.detail),new x(a2,e18.status,{cause:r3})}let r2;try{r2=await ha(e18,g)}catch(t3){throw ma(t3,h,i,m)||(u.error("Failed to read HTTP response",{err:t3}),new x("bad response",e18.status,{cause:t3}))}try{if(!r2)throw new b("Empty response body");return j.parse(r2,void 0,{strict:true})}catch(t3){throw u.error("Failed to parse HTTP response",{err:t3}),new x("bad response",e18.status,{cause:t3})}}finally{clearTimeout(_),v?.()}}function Na(e17){if(!e17)return{detail:"bad response"};let t2;try{t2=j.parse(e17,void 0,{strict:true})}catch{return{detail:e17}}return typeof t2=="object"&&t2&&("detail"in t2||"code"in t2||"error"in t2)?t2:{detail:t2}}function Pa(e17,t2){let n=new Map;for(let r of[e17,t2])for(let[e18,t3]of Object.entries(r??{}))n.set(e18.toLowerCase(),[e18,t3]);return Object.fromEntries(n.values())}async function Fa(e17){let t2=e17.onResponseMeta,n=va.onResponseMeta,r={...e17,...va};for(let t3 of Object.keys(_a3))e17[t3]!==void 0&&(r[t3]=e17[t3]);if(r.headers=Pa(va.headers,e17.headers),t2&&n&&t2!==n){let i=r.logger??ya;r.onResponseMeta=r2=>{O(t2,r2,i,{op:"request.onResponseMeta",scope:"per-request",endpoint:e17.endpoint}),O(n,r2,i,{op:"request.onResponseMeta",scope:"global",endpoint:e17.endpoint})}}return await ja(r)}var Ia;typeof WebSocket<"u"&&(Ia=WebSocket);function Ra(){if(Ia===void 0)throw new b("WebSocket implementation not initialized");return Ia}var za=1006;var Ba=class{constructor(e17){this.next=null,this.value=e17}};var Va=class{constructor(){this._first=null,this._last=null,this.size=0}enqueue(e17){let t2=new Ba(e17);return this._last?this._last.next=t2:this._first=t2,this._last=t2,this.size++,true}dequeue(){if(!this._first)return null;let e17=this._first;return this._first=e17.next,this._first||(this._last=null),this.size--,e17.value}};var Ha=class{constructor(e17,t2){this.subListeners={},this.rpcListeners={},this.rpcId=0,this.onCloseCallbacks=[],this._WS=Ra(),this.url=new URL(e17),this.messageQueue=new Va,this._logger=t2??E}setLogger(e17){this._logger=e17}connect(e17=1e4){return this.connectionPromise||(this.connectionPromise=new Promise((t2,n)=>{let r=false,i=false,a=null,o=e18=>{i||(i=true,a&&clearTimeout(a),this.abandonConnect=void 0,e18())};this.abandonConnect=e18=>o(()=>n(e18));let s=()=>{if(this.ws){try{this.ws.onopen=null,this.ws.onerror=null,this.ws.onmessage=null,this.ws.onclose=null}catch{}try{this.ws.close()}catch{}this.ws=void 0,this.stopMessageHandling()}},c=e18=>{this.connectionPromise=void 0,s();let t3=e18 instanceof Error?e18:new b(String(e18),{cause:e18});this.failPendingRpc(t3),o(()=>n(t3))},l;try{l=new this._WS(this.url.toString()),this.ws=l}catch(e18){c(e18);return}let u=()=>this.ws===l;a=setTimeout(()=>{c(new b(`WebSocket connect timeout after ${e17}ms`))},e17),l.onopen=()=>{u()&&(r=true,o(t2))},l.onerror=e18=>{if(u()){if(!r){c(new b("Failed to open WebSocket"));return}this._logger.error("WebSocket error after open",{ev:e18})}},l.onmessage=e18=>{if(u()){if(this.messageQueue.size>=1e4){this._logger.error("WebSocket message queue exceeded its bound, closing connection",{size:this.messageQueue.size});let e19=new b("WebSocket message queue exceeded its bound");c(e19),this.onCloseCallbacks.forEach(t3=>t3({code:za,reason:e19.message,wasClean:false}));return}this.messageQueue.enqueue(e18.data),this.handlingInterval||(this.handlingInterval=setInterval(this.handleNextMessage.bind(this),0))}},l.onclose=e18=>{if(this.ws&&this.ws!==l)return;if(this.connectionPromise=void 0,!r){let t4=e18?.reason?`, ${e18.reason}`:"";c(new b(`WebSocket closed before open (code ${e18?.code??0}${t4})`));return}this.stopMessageHandling();let t3=e18?.reason?`, ${e18.reason}`:"",n2=e18?.code??0;!(typeof e18.wasClean!="boolean"||e18.wasClean)||n2!==1e3&&n2!==1001?this.failPendingRpc(new b(`WebSocket closed (code ${n2}${t3})`)):this.rpcListeners={},this.onCloseCallbacks.forEach(t4=>t4(e18))}})),this.connectionPromise}sendRequest(e17,t2){if(this.ws?.readyState!==this._WS.OPEN){if(e17==="unsubscribe")return;throw this._logger.error("Attempted sendRequest, but socket was not open"),new b("Socket not open")}let n=this.rpcId;this.rpcId++,this.sendRpcMessage(e17,t2,n)}addSubListener(e17,t2){(this.subListeners[e17]=this.subListeners[e17]||[]).push(t2)}stopMessageHandling(){for(this.handlingInterval&&(clearInterval(this.handlingInterval),this.handlingInterval=void 0);this.messageQueue.size>0;)this.messageQueue.dequeue();this.subListeners={}}failPendingRpc(e17){let t2=this.rpcListeners;this.rpcListeners={};for(let n of Object.keys(t2))try{t2[n].errorCallback(e17)}catch{}}sendRpcMessage(e17,t2,n){if(this.ws?.readyState!==this._WS.OPEN)throw new b("Socket not open");let r=JSON.stringify({jsonrpc:"2.0",method:e17,params:t2,id:n});try{this.ws.send(r)}catch(e18){this._logger.error("WebSocket send failed",{e:e18}),this.connectionPromise=void 0;try{this.ws.close()}catch{}this.ws=void 0,this.stopMessageHandling();let t3=e18 instanceof Error?e18:new b(String(e18),{cause:e18});throw this.failPendingRpc(t3),t3}}addRpcListener(e17,t2,n){this.rpcListeners[n]={callback:e17,errorCallback:t2}}removeRpcListener(e17){delete this.rpcListeners[e17]}removeListener(e17,t2){if(this.subListeners[e17]){if(this.subListeners[e17].length===1){delete this.subListeners[e17];return}this.subListeners[e17]=this.subListeners[e17].filter(e18=>e18!==t2)}}async ensureConnection(e17){this.ws?.readyState!==this._WS.OPEN&&await this.connect(e17)}handleNextMessage(){for(;this.messageQueue.size>0;){let e17=this.messageQueue.dequeue();try{let t2=j.parse(e17,void 0,{strict:true});if("result"in t2&&t2.id!=null)this.rpcListeners[t2.id]&&(this.rpcListeners[t2.id].callback(),this.removeRpcListener(t2.id));else if("error"in t2&&t2.id!=null)this.rpcListeners[t2.id]&&(this.rpcListeners[t2.id].errorCallback(new b(t2.error.message)),this.removeRpcListener(t2.id));else if("method"in t2&&!("id"in t2)){let e18=t2.params?.subId;if(!e18)continue;if(this.subListeners[e18]?.length>0){let n=t2;this.subListeners[e18].forEach(e19=>{try{let t3=e19(n.params?.payload);t3&&typeof t3.then=="function"&&Promise.resolve(t3).catch(e20=>{this._logger.error("Subscription handler threw",{e:e20})})}catch(e20){this._logger.error("Subscription handler threw",{e:e20})}})}}}catch(e18){this._logger.error("Error doing handleNextMessage",{e:e18})}}this.handlingInterval&&(clearInterval(this.handlingInterval),this.handlingInterval=void 0)}createSubscription(e17,t2,n){if(this.ws?.readyState!==this._WS.OPEN)throw this._logger.error("Attempted createSubscription, but socket was not open"),new b("Socket is not open");let r=(Math.random()+1).toString(36).substring(7),i=this.rpcId;this.addRpcListener(()=>{this.addSubListener(r,t2)},n,i);try{this.sendRequest("subscribe",{...e17,subId:r})}catch(e18){throw this.removeRpcListener(i),e18}return r}cancelSubscription(e17,t2,n){if(this.removeListener(e17,t2),this.ws?.readyState!==this._WS.OPEN){this._logger.info("Socket not open, removed listener locally {subId}",{subId:e17});return}let r=this.rpcId;this.rpcId++,this.addRpcListener(()=>{this._logger.info("Unsubscribed {subId}",{subId:e17})},n||(e18=>this._logger.error("Unsubscribe failed",{e:e18})),r);try{this.sendRpcMessage("unsubscribe",{subId:e17},r)}catch(e18){throw this.removeRpcListener(r),e18}}get activeSubscriptions(){return Object.keys(this.subListeners)}close(){let e17=new b("WebSocket closed");if(this.abandonConnect?.(e17),this.ws){try{this.ws.close()}catch{}this.ws=void 0}this.connectionPromise=void 0,this.failPendingRpc(e17),this.stopMessageHandling()}onClose(e17){this.onCloseCallbacks.push(e17)}};function Ua(e17){return Array.isArray(e17)?"array":typeof e17}var Wa=class{constructor(e17,t2){this._lastResponseMetadata=void 0,this._captureResponseMetadata=e18=>{this._lastResponseMetadata=e18},this._mintUrl=Hi(e17),this._request=t2?.customRequest??Fa,this._authProvider=t2?.authProvider,this._logger=t2?.logger??E,xa(this._logger)}get mintUrl(){return this._mintUrl}get lastResponseMetadata(){return this._lastResponseMetadata}async oidcAuth(e17){let t2=(await this.getLazyMintInfo()).nuts[21];if(!t2?.openid_discovery)throw new b("Mint: no NUT-21 openid_discovery");return new oa(t2.openid_discovery,{...e17,clientId:e17?.clientId??t2.client_id??"cashu-client"})}async getInfo(e17){let t2=await(e17??this._request)({endpoint:G(this._mintUrl,"/v1/info"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});return Y.normalizeInfo(t2,this._logger)}async getLazyMintInfo(e17){if(this._mintInfo)return this._mintInfo;let t2=await this.getInfo(e17);return this._mintInfo=new Y(t2,this._logger),this._mintInfo}setMintInfo(e17){this._mintInfo=e17 instanceof Y?e17:new Y(e17,this._logger)}async swap(e17,t2){D(!Array.isArray(e17?.inputs),"swap: inputs must be an array of proofs",this._logger);let n=await this.requestWithAuth("POST","/v1/swap",{requestBody:{...e17,inputs:this.stripWalletFields(e17.inputs)}},t2);if(!H(n)||!Array.isArray(n?.signatures))throw this._logger.error("Invalid response from mint...",{data:n,op:"swap"}),new b("Invalid response from mint");return this.assertSignatureCount(n.signatures,e17.outputs,"swap"),n.signatures=this.normalizeSignatureAmounts(n.signatures),n}async createMintQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/quote/${e17}`,{requestBody:t2},n?.customRequest);return this.normalizeMintQuoteResponse(e17,r,n?.normalize)}async createMintQuoteBolt11(e17,t2){return this.createMintQuote("bolt11",{...e17,amount:A.from(e17.amount).toBigInt()},{customRequest:t2})}async createMintQuoteBolt12(e17,t2){let n={...e17};return e17.amount!==void 0&&(n.amount=A.from(e17.amount).toBigInt()),this.createMintQuote("bolt12",n,{customRequest:t2})}async createMintQuoteOnchain(e17,t2){return this.createMintQuote("onchain",e17,{customRequest:t2})}async checkMintQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("GET",`/v1/mint/quote/${e17}/${t2}`,{},n?.customRequest),i=this.normalizeMintQuoteResponse(e17,r,n?.normalize);if(i.quote!==t2)throw this._logger.error("Invalid response from mint...",{op:`checkMintQuote.${e17}`}),new b("Mint quote response is for a different quote");return i}async checkMintQuoteBolt11(e17,t2){return this.checkMintQuote("bolt11",e17,{customRequest:t2})}async checkMintQuoteBolt12(e17,t2){return this.checkMintQuote("bolt12",e17,{customRequest:t2})}async checkMintQuoteOnchain(e17,t2){return this.checkMintQuote("onchain",e17,{customRequest:t2})}async checkMintQuoteBatch(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger),D(t2.length===0,"checkMintQuoteBatch: no quote ids provided",this._logger),D(new Set(t2).size!==t2.length,"checkMintQuoteBatch: duplicate quote ids provided",this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/quote/${e17}/check`,{requestBody:{quotes:t2}},n?.customRequest);if(!Array.isArray(r)||r.length!==t2.length)throw this._logger.error("Invalid response from mint...",{expectedCount:t2.length,actualCount:Array.isArray(r)?r.length:void 0,op:`checkMintQuoteBatch.${e17}`}),new b("Invalid response from mint");return r.map((r2,i)=>{if(r2.quote!==t2[i])throw this._logger.error("Invalid response from mint...",{index:i,op:`checkMintQuoteBatch.${e17}`}),new b("Invalid response from mint");return this.normalizeMintQuoteResponse(e17,r2,n?.normalize)})}async checkMintQuoteBatchBolt11(e17,t2){return this.checkMintQuoteBatch("bolt11",e17,{customRequest:t2})}async checkMintQuoteBatchBolt12(e17,t2){return this.checkMintQuoteBatch("bolt12",e17,{customRequest:t2})}async mintBolt11(e17,t2){return this.mint("bolt11",e17,{customRequest:t2})}async mintBolt12(e17,t2){return this.mint("bolt12",e17,{customRequest:t2})}async mintOnchain(e17,t2){return this.mint("onchain",e17,{customRequest:t2})}async mint(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/${e17}`,{requestBody:t2},n?.customRequest);if(!H(r)||!Array.isArray(r?.signatures))throw this._logger.error("Invalid response from mint...",{data:r,op:`mint.${e17}`}),new b("Invalid response from mint");return this.assertSignatureCount(r.signatures,t2.outputs,`mint.${e17}`),r.signatures=this.normalizeSignatureAmounts(r.signatures),n?.normalize?n.normalize(r):r}async mintBatchBolt11(e17,t2){return this.mintBatch("bolt11",e17,{customRequest:t2})}async mintBatchBolt12(e17,t2){return this.mintBatch("bolt12",e17,{customRequest:t2})}async mintBatch(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint method: ${e17}`,this._logger);let r={...t2,quote_amounts:t2.quote_amounts.map(e18=>A.from(e18).toBigInt())},i=await this.requestWithAuth("POST",`/v1/mint/${e17}/batch`,{requestBody:r},n?.customRequest);if(!H(i)||!Array.isArray(i?.signatures))throw this._logger.error("Invalid response from mint...",{data:i,op:`mintBatch.${e17}`}),new b("Invalid response from mint");return this.assertSignatureCount(i.signatures,t2.outputs,`mintBatch.${e17}`),i.signatures=this.normalizeSignatureAmounts(i.signatures),n?.normalize?n.normalize(i):i}async createMeltQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/melt/quote/${e17}`,{requestBody:t2},n?.customRequest);return this.normalizeMeltQuoteResponse(e17,r,n?.normalize)}async createMeltQuoteBolt11(e17,t2){let n=await this.createMeltQuote("bolt11",this.normalizeMeltQuoteRequestOptions(e17),{customRequest:t2});return D(n.request!==""&&n.request.toLowerCase()!==e17.request.toLowerCase(),"Melt quote is for a different payment request",this._logger),n}async createMeltQuoteBolt12(e17,t2){return this.createMeltQuote("bolt12",this.normalizeMeltQuoteRequestOptions(e17),{customRequest:t2})}async createMeltQuoteOnchain(e17,t2){return this.createMeltQuote("onchain",{...e17,amount:A.from(e17.amount).toBigInt()},{customRequest:t2})}async checkMeltQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("GET",`/v1/melt/quote/${e17}/${t2}`,{},n?.customRequest),i=this.normalizeMeltQuoteResponse(e17,r,n?.normalize);if(i.quote!==t2)throw this._logger.error("Invalid response from mint...",{data:i,op:`checkMeltQuote.${e17}`}),new b("Melt quote response is for a different quote");return i}async checkMeltQuoteBolt11(e17,t2){return this.checkMeltQuote("bolt11",e17,{customRequest:t2})}async checkMeltQuoteBolt12(e17,t2){return this.checkMeltQuote("bolt12",e17,{customRequest:t2})}async checkMeltQuoteOnchain(e17,t2){return this.checkMeltQuote("onchain",e17,{customRequest:t2})}async melt(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt method: ${e17}`,this._logger),D(!Array.isArray(t2?.inputs),"melt: inputs must be an array of proofs",this._logger);let r=await this.requestWithAuth("POST",`/v1/melt/${e17}`,{requestBody:{...t2,inputs:this.stripWalletFields(t2.inputs)}},n?.customRequest),i=this.normalizeMeltQuoteResponse(e17,r,n?.normalize,true);return i.quote!==t2.quote&&(this._logger.warn("Melt response reports a different quote id",{op:`melt.${e17}`,expected:t2.quote,received:i.quote}),i.quote=t2.quote),i}async meltBolt11(e17,t2){return this.melt("bolt11",e17,t2)}async meltBolt12(e17,t2){return this.melt("bolt12",e17,t2)}async meltOnchain(e17,t2){return this.melt("onchain",e17,t2)}async check(e17,t2){let n=await this.requestWithAuth("POST","/v1/checkstate",{requestBody:e17},t2);if(!H(n)||!Array.isArray(n?.states)||n.states.length>e17.Ys.length)throw this._logger.error("Invalid response from mint...",{data:n,op:"check"}),new b("Invalid response from mint");this.assertRecordEntries(n.states,"check");for(let e18 of n.states)W(e18,"witness");return n}async getKeys(e17,t2,n){let r=t2||this._mintUrl;e17&&(e17=e17.replace(/\//g,"_").replace(/\+/g,"-"));let i=await(n??this._request)({endpoint:e17?G(r,"/v1/keys",e17):G(r,"/v1/keys"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(i)||!Array.isArray(i.keysets))throw this._logger.error("Invalid response from mint...",{data:i,op:"getKeys"}),new b("Invalid response from mint");return this.assertKeysetList(i.keysets,"getKeys"),{...i,keysets:i.keysets.map(e18=>ia(e18))}}async getKeySets(e17){let t2=await(e17??this._request)({endpoint:G(this._mintUrl,"/v1/keysets"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(t2)||!Array.isArray(t2.keysets))throw this._logger.error("Invalid response from mint...",{data:t2,op:"getKeySets"}),new b("Invalid response from mint");return this.assertKeysetList(t2.keysets,"getKeySets"),{...t2,keysets:t2.keysets.map(e18=>ra(e18))}}async restore(e17,t2){let n=await(t2??this._request)({endpoint:G(this._mintUrl,"/v1/restore"),method:"POST",requestBody:e17,onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(n)||!Array.isArray(n?.outputs)||!Array.isArray(n?.signatures)||n.outputs.length!==n.signatures.length||n.outputs.length>e17.outputs.length)throw this._logger.error("Invalid response from mint...",{data:n,op:"restore"}),new b("Invalid response from mint");return n.outputs=this.normalizeMessageAmounts(n.outputs),n.signatures=this.normalizeSignatureAmounts(n.signatures),n}async connectWebSocket(){try{let e17=new URL(this._mintUrl),t2="v1/ws";e17.pathname.endsWith("/")?e17.pathname+=t2:e17.pathname+="/"+t2,e17.protocol=e17.protocol==="https:"?"wss:":"ws:";let n=e17.toString();this.ws||(this.ws=new Ha(n,this._logger)),await this.ws.ensureConnection()}catch(e17){this._logger.error("Failed to connect to WebSocket...",{e:e17});try{this.ws?.close()}catch{}throw this.ws=void 0,new b("Failed to connect to WebSocket...",{cause:e17})}}disconnectWebSocket(){this.ws&&this.ws.close()}get webSocketConnection(){return this.ws}async handleClearAuth(e17,t2,n){if(this._authProvider&&(n??await this.getLazyMintInfo()).requiresClearAuthToken(e17,t2))return this._authProvider.ensureCAT?this._authProvider.ensureCAT():this._authProvider.getCAT()}async handleBlindAuth(e17,t2,n){if(this._authProvider&&(n??await this.getLazyMintInfo()).requiresBlindAuthToken(e17,t2))return await this._authProvider.getBlindAuthToken({method:e17,path:t2})}async requestWithAuth(e17,t2,n={},r){let i=r??this._request,a=this._mintInfo;this._authProvider&&(a=await this.getLazyMintInfo(r));let o=await this.handleBlindAuth(e17,t2,a),s=await this.handleClearAuth(e17,t2,a),c={...n.headers??{},...o?{"Blind-auth":o}:{},...s?{"Clear-auth":s}:{}},l=a?.isSupported(19);return i({...n,endpoint:G(this._mintUrl,t2),method:e17,headers:c,...o||s||n.requestBody?{redirect:"error"}:{},...l?.supported&&l.params?l.params:{},onResponseMeta:this._captureResponseMetadata,logger:n.logger??this._logger})}normalizeMeltQuoteRequestOptions(e17){if(!e17.options)return{...e17};let t2={...e17.options};return e17.options.amountless&&(t2.amountless={amount_msat:A.from(e17.options.amountless.amount_msat).toBigInt()}),"mpp"in e17.options&&e17.options.mpp&&(t2.mpp={amount:A.from(e17.options.mpp.amount).toBigInt()}),{...e17,options:t2}}isValidMethodString(e17){return!!(typeof e17=="string"&&/^[a-z0-9_-]+$/.test(e17))}normalizeSignatureAmounts(e17){return this.assertRecordEntries(e17,"signatures"),e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}normalizeMessageAmounts(e17){return this.assertRecordEntries(e17,"outputs"),e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}stripWalletFields(e17){return e17.map(e18=>{let{dleq:t2,p2pk_e:n,...r}=e18;return r})}assertRecordEntries(e17,t2){if(!e17.every(e18=>U(e18)))throw this._logger.error("Invalid response from mint...",{entries:e17.length,op:t2}),new b("Invalid response from mint")}assertSignatureCount(e17,t2,n){if(!(e17.length<=t2.length))throw this._logger.error("Invalid response from mint...",{signatures:e17.length,outputs:t2.length,op:n}),new b(`Invalid response from mint: ${e17.length} signatures, expected ${t2.length}. The operation may already have been applied; if the wallet is seeded, try restoring (NUT-09) to recover.`)}assertKeysetList(e17,t2){if(e17.length>1e4)throw this._logger.error("Invalid response from mint...",{keysets:e17.length,op:t2}),new b("Invalid response from mint");this.assertRecordEntries(e17,t2)}normalizeMintQuoteResponse(e17,t2,n){let r=`${e17} mint quote`;if(!U(t2))throw this._logger.error("Invalid response from mint...",{type:Ua(t2),op:r}),new b("Invalid response from mint");let i={...t2};return this.normalizeMintBaseFields(i),e17==="bolt11"?this.normalizeMintQuoteBolt11Fields(i):e17==="bolt12"?this.normalizeMintQuoteBolt12Fields(i):e17==="onchain"&&this.normalizeMintQuoteOnchainFields(i),n?n(i):i}normalizeMintBaseFields(e17){e17.amount_paid!=null&&e17.amount_issued!=null&&(e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)),e17.updated_at=J(e17.updated_at,"mintQuote.updated_at",null)}deriveMintQuoteState(e17,t2){return e17.isZero()&&t2.isZero()?sa.UNPAID:e17.greaterThan(t2)?sa.PAID:sa.ISSUED}normalizeMintQuoteBolt11Fields(e17){e17.amount=A.from(e17.amount),e17.expiry=J(e17.expiry,"mintQuoteBolt11.expiry",null),(typeof e17.state!="string"||!Object.values(sa).includes(e17.state))&&e17.amount_paid instanceof A&&e17.amount_issued instanceof A&&(e17.state=this.deriveMintQuoteState(e17.amount_paid,e17.amount_issued))}normalizeMintQuoteBolt12Fields(e17){W(e17,"amount"),e17.amount=e17.amount===null?null:A.from(e17.amount),e17.expiry=J(e17.expiry,"mintQuoteBolt12.expiry",null),e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)}normalizeMintQuoteOnchainFields(e17){e17.expiry=J(e17.expiry,"mintQuoteOnchain.expiry",null),e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)}normalizeMeltQuoteResponse(e17,t2,n,r=false){let i=`${e17} melt quote`;if(!U(t2))throw this._logger.error("Invalid response from mint...",{type:Ua(t2),op:i}),new b("Invalid response from mint");let a={...t2};return r&&((a.request==null||a.request==="")&&delete a.request,a.fee_reserve??delete a.fee_reserve),this.normalizeMeltBaseFields(a,i),e17==="bolt11"||e17==="bolt12"?this.normalizeMeltBoltFields(a,i,r):e17==="onchain"&&this.normalizeMeltOnchainFields(a,r),n?n(a):a}normalizeMeltBaseFields(e17,t2){if(e17.amount=A.from(e17.amount),e17.expiry=J(e17.expiry,"meltQuote.expiry",void 0),e17.change){if(!Array.isArray(e17.change)||e17.change.length>1024)throw this._logger.error("Invalid response from mint...",{type:Ua(e17.change),op:t2}),new b("Invalid response from mint");e17.change=this.normalizeSignatureAmounts(e17.change)}if(!H(e17)||typeof e17.quote!="string"||!(e17.amount instanceof A)||typeof e17.unit!="string"||typeof e17.state!="string"||typeof e17.expiry!="number"||!Object.values(ca).includes(e17.state))throw this._logger.error("Invalid response from mint...",{op:t2}),new b("Invalid response from mint")}normalizeMeltBoltFields(e17,t2,n){let r=t3=>n&&e17[t3]===void 0;if(!r("fee_reserve")&&e17.fee_reserve!=null&&(e17.fee_reserve=A.from(e17.fee_reserve)),typeof e17.request!="string"&&!r("request")||!(e17.fee_reserve instanceof A)&&!r("fee_reserve"))throw this._logger.error("Invalid response from mint...",{op:t2}),new b("Invalid response from mint");W(e17,"payment_preimage")}normalizeMeltOnchainFields(e17,t2){if(t2&&e17.fee_options===void 0){W(e17,"selected_fee_index","outpoint");return}if(!Array.isArray(e17.fee_options)||e17.fee_options.length===0||e17.fee_options.length>1024||(e17.fee_options=e17.fee_options.map(e18=>{let t3=e18;if(!Number.isSafeInteger(t3.fee_index))throw this._logger.error("Invalid response from mint...",{op:"onchain melt quote"}),new b("Invalid response from mint");return{...t3,fee_index:t3.fee_index,fee_reserve:A.from(t3.fee_reserve),estimated_blocks:t3.estimated_blocks}}),W(e17,"selected_fee_index","outpoint"),typeof e17.request!="string"||e17.selected_fee_index!==null&&!Number.isSafeInteger(e17.selected_fee_index)||e17.outpoint!==null&&typeof e17.outpoint!="string"))throw this._logger.error("Invalid response from mint...",{op:"onchain melt quote"}),new b("Invalid response from mint")}};var Ga=class e13{constructor(e17,t2,n,r,i){this._keys={},this._id=e17,this._unit=t2,this._active=n,this._input_fee_ppk=r,this._final_expiry=i}get id(){return this._id}get unit(){return this._unit}get isActive(){return this._active}get fee(){return this._input_fee_ppk??0}get expiry(){return this._final_expiry}get hasKeys(){return Object.keys(this._keys).length>0}get hasHexId(){return V(this._id)}get version(){return this.hasHexId?hexToBytes2(this._id)[0]:-1}get keys(){return this._keys}set keys(e17){this._keys=e17}toMintKeyset(){return{id:this._id,unit:this._unit,active:this._active,input_fee_ppk:this._input_fee_ppk,final_expiry:this._final_expiry}}toMintKeys(){return this.hasKeys?{id:this._id,unit:this._unit,active:this._active,input_fee_ppk:this._input_fee_ppk,final_expiry:this._final_expiry,keys:this._keys}:null}verify(){return this.hasKeys?e13.verifyKeysetId(this.toMintKeys()):false}static verifyKeysetId(e17){try{let t2=e17.keys?Object.keys(e17.keys).length:0;if(t2===0||t2>256||new Set(Object.values(e17.keys).map(e18=>e18.toLowerCase())).size!==t2)return false;let n=et(e17.id)&&!V(e17.id),i=V(e17.id)?hexToBytes2(e17.id)[0]:0;return zi(e17.keys,{input_fee_ppk:e17.input_fee_ppk,expiry:e17.final_expiry,unit:e17.unit,versionByte:i,isDeprecatedBase64:n})===e17.id}catch{return false}}static fromMintApi(t2,n){let r=ra(t2),i=n?ia(n):void 0,a=new e13(r.id,r.unit,r.active,r.input_fee_ppk,r.final_expiry);if(i){if(i.id!==r.id)throw new b(`Mismatched keyset ids: meta=${r.id}, keys=${i.id}`);if(i.unit!==r.unit)throw new b(`Mismatched keyset units: meta=${r.unit}, keys=${i.unit}`);if(i.final_expiry!==void 0&&r.final_expiry!==void 0&&i.final_expiry!==r.final_expiry)throw new b(`Mismatched keyset expiry for id=${r.id}`);a.keys=i.keys}return a}};var Ka=class e14{assertInitialized(){if(Object.keys(this.keysets).length===0)throw new b("KeyChain not initialized")}constructor(e17,t2,n=E){this.keysets=Object.create(null),this.pendingKeyFetches=new Map,this.generation=0,this.refreshSeq=0,this.mint=typeof e17=="string"?new Wa(e17):e17,this.unit=t2,this._logger=n}static fromCache(t2,n,r,i){let a=new e14(t2,n,i);return a.loadFromCache(r),a}static mintToCacheDTO(e17,t2,n){let r=new Map(n.map(e18=>[e18.id,e18]));return{keysets:t2.map(e18=>{let t3=r.get(e18.id),n2={...e18};return t3&&(n2.keys={...t3.keys}),n2}),mintUrl:e17,savedAt:Date.now()}}static cacheToMintDTO(e17){return{keysets:e17.keysets.map(e18=>({id:e18.id,unit:e18.unit,active:e18.active,input_fee_ppk:e18.input_fee_ppk,final_expiry:e18.final_expiry})),keys:e17.keysets.filter(e18=>!!e18.keys).map(e18=>({id:e18.id,unit:e18.unit,active:e18.active,input_fee_ppk:e18.input_fee_ppk,final_expiry:e18.final_expiry,keys:{...e18.keys}}))}}async init(e17){if(Object.keys(this.keysets).length>0&&!e17)return;let t2=++this.refreshSeq,[n,r]=await Promise.all([this.mint.getKeySets(),this.mint.getKeys()]);if(t2!==this.refreshSeq){this._logger.debug("Discarding keychain refresh superseded by a later one",{seq:t2});return}this.buildKeychain(n.keysets,r.keysets),this.savedAt=Date.now()}loadFromCache(t2){let n;try{n=Hi(String(t2.mintUrl))}catch{n=void 0}n!==this.mint.mintUrl&&this._logger.warn(`KeyChain cache is for a different mint: ${n??"unknown"} (expected ${this.mint.mintUrl}). This will become an error in cashu-ts v5.`,{cacheMintUrl:t2.mintUrl,mintUrl:this.mint.mintUrl});let{keysets:r,keys:i}=e14.cacheToMintDTO(t2);this.buildKeychain(r,i),this.savedAt=t2.savedAt}buildKeychain(e17,t2){let n=this.keysets,r=Object.create(null),i=new Map(t2.map(e18=>[e18.id,e18]));for(let t3 of e17){let e18=i.get(t3.id),a=e18?Ga.fromMintApi(t3,e18):Ga.fromMintApi(t3);a.verify()||(a.hasKeys&&this._logger.warn("Discarding keys that do not derive their keyset id",{id:a.id}),a.keys={});let o=n[t3.id];!a.hasKeys&&o?.hasKeys&&o.unit===a.unit&&(a.keys={...o.keys},a.verify()||(this._logger.warn("Dropping carried keys: fresh metadata no longer derives the keyset id",{id:a.id}),a.keys={})),r[a.id]=a}this.keysets=r,this.generation++}getKeyset(e17){let t2=e17?this.keysets[e17]:this.getCheapestKeyset();if(!t2)throw new b(`Keyset '${e17}' not found`);return t2}getCheapestKeyset(){if(Object.keys(this.keysets).length===0)throw new b("KeyChain not initialized");let e17=Object.values(this.keysets).filter(e18=>e18.unit===this.unit&&e18.isActive&&e18.hasHexId&&e18.hasKeys);if(e17.length===0)throw new b(`No active keyset found for unit: ${this.unit}`);let t2=2**53-1;return e17.sort((e18,n)=>n.version-e18.version||e18.fee-n.fee||(n.expiry??t2)-(e18.expiry??t2))[0]}async ensureKeysetKeys(e17){let t2=this.keysets[e17];if(!t2)throw new b(`Keyset '${e17}' not found`);if(t2.hasKeys)return t2;let n=this.pendingKeyFetches.get(e17);if(n)return await n;let r=(async()=>{let n2=this.generation,r2=(await this.mint.getKeys(e17)).keysets.find(t3=>t3.id===e17);if(!r2||!r2.keys||Object.keys(r2.keys).length===0)throw new b(`Mint returned no keys for keyset '${e17}'`);let i=t2.toMintKeyset(),a=Ga.fromMintApi(i,r2);if(!a.verify())throw new b(`Keyset verification failed for ID ${e17}`);if(this.generation!==n2){this._logger.debug("Keychain refreshed during key fetch; returning the live keyset",{id:e17});let t3=this.keysets[e17];if(!t3)throw new b(`Keyset '${e17}' not found`);return t3}return this.keysets[e17]=a,a})();this.pendingKeyFetches.set(e17,r);try{return await r}finally{this.pendingKeyFetches.delete(e17)}}getKeysets(){this.assertInitialized();let e17=Object.values(this.keysets).filter(e18=>e18.unit===this.unit);if(e17.length===0)throw new b(`No keysets found for unit: ${this.unit}`);return e17}isUnitKeyset(e17){if(!e17)return false;let t2=this.keysets[e17];return t2!==void 0&&t2.unit===this.unit}hasKeyset(e17){return!!e17&&this.keysets[e17]!==void 0}getAllKeys(){return this.assertInitialized(),Object.values(this.keysets).map(e17=>e17.toMintKeys()).filter(e17=>e17!==null)}getAllKeysetIds(){return this.assertInitialized(),Object.keys(this.keysets)}get cache(){let t2=Object.values(this.keysets),n=t2.map(e17=>e17.toMintKeyset()),r=t2.map(e17=>e17.toMintKeys()).filter(e17=>e17!==null),i=e14.mintToCacheDTO(this.mint.mintUrl,n,r);return i.savedAt=this.savedAt,i}};var qa=class{constructor(e17,t2,n){this.amountValue=A.from(e17),this.B_=t2,this.id=n}get amount(){return this.amountValue}getSerializedBlindedMessage(){return{amount:this.amountValue,B_:this.B_.toHex(true),id:this.id}}};var Ja=new Set(["locktime","pubkeys","n_sigs","refund","n_sigs_refund","sigflag"]);function Ya(e17){if(!e17||typeof e17!="string")throw new b("tag key must be a non empty string");if(Ja.has(e17))throw new b(`additionalTags must not use reserved key "${e17}"`)}var Z=class e15{constructor(e17,t2,n,r){if(typeof t2!="bigint"||t2<=0n)throw new b("OutputData: blindingFactor must be a positive bigint");if(!(n instanceof Uint8Array)||n.length===0)throw new b("OutputData: secret must be a non-empty Uint8Array");if(e17==null)throw new b("OutputData: blindedMessage is required");this.secret=n,this.blindingFactor=t2,this.blindedMessage=e17,this.ephemeralE=r}toProof(e17,n){if(e17==null)throw new b("Mint response is missing a signature for one of the outputs. Inputs may already be spent; if the wallet is seeded, try restoring (NUT-09) to recover.");if(!this.blindedMessage.amount.isZero()&&e17.id!==this.blindedMessage.id)throw new b(`Mint signature keyset id ${e17.id} does not match output ${this.blindedMessage.id}`);let i;e17.dleq&&(i={s:hexToBytes2(e17.dleq.s),e:hexToBytes2(e17.dleq.e),r:this.blindingFactor});let a=e17.amount.toString(),o=P(n.keys[a]);if(i){let t2=P(this.blindedMessage.B_),n2=P(e17.C_);if(!tr(i,t2,n2,o))throw new b("DLEQ verification failed on mint response")}let s=Ut({id:e17.id,C_:P(e17.C_)},this.blindingFactor,this.secret,o),c={id:e17.id,amount:e17.amount,C:s.C.toHex(true),secret:xe(s.secret),...i&&{dleq:{s:bytesToHex2(i.s),e:bytesToHex2(i.e),r:Ti(this.blindingFactor)}}};return this.ephemeralE&&(c.p2pk_e=this.ephemeralE),c}static createP2PKData(e17,t2,n,r){let i=B(t2,n.keys,r),a=e17.blindKeys&&e17.sigFlag==="SIG_ALL"?Rt():void 0;return i.map(t3=>this.createSingleP2PKData(e17,t3,n.id,a))}static createSingleP2PKData(n,r,a,o){let s=A.from(r),c=On(n),l=Array.isArray(c.pubkey)?c.pubkey:[c.pubkey],u=c.refundKeys??[],d=c.requiredSignatures??1,f=c.requiredRefundSignatures??1,p=c.hashlock,m=typeof p=="string"&&p.length>0,h=m?p:l[0],g=m?l:l.slice(1),_=u,v;if(n.blindKeys){let{blinded:e17,Ehex:t2}=vn([...l,...u],o,!m);m?g=e17.slice(0,l.length):(h=e17[0],g=e17.slice(1,l.length)),_=e17.slice(l.length),v=t2}let y=[],x2=c.locktime??NaN;if(Number.isSafeInteger(x2)&&x2>=0&&y.push(["locktime",String(x2)]),g.length>0&&(y.push(["pubkeys",...g]),d>1&&y.push(["n_sigs",String(d)])),_.length>0&&(y.push(["refund",..._]),f>1&&y.push(["n_sigs_refund",String(f)])),c.sigFlag=="SIG_ALL"&&y.push(["sigflag","SIG_ALL"]),c.additionalTags?.length){let e17=c.additionalTags.map(([e18,...t2])=>(Ya(e18),[e18,...t2.map(String)]));y.push(...e17)}let S2=[m?"HTLC":"P2PK",{nonce:bytesToHex2(randomBytes3(32)),data:h,tags:y}],C=JSON.stringify(S2),ee=[...C].length;if(ee>1024)throw new b(`Secret too long (${ee} characters), maximum is ${ce}`);let w=new TextEncoder().encode(C),{r:T2,B_:te2}=Vt(w);return new e15(new qa(s,te2,a).getSerializedBlindedMessage(),T2,w,v)}static createRandomData(e17,t2,n){return B(e17,t2.keys,n).map(e18=>this.createSingleRandomData(e18,t2.id))}static createSingleRandomData(n,r){let a=A.from(n),o=bytesToHex2(randomBytes3(32)),s=new TextEncoder().encode(o),{r:c,B_:l}=Vt(s);return new e15(new qa(a,l,r).getSerializedBlindedMessage(),c,s)}static createDeterministicData(e17,t2,n,r,i){let a=B(e17,r.keys,i),o=dr(t2,r.id);return a.map((e18,t3)=>Xa(e18,r.id,o(n+t3)))}static createSingleDeterministicData(e17,t2,n,r){return Xa(e17,r,ur(t2,r,n))}static sumOutputAmounts(e17){return A.sum(e17.map(e18=>e18.blindedMessage.amount))}static serialize(e17){return{blindedMessage:{amount:e17.blindedMessage.amount.toString(),B_:e17.blindedMessage.B_,id:e17.blindedMessage.id},blindingFactor:e17.blindingFactor.toString(),secret:bytesToHex2(e17.secret),...e17.ephemeralE&&{ephemeralE:e17.ephemeralE}}}static deserialize(t2){try{if(!/^(0|[1-9]\d*)$/.test(t2.blindingFactor))throw new b("blindingFactor must be a canonical decimal integer");let n=hexToBytes2(t2.secret);xe(n);let i=BigInt(t2.blindingFactor),{B_:a}=Vt(n,i);if(a.toHex(true)!==t2.blindedMessage.B_.toLowerCase())throw new b("stored output does not match its secret. Inputs may already be spent; if the wallet is seeded, try restoring (NUT-09) to recover.");return new e15({amount:A.from(t2.blindedMessage.amount),B_:t2.blindedMessage.B_,id:t2.blindedMessage.id},i,n,t2.ephemeralE?P(t2.ephemeralE).toHex(true):void 0)}catch(e17){throw new b(`Invalid SerializedOutputData: ${e17 instanceof Error?e17.message:String(e17)}`,{cause:e17})}}};function Xa(e17,n,r){let i=A.from(e17),a=bytesToHex2(r.secret),o=new TextEncoder().encode(a),{r:s,B_:c}=Vt(o,bytesToNumberBE(r.blindingFactor));return new Z(new qa(i,c,n).getSerializedBlindedMessage(),s,o)}var Mo;var No=class e16{constructor(e17,t2){this.tokenGeneration=0,this.sessionGeneration=0,this.tokens={},this.pool=[],this.desiredPoolSize=100,this.maxPerMint=100,this.mintUrl=e17,this.req=t2?.request??Fa,this.logger=t2?.logger??E;let n=Math.max(1,t2?.desiredPoolSize??this.desiredPoolSize),r=Math.max(1,t2?.maxPerMint??this.maxPerMint);this.desiredPoolSize=Math.min(n,100),this.maxPerMint=Math.min(r,100),this.desiredPoolSize!==n&&this.logger.warn("AuthManager: desiredPoolSize exceeds internal cap and was clamped",{configured:n,clampedTo:this.desiredPoolSize}),this.maxPerMint!==r&&this.logger.warn("AuthManager: maxPerMint exceeds internal cap and was clamped",{configured:r,clampedTo:this.maxPerMint})}attachOIDC(e17){return this.oidc&&this.oidcListener&&this.oidc.removeTokenListener(this.oidcListener),this.oidc&&this.oidc!==e17&&(this.tokens={}),this.tokenGeneration++,this.sessionGeneration++,this.oidc=e17,this.bindOIDC(),this}bindOIDC(){let e17=this.oidc;if(!e17)return;this.oidcListener&&e17.removeTokenListener(this.oidcListener);let t2=(n,r)=>{if(this.oidc!==e17||r==="refresh"&&this.oidcListener!==t2){this.logger.warn("AuthManager: ignoring tokens from an earlier session");return}this.updateFromOIDC(n,r)};this.oidcListener=t2,e17.addTokenListener(t2)}get poolSize(){return this.pool.length}get poolTarget(){return this.desiredPoolSize}get activeAuthKeysetId(){try{return this.keychain?.getCheapestKeyset().id}catch{return}}get hasCAT(){return!!this.tokens.accessToken}getCAT(){return this.tokens.accessToken}setCAT(e17){this.tokenGeneration++,this.sessionGeneration++,this.tokens={accessToken:e17},this.bindOIDC()}async ensureCAT(e17){if(this.validForAtLeast(e17)||!this.oidc||!this.tokens.refreshToken)return this.tokens.accessToken;if(this.inflightRefresh?.session!==this.sessionGeneration){let e18=this.tokenGeneration,t3=this.sessionGeneration,n=(async()=>{try{let t4=await this.oidc.refresh(this.tokens.refreshToken);this.tokenGeneration===e18&&this.updateFromOIDC(t4,"refresh")}catch(e19){this.logger.warn("AuthManager: CAT refresh failed",{err:e19})}})();this.inflightRefresh={session:t3,promise:n}}let t2=this.inflightRefresh;try{await t2.promise}finally{this.inflightRefresh===t2&&(this.inflightRefresh=void 0)}return this.validForAtLeast(0)?this.tokens.accessToken:void 0}validForAtLeast(t2=e16.MIN_VALID_SECS){let{accessToken:n,expiresAt:r}=this.tokens;return n?r?Date.now()+t2*1e3<r:true:false}updateFromOIDC(e17,t2){if(!e17.access_token)return;this.tokenGeneration++,t2==="signin"&&this.sessionGeneration++;let n=Date.now();if(this.tokens.accessToken=e17.access_token,this.tokens.refreshToken=e17.refresh_token||(t2==="refresh"?this.tokens.refreshToken:void 0),typeof e17.expires_in=="number"&&e17.expires_in>0)this.tokens.expiresAt=n+e17.expires_in*1e3;else{let t3=this.parseJwtExpSec(e17.access_token);this.tokens.expiresAt=t3?t3*1e3:void 0}this.bindOIDC(),this.logger.debug("AuthManager: OIDC tokens updated",{expiresAt:this.tokens.expiresAt})}async ensure(e17){if(await this.init(),this.pool.length>=e17)return;let t2=Math.max(this.desiredPoolSize,e17),n=this.getBatMaxMint(),r=Math.min(t2-this.pool.length,n);r<=0||await this.topUp(r)}async getBlindAuthToken({method:e17,path:t2}){return this.info&&!this.info.requiresBlindAuthToken(e17,t2)&&this.logger.warn("Endpoint is not marked as protected by NUT-22; still issuing BAT",{method:e17,path:t2}),this.withLock(async()=>{if(await this.ensure(1),this.pool.length===0)throw new b("AuthManager: no BATs available and minting failed");let n=this.pool.pop();return this.logger.debug("AuthManager: BAT requested",{method:e17,path:t2,remaining:this.pool.length}),Po(n)})}importPool(e17,t2="replace"){t2==="replace"&&(this.pool=[]);let n=new Map(this.pool.map(e18=>[e18.secret,e18]));for(let t3 of e17)!t3||!t3.secret||!t3.C||!t3.id||n.has(t3.secret)||(this.pool.push(t3),n.set(t3.secret,t3))}exportPool(){return this.pool.map(e17=>({...e17,dleq:e17.dleq?{...e17.dleq}:void 0}))}parseJwtExpSec(e17){if(!e17)return;let t2=e17.split(".");if(t2.length===3)try{let e18=new TextDecoder("utf-8").decode(Xe(t2[1])),n=JSON.parse(e18),r=typeof n.exp=="number"?n.exp:Number(n.exp);if(Number.isFinite(r)&&r>0)return r}catch{this.logger.warn("JWT access token was malformed.")}}async withLock(e17){let t2=this.lockChain??Promise.resolve(),n,r=new Promise(e18=>{n=e18}),i=t2.then(()=>r);this.lockChain=i;try{return await t2,await e17()}finally{n(),this.lockChain===i&&(this.lockChain=void 0)}}async init(){if(!this.info){let e17=await this.req({endpoint:G(this.mintUrl,"/v1/info"),method:"GET",logger:this.logger});this.info=new Y(e17,this.logger)}if(!this.keychain){let[e17,t2]=await Promise.all([this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/keysets"),method:"GET",logger:this.logger}),this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/keys"),method:"GET",logger:this.logger})]);if(!Array.isArray(e17.keysets)||!Array.isArray(t2.keysets))throw new b("AuthManager: mint returned malformed auth keysets");if(e17.keysets.length>1e4||t2.keysets.length>1e4)throw new b("AuthManager: mint returned more auth keysets than can be processed");let n=e17.keysets.map(e18=>ra(e18)),r=t2.keysets.map(e18=>ia(e18)),i=Ka.mintToCacheDTO(this.mintUrl,n,r);this.keychain=Ka.fromCache(this.mintUrl,"auth",i),this.keychain.getCheapestKeyset()}}getBatMaxMint(){if(!this.info)throw new b("AuthManager: mint info not loaded");let e17=this.info.nuts[22],t2=J(e17?.bat_max_mint,"nuts.22.bat_max_mint",this.maxPerMint);return Math.max(1,Math.min(this.maxPerMint,t2))}getActiveKeys(){if(!this.keychain)throw new b("AuthManager: keyset not loaded for active keyset");return this.keychain.getCheapestKeyset()}async topUp(e17){if(!this.info)throw new b("AuthManager: mint info not loaded");let t2=this.info.requiresClearAuthToken("POST","/v1/auth/blind/mint"),n=this.sessionGeneration,r;if(t2){if(r=await this.ensureCAT(),this.sessionGeneration!==n)throw new b("AuthManager: session changed while obtaining BATs");if(!r)throw new b("AuthManager: Clear-auth token required for /v1/auth/blind/mint but not available. Authenticate with the mint to obtain a CAT first.")}let i=this.getActiveKeys(),a=Z.createRandomData(e17,i),o={outputs:a.map(e18=>e18.blindedMessage)},s={};r&&(s["Clear-auth"]=r);let c=await this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/mint"),method:"POST",headers:s,...r?{redirect:"error"}:{},requestBody:o,logger:this.logger});if(!Array.isArray(c?.signatures)||c.signatures.length!==a.length)throw new b("AuthManager: bad BAT mint response");let l=c.signatures.map(e18=>({...e18,amount:A.from(e18.amount)})),u=a.map((e18,t3)=>e18.toProof(l[t3],i));for(let e18 of u)if(!Ji(e18,i))throw new b("AuthManager: mint returned BAT with invalid DLEQ");if(t2&&this.sessionGeneration!==n)throw new b("AuthManager: session changed while obtaining BATs");this.pool.push(...u),this.logger.debug("AuthManager: performed topUp",{minted:u.length,pool:this.pool.length})}};Mo=No,Mo.MIN_VALID_SECS=30;function Po(e17){return`authA${Ye(utf8ToBytes3(JSON.stringify({id:e17.id,secret:e17.secret,C:e17.C})))}`}import{computeSmartDenominations,getWallet,lookupInvoice,maxKeysetDenomination,missingDleqRecoveryDiagnostic,NwcError,parseNwcUri,payInvoiceReconciling,prepareP2PKMint,restoreProofsFromOutputs,serializeOutputData,swapFeeForProofs,toLockPubkey as toLockPubkey2}from"@dvmkit/sdk/internal";import{DRAIN_DELIVERY_RESERVE_SATS}from"@dvmkit/sdk/server";init_defaults();import{appendFileSync as appendFileSync2,chmodSync as chmodSync6,existsSync as existsSync11,mkdirSync as mkdirSync9,readFileSync as readFileSync11,writeFileSync as writeFileSync9}from"fs";function appendSwapped(entry){appendEntry2({...entry,kind:"swapped",ts:Date.now()})}function appendParked(entry){appendEntry2({...entry,kind:"parked",ts:Date.now()})}function appendMintQuote(entry){appendEntry2({...entry,kind:"mint_quote",ts:Date.now()})}function appendMintPaid(entry){appendEntry2({...entry,kind:"mint_paid",ts:Date.now()})}function appendMintPaymentAttempt(entry){appendEntry2({...entry,kind:"mint_payment_attempt",ts:Date.now()})}function appendMintPaymentRejected(entry){appendEntry2({...entry,kind:"mint_payment_rejected",ts:Date.now()})}function appendMintOutputs(entry){appendEntry2({...entry,kind:"mint_outputs",ts:Date.now()})}function appendMintAbandoned(entry){appendEntry2({...entry,kind:"mint_abandoned",ts:Date.now()})}function readUnparked(dvmId){return scan(dvmId).unparked}function readOpenMintQuotes(dvmId){return scan(dvmId).openQuotes}function scan(dvmId){if(!existsSync11(DRAIN_PARK_LOG_FILE))return{unparked:[],openQuotes:[]};const raw2=readFileSync11(DRAIN_PARK_LOG_FILE,"utf-8");const swapped=new Map;const parked=new Set;const quotes=new Map;const paid=new Map;const paymentRejected=new Map;const outputs=new Map;const abandoned=new Set;for(const line of raw2.split("\n")){if(!line)continue;let entry;try{entry=JSON.parse(line)}catch{continue}if(entry.dvm_id!==dvmId)continue;const key=`${entry.dvm_id}|${entry.credit_id}|${entry.drain_id}`;switch(entry.kind){case"swapped":swapped.set(key,entry);break;case"parked":parked.add(key);break;case"mint_quote":quotes.set(key,entry);break;case"mint_paid":paid.set(key,entry);break;case"mint_payment_attempt":if(paymentRejected.get(key)?.quote_id===entry.quote_id)paymentRejected.delete(key);break;case"mint_payment_rejected":paymentRejected.set(key,entry);break;case"mint_outputs":outputs.set(key,entry);break;case"mint_abandoned":abandoned.add(`${key}|${entry.quote_id}`);break}}const openQuotes=[];for(const[key,quote]of quotes){if(parked.has(key)||swapped.has(key)||abandoned.has(`${key}|${quote.quote_id}`))continue;const paidEntry=paid.get(key);const paymentRejectedEntry=paymentRejected.get(key);const outputsEntry=outputs.get(key);openQuotes.push({quote,...paidEntry?.quote_id===quote.quote_id?{paid:paidEntry}:{},...paymentRejectedEntry?.quote_id===quote.quote_id?{paymentRejected:paymentRejectedEntry}:{},...outputsEntry?.quote_id===quote.quote_id?{outputs:outputsEntry}:{}})}return{unparked:[...swapped.entries()].filter(([k2])=>!parked.has(k2)).map(([,e17])=>e17),openQuotes}}function appendEntry2(entry){if(!existsSync11(BUILDER_CONFIG_DIR)){mkdirSync9(BUILDER_CONFIG_DIR,{recursive:true,mode:448})}if(!existsSync11(DRAIN_PARK_LOG_FILE)){writeFileSync9(DRAIN_PARK_LOG_FILE,"",{mode:384})}else{chmodSync6(DRAIN_PARK_LOG_FILE,384)}appendFileSync2(DRAIN_PARK_LOG_FILE,JSON.stringify(entry)+"\n")}function bolt11AmountMsats(bolt11){const trimmed=bolt11.trim();const separator=trimmed.lastIndexOf("1");if(separator<=0)return null;const match=/^ln(?:bcrt|bc|tbs|tb|sb)(\d+)([munp])?$/i.exec(trimmed.slice(0,separator));if(!match)return null;const digits=Number(match[1]);if(!Number.isFinite(digits))return null;const multiplier=match[2]?.toLowerCase()??"";switch(multiplier){case"m":return digits*1e8;case"u":return digits*1e5;case"n":return digits*100;case"p":return Math.round(digits/10);default:return digits*1e11}}async function serviceDrains(ctx){const summary={fetched:false,drains_seen:0,drains_parked:0,drains_jit_minted:0,drains_failed:0,drains_queued:0,drains_manual:0,drains_channel:0,recovered:0,cashu_liability_sats_after:0};const recovery=new Map;summary.recovered+=await recoverParks(ctx,summary,recovery);await recoverMintQuotes(ctx,summary,recovery);let pending3;try{pending3=await ctx.client.getPendingDrains()}catch(err){rethrowIfBadSignature(err);rethrowIfAdminStateUnavailable(err);if(isMissingDrainSurface(err)){return{...summary,fetched:true}}logDrain(ctx,"drain_pending_fetch_failed",{error:err instanceof Error?err.message:String(err)});return summary}summary.fetched=true;summary.drains_seen=pending3.drains.length;const stillPending=new Set((await refreshedPendingIds(ctx,pending3.drains,summary)).map(d=>`${d.credit_id}|${d.drain_id}`));let cashuOutstandingSats=0;for(const drain of pending3.drains){if(!stillPending.has(`${drain.credit_id}|${drain.drain_id}`))continue;if(drain.method==="cashu"){const key=`${drain.credit_id}|${drain.drain_id}`;const recovered=recovery.get(key);if(recovered==="resolved"){continue}if(recovered){if(recovered==="failed")summary.drains_failed++;else summary.drains_queued++;cashuOutstandingSats+=drain.amount_sats??0;continue}const outcome=await parkOne(ctx,drain,pending3.mints??[]);if(outcome==="parked"||outcome==="jit_minted"){summary.drains_parked++;if(outcome==="jit_minted")summary.drains_jit_minted++}else{if(outcome==="queued")summary.drains_queued++;else summary.drains_failed++;cashuOutstandingSats+=drain.amount_sats??0}}else if(drain.settlement==="channel"){summary.drains_channel++;logDrain(ctx,"drain_channel_settlement_pending",{credit_id:drain.credit_id,drain_id:drain.drain_id,method:drain.method,channel_id:drain.channel_id,amount_micro:drain.amount_micro,hint:"Usually nothing to do \u2014 the caller's channel close settles this one, and their next drain poll finishes the reconciliation. It is not on `dvmctl credit drains-owed`. The exception is a Tempo close that landed on chain and refunded less than the ledger extinguished: that one never clears on a poll, and `dvmctl credit tempo-wedged` is where it shows up (tracked change)."})}else{summary.drains_manual++;logDrain(ctx,"drain_manual_settlement_required",{credit_id:drain.credit_id,drain_id:drain.drain_id,method:drain.method,payout:drain.payout,amount_micro:drain.amount_micro,amount_native:drain.amount_native,hint:`No builder-held on-chain send keys exist; send this refund yourself and record it with \`dvmctl credit drain-settle ${ctx.handle} ${drain.credit_id} ${drain.drain_id} --tx <hash>\`. \`dvmctl credit drains-owed ${ctx.handle}\` lists everything outstanding.`})}}summary.cashu_liability_sats_after=cashuOutstandingSats;return summary}async function recoverParks(ctx,summary,recovery){const unparked=readUnparked(ctx.dvmId);let recovered=0;for(const entry of unparked){const key=`${entry.credit_id}|${entry.drain_id}`;recovery.set(key,"queued");try{await ctx.client.parkDrain({credit_id:entry.credit_id,drain_id:entry.drain_id,token:entry.token,consumed_row_ids:entry.consumed_row_ids,keep_proofs:entry.keep_proofs,...entry.mint_evidence?{mint_evidence:entry.mint_evidence}:{}});appendParked({dvm_id:ctx.dvmId,credit_id:entry.credit_id,drain_id:entry.drain_id});recovered++;recovery.set(key,"resolved");if(entry.mint_evidence)summary.drains_jit_minted++;logDrain(ctx,"drain_park_recovered",{credit_id:entry.credit_id,drain_id:entry.drain_id})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_recovery_failed",{credit_id:entry.credit_id,drain_id:entry.drain_id,error:err instanceof Error?err.message:String(err)})}}return recovered}async function refreshedPendingIds(ctx,drains,summary){if(summary.recovered===0)return drains;try{const fresh=await ctx.client.getPendingDrains();return fresh.drains}catch(err){rethrowIfAdminStateUnavailable(err);return drains}}async function parkOne(ctx,drain,mints){const amountSats=drain.amount_sats;const refundPubkey=drain.payout.refund_pubkey;if(!amountSats||amountSats<=0||!refundPubkey){logDrain(ctx,"drain_park_unpriceable",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats});return"failed"}let batches;try{batches=(await ctx.client.getPendingBatches()).batches}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_batches_fetch_failed",{error:err instanceof Error?err.message:String(err)});return"failed"}const jitAvailable=ctx.refundNwc!==void 0;const selected=await selectCoveringBatch(ctx,drain,amountSats,batches,jitAvailable);if(!selected){return jitMintOne(ctx,drain,amountSats,refundPubkey,mints)}const{batch,wallet,inputProofs,keypair}=selected;let send;let keep;try{const result=await wallet.ops.send(amountSats,inputProofs).asP2PK({pubkey:refundPubkey}).keepAsP2PK({pubkey:batch.lock_pubkey}).privkey(keypair.privkey).includeFees(false).run();send=result.send;keep=result.keep}catch(err){const recoveryDiagnostic=missingDleqRecoveryDiagnostic(inputProofs,err);logDrain(ctx,"drain_swap_failed",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,error:err instanceof Error?err.message:String(err),...recoveryDiagnostic?{recovery:recoveryDiagnostic}:{}});return"failed"}const inputIndexBySecret=new Map(batch.proofs.map((p,i)=>[p.secret,i]));const untouchedInputs=new Set;const change=[];for(const p of keep){const index=inputIndexBySecret.get(p.secret);if(index===void 0)change.push(p);else untouchedInputs.add(index)}const consumedRowIds=batch.row_ids.filter((_,i)=>!untouchedInputs.has(i));const keepProofs=change.map(p=>({id:p.id,amount:amountForJson(p.amount),secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));if(consumedRowIds.length===0){logDrain(ctx,"drain_park_no_consumed_rows",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,batch_rows:batch.row_ids.length});return"failed"}const encoded=ki({mint:batch.mint_url,proofs:rehydrateLocalProofs(send)});appendSwapped({dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id,token:encoded,consumed_row_ids:consumedRowIds,keep_proofs:keepProofs});try{await ctx.client.parkDrain({credit_id:drain.credit_id,drain_id:drain.drain_id,token:encoded,consumed_row_ids:consumedRowIds,keep_proofs:keepProofs})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_post_failed",{credit_id:drain.credit_id,drain_id:drain.drain_id,error:err instanceof Error?err.message:String(err)});return"failed"}appendParked({dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id});logDrain(ctx,"drain_parked",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,mint_url:batch.mint_url});return"parked"}async function jitMintOne(ctx,drain,amountSats,refundPubkey,mints){const ids={credit_id:drain.credit_id,drain_id:drain.drain_id};if(!ctx.refundNwc){logDrain(ctx,"drain_jit_unconfigured",{...ids,amount_sats:amountSats,display:"This refund needs sats bought from the hub, and no send wallet is configured to buy them.",hint:"Pass `--refund-nwc <wallet connection URI>` (or set DVMKIT_REFUND_NWC_URL) with a send-capable NWC connection on the wallet holding your Bitcoin deposits. The refund waits, visible in `dvmctl credit drains-owed`, until one is wired."});return"queued"}const keypair=ctx.currentKeypair;if(!keypair){logDrain(ctx,"drain_jit_no_lock_key",{...ids,display:"This refund needs to be minted, and no current builder lock key was resolved to sign the mint quote.",hint:"The mint quote is locked to your builder key (NUT-20) so nobody else can redeem the invoice you pay for. Check `~/.dvmkit/builder.mnemonic` is present and that this DVM is registered in `~/.dvmkit/builder.json`."});return"queued"}let conn;try{conn=parseNwcUri(ctx.refundNwc.uri)}catch(err){logDrain(ctx,"drain_jit_nwc_invalid",{...ids,error:err instanceof Error?err.message:String(err),display:"The refund send wallet's connection string could not be read, so nothing was paid.",hint:"Re-copy the `wallet connection URI` URI from the wallet that holds your Bitcoin deposits. The refund stays owed and retries on the next tick."});return"queued"}const tried=[];for(const mintUrl of mints){tried.push(mintUrl);let wallet;let quote;try{wallet=await(ctx.walletProvider??getWallet)(mintUrl);quote=await wallet.createLockedMintQuote(amountSats,keypair.pubkey)}catch(err){logDrain(ctx,"drain_jit_quote_failed",{...ids,mint_url:mintUrl,amount_sats:amountSats,error:err instanceof Error?err.message:String(err)});continue}const invoiceMsats=bolt11AmountMsats(quote.request);const ceilingMsats=(amountSats+DRAIN_DELIVERY_RESERVE_SATS)*1e3;if(invoiceMsats===null||invoiceMsats>ceilingMsats){logDrain(ctx,"drain_jit_quote_refused",{...ids,mint_url:mintUrl,quote_id:quote.quote,reason:invoiceMsats===null?"invoice_unreadable":"above_ceiling",face_sats:amountSats,invoice_msats:invoiceMsats,ceiling_msats:ceilingMsats,display:invoiceMsats===null?`${mintUrl} asked for this refund's ecash with an invoice whose amount could not be read, so nothing was paid.`:`${mintUrl} wants more for this refund's ecash than the refund is worth, so nothing was paid.`,hint:"Nothing left the wallet, and the unpaid quote simply expires at the mint. Another accepted mint is tried next; if every one of them is refused this way, check the mints' own fee settings before adding one that is cheaper."});continue}const entry={dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:mintUrl,quote_id:quote.quote,bolt11:quote.request,invoice_msats:invoiceMsats,face_sats:amountSats,reserve_sats:DRAIN_DELIVERY_RESERVE_SATS,expires_at:quote.expiry??null,refund_pubkey:refundPubkey};appendMintQuote(entry);logDrain(ctx,"drain_jit_quoted",{...ids,mint_url:mintUrl,quote_id:quote.quote,face_sats:amountSats,invoice_msats:entry.invoice_msats});return advanceMintQuote(ctx,conn,keypair,{quote:{...entry,kind:"mint_quote",ts:Date.now()}})}logDrain(ctx,"drain_jit_no_mint",{...ids,amount_sats:amountSats,mints_tried:tried,display:tried.length===0?"This refund has to be bought at a Cashu mint and this DVM has none configured, so nothing could be minted.":"No accepted mint would quote for this refund, so nothing was bought and nothing was paid.",hint:tried.length===0?"Set DVMKIT_CASHU_MINTS on the DVM. A non-channel Bitcoin refund is P2PK-locked ecash and nothing else, so without a mint there is no shape it can take.":`Each mint's own error is on the \`drain_jit_quote_failed\` line above it. The refund stays owed and retries every tick \u2014 nothing here needs undoing, and no payment was attempted.`});return"queued"}async function advanceMintQuote(ctx,conn,keypair,open){const entry=open.quote;const refundPubkey=entry.refund_pubkey;const ids={credit_id:entry.credit_id,drain_id:entry.drain_id};const base3={...ids,mint_url:entry.mint_url,quote_id:entry.quote_id};let wallet;try{wallet=await(ctx.walletProvider??getWallet)(entry.mint_url)}catch(err){logDrain(ctx,"drain_jit_mint_unreachable",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint holding this refund's quote is not answering, so the refund is still on its way.",hint:"Nothing is lost and nothing needs undoing \u2014 the quote is recorded and the next tick picks it up where it left off. If the mint stays down, its own status page is the place to look."});return"queued"}let paid=Boolean(open.paid);let feesPaidMsats=open.paid?.fees_paid_msats??null;let checked;if(!paid){if(quoteDead(entry)){appendMintAbandoned({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,reason:"quote_expired_unpaid"});logDrain(ctx,"drain_jit_quote_abandoned",{...base3,reason:"quote_expired_unpaid",display:"The mint's quote for this refund ran out before it could be paid, so it was dropped unpaid.",hint:"Nothing left the wallet \u2014 the refund is still owed and the next tick asks for a fresh quote. If this repeats, the wallet is the place to look: an exhausted budget or an empty balance keeps a quote from being paid until it expires."});return"queued"}appendMintPaymentAttempt({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id});try{const outcome=await(ctx.payInvoice??payInvoiceReconciling)(conn,entry.bolt11);paid=true;feesPaidMsats=outcome.feesPaidMsats??null;appendMintPaid({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,preimage:outcome.preimage,...outcome.feesPaidMsats!==void 0?{fees_paid_msats:outcome.feesPaidMsats}:{},...outcome.outcome==="paid"?{}:{recovered:true}});logDrain(ctx,"drain_jit_invoice_paid",{...base3,pay_outcome:outcome.outcome,invoice_msats:entry.invoice_msats,fees_paid_msats:outcome.feesPaidMsats??null})}catch(err){const code=err instanceof NwcError?err.code:"pay_failed";const definitivelyRejected=err instanceof NwcError&&err.method==="pay_invoice"&&code.toLowerCase()!=="payment_unknown";if(definitivelyRejected){appendMintPaymentRejected({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,error_code:code})}logDrain(ctx,code==="payment_unknown"?"drain_jit_payment_unknown":"drain_jit_payment_failed",{...base3,error_code:code,error:err instanceof Error?err.message:String(err),display:code==="payment_unknown"?"The wallet buying this refund never answered, so we cannot yet say whether the payment went out.":"The wallet could not pay for this refund's ecash, so no money left and the refund is still owed.",hint:code==="payment_unknown"?"Nothing is retried until this resolves \u2014 the next tick asks the wallet about this exact invoice, so it cannot be paid twice. Check the wallet is online and reachable over its relays.":"The wallet's own error is on this line. A balance too low or a budget exhausted are the usual two; the same invoice is retried on the next tick once the wallet can pay it."});return"queued"}}try{checked=await wallet.checkMintQuoteBolt11(entry.quote_id)}catch(err){logDrain(ctx,"drain_jit_quote_state_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint would not say whether it has been paid for this refund yet, so it stays in flight.",hint:"The payment is recorded, so nothing is re-sent. The next tick asks again."});return"queued"}let proofs;if(checked.state===sa.ISSUED){if(!open.outputs){logDrain(ctx,"drain_jit_issued_unrecoverable",{...base3,display:"This refund's ecash was already issued by the mint, but this machine has no record of the outputs needed to collect it.",hint:"Do not re-run the refund \u2014 the drain is deliberately held so it cannot be bought twice. This means another host minted the same quote, or `~/.dvmkit/drain-park.log` lost a line. Check for a second worker against this DVM before doing anything else."});return"failed"}try{proofs=await restoreProofsFromOutputs(wallet,open.outputs.keyset_id,open.outputs.outputs)}catch(err){logDrain(ctx,"drain_jit_restore_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has this refund's ecash and would not hand back the signatures for it this time.",hint:"The outputs are on disk, so this is recoverable \u2014 the next tick asks the mint again. Nothing was paid twice."});return"queued"}logDrain(ctx,"drain_jit_restored",{...base3,proofs:proofs.length})}else if(checked.state===sa.PAID){const denominations=computeSmartDenominations(entry.face_sats,{maxDenomination:maxKeysetDenomination(wallet),includeTinyTail:false});let preview;try{preview=await prepareP2PKMint(wallet,entry.face_sats,checked,keypair.privkey,refundPubkey,denominations)}catch(err){logDrain(ctx,"drain_jit_prepare_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has been paid for this refund but would not prepare the notes this time.",hint:"The payment stands and is not repeated \u2014 the next tick prepares them again."});return"queued"}appendMintOutputs({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,keyset_id:preview.keysetId,outputs:serializeOutputData(preview.outputData)});try{proofs=await wallet.completeMint(preview)}catch(err){logDrain(ctx,"drain_jit_mint_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has been paid for this refund and did not return the notes, so it is still in flight.",hint:"The blinded outputs are recorded, so the next tick can collect the same notes from the mint \u2014 nothing is bought twice."});return"queued"}}else{if(quoteExpired(checked)){logDrain(ctx,"drain_jit_quote_expired_paid",{...base3,invoice_msats:entry.invoice_msats,display:"The hub paid for this refund's ecash and the mint's quote expired before it credited the payment.",hint:`Do not re-run the refund \u2014 the drain is held so it cannot be bought twice. Ask ${entry.mint_url} about quote ${entry.quote_id}; the payment either sits with them or was returned by Lightning. Settle it there, then remove this quote's lines from \`~/.dvmkit/drain-park.log\` to release the drain.`});return"failed"}logDrain(ctx,"drain_jit_awaiting_credit",{...base3,quote_state:checked.state,display:"The hub has paid for this refund's ecash and the mint has not credited the payment yet.",hint:"Ordinary for a moment after a Lightning payment. The next tick collects the notes; nothing is paid again in the meantime."});return"queued"}const evidence={source:"jit_mint",mint_url:entry.mint_url,quote_id:entry.quote_id,face_sats:entry.face_sats,reserve_sats:entry.reserve_sats,invoice_msats:entry.invoice_msats,routing_fee_msats:feesPaidMsats};const encoded=ki({mint:entry.mint_url,proofs:rehydrateLocalProofs(proofs)});appendSwapped({dvm_id:ctx.dvmId,...ids,token:encoded,consumed_row_ids:[],keep_proofs:[],mint_evidence:evidence});try{await ctx.client.parkDrain({...ids,token:encoded,consumed_row_ids:[],keep_proofs:[],mint_evidence:evidence})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_post_failed",{...base3,error:err instanceof Error?err.message:String(err)});return"queued"}appendParked({dvm_id:ctx.dvmId,...ids});logDrain(ctx,"drain_jit_parked",{...base3,face_sats:entry.face_sats,reserve_sats:entry.reserve_sats,invoice_msats:entry.invoice_msats,routing_fee_msats:feesPaidMsats,reserve_remainder_msats:entry.reserve_sats*1e3-deliveryCostMsats(entry,feesPaidMsats)});return"jit_minted"}var MINT_QUOTE_STALE_MS=24*60*60*1e3;function quoteDead(entry){if(entry.expires_at!==null)return entry.expires_at*1e3<Date.now();return Date.now()-entry.ts>MINT_QUOTE_STALE_MS}function quoteExpired(quote){return typeof quote.expiry==="number"&&quote.expiry*1e3<Date.now()}function deliveryCostMsats(entry,routingFeeMsats){return entry.invoice_msats-entry.face_sats*1e3+(routingFeeMsats??0)}async function recoverMintQuotes(ctx,summary,recovery){const open=readOpenMintQuotes(ctx.dvmId);if(open.length===0)return;const keypair=ctx.currentKeypair;let conn;if(ctx.refundNwc){try{conn=parseNwcUri(ctx.refundNwc.uri)}catch{conn=void 0}}for(const item of open){const key=`${item.quote.credit_id}|${item.quote.drain_id}`;recovery.set(key,"queued");if(!conn||!keypair){logDrain(ctx,"drain_jit_recovery_unconfigured",{credit_id:item.quote.credit_id,drain_id:item.quote.drain_id,quote_id:item.quote.quote_id,display:"A refund purchase from a previous run is unfinished and cannot be checked without the send wallet.",hint:"Re-wire `--refund-nwc` (or DVMKIT_REFUND_NWC_URL) before the next tick. The refund is deliberately held until then, because the earlier payment may already have gone out."});continue}let carried=item;if(!item.paid){const settled=await reconcileQuotePayment(ctx,conn,item.quote,item.paymentRejected);if(settled==="unknown")continue;if(settled!=="unpaid")carried={...item,paid:settled}}const outcome=await advanceMintQuote(ctx,conn,keypair,carried);if(outcome==="jit_minted"){summary.recovered++;summary.drains_jit_minted++;recovery.set(key,"resolved")}else if(outcome==="failed"){recovery.set(key,"failed")}}}async function reconcileQuotePayment(ctx,conn,entry,paymentRejected){const base3={credit_id:entry.credit_id,drain_id:entry.drain_id,quote_id:entry.quote_id,mint_url:entry.mint_url};let tx;try{tx=await(ctx.lookupInvoiceImpl??lookupInvoice)(conn,{invoice:entry.bolt11})}catch(err){const code=err instanceof NwcError?err.code:"lookup_failed";if(code==="NOT_FOUND"){logDrain(ctx,"drain_jit_payment_never_sent",base3);return"unpaid"}logDrain(ctx,"drain_jit_recovery_lookup_failed",{...base3,error_code:code,error:err instanceof Error?err.message:String(err),display:"A refund purchase from a previous run could not be checked, so it is being left alone.",hint:"Nothing is re-sent while this is unanswered \u2014 that is what stops a refund being bought twice. The next tick asks the wallet again."});return"unknown"}if(tx.settledAt){const paid={dvm_id:ctx.dvmId,credit_id:entry.credit_id,drain_id:entry.drain_id,quote_id:entry.quote_id,preimage:tx.preimage??"",...tx.feesPaidMsats!==void 0?{fees_paid_msats:tx.feesPaidMsats}:{},recovered:true};appendMintPaid(paid);logDrain(ctx,"drain_jit_payment_recovered",{...base3,fees_paid_msats:tx.feesPaidMsats??null});return{...paid,kind:"mint_paid",ts:Date.now()}}if(tx.expiresAt&&tx.expiresAt*1e3<Date.now()){logDrain(ctx,"drain_jit_payment_expired_unpaid",base3);return"unpaid"}if(paymentRejected){logDrain(ctx,"drain_jit_payment_retrying",{...base3,error_code:paymentRejected.error_code,display:"The wallet rejected the previous attempt and confirms this refund invoice is still unpaid, so the same invoice is being retried.",hint:"No new mint quote was opened. If the wallet rejects this attempt too, its error is reported and the same safe check runs again next tick."});return"unpaid"}logDrain(ctx,"drain_jit_payment_in_flight",{...base3,display:"A refund purchase from a previous run is still in flight, so nothing new was attempted for it.",hint:"The refund is held until the wallet reports one way or the other \u2014 that is what keeps it from being paid for twice."});return"unknown"}function rehydrateLocalProofs(proofs){return proofs.map(proof=>({...proof,amount:localAmount(proof.amount)}))}function amountForJson(amount2){return localAmount(amount2).toNumber()}function localAmount(amount2){if(typeof amount2==="number"||typeof amount2==="bigint"||typeof amount2==="string"){return A.from(amount2)}if(amount2&&typeof amount2==="object"&&"toBigInt"in amount2){const toBigInt3=amount2.toBigInt;if(typeof toBigInt3==="function"){const value=toBigInt3.call(amount2);if(typeof value==="bigint")return A.from(value)}}throw new TypeError("Cashu proof amount must be a primitive AmountLike or expose toBigInt()")}async function selectCoveringBatch(ctx,drain,amountSats,batches,jitAvailable){const nextStep=jitAvailable?"The refund is minted just in time from the hub instead \u2014 see the `drain_jit_*` lines below.":"";let closestShortfall;const unpriceableMints=new Set;for(const batch of batches){const keypair=ctx.keypairsByPubkey.get(toLockPubkey2(batch.lock_pubkey));if(!keypair)continue;if(batch.total_sats<amountSats)continue;let wallet;let inputProofs;let reserveSats;try{wallet=await(ctx.walletProvider??getWallet)(batch.mint_url);inputProofs=batch.proofs.map(p=>({id:p.id,amount:p.amount,secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));reserveSats=swapFeeForProofs(wallet,inputProofs)}catch(err){unpriceableMints.add(batch.mint_url);logDrain(ctx,"drain_park_batch_unpriceable",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,error:err instanceof Error?err.message:String(err)});continue}const requiredSats=amountSats+reserveSats;if(batch.total_sats>=requiredSats)return{batch,wallet,inputProofs,keypair};if(!closestShortfall||requiredSats-batch.total_sats<closestShortfall.requiredSats-closestShortfall.batch.total_sats){closestShortfall={batch,requiredSats}}}if(closestShortfall){const{batch,requiredSats}=closestShortfall;const shortBySats=requiredSats-batch.total_sats;logDrain(ctx,"drain_park_batch_below_swap_cost",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,mint_url:batch.mint_url,total_sats:batch.total_sats,required_sats:requiredSats,short_by_sats:shortBySats,batches_seen:batches.length,unpriceable_mints:[...unpriceableMints],hint:`A batch at ${batch.mint_url} holds ${batch.total_sats} sats, which covers the ${amountSats}-sat refund but not the ${requiredSats-amountSats} sats that mint charges to swap it. ${nextStep||`It parks automatically once ${shortBySats} more sats land on that lock key; to intervene, fund the DVM or settle manually.`}`});return void 0}if(unpriceableMints.size>0){const mints=[...unpriceableMints];logDrain(ctx,"drain_park_no_priceable_batch",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,batches_seen:batches.length,unpriceable_mints:mints,hint:`No accumulator batch could be priced: ${mints.join(", ")} would not answer for its keyset, so the refund's swap cost is unknown. This is a mint problem, not a funding one${nextStep?`. ${nextStep}`:" \u2014 the drain retries on the next tick"}, and the per-batch \`drain_park_batch_unpriceable\` lines carry each mint's error.`});return void 0}logDrain(ctx,"drain_park_insufficient_batch",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,batches_seen:batches.length,hint:`No single accumulator batch covers this refund yet. ${nextStep||"It parks automatically once enough value accumulates on one lock key at one mint; to intervene, fund the DVM or settle manually."}`});return void 0}function isMissingDrainSurface(err){return err instanceof AdminApiError&&err.status===404}function logDrain(ctx,level,fields){console.log(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,...fields}))}var DEFAULT_INTERVAL_SECONDS=60;var MIN_INTERVAL_SECONDS=10;var MAX_INTERVAL_SECONDS=3600;var DEFAULT_HOLD_ALARM_AFTER="24h";async function meltPending(handle2,opts){const ctx=await resolveContext(handle2,opts);if(!opts.watch){const summary=await runTick(ctx);emitTickSummary(summary,!!opts.human,ctx.handle);const failure=runFailure(ctx,summary);if(failure)throw failure;recordCleanRun(ctx,summary);return}await runWatchLoop(ctx,opts)}async function resolveContext(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair,keypairsByPubkey}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);const endpoint=await resolveEndpoint(handle2,opts.endpoint,selected);const drainsOnly=!!opts.drainsOnly;const payoutLnAddress=opts.payout??getBuilderDvm(canonicalDvmId)?.payout_ln_address;const payoutNwc=parsePayoutNwc(opts.payoutNwc??process.env.DVMKIT_MELT_PAYOUT_NWC_URL);if(!payoutLnAddress&&!payoutNwc&&!drainsOnly){throw new DvmError27("no_payout_destination","No payout destination configured.","Pass `--payout <ln-address>` or set a default with `dvmctl set-payout "+handle2+" <ln-address>`. To melt into a wallet reachable over NWC instead, pass `--payout-nwc <wallet connection URI>` or set DVMKIT_MELT_PAYOUT_NWC_URL.")}const refundNwcUri=opts.refundNwc??process.env.DVMKIT_REFUND_NWC_URL;const client=new AdminCashuClient({endpoint,dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey});return{handle:handle2,dvmId:canonicalDvmId,endpoint,currentKeypair,keypairsByPubkey,payoutLnAddress:payoutLnAddress??"",...payoutNwc?{payoutNwc}:{},drainsOnly,holdAlarmAfterMs:resolveHoldAlarmAfterMs(opts.holdAlarmAfter),client,drainCtx:{dvmId:canonicalDvmId,handle:handle2,client,keypairsByPubkey,currentKeypair,...refundNwcUri?{refundNwc:{uri:refundNwcUri}}:{}},fetchImpl:globalThis.fetch.bind(globalThis),human:!!opts.human}}function parsePayoutNwc(uri){if(!uri)return void 0;try{return parseNwcUri2(uri)}catch(err){throw new DvmError27("invalid_payout_nwc",`Melt payout NWC connection could not be read: ${err instanceof Error?err.message:String(err)}`,"Re-copy the `wallet connection URI` URI from the wallet the melt should land in. Nothing was melted \u2014 the run refuses rather than paying the Lightning address you did not name.")}}async function payoutInvoice(ctx,msats){if(ctx.payoutNwc){const invoice=await makeInvoice(ctx.payoutNwc,{amountMsats:msats,description:`dvmkit melt \xB7 ${ctx.handle}`});if(!invoice.invoice){throw new DvmError27("payout_invoice_missing","The payout wallet returned no bolt11 for the melt.","The wallet answered `make_invoice` without an invoice. Check the connection permits `make_invoice`, then re-run \u2014 nothing has melted.")}return invoice.invoice}return fetchLnurlPayInvoice(ctx.payoutLnAddress,msats)}function resolveHoldAlarmAfterMs(input){try{return parseDurationMs(input??DEFAULT_HOLD_ALARM_AFTER)}catch(err){throw new DvmError27("invalid_duration",err instanceof Error?err.message:String(err),"`--hold-alarm-after` takes one count and one unit \u2014 e.g. `6h`, `2d`. Raise it to give a flaky DVM more rope; lower it to hear about a stalled melt sooner.")}}async function resolveEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError27("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}async function runTick(ctx){const recovered=await runRecovery(ctx);const drains=await serviceDrains(ctx.drainCtx);const meltHeld=!drains.fetched||drains.cashu_liability_sats_after>0;const unknownSince=recordDrainState(ctx.dvmId,drains.fetched);const heldForMs=drainHoldElapsedMs(unknownSince);if(heldForMs!==null&&heldForMs>=ctx.holdAlarmAfterMs){logErr(ctx,"melt_pending_drain_hold_sustained",new Error(`Pending drains have been unreadable since ${unknownSince}; the payout melt has been held ever since.`),{drain_state_unknown_since:unknownSince,hold_alarm_after_ms:ctx.holdAlarmAfterMs})}const summaryBase={tick:new Date().toISOString(),dvm_id:ctx.dvmId,endpoint:ctx.endpoint,recovered,drains_seen:drains.drains_seen,drains_parked:drains.drains_parked,drains_jit_minted:drains.drains_jit_minted,drains_failed:drains.drains_failed,drains_queued:drains.drains_queued,drains_manual:drains.drains_manual,drains_channel:drains.drains_channel,drains_recovered:drains.recovered,drain_state_known:drains.fetched,drain_state_unknown_since:unknownSince,melt_held:meltHeld};const noBatches={batches_seen:0,batches_melted:0,batches_skipped:0,batches_failed:0,sats_melted:0};if(ctx.drainsOnly){logEvent(ctx,"melt_pending_skipped_drains_only",{drain_liability_sats:drains.cashu_liability_sats_after,drain_state_known:drains.fetched});return{...summaryBase,...noBatches,melt_skipped:"drains_only"}}if(meltHeld){logEvent(ctx,"melt_pending_held_for_drains",{drain_liability_sats:drains.cashu_liability_sats_after,drain_state_known:drains.fetched});return{...summaryBase,...noBatches,melt_skipped:null}}let res;try{res=await ctx.client.getPendingBatches()}catch(err){rethrowIfBadSignature(err);rethrowIfAdminStateUnavailable(err);throw new DvmError27("pending_batches_fetch_failed","Failed to fetch pending batches from the DVM admin endpoint.",err instanceof Error?err.message:String(err))}let melted=0;let skipped=0;let failed=0;let satsMelted=0;for(const batch of res.batches){const outcome=await meltBatch(ctx,batch);if(outcome==="melted"){melted++;satsMelted+=batch.total_sats}else if(outcome==="skipped")skipped++;else failed++}return{...summaryBase,batches_seen:res.batches.length,batches_melted:melted,batches_skipped:skipped,batches_failed:failed,sats_melted:satsMelted,melt_skipped:null}}async function meltBatch(ctx,batch){const batchKeypair=ctx.keypairsByPubkey.get(toLockPubkey3(batch.lock_pubkey));if(!batchKeypair){logEvent(ctx,"melt_pending_skip_unknown_lock",{mint_url:batch.mint_url,lock_pubkey:batch.lock_pubkey,row_ids:batch.row_ids,total_sats:batch.total_sats});return"skipped"}let inputFees;try{inputFees=await getInputFeesForProofs(batch.mint_url,batch.proofs)}catch(err){logErr(ctx,"melt_pending_input_fee_lookup_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<=inputFees){logEvent(ctx,"melt_pending_input_fees_exceed_batch",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,input_fees:inputFees});return"skipped"}let bolt11;try{bolt11=await payoutInvoice(ctx,(batch.total_sats-inputFees)*1e3)}catch(err){logErr(ctx,"melt_pending_payout_invoice_failed",err,{destination:ctx.payoutNwc?"nwc":"lnurl",mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,input_fees:inputFees});return"failed"}let quote;try{quote=await fetchMeltQuote(batch.mint_url,bolt11)}catch(err){logErr(ctx,"melt_pending_quote_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<quote.amountSats+quote.feeReserveSats+inputFees){const target=batch.total_sats-quote.feeReserveSats-inputFees;if(target<=0){logEvent(ctx,"melt_pending_fee_reserve_exceeds_batch",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,fee_reserve_sats:quote.feeReserveSats,input_fees:inputFees});return"skipped"}try{bolt11=await payoutInvoice(ctx,target*1e3)}catch(err){logErr(ctx,"melt_pending_payout_invoice_failed",err,{destination:ctx.payoutNwc?"nwc":"lnurl",mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,target_sats:target});return"failed"}try{quote=await fetchMeltQuote(batch.mint_url,bolt11)}catch(err){logErr(ctx,"melt_pending_quote_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<quote.amountSats+quote.feeReserveSats+inputFees){logEvent(ctx,"melt_pending_fee_reserve_too_high",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,amount_sats:quote.amountSats,fee_reserve_sats:quote.feeReserveSats,input_fees:inputFees,retry:true});return"skipped"}}const proofs=batch.proofs.map(p=>({id:p.id,amount:p.amount,secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));appendPreMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,mint_url:batch.mint_url,quote_id:quote.rawQuote.quote,lock_pubkey:batch.lock_pubkey,total_sats:batch.total_sats});let preimage;try{const meltResult=await meltProofsWithQuoteSigned(batch.mint_url,proofs,quote,batchKeypair.privkey);preimage=meltResult.preimage}catch(err){const recoveryDiagnostic=missingDleqRecoveryDiagnostic2(proofs,err);appendPostMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,status:"failed",error:err instanceof Error?err.message:String(err)});logErr(ctx,"melt_pending_melt_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,...recoveryDiagnostic?{recovery:recoveryDiagnostic}:{}});return"failed"}if(!preimage){logErr(ctx,"melt_pending_missing_preimage_after_success",new Error("mint returned PAID without payment_preimage"),{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote});return"failed"}try{await ctx.client.markMelted({row_ids:batch.row_ids,melt_quote_id:quote.rawQuote.quote,payment_preimage:preimage})}catch(err){rethrowIfAdminStateUnavailable(err);logErr(ctx,"melt_pending_mark_melted_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote});return"failed"}appendPostMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,status:"success",preimage});logEvent(ctx,"melt_pending_batch_melted",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,quote_id:quote.rawQuote.quote});return"melted"}async function meltProofsWithQuoteSigned(mintUrl,proofs,quote,privkeyHex){const{getWallet:getWallet2}=await import("@dvmkit/sdk/internal");const wallet=await getWallet2(mintUrl);const{quote:melted}=await wallet.meltProofsBolt11(quote.rawQuote,proofs,{privkey:privkeyHex});return{preimage:melted.payment_preimage??null}}async function runRecovery(ctx){const inFlight=readInFlight(ctx.dvmId);if(inFlight.length===0)return 0;let recovered=0;for(const entry of inFlight){const result=await recoverOne(ctx,entry);if(result==="recovered")recovered++}return recovered}async function recoverOne(ctx,entry){let quote;try{quote=await checkMeltQuoteState(entry.mintUrl,entry.quoteId)}catch(err){logErr(ctx,"melt_pending_recovery_check_failed",err,{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}if(quote.state==="PENDING"){logEvent(ctx,"melt_pending_recovery_pending",{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}if(quote.state==="UNPAID"){appendPostMelt({dvm_id:ctx.dvmId,row_ids:entry.rowIds,quote_id:entry.quoteId,status:"failed",error:"ln_unpaid_on_recovery"});logEvent(ctx,"melt_pending_recovery_abandoned",{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"abandoned"}const preimage=quote.payment_preimage;if(!preimage){logErr(ctx,"melt_pending_recovery_paid_no_preimage",new Error("mint returned PAID without payment_preimage"),{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}try{await ctx.client.markMelted({row_ids:entry.rowIds,melt_quote_id:entry.quoteId,payment_preimage:preimage})}catch(err){rethrowIfAdminStateUnavailable(err);logErr(ctx,"melt_pending_recovery_mark_melted_failed",err,{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}appendPostMelt({dvm_id:ctx.dvmId,row_ids:entry.rowIds,quote_id:entry.quoteId,status:"success",preimage});logEvent(ctx,"melt_pending_recovery_marked_melted",{mint_url:entry.mintUrl,quote_id:entry.quoteId,row_ids:entry.rowIds});return"recovered"}async function runWatchLoop(ctx,opts){const requestedInterval=opts.interval??DEFAULT_INTERVAL_SECONDS;const intervalSeconds=Math.min(Math.max(MIN_INTERVAL_SECONDS,requestedInterval),MAX_INTERVAL_SECONDS);let stopping=false;let inFlight=null;const fatalHolder={err:null};logEvent(ctx,"melt_pending_watch_started",{interval_seconds:intervalSeconds,endpoint:ctx.endpoint});async function tick(){if(stopping)return;if(inFlight){logEvent(ctx,"melt_pending_tick_skipped_in_flight",{});return}inFlight=(async()=>{try{const summary=await runTick(ctx);emitTickSummary(summary,ctx.human,ctx.handle);const failure=runFailure(ctx,summary);if(failure){logErr(ctx,"melt_pending_tick_not_recorded",failure)}else{recordCleanRun(ctx,summary)}}catch(err){if(err instanceof DvmError27&&err.code==="admin_bad_signature"){fatalHolder.err=err;shutdown();return}logErr(ctx,"melt_pending_tick_error",err)}finally{inFlight=null}})();await inFlight}const timer=setInterval(()=>{void tick()},intervalSeconds*1e3);const shutdown=()=>{if(stopping)return;stopping=true;clearInterval(timer);logEvent(ctx,"melt_pending_watch_stopping",{})};process.on("SIGTERM",shutdown);process.on("SIGINT",shutdown);await tick();await new Promise(resolve8=>{const poll=setInterval(()=>{if(stopping&&!inFlight){clearInterval(poll);resolve8()}},100);if(typeof poll.unref==="function")poll.unref()});if(fatalHolder.err)throw fatalHolder.err}function runFailure(ctx,summary){if(summary.batches_failed>0){return new DvmError27("batches_failed",`${summary.batches_failed} batch(es) failed during this sweep.`,"Inspect the per-batch logs above. Transient failures retry on next run.")}if(ctx.drainsOnly)return drainsUnsettled(summary);return holdSustained(summary,ctx.holdAlarmAfterMs)}function recordCleanRun(ctx,summary){recordMeltSuccess(ctx.dvmId,ctx.drainsOnly?"drains_only":"sweep",{handle:ctx.handle,batches_melted:summary.batches_melted,sats_melted:summary.sats_melted,drains_parked:summary.drains_parked})}function drainsUnsettled(summary){if(!summary.drain_state_known){return new DvmError27("drain_state_unknown",`Could not read this DVM's pending credit drains, so ${settledBeforeFetchFailed(summary)} and this run cannot say what it still owes.`,"See `drain_pending_fetch_failed` above for the underlying error, then check the DVM is reachable and still advertises the lock pubkey this mnemonic derives. Every drain counter except `drains_recovered` reads zero for want of an answer, not for want of work.")}const unsettled=summary.drains_failed+summary.drains_manual+summary.drains_queued;if(unsettled===0)return null;return new DvmError27("drains_unsettled",`${unsettled} credit drain(s) went unsettled (failed=${summary.drains_failed}, manual=${summary.drains_manual}, queued=${summary.drains_queued}).`,unsettledDrainHint(summary))}function holdSustained(summary,holdAlarmAfterMs){const heldForMs=drainHoldElapsedMs(summary.drain_state_unknown_since);if(heldForMs===null||heldForMs<holdAlarmAfterMs)return null;return new DvmError27("drain_state_unknown_sustained",`This DVM's pending credit drains have been unreadable since ${summary.drain_state_unknown_since} (${Math.floor(heldForMs/36e5)}h), so the payout melt has been held that whole time and no owed refund has parked.`,"See `drain_pending_fetch_failed` above for the underlying error. Check the DVM is up and its admin endpoint reachable \u2014 nothing melts and no caller gets refunded until it answers. Raise `--hold-alarm-after` if this DVM is expected to be offline this long.")}function settledBeforeFetchFailed(summary){if(summary.drains_recovered===0)return"nothing was settled";return`only the ${summary.drains_recovered} park(s) replayed from the local log were settled`}function unsettledDrainHint(summary){const hints=[];if(summary.drains_failed>0){hints.push("`failed`: inspect the per-drain logs above \u2014 `drain_park_insufficient_batch` means no single accumulator batch covers the refund yet, so fund the DVM or wait for accumulation and it parks on a later run.")}if(summary.drains_manual>0){hints.push("`manual`: x402/tempo refunds have no builder-held send keys \u2014 list them with `dvmctl credit drains-owed <handle>`, send each one yourself, and record it with `dvmctl credit drain-settle <handle> <credit-id> <drain-id> --tx <hash>`.")}if(summary.drains_queued>0){hints.push("`queued`: nothing was attempted for these \u2014 the `drain_jit_*` lines above say why. `drain_jit_unconfigured` means no send wallet is wired to buy the refund's ecash; `drain_jit_no_mint` means no accepted mint would quote; anything naming a payment in flight is being held on purpose so the refund cannot be bought twice.")}return hints.join(" ")}function emitTickSummary(summary,human,slug){if(human){info(`[${summary.tick}] dvm=${summary.dvm_id} seen=${summary.batches_seen} melted=${summary.batches_melted} skipped=${summary.batches_skipped} failed=${summary.batches_failed} sats=${summary.sats_melted} recovered=${summary.recovered} drains=${summary.drains_seen} parked=${summary.drains_parked} drain_failed=${summary.drains_failed} drain_queued=${summary.drains_queued} drain_manual=${summary.drains_manual}${drainStateNote(summary)}${meltNote(summary)}`)}else{json({...summary,dvmId:summary.dvm_id,slug})}}function drainStateNote(summary){if(summary.drain_state_known)return"";if(!summary.drain_state_unknown_since)return" (drain state unknown)";return` (drain state unknown since ${summary.drain_state_unknown_since})`}function meltNote(summary){if(summary.melt_skipped==="drains_only")return" (melt skipped: --drains-only)";return summary.melt_held?" (melt held for drains)":""}function logEvent(ctx,level,fields){console.log(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,...fields}))}function logErr(ctx,level,err,fields={}){console.error(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,error:err instanceof Error?err.message:String(err),...err instanceof DvmError27?{code:err.code,...err.hint?{hint:err.hint}:{},...err.display?{display:err.display}:{}}:err instanceof AdminApiError?{status:err.status,reason:err.body.reason,code:err.body.error}:{},...fields}))}import{DvmError as DvmError28}from"@dvmkit/sdk/internal";init_builder_store();init_defaults();init_output();function meltStatus(handle2,opts){const maxAgeMs=resolveMaxAgeMs(opts.maxAge);const records=readMeltSuccesses();const nowMs=Date.now();const except=validatedExcept(opts.except??[]);const configured=configuredTargets();const all=targets(handle2,configured);refuseAmbiguousExcept(all,except);const watched=all.filter(target=>!except.has(target.handle));const excepted=all.filter(target=>except.has(target.handle)).map(t2=>t2.handle);const configuredIds=new Set(configured.map(target=>target.dvmId));const orphaned=[...records.keys()].filter(dvmId=>!configuredIds.has(dvmId)).sort();const dvms=all.map(target=>{const record2=records.get(target.dvmId)??null;return entryFor(target,record2,except.has(target.handle)?null:maxAgeMs,nowMs)});emit(dvms,{maxAgeMs,excepted,orphaned,human:!!opts.human});if(maxAgeMs!==null&&watched.length===0)nothingToWatch(all.length>0);const stale=dvms.filter(entry=>entry.stale);if(stale.length>0)throw staleError(stale,opts.maxAge??"")}function entryFor(target,record2,maxAgeMs,nowMs){const sweep=record2?.sweep??null;const drainsOnly=record2?.drains_only??null;const sweepAgeMs=meltRunAgeMs(sweep,nowMs);const drainsOnlyAgeMs=meltRunAgeMs(drainsOnly,nowMs);return{handle:target.handle,dvm_id:target.dvmId,last_success_at:sweep?.at??null,age_seconds:sweepAgeMs===null?null:Math.floor(sweepAgeMs/1e3),stale:maxAgeMs!==null&&(sweepAgeMs===null||sweepAgeMs>maxAgeMs),batches_melted:sweep?.batches_melted??null,sats_melted:sweep?.sats_melted??null,drains_parked:sweep?.drains_parked??null,last_drains_only_at:drainsOnly?.at??null,drains_only_age_seconds:drainsOnlyAgeMs===null?null:Math.floor(drainsOnlyAgeMs/1e3)}}var HANDLE_PATTERN=/^[a-z0-9][a-z0-9-]{0,62}$/;function validatedExcept(handles){for(const handle2 of handles){if(HANDLE_PATTERN.test(handle2))continue;throw new DvmError28("invalid_except_handle",`\`--except ${handle2||"<empty>"}\` is not a DVM handle.`,"A handle is lowercase letters, digits and hyphens. This most often means the value was swallowed from the next flag \u2014 `--except --max-age 30h` reads `--max-age` as the handle and leaves no window, which turns the alarm into a plain read that always exits 0. Put a handle after every `--except`, and repeat the flag for each one.")}return new Set(handles)}function refuseAmbiguousExcept(all,except){for(const handle2 of except){const matches2=all.filter(target=>target.handle===handle2);if(matches2.length<=1)continue;throw new DvmError28("ambiguous_dvm_handle",`${matches2.length} builder.json entries claim the handle "${handle2}", so \`--except ${handle2}\` cannot pick between them.`,`Excepting by handle would drop every one of them from the alarm, including a DVM that is still melting. Candidates: ${matches2.map(target=>target.dvmId).join(", ")}. Remove the entry you no longer operate from ${BUILDER_FILE}, then except the handle.`)}}function targets(handle2,configured){if(handle2){const entry=requireBuilderDvmByHandle(handle2);return[{handle:entry.handle,dvmId:entry.canonical_dvm_id}]}return configured}function configuredTargets(){return Object.values(loadBuilderStore().dvms).map(entry=>({handle:entry.handle,dvmId:entry.canonical_dvm_id})).sort((a,b2)=>a.handle.localeCompare(b2.handle))}function nothingToWatch(allExcepted){throw new DvmError28("no_dvms_recorded",allExcepted?"Every DVM on this machine was excluded by `--except`, so `--max-age` has nothing to check.":`No DVMs are recorded in ${BUILDER_FILE}, so \`--max-age\` has nothing to check.`,allExcepted?"Exiting 0 here would be an alarm that can never fire, so this refuses instead. Drop an `--except` flag, or drop `--max-age` if you only wanted to read the record.":"Exiting 0 here would be an alarm that can never fire, so this refuses instead. The usual cause is the command running against the wrong state directory rather than a machine with no DVMs \u2014 a cron or launchd job runs in a minimal environment, so check `HOME` is the account that deployed these DVMs, or set `DVMKIT_CONFIG_DIR` explicitly in the job.")}function resolveMaxAgeMs(input){if(!input)return null;try{return parseDurationMs(input)}catch(err){throw new DvmError28("invalid_duration",err instanceof Error?err.message:String(err),"`--max-age` takes one count and one unit \u2014 e.g. `30h`, `2d`. Set it a little longer than your melt cadence so a single late run is not an alarm.")}}function staleError(stale,maxAge){const named=stale.map(entry=>entry.last_success_at?`${entry.handle} (last swept ${entry.last_success_at})`:`${entry.handle} (never swept on this machine)`).join(", ");return new DvmError28("melt_stale",`${stale.length} DVM(s) have not melted successfully within ${maxAge}: ${named}.`,"Nothing is being swept to your payout destination and no caller refund is parking for these DVMs. Check the scheduled `dvmctl melt-pending` is still running at all \u2014 a cron that stopped firing or a `--watch` daemon that was killed is the usual cause \u2014 then run it by hand and read the error it gives you. A `--drains-only` run does not count here: it settles refunds and sweeps nothing. If one of these DVMs has no Cashu rail and is never meant to melt, exclude it with `--except <handle>`.")}function emit(dvms,ctx){if(!ctx.human){json({dvms,max_age_seconds:ctx.maxAgeMs===null?null:Math.floor(ctx.maxAgeMs/1e3),excepted:ctx.excepted,orphaned:ctx.orphaned,state_file:MELT_SUCCESS_FILE});return}if(dvms.length===0){info("No configured DVMs to report on.")}else{for(const entry of dvms){info(`${entry.handle}: ${entry.last_success_at?`last clean sweep ${entry.last_success_at} (${describeAge(entry.age_seconds)} ago)`:"no clean sweep recorded on this machine"}${entry.stale?" \u2014 STALE":""}${entry.last_drains_only_at?` \xB7 last drains-only ${describeAge(entry.drains_only_age_seconds)} ago`:""}`)}}if(ctx.excepted.length>0)info(`Excepted: ${ctx.excepted.join(", ")}`);if(ctx.orphaned.length>0){info(`Orphaned melt records (not watched): ${ctx.orphaned.join(", ")}`)}info(`Record: ${MELT_SUCCESS_FILE}`)}function describeAge(seconds2){if(seconds2===null)return"unknown";if(seconds2<3600)return`${Math.floor(seconds2/60)}m`;if(seconds2<86400)return`${Math.floor(seconds2/3600)}h`;return`${Math.floor(seconds2/86400)}d`}init_output();async function metrics(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const hours2=opts.hours?Number(opts.hours):24;const result=await getMetrics(platformUrl,token,dvmId,hours2);if(opts.human){if(result.length===0){info(`No metrics for "${slug}" in the last ${hours2} hours.`);return}let total=0;let ok=0;let clientErr=0;let serverErr=0;let latencyMs=0;for(const m of result){total+=m.totalRequests;ok+=m.status2xx;clientErr+=m.status4xx;serverErr+=m.status5xx;latencyMs+=m.totalLatencyMs}const pct=n=>total>0?(n/total*100).toFixed(1):"0.0";const avgLatency=total>0?Math.round(latencyMs/total):0;info(` Last ${hours2} hours:`);info(` Total requests: ${total.toLocaleString()}`);info(` Success (2xx): ${ok.toLocaleString()} (${pct(ok)}%)`);info(` Client err (4xx): ${clientErr.toLocaleString()} (${pct(clientErr)}%)`);info(` Server err (5xx): ${serverErr.toLocaleString()} (${pct(serverErr)}%)`);info(` Avg latency: ${avgLatency}ms`)}else{json({dvmId,slug,hours:hours2,metrics:result})}}init_output();async function orgs(opts){const{token,platformUrl}=requireAuth();const activeOrg=loadActiveOrg();const memberships=await listBuilderOrgs(platformUrl,token);if(opts.human){printHuman(memberships,activeOrg);return}const sharedCount=memberships.filter(membership=>!membership.isPersonal).length;json({activeOrg,memberships:memberships.map(membership=>outputMembership(membership,activeOrg)),display:sharedCount===0?`No shared org memberships. Active context: ${activeOrg??"personal"}.`:`${sharedCount} shared org membership${sharedCount===1?"":"s"}. Active context: ${activeOrg??"personal"}.`,hint:sharedCount===0?"Create or join a shared org in the dashboard. After you join, run `dvmctl switch <handle>`.":"Run `dvmctl switch <handle>` to choose a shared org, or `dvmctl switch --personal` for your personal context."})}function outputMembership(membership,activeOrg){const active=membership.isPersonal?activeOrg===null:membership.handle===activeOrg;if(membership.isPersonal){return{displayName:membership.displayName,role:membership.role,personal:true,active}}return{handle:membership.handle,displayName:membership.displayName,role:membership.role,personal:false,active}}function printHuman(memberships,activeOrg){info(`Active context: ${activeOrg??"personal"}`);const shared=memberships.filter(membership=>!membership.isPersonal);if(shared.length===0)info("No shared org memberships.");if(memberships.length===0)return;const rows=memberships.map(membership=>({handle:membership.isPersonal?"\u2014":membership.handle,name:membership.isPersonal?`${membership.displayName??"Personal context"} (personal)`:membership.displayName??"\u2014",role:membership.role,active:membership.isPersonal?activeOrg===null:membership.handle===activeOrg}));const handleW=Math.max(6,...rows.map(row=>row.handle.length));const nameW=Math.max(4,...rows.map(row=>row.name.length));const roleW=Math.max(4,...rows.map(row=>row.role.length));info("");info(` ${"HANDLE".padEnd(handleW)} ${"NAME".padEnd(nameW)} ${"ROLE".padEnd(roleW)} ACTIVE`);for(const row of rows){info(` ${row.handle.padEnd(handleW)} ${row.name.padEnd(nameW)} ${row.role.padEnd(roleW)} ${row.active?"yes":"no"}`)}}init_output();async function pause(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await pauseDvm(platformUrl,token,dvmId);if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Paused "${slug}". It's no longer serving traffic and its machines are stopping. Data, secrets and database are untouched \u2014 run \`dvmctl resume ${slug}\` to bring it back.`)}else{json({...result,dvmId,slug})}}init_output();async function restart(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await restartMachines(platformUrl,token,dvmId,{machine:opts.machine});if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Restarted ${result.restarted} machine${result.restarted===1?"":"s"}.`)}else{json({...result,dvmId,slug})}}init_output();async function resume(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await resumeDvm(platformUrl,token,dvmId);if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Resumed "${slug}". It's serving traffic again on the same image.`)}else{json({...result,dvmId,slug})}}import{writeFileSync as writeFileSync10}from"fs";init_output();async function revenueCsv(opts){const{token,platformUrl}=requireAuth();const{csv,org}=await getRevenueCsv(platformUrl,token,{from:opts.from,to:opts.to,currency:opts.currency,rail:opts.rail,org:resolveOrgScope(opts)});info(` Org: ${org}`);if(opts.out){writeFileSync10(opts.out,csv,"utf-8");info(` CSV written to ${opts.out}`)}else{process.stdout.write(csv)}}init_output();async function revenueEvents(opts){const{token,platformUrl}=requireAuth();const result=await getRevenueEvents(platformUrl,token,{from:opts.from,to:opts.to,rail:opts.rail,limit:opts.limit!==void 0?Number(opts.limit):void 0,org:resolveOrgScope(opts)});if(opts.human){info(` Org: ${scopeLabel(result.org)}`);if(result.events.length===0){info(` No revenue events found for ${scopeLabel(result.org)}.`);return}info(` ${result.total} total events (showing ${result.events.length})`);info("");const dateW=10;const railW=Math.max(4,...result.events.map(e17=>e17.rail.length));const typeW=Math.max(4,...result.events.map(e17=>e17.source_type.length));const slugW=Math.max(3,...result.events.map(e17=>e17.dvm_slug.length));info(` ${"DATE".padEnd(dateW)} ${"RAIL".padEnd(railW)} ${"TYPE".padEnd(typeW)} ${"DVM".padEnd(slugW)} ${"AMOUNT".padStart(12)} DESCRIPTION`);for(const e17 of result.events){const cur=e17.fiat_currency.toUpperCase();info(` ${e17.date.padEnd(dateW)} ${e17.rail.padEnd(railW)} ${e17.source_type.padEnd(typeW)} ${e17.dvm_slug.padEnd(slugW)} ${formatMinor(e17.net_fiat_minor,cur).padStart(12)} ${e17.line_description}`)}}else{json(result)}}function formatMinor(minor,currency){const major=(minor/100).toFixed(2);const symbols={GBP:"\xA3",EUR:"\u20AC",USD:"$",CHF:"CHF "};const sym=symbols[currency]??`${currency} `;return`${sym}${major}`}init_output();async function revenueSummary(opts){const{token,platformUrl}=requireAuth();const result=await getRevenueSeries(platformUrl,token,{from:opts.from,to:opts.to,currency:opts.currency,org:resolveOrgScope(opts)});if(opts.human){const s=result.summary;const cur=s.fiat_currency.toUpperCase();info(` Revenue summary for ${scopeLabel(result.org)} (${s.period.from} to ${s.period.to})`);info("");info(` Total: ${formatMinor2(s.total_gross_fiat_minor,cur)} (${s.total_count} events)`);info("");if(s.by_source_type_rail.length>0){const typeW=Math.max(4,...s.by_source_type_rail.map(r=>r.source_type.length));const railW=Math.max(4,...s.by_source_type_rail.map(r=>r.rail.length));info(` ${"TYPE".padEnd(typeW)} ${"RAIL".padEnd(railW)} ${"AMOUNT".padStart(12)} COUNT`);for(const row of s.by_source_type_rail){info(` ${row.source_type.padEnd(typeW)} ${row.rail.padEnd(railW)} ${formatMinor2(row.gross_fiat_minor,cur).padStart(12)} ${row.count}`)}}if(s.cross_rate_note){info("");info(` Note: ${s.cross_rate_note}`)}for(const w of result.stale_fx_warnings){info(` ! ${w}`)}}else{json(result)}}function formatMinor2(minor,currency){const major=(minor/100).toFixed(2);const symbols={GBP:"\xA3",EUR:"\u20AC",USD:"$",CHF:"CHF "};const sym=symbols[currency]??`${currency} `;return`${sym}${major}`}init_output();async function rollback(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const{deployId,rolledBackTo}=await rollbackDvm(platformUrl,token,dvmId,{to:opts.to});if(opts.human){info(`Rolling back ${slug} to deploy ${rolledBackTo}...`);info(` Deploy queued: ${deployId}`)}else{progress({event:"queued",deploy_id:deployId,rolled_back_to:rolledBackTo})}const result=await streamDeployLogs(platformUrl,token,deployId,{onLog:line=>{if(opts.human)info(` ${line}`);else progress({event:"log",line})},onPhase:({status:status2,elapsedMs})=>{if(opts.human)info(` ${status2}... (${fmtElapsed(elapsedMs)})`);else progress({event:"phase",phase:status2,elapsed_ms:elapsedMs})}});if(opts.human){info(`
2431
+ ${e19}}`:`{${i2.join(",")}}`;return r=e19,a2}finally{a.delete(c)}}default:return}};return s({"":e17},"")}function Ke(e17){return e17.trim().replace(/[\t\n\f\r ]+/g,"").replace(/={1,2}$/,"")}function qe(e17){return base643.encode(e17)}function Je(e17){return base64urlnopad.encode(e17)}function Ye(e17){return base64url3.encode(e17)}function Xe(e17){try{return base64urlnopad.decode(Ke(e17))}catch(e18){throw new b("Invalid base64url string",{cause:e18})}}function Ze(e17){try{return base64nopad.decode(Ke(e17))}catch(e18){throw new b("Invalid base64 string",{cause:e18})}}function Qe(e17){try{return Xe(e17)}catch(t2){try{return Ze(e17)}catch{throw t2}}}function et(e17){if(typeof e17!="string"||e17.length===0)return false;let t2=Ke(e17);if(t2.length===0)return false;let n=/[-_]/.test(t2)?base64urlnopad:base64nopad;try{return n.encode(n.decode(t2))===t2}catch{return false}}function st(e17){let t2=[];return ct(e17,t2),new Uint8Array(t2)}function ct(e17,t2){if(e17===null)t2.push(246);else if(e17===void 0)t2.push(247);else if(typeof e17=="boolean")t2.push(e17?245:244);else if(typeof e17=="number")mt(e17,t2);else if(typeof e17=="bigint")ut(e17,t2);else if(typeof e17=="string")gt(e17,t2);else if(Array.isArray(e17))_t(e17,t2);else if(e17 instanceof Uint8Array)ht(e17,t2);else if(typeof e17=="object"&&e17&&!Array.isArray(e17))vt(e17,t2);else throw new b("Unsupported type")}function lt(e17,t2){e17<24?t2.push(e17):e17<256?t2.push(24,e17):e17<65536?t2.push(25,e17>>>8&255,e17&255):e17<4294967296?t2.push(26,e17>>>24&255,e17>>>16&255,e17>>>8&255,e17&255):ut(BigInt(e17),t2)}function ut(e17,t2){e17>=0n?dt(0,e17,t2):dt(1,-1n-e17,t2)}function dt(e17,t2,n){let r=e17<<5;if(t2<24n)n.push(r|Number(t2));else if(t2<256n)n.push(r|24,Number(t2));else if(t2<65536n){let e18=Number(t2);n.push(r|25,e18>>>8&255,e18&255)}else if(t2<4294967296n){let e18=Number(t2);n.push(r|26,e18>>>24&255,e18>>>16&255,e18>>>8&255,e18&255)}else if(t2<18446744073709551616n){let e18=Number(t2>>32n),i=Number(t2&4294967295n);n.push(r|27,e18>>>24&255,e18>>>16&255,e18>>>8&255,e18&255,i>>>24&255,i>>>16&255,i>>>8&255,i&255)}else throw new b("BigInt value out of uint64 range")}function ft(e17,t2){let n=-1-e17;n<24?t2.push(32|n):n<256?t2.push(56,n&255):n<65536?t2.push(57,n>>>8&255,n&255):n<4294967296?t2.push(58,n>>>24&255,n>>>16&255,n>>>8&255,n&255):ut(BigInt(e17),t2)}function pt(e17,t2){let n=new DataView(new ArrayBuffer(8));n.setFloat64(0,e17,false),t2.push(251);for(let e18=0;e18<8;e18++)t2.push(n.getUint8(e18))}function mt(e17,t2){Number.isInteger(e17)?e17>=0?lt(e17,t2):ft(e17,t2):pt(e17,t2)}function ht(e17,t2){let n=e17.length;if(n<24)t2.push(64+n);else if(n<256)t2.push(88,n);else if(n<65536)t2.push(89,n>>8&255,n&255);else if(n<4294967296)t2.push(90,n>>>24&255,n>>>16&255,n>>>8&255,n&255);else throw new b("Byte string too long to encode");for(let n2=0;n2<e17.length;n2++)t2.push(e17[n2])}function gt(e17,t2){let n=new TextEncoder().encode(e17),r=n.length;if(r<24)t2.push(96+r);else if(r<256)t2.push(120,r);else if(r<65536)t2.push(121,r>>>8&255,r&255);else if(r<4294967296)t2.push(122,r>>>24&255,r>>>16&255,r>>>8&255,r&255);else throw new b("String too long to encode");for(let e18=0;e18<n.length;e18++)t2.push(n[e18])}function _t(e17,t2){let n=e17.length;if(n<24)t2.push(128|n);else if(n<256)t2.push(152,n);else if(n<65536)t2.push(153,n>>>8&255,n&255);else throw new b("Unsupported array length");for(let n2 of e17)ct(n2,t2)}function vt(e17,t2){let n=Object.keys(e17),r=n.length;if(r>=4294967296)throw new b("Object has too many keys to encode");r<24?t2.push(160|r):r<256?t2.push(184,r):r<65536?t2.push(185,r>>8&255,r&255):t2.push(186,r>>24&255,r>>16&255,r>>8&255,r&255);for(let r2 of n)gt(r2,t2),ct(e17[r2],t2)}var jt=utf8ToBytes3("Secp256k1_HashToCurve_Cashu_");function Mt(r){let i=sha2565(concatBytes(jt,r)),a=0,o=new Uint8Array(4),s=new DataView(o.buffer),c=2**16;for(let r2=0;r2<c;r2++){s.setUint32(0,a,true);let r3=sha2565(concatBytes(i,o));try{return P(bytesToHex2(concatBytes(new Uint8Array([2]),r3)))}catch{a++}}throw new b("No valid point found")}function Nt(t2){let n=t2.map(e17=>e17.toHex(false)).join("");return sha2565(new TextEncoder().encode(n))}function P(e17){return secp256k1.Point.fromHex(e17)}var Lt=e17=>{let t2,n=e18=>bytesToNumberBE(Qe(e18))%BigInt(2**31-1);if(e17.length===12&&et(e17))t2=n(e17);else if(V(e17))t2=wi(e17)%BigInt(2**31-1);else if(et(e17))t2=n(e17);else throw new b("Invalid keyset id: neither hex nor base64");return t2};function Rt(){return secp256k1.utils.randomSecretKey()}function Vt(e17,t2){let n=Mt(e17);if(t2===void 0)t2=secp256k1.Point.Fn.fromBytes(Rt());else if(t2===0n)throw new b("Blinding factor r must be non-zero");let r=secp256k1.Point.BASE.multiply(t2);return{B_:n.add(r),r:t2,secret:e17}}function Ht(e17,t2,n){return e17.subtract(n.multiply(t2))}function Ut(e17,t2,n,r){let i=Ht(e17.C_,t2,r);return{id:e17.id,secret:n,C:i}}var gn=utf8ToBytes3("Cashu_P2BK_v1");var _n=gn.slice();function vn(e17,n,r=true){let i=+!r,a=e17.length+i;if(a>11)throw new b(`Too many pubkeys, ${a} slots provided, maximum allowed is 11 in total`);if(!e17.length)return{blinded:[],Ehex:""};n=n??secp256k1.utils.randomSecretKey();let o=secp256k1.Point.Fn.fromBytes(n),s=secp256k1.getPublicKey(n,true);return{blinded:e17.map((e18,t2)=>{let n2=P(e18),r2=xn(n2,o,t2+i),a2=n2.add(secp256k1.Point.BASE.multiply(r2));if(a2.equals(secp256k1.Point.ZERO))throw new b("Blinded key at infinity");return a2.toHex(true)}),Ehex:bytesToHex2(s)}}function xn(t2,r,i){if(!Number.isInteger(i)||i<0||i>255)throw new b("P2BK: slot index must be an integer in [0, 255]");let a=t2.multiply(r).toBytes(true).slice(1),o=new Uint8Array([i&255]),s=bytesToNumberBE(sha2565(concatBytes(gn,a,o)));if((s===0n||s>=secp256k1.Point.CURVE().n)&&(s=bytesToNumberBE(sha2565(concatBytes(gn,a,o,new Uint8Array([255])))),s===0n||s>=secp256k1.Point.CURVE().n))throw new b("P2BK: tweak derivation failed");return s}var Sn={SIG_INPUTS:"SIG_INPUTS",SIG_ALL:"SIG_ALL"};var Cn=new Set(Object.values(Sn));function Dn(e17){let t2=e17.toLowerCase();if(t2.length===66&&(t2.startsWith("02")||t2.startsWith("03")))return t2;if(t2.length===64)return`02${t2}`;throw new b(`Invalid pubkey, expected 33 byte compressed or 32 byte x only, got length ${t2.length}`)}function L(e17){let t2=new Set,n=[];for(let r of e17){let e18=Dn(r),i=e18.slice(-64);t2.has(i)||(t2.add(i),n.push(e18))}return n}function On(e17){let t2=L(Array.isArray(e17.pubkey)?e17.pubkey:[e17.pubkey]),n=L(e17.refundKeys??[]),r=typeof e17.hashlock=="string"&&e17.hashlock.length>0;if(t2.length===0&&!r)throw new b("P2PK requires at least one pubkey");let i=+!!r+t2.length+n.length;if(i>11)throw new b(`Too many pubkeys, ${i} slots provided, maximum allowed is 11 in total`);e17.sigFlag!==void 0&&Wn(e17.sigFlag);let a=t2.length>0?e17.requiredSignatures??1:e17.requiredSignatures,o=e17.requiredRefundSignatures;return Kn({mainKeyCount:t2.length,refundKeyCount:n.length,nSigs:a,nSigsRefund:o,hasLocktime:e17.locktime!==void 0}),{pubkey:t2.length===1?t2[0]:t2,...e17.locktime===void 0?{}:{locktime:e17.locktime},...n.length>0?{refundKeys:n}:{},...a!==void 0&&a>1?{requiredSignatures:a}:{},...o!==void 0&&o>1?{requiredRefundSignatures:o}:{},...e17.additionalTags?.length?{additionalTags:e17.additionalTags}:{},...e17.blindKeys?{blindKeys:true}:{},...e17.sigFlag===void 0?{}:{sigFlag:e17.sigFlag},...e17.hashlock?{hashlock:e17.hashlock}:{}}}function Wn(e17){if(!Cn.has(e17))throw new b(`Invalid sigflag "${e17}": must be "SIG_INPUTS" or "SIG_ALL"`)}function Gn(e17,t2){if(!Number.isInteger(e17)||e17<1)throw new b(`${t2} must be a positive integer, got ${e17}`);return e17}function Kn(e17){let{mainKeyCount:t2,refundKeyCount:n,nSigs:r,nSigsRefund:i,hasLocktime:a}=e17;if(r!==void 0&&(Gn(r,"requiredSignatures (n_sigs)"),r>t2))throw new b(`requiredSignatures (n_sigs) (${r}) exceeds available pubkeys (${t2})`);if(i!==void 0){if(Gn(i,"requiredRefundSignatures (n_sigs_refund)"),n===0)throw new b("requiredRefundSignatures (n_sigs_refund) requires refund keys");if(i>n)throw new b(`requiredRefundSignatures (n_sigs_refund) (${i}) exceeds available refund keys (${n})`)}if(n>0&&!a)throw new b("refund keys require a locktime")}var $n=utf8ToBytes3("Cashu_DLEQ_R_v1");var tr=(e17,t2,n,r)=>{let i=secp256k1.Point.Fn.fromBytes(e17.s),a=secp256k1.Point.Fn.fromBytes(e17.e),o=secp256k1.Point.BASE.multiply(i),s=r.multiply(a),c=t2.multiply(i),l=n.multiply(a);return equalBytes2(Nt([o.subtract(s),c.subtract(l),r,n]),e17.e)};var nr=(e17,t2,n,r)=>{if(t2.r===void 0)throw new b("verifyDLEQProof_reblind: Undefined blinding factor");let i=Mt(e17),a=n.add(r.multiply(t2.r)),o=secp256k1.Point.BASE.multiply(t2.r);return tr(t2,i.add(o),a,r)};var ir="m/129372'/0'";var ar=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");var or=(function(e17){return e17[e17.SECRET=0]="SECRET",e17[e17.BLINDING_FACTOR=1]="BLINDING_FACTOR",e17})(or||{});var sr=(function(e17){return e17[e17.DEPRECATED_BIP32=0]="DEPRECATED_BIP32",e17[e17.HMAC_SHA256=1]="HMAC_SHA256",e17})(sr||{});function ur(e17,t2,n){return dr(e17,t2)(n)}function dr(e17,t2){switch(pr(e17),fr(t2)){case sr.DEPRECATED_BIP32:{let n=Lt(t2),r=HDKey.fromMasterSeed(e17).derive(`${ir}/${n}'`);return e18=>mr(r,e18)}case sr.HMAC_SHA256:return n=>hr(e17,t2,n)}}function fr(e17){if(e17.length===12&&et(e17))return sr.DEPRECATED_BIP32;let t2=/^[a-fA-F0-9]+$/.test(e17),n=e17.startsWith("01");if(t2&&n&&e17.length%2!=0)throw new b("Invalid hex string: odd length.");if(!t2&&et(e17)||t2&&e17.startsWith("00"))return sr.DEPRECATED_BIP32;if(t2&&n)return sr.HMAC_SHA256;throw new b(`Unrecognized keyset ID version ${e17.slice(0,2)}`)}function pr(e17){if(!(e17 instanceof Uint8Array)||e17.length<16||e17.length>64)throw new b("seed must be a 16 to 64 byte Uint8Array")}function mr(e17,t2){if(!Number.isInteger(t2)||t2<0||t2>=2147483648)throw new b("Counter must be an integer in the range 0 <= counter < 2^31");let n=e17.deriveChild(HARDENED_OFFSET+t2),r=n.deriveChild(0).privateKey,i=n.deriveChild(1).privateKey;if(r===null||i===null)throw new b("Could not derive private key");return{secret:r,blindingFactor:i}}function hr(e17,t2,n){if(!Number.isSafeInteger(n)||n<0)throw new b("Counter must be an integer in the range 0 <= counter <= 2^53 - 1");return{secret:gr(e17,t2,n,or.SECRET),blindingFactor:gr(e17,t2,n,or.BLINDING_FACTOR)}}function gr(t2,i,o,s){let c=concatBytes(utf8ToBytes3("Cashu_KDF_HMAC_SHA256"),hexToBytes2(i),numberToBytesBE(o,8));switch(s){case or.SECRET:c=concatBytes(c,hexToBytes2("00"));break;case or.BLINDING_FACTOR:c=concatBytes(c,hexToBytes2("01"))}let l=hmac2(sha2565,t2,c);if(s===or.BLINDING_FACTOR){let e17=bytesToNumberBE(l),t3=e17>=ar?e17-ar:e17;if(t3===0n)throw new b("Derived invalid blinding scalar r == 0");return numberToBytesBE(t3,32)}return l}var wr=utf8ToBytes3("Cashu_MintQuoteSig_v1");function B(e17,t2,n,r){let i=Ci(e17,"splitAmount.value",true),a=n?.map(e18=>Ci(e18,"splitAmount.split",true));if(a){if(a.length>8192)throw new b(`Cannot split amount: split would exceed ${ae} outputs`);let e18=A.sum(a);if(i.isZero()&&e18.isZero())return a;let n2=a.filter(e19=>!e19.isZero()),r2=A.sum(n2);if(r2.greaterThan(i))throw new b(`Split is greater than total amount: ${r2.toString()} > ${i.toString()}`);if(n2.some(e19=>!Si(e19,t2)))throw new b("Provided amount preferences do not match the amounts of the mint keyset.");if(r2.equals(i))return n2;a=n2,i=i.subtract(r2)}else a=[];let o=xi(t2,"desc");if(o.length===0)throw new b("Cannot split amount, keyset is inactive or contains no keys");for(let e18 of o){if(e18.isZero())continue;let t3=i.divideBy(e18),n2=ae-a.length;if(n2<=0||t3.greaterThan(n2))throw new b(`Cannot split amount: fill would exceed ${ae} outputs`);let r2=t3.toNumber();for(let t4=0;t4<r2;t4++)a.push(e18);if(i=i.subtract(e18.multiplyBy(t3)),i.isZero())break}if(!i.isZero())throw new b(`Unable to split remaining amount: ${i.toString()}`);return r&&(a=a.sort((e18,t3)=>r==="desc"?t3.compareTo(e18):e18.compareTo(t3))),a}function xi(e17,t2){let n=Object.keys(e17).map(e18=>A.from(e18));return n.sort((e18,n2)=>t2==="desc"?n2.compareTo(e18):e18.compareTo(n2)),n}function Si(e17,t2){let n=Ci(e17,"hasCorrespondingKey.amount",true).toString();return Object.prototype.hasOwnProperty.call(t2,n)}function Ci(e17,t2,n=false){let r=A.from(e17);if(!n&&r.isZero())throw new b(`Amount must be positive: ${r.toString()}, op: ${t2}`);return r}function wi(e17){return e17?BigInt(`0x${e17}`):0n}function Ti(e17){return e17.toString(16).padStart(64,"0")}function V(e17){return typeof e17=="string"&&e17.length%2==0&&/^[a-f0-9]+$/i.test(e17)}function Ei(e17){return Array.isArray(e17)?e17.some(e18=>!V(e18.id)):!V(e17.id)}function Oi(e17){return e17.map(e18=>{let t2={...e18};return t2.id=t2.id.slice(0,16),t2})}function ki(e17,t2){let n=q(e17.proofs);if(Ei(n))throw new b("Proofs contain a legacy keyset ID and cannot be encoded. Swap them at the mint first.");return Ai({...e17,proofs:n},t2?.removeDleq)}function Ai(e17,t2){let n=e17.proofs;if(t2&&(n=Ki(n)),Ei(n))throw new b("can not encode to v4 token if proofs contain non-hex keyset id");return n=Oi(n),"cashuB"+Je(st(Mi({...e17,proofs:n})))}function ji(e17){if(e17.r==null)throw new b("Missing blinding factor in included DLEQ proof");return{e:hexToBytes2(e17.e),s:hexToBytes2(e17.s),r:hexToBytes2(e17.r)}}function Mi(e17){let t2=Object.create(null),n=e17.mint;for(let n2=0;n2<e17.proofs.length;n2++){let r=e17.proofs[n2];t2[r.id]?t2[r.id].push(r):t2[r.id]=[r]}let i={m:n,u:e17.unit||"sat",t:Object.keys(t2).map(e18=>({i:hexToBytes2(e18),p:t2[e18].map(e19=>({a:e19.amount.toBigInt(),s:e19.secret,c:hexToBytes2(e19.C),...e19.dleq&&{d:ji(e19.dleq)},...e19.p2pk_e&&{pe:hexToBytes2(e19.p2pk_e)},...e19.witness&&{w:JSON.stringify(e19.witness)}}))}))};return e17.memo&&(i.d=e17.memo),i}function zi(n,i){for(let e17 of Object.keys(n))if(e17.length>20)throw new b("Invalid keyset denomination: exceeds 20 digits");let o=i?.unit??"sat",s=i?.expiry,c=i?.versionByte??1,l=i?.input_fee_ppk;if(i?.isDeprecatedBase64??false)return qe(sha2565(utf8ToBytes3(Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([,e17])=>e17).reduce((e17,t2)=>e17+t2,"")))).slice(0,12);switch(c){case 0:return"00"+bytesToHex2(sha2565(Bi(...Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([,e17])=>hexToBytes2(e17))))).slice(0,14);case 1:{if(!o)throw new b("Cannot compute keyset ID version 01: unit is required.");let r=Object.entries(n).sort(([e17],[t2])=>A.from(e17).compareTo(t2)).map(([e17,t2])=>`${e17}:${t2}`).join(",");return r+=`|unit:${o}`,l&&(r+=`|input_fee_ppk:${l}`),s&&(r+=`|final_expiry:${s}`),"01"+bytesToHex2(sha2565(utf8ToBytes3(r)))}default:throw new b(`Unrecognized keyset ID version: ${c}`)}}function Bi(...e17){let t2=e17.reduce((e18,t3)=>e18+t3.length,0),n=new Uint8Array(t2),r=0;for(let t3 of e17)n.set(t3,r),r+=t3.length;return n}function H(e17){return typeof e17=="object"&&!!e17}function U(e17){return H(e17)&&!Array.isArray(e17)}function W(e17,...t2){for(let n of t2)e17[n]===void 0&&(e17[n]=null)}function G(...e17){return e17.map(e18=>e18.replace(/(^\/+|\/+$)/g,"")).join("/")}function Hi(e17){let t2;try{t2=new URL(e17)}catch(t3){throw new b(`Invalid mint URL: ${e17}`,{cause:t3})}if(t2.protocol!=="http:"&&t2.protocol!=="https:")throw new b(`Invalid mint URL scheme: ${t2.protocol}`);if(t2.username||t2.password)throw new b("Mint URL must not contain credentials");if(t2.search||t2.href.includes("?"))throw new b("Mint URL must not contain query parameters");if(t2.hash||t2.href.includes("#"))throw new b("Mint URL must not contain a fragment");if(/%[0-9a-f]{2}/i.test(t2.pathname))throw new b("Mint URL path must not contain percent-encoded characters");return t2.href.replace(/\/+$/,"")}function q(e17){return e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}function Ki(e17){return e17.map(e18=>{let{dleq:t2,...n}=e18;return n})}function Ji(e17,t2,n){let i=n?.require??true;if(!Si(e17.amount,t2.keys))throw new b(Object.keys(t2.keys).length===0?`No keys loaded for keyset ${t2.id}`:`Undefined key for amount ${e17.amount.toString()} in keyset ${t2.id}`);if(e17?.dleq==null)return!i;if(e17.dleq.r==null)return false;let a=t2.keys[e17.amount.toString()];try{let t3={e:hexToBytes2(e17.dleq.e),s:hexToBytes2(e17.dleq.s),r:wi(e17.dleq.r)};return nr(new TextEncoder().encode(e17.secret),t3,P(e17.C),P(a))}catch{return false}}function J(e17,t2,n){if(e17==null){if(arguments.length>=3)return n;throw new b(`Invalid ${t2}: missing value`)}try{return A.from(e17).toNumber()}catch(e18){throw new b(`Invalid ${t2}: ${e18 instanceof Error?e18.message:String(e18)}`,{cause:e18})}}function ra(e17){return{...e17,input_fee_ppk:J(e17.input_fee_ppk,"keyset.input_fee_ppk",void 0),final_expiry:J(e17.final_expiry,"keyset.final_expiry",void 0)}}function ia(e17){return{...e17,input_fee_ppk:J(e17.input_fee_ppk,"keys.input_fee_ppk",void 0),final_expiry:J(e17.final_expiry,"keys.final_expiry",void 0)}}function aa(e17,t2){let n;try{n=new URL(e17).protocol}catch{throw new b(`OIDCAuth: ${t2} is not a valid URL`)}if(n!=="https:"&&n!=="http:")throw new b(`OIDCAuth: ${t2} must be an http(s) URL`)}var oa=class t{static fromMintInfo(e17,n){let r=e17?.nuts?.["21"];if(!r?.openid_discovery)throw new b("OIDCAuth: mint does not advertise NUT-21 openid_discovery");let i=n?.clientId??r.client_id??"cashu-client";return new t(r.openid_discovery,{...n,clientId:i})}constructor(e17,t2){this.tokenListeners=[],this.inflightRefreshes=new Map,this.discoveryUrl=e17,this.logger=t2?.logger??E,this.clientId=t2?.clientId??"cashu-client",this.scope=t2?.scope??"openid",this.onTokens=t2?.onTokens}setClient(e17){this.clientId=e17}setScope(e17){this.scope=e17??"openid"}addTokenListener(e17){this.tokenListeners=[...this.tokenListeners,e17]}removeTokenListener(e17){this.tokenListeners=this.tokenListeners.filter(t2=>t2!==e17)}async loadConfig(){if(this.config)return this.config;let e17=await fetch(this.discoveryUrl,{method:"GET",headers:{Accept:"application/json"}}),t2=await e17.text(),n,r;try{n=t2?JSON.parse(t2):void 0}catch(e18){r=e18,this.logger.warn("OIDCAuth: bad discovery JSON",{err:e18})}if(!e17.ok||!n)throw new b("OIDCAuth: invalid discovery document",{cause:r});let i=n;if(typeof i.token_endpoint!="string"||i.token_endpoint.length===0)throw new b("OIDCAuth: invalid discovery document, missing token_endpoint");return this.config=i,i}generatePKCE(){let t2=Je(randomBytes3(48));return{verifier:t2,challenge:Je(sha2565(utf8ToBytes3(t2)))}}async buildAuthCodeUrl(e17){let t2=await this.loadConfig(),n=e17.scope??this.scope,r=e17.codeChallengeMethod;r!==void 0&&r!=="S256"&&this.logger.warn(`OIDCAuth: codeChallengeMethod '${typeof r=="string"?r:typeof r}' is not supported by cashu-ts v5, which accepts S256 only.`);let i=new URLSearchParams({response_type:"code",client_id:this.clientId,redirect_uri:e17.redirectUri,scope:n,code_challenge_method:e17.codeChallengeMethod??"S256",code_challenge:e17.codeChallenge});if(e17.state&&i.set("state",e17.state),!t2.authorization_endpoint)throw new b("OIDCAuth: discovery lacks authorization_endpoint");return aa(t2.authorization_endpoint,"authorization_endpoint"),`${t2.authorization_endpoint}?${i.toString()}`}async exchangeAuthCode(e17){let t2=await this.loadConfig(),n=this.toForm({grant_type:"authorization_code",code:e17.code,redirect_uri:e17.redirectUri,client_id:this.clientId,code_verifier:e17.codeVerifier}),r=await this.postFormStrict(t2.token_endpoint,n);return this.handleTokens(r,"signin"),r}async deviceStart(){let e17=(await this.loadConfig()).device_authorization_endpoint;if(!e17)throw new b("OIDCAuth: provider lacks device_authorization_endpoint");let t2=this.toForm({client_id:this.clientId,scope:this.scope}),n=await this.postFormStrict(e17,t2);return aa(n.verification_uri,"verification_uri"),n.verification_uri_complete!==void 0&&aa(n.verification_uri_complete,"verification_uri_complete"),n}async devicePoll(e17,t2=5){let n=await this.loadConfig(),r=Number(t2),i=Number.isFinite(r)?Math.max(1,r):5;for(;;){await this.sleep(i*1e3);let t3=this.toForm({grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:e17,client_id:this.clientId}),r2=await this.postFormLoose(n.token_endpoint,t3);if(r2.access_token)return this.handleTokens(r2,"signin",this.tokenListeners),r2;let a=(r2.error??"").toString();if(a!=="authorization_pending"){if(a==="slow_down"){i=Math.max(i+5,i*2);continue}throw new b(`OIDCAuth: ${r2.error_description||a||"device authorization failed"}`)}}}async startDeviceAuth(e17=5){let t2=await this.deviceStart(),n=Number(t2.interval),r=Number.isFinite(n)&&n>0?n:1,i=Number(e17),a=Math.max(r,Number.isFinite(i)?i:5),o=new AbortController,s,c=new Promise(e18=>{s=e18}),l=new Set,u=()=>{if(o.signal.aborted)throw new b("OIDCAuth: device polling cancelled")},d=async()=>{let e18=await this.loadConfig(),n2=Math.max(1,a);for(;;){u();let r2;await this.sleep(n2*1e3,e19=>{r2=e19,l.add(e19)}),l.delete(r2),u();let i2=this.toForm({grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:t2.device_code,client_id:this.clientId}),a2=await this.postFormLoose(e18.token_endpoint,i2,o.signal);if(u(),a2.access_token)return this.handleTokens(a2,"signin"),a2;let s2=(a2.error??"").toString();if(s2!=="authorization_pending"){if(s2==="slow_down"){n2=Math.max(n2+5,n2*2);continue}throw new b(`OIDCAuth: ${a2.error_description||s2||"device authorization failed"}`)}}},f=()=>{o.abort(),s();for(let e18 of l)e18();l.clear()};return{...t2,poll:()=>Promise.race([d(),c.then(()=>{throw new b("OIDCAuth: device polling cancelled")})]),cancel:f}}async refresh(e17){let t2=this.clientId,n=this.tokenListeners,r=this.inflightRefreshes.get(e17);if(r?.listeners===n&&r.clientId===t2)return r.promise;let i=(async()=>{let r2=await this.loadConfig(),i2=this.toForm({grant_type:"refresh_token",refresh_token:e17,client_id:t2}),a=await this.postFormStrict(r2.token_endpoint,i2);return a.refresh_token||(a.refresh_token=e17),this.handleTokens(a,"refresh",n),a})();this.inflightRefreshes.set(e17,{clientId:t2,listeners:n,promise:i});try{return await i}finally{this.inflightRefreshes.get(e17)?.promise===i&&this.inflightRefreshes.delete(e17)}}async passwordGrant(e17,t2){let n=await this.loadConfig(),r=this.toForm({grant_type:"password",client_id:this.clientId,username:e17,password:t2,scope:this.scope}),i=await this.postFormStrict(n.token_endpoint,r);return this.handleTokens(i,"signin"),i}handleTokens(e17,t2,n=this.tokenListeners){if(!e17.access_token)throw new b(`OIDCAuth: ${e17.error_description||e17.error||"token response missing access_token"}`);queueMicrotask(()=>O(e18=>this.onTokens?.(e18,t2),e17,this.logger,{where:"OIDCAuth.handleTokens"}));let r=new Set(this.tokenListeners);for(let i of n)r.has(i)&&queueMicrotask(()=>O(e18=>i(e18,t2),e17,this.logger,{where:"OIDCAuth.handleTokens.listener"}))}toForm(e17){let t2=e18=>encodeURIComponent(e18).replace(/%20/g,"+");return Object.entries(e17).map(([e18,n])=>`${t2(e18)}=${t2(n)}`).join("&")}async postFormStrict(e17,t2){try{let n=await fetch(e17,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t2,redirect:"error"}),r=await n.text(),i,a;try{i=r?JSON.parse(r):void 0}catch(e18){a=e18,this.logger.warn("OIDCAuth: bad JSON (strict)",{err:e18})}if(!n.ok){let e18=i??{};throw new b(`OIDCAuth: ${e18.error_description||e18.error||`HTTP ${n.status}`}`,{cause:a})}return this.logger.debug("OIDCAuth Response",{status:n.status}),i??{}}catch(e18){throw this.logger.error("OIDCAuth: postFormStrict failed",{err:e18}),e18}}async postFormLoose(e17,t2,n){try{let r=await fetch(e17,{method:"POST",signal:n,headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t2,redirect:"error"}),i=await r.text(),a;try{a=i?JSON.parse(i):void 0}catch(e18){this.logger.warn("OIDCAuth: bad JSON (loose)",{err:e18})}return this.logger.debug("OIDCAuth Response",{status:r.status}),a??{}}catch(e18){return this.logger.error("OIDCAuth: postFormLoose network error",{err:e18}),{error:"network_error",error_description:String(e18)}}}sleep(e17,t2){let n=Math.min(e17,ue);return new Promise(e18=>{let r=setTimeout(e18,n);t2?.(()=>{clearTimeout(r),e18()})})}};var Y=class e10{constructor(t2,n){let r=n??E;this._mintInfo=e10.normalizeInfo(t2,r),this._maxArrayLength=e10.resolveMaxArrayLength(t2.max_array_length,r);let i=this.toEndpoints(this._mintInfo?.nuts?.[22]?.protected_endpoints,r);this._protected22=this.buildIndex(i);let a=this.toEndpoints(this._mintInfo?.nuts?.[21]?.protected_endpoints,r);this._protected21=this.buildIndex(a)}static normalizeInfo(t2,n=E){if(!U(t2)||!U(t2.nuts))throw n.error("MintInfo: malformed info response",{op:"normalizeInfo"}),new b("Invalid response from mint");return{...t2,...Array.isArray(t2.contact)?{contact:e10.capList(t2.contact,"contact",n)}:{},...Array.isArray(t2.urls)?{urls:e10.capList(t2.urls,"urls",n)}:{},nuts:{...t2.nuts,...t2.nuts[4]?{4:e10.normalizeSwapSection(t2.nuts[4],"nuts.4",n)}:{},...t2.nuts[5]?{5:e10.normalizeSwapSection(t2.nuts[5],"nuts.5",n)}:{},...t2.nuts[15]?{15:e10.normalizeNut15(t2.nuts[15],n)}:{},...t2.nuts[17]?{17:e10.normalizeNut17(t2.nuts[17],n)}:{},...t2.nuts[19]?{19:e10.normalizeNut19(t2.nuts[19],n)}:{},...t2.nuts[21]?{21:e10.normalizeNut21(t2.nuts[21],n)}:{},...t2.nuts[22]?{22:e10.normalizeNut22(t2.nuts[22],n)}:{},...t2.nuts[29]?{29:e10.normalizeNut29(t2.nuts[29],n)}:{}}}}static dropSection(e17,t2){t2.warn(`MintInfo: ${e17} is malformed and was omitted`)}static normalizeSwapSection(t2,n,r){if(!U(t2))throw r.error("MintInfo: malformed info response",{op:n}),new b("Invalid response from mint");return{...t2,methods:e10.normalizeSwapMethods(t2.methods,r)}}static normalizeSwapMethods(t2,n){return Array.isArray(t2)?e10.capList(t2,"nuts.4/5.methods",n).filter(e17=>U(e17)).map(e17=>{let t3={...e17};return W(t3,"min_amount","max_amount"),t3}):(n.warn("MintInfo: nuts.4/5.methods is malformed and was omitted"),[])}static resolveMaxArrayLength(e17,t2){if(e17==null)return 500;let n;try{n=J(e17,"max_array_length")}catch{return t2.warn("MintInfo: max_array_length is malformed, defaulting to internal default",{value:e17}),500}if(n<1||n>1e4){let e18=Math.min(Math.max(n,1),oe);return t2.warn("MintInfo: max_array_length is out of range and was clamped",{advertised:n,clampedTo:e18}),e18}return n}static normalizeNut15(t2,n){return U(t2)?Array.isArray(t2.methods)?{...t2,methods:e10.capList(t2.methods,"nuts.15.methods",n)}:t2:e10.dropSection("nuts.15",n)}static normalizeNut17(t2,n){return U(t2)?Array.isArray(t2.supported)?{...t2,supported:e10.capList(t2.supported,"nuts.17.supported",n)}:t2:e10.dropSection("nuts.17",n)}static normalizeNut19(t2,n){return U(t2)?{...t2,...Array.isArray(t2.cached_endpoints)?{cached_endpoints:e10.capList(t2.cached_endpoints,"nuts.19.cached_endpoints",n)}:{},ttl:J(t2.ttl,"nuts.19.ttl",null)}:e10.dropSection("nuts.19",n)}static normalizeNut21(t2,n){return U(t2)?Array.isArray(t2.protected_endpoints)?{...t2,protected_endpoints:e10.capList(t2.protected_endpoints,"nuts.21.protected_endpoints",n)}:t2:e10.dropSection("nuts.21",n)}static normalizeNut22(t2,n){if(!U(t2))return e10.dropSection("nuts.22",n);let r=100;try{r=J(t2.bat_max_mint,"nuts.22.bat_max_mint",100)}catch{n.warn("MintInfo: nuts.22.bat_max_mint is malformed, defaulting to internal cap",{value:t2.bat_max_mint})}return r>100&&(n.warn("MintInfo: nuts.22.bat_max_mint exceeds internal cap and was clamped",{advertised:r,clampedTo:100}),r=100),{...t2,...Array.isArray(t2.protected_endpoints)?{protected_endpoints:e10.capList(t2.protected_endpoints,"nuts.22.protected_endpoints",n)}:{},bat_max_mint:r}}static normalizeNut29(t2,n){if(!U(t2))return e10.dropSection("nuts.29",n);let r=100;try{r=J(t2.max_batch_size,"nuts.29.max_batch_size",100)}catch{n.warn("MintInfo: nuts.29.max_batch_size is malformed, defaulting to internal cap",{value:t2.max_batch_size})}return r>100&&(n.warn("MintInfo: nuts.29.max_batch_size exceeds internal cap and was clamped",{advertised:r,clampedTo:100}),r=100),{methods:Array.isArray(t2.methods)?e10.capList(t2.methods,"nuts.29.methods",n):t2.methods,max_batch_size:r}}isSupported(e17){switch(e17){case 4:case 5:return this.checkMintMelt(e17);case 7:case 8:case 9:case 10:case 11:case 12:case 14:case 20:return this.checkGenericNut(e17);case 17:return this.checkNut17();case 15:return this.checkNut15();case 19:return this.checkNut19();case 29:return this.checkNut29();default:throw new b("nut is not supported by cashu-ts")}}requiresBlindAuthToken(e17,t2){return this.matchesProtected(this._protected22,e17,t2)}requiresClearAuthToken(e17,t2){return this.matchesProtected(this._protected21,e17,t2)}matchesProtected(e17,t2,n){if(!e17)return false;let r=e17.exact[t2],i=e17.prefix[t2];if(!r||!i)return false;if(e17.exact[t2].has(n))return true;for(let r2 of e17.prefix[t2])if(n.startsWith(r2))return true;return false}checkGenericNut(e17){return this._mintInfo.nuts[e17]?.supported?{supported:true}:{supported:false}}checkMintMelt(t2){let n=this._mintInfo.nuts[t2];return n&&n.methods.length>0&&!n.disabled?{disabled:false,params:e10.snapshot(n.methods)}:{disabled:true,params:e10.snapshot(n?.methods??[])}}checkNut17(){return this._mintInfo.nuts[17]&&this._mintInfo.nuts[17].supported.length>0?{supported:true,params:e10.snapshot(this._mintInfo.nuts[17].supported)}:{supported:false}}checkNut15(){return this._mintInfo.nuts[15]&&this._mintInfo.nuts[15].methods.length>0?{supported:true,params:e10.snapshot(this._mintInfo.nuts[15].methods)}:{supported:false}}checkNut19(){let t2=this._mintInfo.nuts?.[19];if(t2&&(t2?.cached_endpoints?.length||0)>0){let n=J(t2.ttl,"nuts.19.ttl",null);return{supported:true,params:{ttl:n===null?Infinity:Math.max(n,0)*1e3,cached_endpoints:e10.snapshot(t2.cached_endpoints)}}}return{supported:false}}checkNut29(){let t2=this._mintInfo.nuts?.[29];return t2?{supported:true,params:e10.snapshot(t2)}:{supported:false}}static capList(e17,t2,n){return e17.length<=1024?e17:(n.warn(`MintInfo: ${t2} exceeds internal cap and was truncated`,{advertised:e17.length,cap:se}),e17.slice(0,se))}toEndpoints(t2,n){if(!Array.isArray(t2))return[];let r=e10.capList(t2,"nuts.21/22.protected_endpoints",n),i=[];for(let e17 of r)if(e17&&typeof e17=="object"){let t3=e17,n2=t3.method,r2=t3.path;if(typeof n2=="string"&&typeof r2=="string"){let e18=n2.toUpperCase();(e18==="GET"||e18==="POST")&&i.push({method:e18,path:r2})}}return i}buildIndex(e17){if(!e17?.length)return;let t2={GET:new Set,POST:new Set},n={GET:[],POST:[]};for(let r of e17){let e18=r.path;if(e18.startsWith("^")&&(e18=e18.slice(1)),e18.endsWith("$")&&(e18=e18.slice(0,-1)),e18.endsWith(".*")){n[r.method].push(e18.slice(0,-2));continue}if(e18.endsWith("*")){n[r.method].push(e18.slice(0,-1));continue}t2[r.method].add(e18)}return n.GET.sort((e18,t3)=>t3.length-e18.length),n.POST.sort((e18,t3)=>t3.length-e18.length),{exact:t2,prefix:n}}static snapshot(t2,n=0){if(typeof t2!="object"||!t2||t2 instanceof A)return t2;if(n>64)throw new b("Mint info nesting exceeds 64 levels");if(Array.isArray(t2))return t2.map(t3=>e10.snapshot(t3,n+1));let r={};for(let[i,a]of Object.entries(t2))r[i]=e10.snapshot(a,n+1);return r}get cache(){return e10.snapshot(this._mintInfo)}get contact(){return e10.snapshot(this._mintInfo.contact)}get description(){return this._mintInfo.description}get description_long(){return this._mintInfo.description_long}get name(){return this._mintInfo.name}get pubkey(){return this._mintInfo.pubkey}get nuts(){return e10.snapshot(this._mintInfo.nuts)}get version(){return this._mintInfo.version}get motd(){return this._mintInfo.motd}get icon_url(){return this._mintInfo.icon_url}get urls(){return e10.snapshot(this._mintInfo.urls)}get time(){return this._mintInfo.time}get tos_url(){return this._mintInfo.tos_url}get maxArrayLength(){return this._maxArrayLength}supportsNut04Description(e17,t2){return this._mintInfo.nuts[4]?.methods.some(n=>n.method===e17&&(t2?n.unit===t2:true)&&(n.options?.description===true||n.description===true))}supportsMintMeltMethod(e17,t2,n){let{disabled:r,params:i}=this.isSupported(e17==="mint"?4:5);return r?false:i.some(e18=>e18.method===t2&&e18.unit===n)}supportsAmountless(e17="bolt11",t2="sat"){let n=this._mintInfo?.nuts?.[5]?.methods??[];return Array.isArray(n)?n.some(n2=>n2.method===e17&&n2.unit===t2&&n2.options?.amountless===true):false}};var sa={UNPAID:"UNPAID",PAID:"PAID",ISSUED:"ISSUED"};var ca={UNPAID:"UNPAID",PENDING:"PENDING",PAID:"PAID"};function ua(e17){return e17.window!==void 0&&e17.window.document!==void 0?true:e17.WorkerGlobalScope!==void 0&&e17.self!==void 0&&e17.self instanceof e17.WorkerGlobalScope}var da=ua(globalThis);function fa(e17,t2,n=da){return Pa({Accept:"application/json, text/plain, */*",...e17?{"Content-Type":"application/json"}:void 0,...n?void 0:{"User-Agent":"Mozilla/5.0"}},t2)}function pa(e17,t2){return e17 instanceof Error?e17.message:t2}function ma(e17,t2,n,r){if(t2?.signal.aborted)return new Ea(`Request timed out after ${n}ms`,{cause:e17});if(r?.aborted)return new X(pa(e17,"Request aborted by caller"))}async function ha(e17,t2){if(t2?.aborted)throw new b("response body read aborted");let n=e17.text();if(!t2)return n;n.catch(()=>void 0);let r,i=new Promise((e18,n2)=>{r=()=>n2(new b("response body read aborted")),t2.aborted?r():t2.addEventListener("abort",r,{once:true})});i.catch(()=>void 0);try{return await Promise.race([n,i])}finally{r&&t2.removeEventListener("abort",r)}}function ga(e17){if(e17===null)return;let t2=e17.trim();if(t2!==""){if(/^\d+$/.test(t2))return Math.max(Number(t2)*1e3,0);if(/[a-zA-Z]/.test(t2)){let e18=new Date(t2).getTime();if(!Number.isNaN(e18))return Math.max(e18-Date.now(),0)}}}var _a3={endpoint:true,requestBody:true,logger:true,ttl:true,cached_endpoints:true,requestTimeout:true,onResponseMeta:true};var va={};var ya=E;function xa(e17){ya=e17}var Sa=9;var Ca=1e3;var wa=100;var Ta=["blind-auth","clear-auth"];var X=class e11 extends S{constructor(t2){super(t2),this.name="CallerAbortError",Object.setPrototypeOf(this,e11.prototype)}};var Ea=class e12 extends S{constructor(t2,n){super(t2,n),this.name="UncancellableReadError",Object.setPrototypeOf(this,e12.prototype)}};function Da(e17){return e17 instanceof X||e17 instanceof Ea?false:e17 instanceof S?true:e17 instanceof x&&e17.status>=500}function Oa(e17,t2){return t2?new Promise((n,r)=>{if(t2.aborted){r(new X("Request aborted by caller"));return}let i=()=>{clearTimeout(a),t2.removeEventListener("abort",i),r(new X("Request aborted by caller"))};t2.addEventListener("abort",i,{once:true});let a=setTimeout(()=>{t2.removeEventListener("abort",i),n()},e17)}):new Promise(t3=>setTimeout(t3,e17))}function ka(e17){try{return new URL(e17).pathname}catch{return e17.startsWith("/")?e17.split(/[?#]/,1)[0]:void 0}}function Aa(e17,t2){return e17===t2?true:e17.endsWith(t2)}async function ja(e17){let{ttl:t2,cached_endpoints:n,endpoint:r}=e17,i=e17.logger??ya;if(Object.keys(e17.headers??{}).some(e18=>e18.toLowerCase()==="blind-auth"))return await Ma(e17);let a=ka(r),o=e17.method?.toUpperCase()??"GET";if(!(a!==void 0&&n?.some(e18=>Aa(a,e18.path)&&e18.method===o)&&t2))return await Ma(e17);let s=0,c=Date.now(),l=async()=>{try{return await Ma(e17)}catch(n2){if(Da(n2)){let r2=Date.now()-c;if(s<Sa&&(!t2||r2<t2)){let a2=Math.min(2**s*wa,Ca),o2=Math.random()*a2;if(r2+o2>t2)throw i.error(`Network Error: request abandoned after ${s} retries`,{e:n2,retries:s}),n2;return s++,i.info(`Network Error: attempting retry ${s} in ${o2}ms`,{e:n2,retries:s,delay:o2}),await Oa(o2,e17.signal),l()}}throw i.error("Request failed and could not be retried",{e:n2}),n2}};return l()}async function Ma(e17){let{endpoint:t2,requestBody:n,headers:r,requestTimeout:i,onResponseMeta:a,cached_endpoints:o,ttl:s,logger:c,...l}=e17,u=c??ya,d=n?j.stringify(n):void 0,f=fa(d,r),p=Object.keys(f).some(e18=>Ta.includes(e18.toLowerCase())),m=e17.signal??void 0;if(m?.aborted)throw new X("Request aborted by caller");let h=i===void 0?void 0:new AbortController,g=m,_,v;if(h)if(_=setTimeout(()=>h.abort(),i),!m)g=h.signal;else{let e18=new AbortController,t3=()=>e18.abort();m.addEventListener("abort",t3,{once:true}),h.signal.addEventListener("abort",t3,{once:true}),v=()=>{m.removeEventListener("abort",t3),h.signal.removeEventListener("abort",t3)},g=e18.signal}try{let e18;try{e18=await fetch(t2,{body:d,headers:f,cache:"no-store",credentials:"omit",referrer:"",referrerPolicy:"no-referrer",...d===void 0?void 0:{redirect:"error"},...l,...p?{redirect:"error"}:void 0,signal:g})}catch(e19){let t3=!!h?.signal.aborted,n3=!!m?.aborted;throw t3?new S(`Request timed out after ${i}ms`,{cause:e19}):n3?new X(pa(e19,"Request aborted by caller")):e19 instanceof Error&&(e19.name==="AbortError"||e19.name==="TimeoutError")?new S(e19.message,{cause:e19}):new S(pa(e19,"Network request failed"),{cause:e19})}let n2=ga(e18.headers.get("Retry-After"));if(a&&e18.headers&&O(a,{endpoint:t2,status:e18.status,retryAfterMs:n2,rateLimit:e18.headers.get("RateLimit")??void 0,rateLimitPolicy:e18.headers.get("RateLimit-Policy")??void 0,headers:e18.headers},u,{op:"request.onResponseMeta",status:e18.status,endpoint:t2}),!e18.ok){if(e18.status===429){let t4=e18.body;throw t4&&typeof t4.cancel=="function"&&t4.cancel().catch(()=>void 0),new T("429 Too Many Requests",n2)}let t3,r3;try{t3=Na(await ha(e18,g))}catch(e19){let n3=ma(e19,h,i,m);if(n3)throw n3;r3=e19,t3={error:"bad response"}}if(e18.status===400&&"code"in t3&&typeof t3.code=="number"&&"detail"in t3&&typeof t3.detail=="string")throw new te(t3.code,t3.detail);let a2="HTTP request failed";throw"error"in t3&&typeof t3.error=="string"?a2=t3.error:"detail"in t3&&typeof t3.detail=="string"&&(a2=t3.detail),new x(a2,e18.status,{cause:r3})}let r2;try{r2=await ha(e18,g)}catch(t3){throw ma(t3,h,i,m)||(u.error("Failed to read HTTP response",{err:t3}),new x("bad response",e18.status,{cause:t3}))}try{if(!r2)throw new b("Empty response body");return j.parse(r2,void 0,{strict:true})}catch(t3){throw u.error("Failed to parse HTTP response",{err:t3}),new x("bad response",e18.status,{cause:t3})}}finally{clearTimeout(_),v?.()}}function Na(e17){if(!e17)return{detail:"bad response"};let t2;try{t2=j.parse(e17,void 0,{strict:true})}catch{return{detail:e17}}return typeof t2=="object"&&t2&&("detail"in t2||"code"in t2||"error"in t2)?t2:{detail:t2}}function Pa(e17,t2){let n=new Map;for(let r of[e17,t2])for(let[e18,t3]of Object.entries(r??{}))n.set(e18.toLowerCase(),[e18,t3]);return Object.fromEntries(n.values())}async function Fa(e17){let t2=e17.onResponseMeta,n=va.onResponseMeta,r={...e17,...va};for(let t3 of Object.keys(_a3))e17[t3]!==void 0&&(r[t3]=e17[t3]);if(r.headers=Pa(va.headers,e17.headers),t2&&n&&t2!==n){let i=r.logger??ya;r.onResponseMeta=r2=>{O(t2,r2,i,{op:"request.onResponseMeta",scope:"per-request",endpoint:e17.endpoint}),O(n,r2,i,{op:"request.onResponseMeta",scope:"global",endpoint:e17.endpoint})}}return await ja(r)}var Ia;typeof WebSocket<"u"&&(Ia=WebSocket);function Ra(){if(Ia===void 0)throw new b("WebSocket implementation not initialized");return Ia}var za=1006;var Ba=class{constructor(e17){this.next=null,this.value=e17}};var Va=class{constructor(){this._first=null,this._last=null,this.size=0}enqueue(e17){let t2=new Ba(e17);return this._last?this._last.next=t2:this._first=t2,this._last=t2,this.size++,true}dequeue(){if(!this._first)return null;let e17=this._first;return this._first=e17.next,this._first||(this._last=null),this.size--,e17.value}};var Ha=class{constructor(e17,t2){this.subListeners={},this.rpcListeners={},this.rpcId=0,this.onCloseCallbacks=[],this._WS=Ra(),this.url=new URL(e17),this.messageQueue=new Va,this._logger=t2??E}setLogger(e17){this._logger=e17}connect(e17=1e4){return this.connectionPromise||(this.connectionPromise=new Promise((t2,n)=>{let r=false,i=false,a=null,o=e18=>{i||(i=true,a&&clearTimeout(a),this.abandonConnect=void 0,e18())};this.abandonConnect=e18=>o(()=>n(e18));let s=()=>{if(this.ws){try{this.ws.onopen=null,this.ws.onerror=null,this.ws.onmessage=null,this.ws.onclose=null}catch{}try{this.ws.close()}catch{}this.ws=void 0,this.stopMessageHandling()}},c=e18=>{this.connectionPromise=void 0,s();let t3=e18 instanceof Error?e18:new b(String(e18),{cause:e18});this.failPendingRpc(t3),o(()=>n(t3))},l;try{l=new this._WS(this.url.toString()),this.ws=l}catch(e18){c(e18);return}let u=()=>this.ws===l;a=setTimeout(()=>{c(new b(`WebSocket connect timeout after ${e17}ms`))},e17),l.onopen=()=>{u()&&(r=true,o(t2))},l.onerror=e18=>{if(u()){if(!r){c(new b("Failed to open WebSocket"));return}this._logger.error("WebSocket error after open",{ev:e18})}},l.onmessage=e18=>{if(u()){if(this.messageQueue.size>=1e4){this._logger.error("WebSocket message queue exceeded its bound, closing connection",{size:this.messageQueue.size});let e19=new b("WebSocket message queue exceeded its bound");c(e19),this.onCloseCallbacks.forEach(t3=>t3({code:za,reason:e19.message,wasClean:false}));return}this.messageQueue.enqueue(e18.data),this.handlingInterval||(this.handlingInterval=setInterval(this.handleNextMessage.bind(this),0))}},l.onclose=e18=>{if(this.ws&&this.ws!==l)return;if(this.connectionPromise=void 0,!r){let t4=e18?.reason?`, ${e18.reason}`:"";c(new b(`WebSocket closed before open (code ${e18?.code??0}${t4})`));return}this.stopMessageHandling();let t3=e18?.reason?`, ${e18.reason}`:"",n2=e18?.code??0;!(typeof e18.wasClean!="boolean"||e18.wasClean)||n2!==1e3&&n2!==1001?this.failPendingRpc(new b(`WebSocket closed (code ${n2}${t3})`)):this.rpcListeners={},this.onCloseCallbacks.forEach(t4=>t4(e18))}})),this.connectionPromise}sendRequest(e17,t2){if(this.ws?.readyState!==this._WS.OPEN){if(e17==="unsubscribe")return;throw this._logger.error("Attempted sendRequest, but socket was not open"),new b("Socket not open")}let n=this.rpcId;this.rpcId++,this.sendRpcMessage(e17,t2,n)}addSubListener(e17,t2){(this.subListeners[e17]=this.subListeners[e17]||[]).push(t2)}stopMessageHandling(){for(this.handlingInterval&&(clearInterval(this.handlingInterval),this.handlingInterval=void 0);this.messageQueue.size>0;)this.messageQueue.dequeue();this.subListeners={}}failPendingRpc(e17){let t2=this.rpcListeners;this.rpcListeners={};for(let n of Object.keys(t2))try{t2[n].errorCallback(e17)}catch{}}sendRpcMessage(e17,t2,n){if(this.ws?.readyState!==this._WS.OPEN)throw new b("Socket not open");let r=JSON.stringify({jsonrpc:"2.0",method:e17,params:t2,id:n});try{this.ws.send(r)}catch(e18){this._logger.error("WebSocket send failed",{e:e18}),this.connectionPromise=void 0;try{this.ws.close()}catch{}this.ws=void 0,this.stopMessageHandling();let t3=e18 instanceof Error?e18:new b(String(e18),{cause:e18});throw this.failPendingRpc(t3),t3}}addRpcListener(e17,t2,n){this.rpcListeners[n]={callback:e17,errorCallback:t2}}removeRpcListener(e17){delete this.rpcListeners[e17]}removeListener(e17,t2){if(this.subListeners[e17]){if(this.subListeners[e17].length===1){delete this.subListeners[e17];return}this.subListeners[e17]=this.subListeners[e17].filter(e18=>e18!==t2)}}async ensureConnection(e17){this.ws?.readyState!==this._WS.OPEN&&await this.connect(e17)}handleNextMessage(){for(;this.messageQueue.size>0;){let e17=this.messageQueue.dequeue();try{let t2=j.parse(e17,void 0,{strict:true});if("result"in t2&&t2.id!=null)this.rpcListeners[t2.id]&&(this.rpcListeners[t2.id].callback(),this.removeRpcListener(t2.id));else if("error"in t2&&t2.id!=null)this.rpcListeners[t2.id]&&(this.rpcListeners[t2.id].errorCallback(new b(t2.error.message)),this.removeRpcListener(t2.id));else if("method"in t2&&!("id"in t2)){let e18=t2.params?.subId;if(!e18)continue;if(this.subListeners[e18]?.length>0){let n=t2;this.subListeners[e18].forEach(e19=>{try{let t3=e19(n.params?.payload);t3&&typeof t3.then=="function"&&Promise.resolve(t3).catch(e20=>{this._logger.error("Subscription handler threw",{e:e20})})}catch(e20){this._logger.error("Subscription handler threw",{e:e20})}})}}}catch(e18){this._logger.error("Error doing handleNextMessage",{e:e18})}}this.handlingInterval&&(clearInterval(this.handlingInterval),this.handlingInterval=void 0)}createSubscription(e17,t2,n){if(this.ws?.readyState!==this._WS.OPEN)throw this._logger.error("Attempted createSubscription, but socket was not open"),new b("Socket is not open");let r=(Math.random()+1).toString(36).substring(7),i=this.rpcId;this.addRpcListener(()=>{this.addSubListener(r,t2)},n,i);try{this.sendRequest("subscribe",{...e17,subId:r})}catch(e18){throw this.removeRpcListener(i),e18}return r}cancelSubscription(e17,t2,n){if(this.removeListener(e17,t2),this.ws?.readyState!==this._WS.OPEN){this._logger.info("Socket not open, removed listener locally {subId}",{subId:e17});return}let r=this.rpcId;this.rpcId++,this.addRpcListener(()=>{this._logger.info("Unsubscribed {subId}",{subId:e17})},n||(e18=>this._logger.error("Unsubscribe failed",{e:e18})),r);try{this.sendRpcMessage("unsubscribe",{subId:e17},r)}catch(e18){throw this.removeRpcListener(r),e18}}get activeSubscriptions(){return Object.keys(this.subListeners)}close(){let e17=new b("WebSocket closed");if(this.abandonConnect?.(e17),this.ws){try{this.ws.close()}catch{}this.ws=void 0}this.connectionPromise=void 0,this.failPendingRpc(e17),this.stopMessageHandling()}onClose(e17){this.onCloseCallbacks.push(e17)}};function Ua(e17){return Array.isArray(e17)?"array":typeof e17}var Wa=class{constructor(e17,t2){this._lastResponseMetadata=void 0,this._captureResponseMetadata=e18=>{this._lastResponseMetadata=e18},this._mintUrl=Hi(e17),this._request=t2?.customRequest??Fa,this._authProvider=t2?.authProvider,this._logger=t2?.logger??E,xa(this._logger)}get mintUrl(){return this._mintUrl}get lastResponseMetadata(){return this._lastResponseMetadata}async oidcAuth(e17){let t2=(await this.getLazyMintInfo()).nuts[21];if(!t2?.openid_discovery)throw new b("Mint: no NUT-21 openid_discovery");return new oa(t2.openid_discovery,{...e17,clientId:e17?.clientId??t2.client_id??"cashu-client"})}async getInfo(e17){let t2=await(e17??this._request)({endpoint:G(this._mintUrl,"/v1/info"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});return Y.normalizeInfo(t2,this._logger)}async getLazyMintInfo(e17){if(this._mintInfo)return this._mintInfo;let t2=await this.getInfo(e17);return this._mintInfo=new Y(t2,this._logger),this._mintInfo}setMintInfo(e17){this._mintInfo=e17 instanceof Y?e17:new Y(e17,this._logger)}async swap(e17,t2){D(!Array.isArray(e17?.inputs),"swap: inputs must be an array of proofs",this._logger);let n=await this.requestWithAuth("POST","/v1/swap",{requestBody:{...e17,inputs:this.stripWalletFields(e17.inputs)}},t2);if(!H(n)||!Array.isArray(n?.signatures))throw this._logger.error("Invalid response from mint...",{data:n,op:"swap"}),new b("Invalid response from mint");return this.assertSignatureCount(n.signatures,e17.outputs,"swap"),n.signatures=this.normalizeSignatureAmounts(n.signatures),n}async createMintQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/quote/${e17}`,{requestBody:t2},n?.customRequest);return this.normalizeMintQuoteResponse(e17,r,n?.normalize)}async createMintQuoteBolt11(e17,t2){return this.createMintQuote("bolt11",{...e17,amount:A.from(e17.amount).toBigInt()},{customRequest:t2})}async createMintQuoteBolt12(e17,t2){let n={...e17};return e17.amount!==void 0&&(n.amount=A.from(e17.amount).toBigInt()),this.createMintQuote("bolt12",n,{customRequest:t2})}async createMintQuoteOnchain(e17,t2){return this.createMintQuote("onchain",e17,{customRequest:t2})}async checkMintQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("GET",`/v1/mint/quote/${e17}/${t2}`,{},n?.customRequest),i=this.normalizeMintQuoteResponse(e17,r,n?.normalize);if(i.quote!==t2)throw this._logger.error("Invalid response from mint...",{op:`checkMintQuote.${e17}`}),new b("Mint quote response is for a different quote");return i}async checkMintQuoteBolt11(e17,t2){return this.checkMintQuote("bolt11",e17,{customRequest:t2})}async checkMintQuoteBolt12(e17,t2){return this.checkMintQuote("bolt12",e17,{customRequest:t2})}async checkMintQuoteOnchain(e17,t2){return this.checkMintQuote("onchain",e17,{customRequest:t2})}async checkMintQuoteBatch(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint quote method: ${e17}`,this._logger),D(t2.length===0,"checkMintQuoteBatch: no quote ids provided",this._logger),D(new Set(t2).size!==t2.length,"checkMintQuoteBatch: duplicate quote ids provided",this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/quote/${e17}/check`,{requestBody:{quotes:t2}},n?.customRequest);if(!Array.isArray(r)||r.length!==t2.length)throw this._logger.error("Invalid response from mint...",{expectedCount:t2.length,actualCount:Array.isArray(r)?r.length:void 0,op:`checkMintQuoteBatch.${e17}`}),new b("Invalid response from mint");return r.map((r2,i)=>{if(r2.quote!==t2[i])throw this._logger.error("Invalid response from mint...",{index:i,op:`checkMintQuoteBatch.${e17}`}),new b("Invalid response from mint");return this.normalizeMintQuoteResponse(e17,r2,n?.normalize)})}async checkMintQuoteBatchBolt11(e17,t2){return this.checkMintQuoteBatch("bolt11",e17,{customRequest:t2})}async checkMintQuoteBatchBolt12(e17,t2){return this.checkMintQuoteBatch("bolt12",e17,{customRequest:t2})}async mintBolt11(e17,t2){return this.mint("bolt11",e17,{customRequest:t2})}async mintBolt12(e17,t2){return this.mint("bolt12",e17,{customRequest:t2})}async mintOnchain(e17,t2){return this.mint("onchain",e17,{customRequest:t2})}async mint(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/mint/${e17}`,{requestBody:t2},n?.customRequest);if(!H(r)||!Array.isArray(r?.signatures))throw this._logger.error("Invalid response from mint...",{data:r,op:`mint.${e17}`}),new b("Invalid response from mint");return this.assertSignatureCount(r.signatures,t2.outputs,`mint.${e17}`),r.signatures=this.normalizeSignatureAmounts(r.signatures),n?.normalize?n.normalize(r):r}async mintBatchBolt11(e17,t2){return this.mintBatch("bolt11",e17,{customRequest:t2})}async mintBatchBolt12(e17,t2){return this.mintBatch("bolt12",e17,{customRequest:t2})}async mintBatch(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid mint method: ${e17}`,this._logger);let r={...t2,quote_amounts:t2.quote_amounts.map(e18=>A.from(e18).toBigInt())},i=await this.requestWithAuth("POST",`/v1/mint/${e17}/batch`,{requestBody:r},n?.customRequest);if(!H(i)||!Array.isArray(i?.signatures))throw this._logger.error("Invalid response from mint...",{data:i,op:`mintBatch.${e17}`}),new b("Invalid response from mint");return this.assertSignatureCount(i.signatures,t2.outputs,`mintBatch.${e17}`),i.signatures=this.normalizeSignatureAmounts(i.signatures),n?.normalize?n.normalize(i):i}async createMeltQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("POST",`/v1/melt/quote/${e17}`,{requestBody:t2},n?.customRequest);return this.normalizeMeltQuoteResponse(e17,r,n?.normalize)}async createMeltQuoteBolt11(e17,t2){let n=await this.createMeltQuote("bolt11",this.normalizeMeltQuoteRequestOptions(e17),{customRequest:t2});return D(n.request!==""&&n.request.toLowerCase()!==e17.request.toLowerCase(),"Melt quote is for a different payment request",this._logger),n}async createMeltQuoteBolt12(e17,t2){return this.createMeltQuote("bolt12",this.normalizeMeltQuoteRequestOptions(e17),{customRequest:t2})}async createMeltQuoteOnchain(e17,t2){return this.createMeltQuote("onchain",{...e17,amount:A.from(e17.amount).toBigInt()},{customRequest:t2})}async checkMeltQuote(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt quote method: ${e17}`,this._logger);let r=await this.requestWithAuth("GET",`/v1/melt/quote/${e17}/${t2}`,{},n?.customRequest),i=this.normalizeMeltQuoteResponse(e17,r,n?.normalize);if(i.quote!==t2)throw this._logger.error("Invalid response from mint...",{data:i,op:`checkMeltQuote.${e17}`}),new b("Melt quote response is for a different quote");return i}async checkMeltQuoteBolt11(e17,t2){return this.checkMeltQuote("bolt11",e17,{customRequest:t2})}async checkMeltQuoteBolt12(e17,t2){return this.checkMeltQuote("bolt12",e17,{customRequest:t2})}async checkMeltQuoteOnchain(e17,t2){return this.checkMeltQuote("onchain",e17,{customRequest:t2})}async melt(e17,t2,n){D(!this.isValidMethodString(e17),`Invalid melt method: ${e17}`,this._logger),D(!Array.isArray(t2?.inputs),"melt: inputs must be an array of proofs",this._logger);let r=await this.requestWithAuth("POST",`/v1/melt/${e17}`,{requestBody:{...t2,inputs:this.stripWalletFields(t2.inputs)}},n?.customRequest),i=this.normalizeMeltQuoteResponse(e17,r,n?.normalize,true);return i.quote!==t2.quote&&(this._logger.warn("Melt response reports a different quote id",{op:`melt.${e17}`,expected:t2.quote,received:i.quote}),i.quote=t2.quote),i}async meltBolt11(e17,t2){return this.melt("bolt11",e17,t2)}async meltBolt12(e17,t2){return this.melt("bolt12",e17,t2)}async meltOnchain(e17,t2){return this.melt("onchain",e17,t2)}async check(e17,t2){let n=await this.requestWithAuth("POST","/v1/checkstate",{requestBody:e17},t2);if(!H(n)||!Array.isArray(n?.states)||n.states.length>e17.Ys.length)throw this._logger.error("Invalid response from mint...",{data:n,op:"check"}),new b("Invalid response from mint");this.assertRecordEntries(n.states,"check");for(let e18 of n.states)W(e18,"witness");return n}async getKeys(e17,t2,n){let r=t2||this._mintUrl;e17&&(e17=e17.replace(/\//g,"_").replace(/\+/g,"-"));let i=await(n??this._request)({endpoint:e17?G(r,"/v1/keys",e17):G(r,"/v1/keys"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(i)||!Array.isArray(i.keysets))throw this._logger.error("Invalid response from mint...",{data:i,op:"getKeys"}),new b("Invalid response from mint");return this.assertKeysetList(i.keysets,"getKeys"),{...i,keysets:i.keysets.map(e18=>ia(e18))}}async getKeySets(e17){let t2=await(e17??this._request)({endpoint:G(this._mintUrl,"/v1/keysets"),onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(t2)||!Array.isArray(t2.keysets))throw this._logger.error("Invalid response from mint...",{data:t2,op:"getKeySets"}),new b("Invalid response from mint");return this.assertKeysetList(t2.keysets,"getKeySets"),{...t2,keysets:t2.keysets.map(e18=>ra(e18))}}async restore(e17,t2){let n=await(t2??this._request)({endpoint:G(this._mintUrl,"/v1/restore"),method:"POST",requestBody:e17,onResponseMeta:this._captureResponseMetadata,logger:this._logger});if(!H(n)||!Array.isArray(n?.outputs)||!Array.isArray(n?.signatures)||n.outputs.length!==n.signatures.length||n.outputs.length>e17.outputs.length)throw this._logger.error("Invalid response from mint...",{data:n,op:"restore"}),new b("Invalid response from mint");return n.outputs=this.normalizeMessageAmounts(n.outputs),n.signatures=this.normalizeSignatureAmounts(n.signatures),n}async connectWebSocket(){try{let e17=new URL(this._mintUrl),t2="v1/ws";e17.pathname.endsWith("/")?e17.pathname+=t2:e17.pathname+="/"+t2,e17.protocol=e17.protocol==="https:"?"wss:":"ws:";let n=e17.toString();this.ws||(this.ws=new Ha(n,this._logger)),await this.ws.ensureConnection()}catch(e17){this._logger.error("Failed to connect to WebSocket...",{e:e17});try{this.ws?.close()}catch{}throw this.ws=void 0,new b("Failed to connect to WebSocket...",{cause:e17})}}disconnectWebSocket(){this.ws&&this.ws.close()}get webSocketConnection(){return this.ws}async handleClearAuth(e17,t2,n){if(this._authProvider&&(n??await this.getLazyMintInfo()).requiresClearAuthToken(e17,t2))return this._authProvider.ensureCAT?this._authProvider.ensureCAT():this._authProvider.getCAT()}async handleBlindAuth(e17,t2,n){if(this._authProvider&&(n??await this.getLazyMintInfo()).requiresBlindAuthToken(e17,t2))return await this._authProvider.getBlindAuthToken({method:e17,path:t2})}async requestWithAuth(e17,t2,n={},r){let i=r??this._request,a=this._mintInfo;this._authProvider&&(a=await this.getLazyMintInfo(r));let o=await this.handleBlindAuth(e17,t2,a),s=await this.handleClearAuth(e17,t2,a),c={...n.headers??{},...o?{"Blind-auth":o}:{},...s?{"Clear-auth":s}:{}},l=a?.isSupported(19);return i({...n,endpoint:G(this._mintUrl,t2),method:e17,headers:c,...o||s||n.requestBody?{redirect:"error"}:{},...l?.supported&&l.params?l.params:{},onResponseMeta:this._captureResponseMetadata,logger:n.logger??this._logger})}normalizeMeltQuoteRequestOptions(e17){if(!e17.options)return{...e17};let t2={...e17.options};return e17.options.amountless&&(t2.amountless={amount_msat:A.from(e17.options.amountless.amount_msat).toBigInt()}),"mpp"in e17.options&&e17.options.mpp&&(t2.mpp={amount:A.from(e17.options.mpp.amount).toBigInt()}),{...e17,options:t2}}isValidMethodString(e17){return!!(typeof e17=="string"&&/^[a-z0-9_-]+$/.test(e17))}normalizeSignatureAmounts(e17){return this.assertRecordEntries(e17,"signatures"),e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}normalizeMessageAmounts(e17){return this.assertRecordEntries(e17,"outputs"),e17.map(e18=>({...e18,amount:A.from(e18.amount)}))}stripWalletFields(e17){return e17.map(e18=>{let{dleq:t2,p2pk_e:n,...r}=e18;return r})}assertRecordEntries(e17,t2){if(!e17.every(e18=>U(e18)))throw this._logger.error("Invalid response from mint...",{entries:e17.length,op:t2}),new b("Invalid response from mint")}assertSignatureCount(e17,t2,n){if(!(e17.length<=t2.length))throw this._logger.error("Invalid response from mint...",{signatures:e17.length,outputs:t2.length,op:n}),new b(`Invalid response from mint: ${e17.length} signatures, expected ${t2.length}. The operation may already have been applied; if the wallet is seeded, try restoring (NUT-09) to recover.`)}assertKeysetList(e17,t2){if(e17.length>1e4)throw this._logger.error("Invalid response from mint...",{keysets:e17.length,op:t2}),new b("Invalid response from mint");this.assertRecordEntries(e17,t2)}normalizeMintQuoteResponse(e17,t2,n){let r=`${e17} mint quote`;if(!U(t2))throw this._logger.error("Invalid response from mint...",{type:Ua(t2),op:r}),new b("Invalid response from mint");let i={...t2};return this.normalizeMintBaseFields(i),e17==="bolt11"?this.normalizeMintQuoteBolt11Fields(i):e17==="bolt12"?this.normalizeMintQuoteBolt12Fields(i):e17==="onchain"&&this.normalizeMintQuoteOnchainFields(i),n?n(i):i}normalizeMintBaseFields(e17){e17.amount_paid!=null&&e17.amount_issued!=null&&(e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)),e17.updated_at=J(e17.updated_at,"mintQuote.updated_at",null)}deriveMintQuoteState(e17,t2){return e17.isZero()&&t2.isZero()?sa.UNPAID:e17.greaterThan(t2)?sa.PAID:sa.ISSUED}normalizeMintQuoteBolt11Fields(e17){e17.amount=A.from(e17.amount),e17.expiry=J(e17.expiry,"mintQuoteBolt11.expiry",null),(typeof e17.state!="string"||!Object.values(sa).includes(e17.state))&&e17.amount_paid instanceof A&&e17.amount_issued instanceof A&&(e17.state=this.deriveMintQuoteState(e17.amount_paid,e17.amount_issued))}normalizeMintQuoteBolt12Fields(e17){W(e17,"amount"),e17.amount=e17.amount===null?null:A.from(e17.amount),e17.expiry=J(e17.expiry,"mintQuoteBolt12.expiry",null),e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)}normalizeMintQuoteOnchainFields(e17){e17.expiry=J(e17.expiry,"mintQuoteOnchain.expiry",null),e17.amount_paid=A.from(e17.amount_paid),e17.amount_issued=A.from(e17.amount_issued)}normalizeMeltQuoteResponse(e17,t2,n,r=false){let i=`${e17} melt quote`;if(!U(t2))throw this._logger.error("Invalid response from mint...",{type:Ua(t2),op:i}),new b("Invalid response from mint");let a={...t2};return r&&((a.request==null||a.request==="")&&delete a.request,a.fee_reserve??delete a.fee_reserve),this.normalizeMeltBaseFields(a,i),e17==="bolt11"||e17==="bolt12"?this.normalizeMeltBoltFields(a,i,r):e17==="onchain"&&this.normalizeMeltOnchainFields(a,r),n?n(a):a}normalizeMeltBaseFields(e17,t2){if(e17.amount=A.from(e17.amount),e17.expiry=J(e17.expiry,"meltQuote.expiry",void 0),e17.change){if(!Array.isArray(e17.change)||e17.change.length>1024)throw this._logger.error("Invalid response from mint...",{type:Ua(e17.change),op:t2}),new b("Invalid response from mint");e17.change=this.normalizeSignatureAmounts(e17.change)}if(!H(e17)||typeof e17.quote!="string"||!(e17.amount instanceof A)||typeof e17.unit!="string"||typeof e17.state!="string"||typeof e17.expiry!="number"||!Object.values(ca).includes(e17.state))throw this._logger.error("Invalid response from mint...",{op:t2}),new b("Invalid response from mint")}normalizeMeltBoltFields(e17,t2,n){let r=t3=>n&&e17[t3]===void 0;if(!r("fee_reserve")&&e17.fee_reserve!=null&&(e17.fee_reserve=A.from(e17.fee_reserve)),typeof e17.request!="string"&&!r("request")||!(e17.fee_reserve instanceof A)&&!r("fee_reserve"))throw this._logger.error("Invalid response from mint...",{op:t2}),new b("Invalid response from mint");W(e17,"payment_preimage")}normalizeMeltOnchainFields(e17,t2){if(t2&&e17.fee_options===void 0){W(e17,"selected_fee_index","outpoint");return}if(!Array.isArray(e17.fee_options)||e17.fee_options.length===0||e17.fee_options.length>1024||(e17.fee_options=e17.fee_options.map(e18=>{let t3=e18;if(!Number.isSafeInteger(t3.fee_index))throw this._logger.error("Invalid response from mint...",{op:"onchain melt quote"}),new b("Invalid response from mint");return{...t3,fee_index:t3.fee_index,fee_reserve:A.from(t3.fee_reserve),estimated_blocks:t3.estimated_blocks}}),W(e17,"selected_fee_index","outpoint"),typeof e17.request!="string"||e17.selected_fee_index!==null&&!Number.isSafeInteger(e17.selected_fee_index)||e17.outpoint!==null&&typeof e17.outpoint!="string"))throw this._logger.error("Invalid response from mint...",{op:"onchain melt quote"}),new b("Invalid response from mint")}};var Ga=class e13{constructor(e17,t2,n,r,i){this._keys={},this._id=e17,this._unit=t2,this._active=n,this._input_fee_ppk=r,this._final_expiry=i}get id(){return this._id}get unit(){return this._unit}get isActive(){return this._active}get fee(){return this._input_fee_ppk??0}get expiry(){return this._final_expiry}get hasKeys(){return Object.keys(this._keys).length>0}get hasHexId(){return V(this._id)}get version(){return this.hasHexId?hexToBytes2(this._id)[0]:-1}get keys(){return this._keys}set keys(e17){this._keys=e17}toMintKeyset(){return{id:this._id,unit:this._unit,active:this._active,input_fee_ppk:this._input_fee_ppk,final_expiry:this._final_expiry}}toMintKeys(){return this.hasKeys?{id:this._id,unit:this._unit,active:this._active,input_fee_ppk:this._input_fee_ppk,final_expiry:this._final_expiry,keys:this._keys}:null}verify(){return this.hasKeys?e13.verifyKeysetId(this.toMintKeys()):false}static verifyKeysetId(e17){try{let t2=e17.keys?Object.keys(e17.keys).length:0;if(t2===0||t2>256||new Set(Object.values(e17.keys).map(e18=>e18.toLowerCase())).size!==t2)return false;let n=et(e17.id)&&!V(e17.id),i=V(e17.id)?hexToBytes2(e17.id)[0]:0;return zi(e17.keys,{input_fee_ppk:e17.input_fee_ppk,expiry:e17.final_expiry,unit:e17.unit,versionByte:i,isDeprecatedBase64:n})===e17.id}catch{return false}}static fromMintApi(t2,n){let r=ra(t2),i=n?ia(n):void 0,a=new e13(r.id,r.unit,r.active,r.input_fee_ppk,r.final_expiry);if(i){if(i.id!==r.id)throw new b(`Mismatched keyset ids: meta=${r.id}, keys=${i.id}`);if(i.unit!==r.unit)throw new b(`Mismatched keyset units: meta=${r.unit}, keys=${i.unit}`);if(i.final_expiry!==void 0&&r.final_expiry!==void 0&&i.final_expiry!==r.final_expiry)throw new b(`Mismatched keyset expiry for id=${r.id}`);a.keys=i.keys}return a}};var Ka=class e14{assertInitialized(){if(Object.keys(this.keysets).length===0)throw new b("KeyChain not initialized")}constructor(e17,t2,n=E){this.keysets=Object.create(null),this.pendingKeyFetches=new Map,this.generation=0,this.refreshSeq=0,this.mint=typeof e17=="string"?new Wa(e17):e17,this.unit=t2,this._logger=n}static fromCache(t2,n,r,i){let a=new e14(t2,n,i);return a.loadFromCache(r),a}static mintToCacheDTO(e17,t2,n){let r=new Map(n.map(e18=>[e18.id,e18]));return{keysets:t2.map(e18=>{let t3=r.get(e18.id),n2={...e18};return t3&&(n2.keys={...t3.keys}),n2}),mintUrl:e17,savedAt:Date.now()}}static cacheToMintDTO(e17){return{keysets:e17.keysets.map(e18=>({id:e18.id,unit:e18.unit,active:e18.active,input_fee_ppk:e18.input_fee_ppk,final_expiry:e18.final_expiry})),keys:e17.keysets.filter(e18=>!!e18.keys).map(e18=>({id:e18.id,unit:e18.unit,active:e18.active,input_fee_ppk:e18.input_fee_ppk,final_expiry:e18.final_expiry,keys:{...e18.keys}}))}}async init(e17){if(Object.keys(this.keysets).length>0&&!e17)return;let t2=++this.refreshSeq,[n,r]=await Promise.all([this.mint.getKeySets(),this.mint.getKeys()]);if(t2!==this.refreshSeq){this._logger.debug("Discarding keychain refresh superseded by a later one",{seq:t2});return}this.buildKeychain(n.keysets,r.keysets),this.savedAt=Date.now()}loadFromCache(t2){let n;try{n=Hi(String(t2.mintUrl))}catch{n=void 0}n!==this.mint.mintUrl&&this._logger.warn(`KeyChain cache is for a different mint: ${n??"unknown"} (expected ${this.mint.mintUrl}). This will become an error in cashu-ts v5.`,{cacheMintUrl:t2.mintUrl,mintUrl:this.mint.mintUrl});let{keysets:r,keys:i}=e14.cacheToMintDTO(t2);this.buildKeychain(r,i),this.savedAt=t2.savedAt}buildKeychain(e17,t2){let n=this.keysets,r=Object.create(null),i=new Map(t2.map(e18=>[e18.id,e18]));for(let t3 of e17){let e18=i.get(t3.id),a=e18?Ga.fromMintApi(t3,e18):Ga.fromMintApi(t3);a.verify()||(a.hasKeys&&this._logger.warn("Discarding keys that do not derive their keyset id",{id:a.id}),a.keys={});let o=n[t3.id];!a.hasKeys&&o?.hasKeys&&o.unit===a.unit&&(a.keys={...o.keys},a.verify()||(this._logger.warn("Dropping carried keys: fresh metadata no longer derives the keyset id",{id:a.id}),a.keys={})),r[a.id]=a}this.keysets=r,this.generation++}getKeyset(e17){let t2=e17?this.keysets[e17]:this.getCheapestKeyset();if(!t2)throw new b(`Keyset '${e17}' not found`);return t2}getCheapestKeyset(){if(Object.keys(this.keysets).length===0)throw new b("KeyChain not initialized");let e17=Object.values(this.keysets).filter(e18=>e18.unit===this.unit&&e18.isActive&&e18.hasHexId&&e18.hasKeys);if(e17.length===0)throw new b(`No active keyset found for unit: ${this.unit}`);let t2=2**53-1;return e17.sort((e18,n)=>n.version-e18.version||e18.fee-n.fee||(n.expiry??t2)-(e18.expiry??t2))[0]}async ensureKeysetKeys(e17){let t2=this.keysets[e17];if(!t2)throw new b(`Keyset '${e17}' not found`);if(t2.hasKeys)return t2;let n=this.pendingKeyFetches.get(e17);if(n)return await n;let r=(async()=>{let n2=this.generation,r2=(await this.mint.getKeys(e17)).keysets.find(t3=>t3.id===e17);if(!r2||!r2.keys||Object.keys(r2.keys).length===0)throw new b(`Mint returned no keys for keyset '${e17}'`);let i=t2.toMintKeyset(),a=Ga.fromMintApi(i,r2);if(!a.verify())throw new b(`Keyset verification failed for ID ${e17}`);if(this.generation!==n2){this._logger.debug("Keychain refreshed during key fetch; returning the live keyset",{id:e17});let t3=this.keysets[e17];if(!t3)throw new b(`Keyset '${e17}' not found`);return t3}return this.keysets[e17]=a,a})();this.pendingKeyFetches.set(e17,r);try{return await r}finally{this.pendingKeyFetches.delete(e17)}}getKeysets(){this.assertInitialized();let e17=Object.values(this.keysets).filter(e18=>e18.unit===this.unit);if(e17.length===0)throw new b(`No keysets found for unit: ${this.unit}`);return e17}isUnitKeyset(e17){if(!e17)return false;let t2=this.keysets[e17];return t2!==void 0&&t2.unit===this.unit}hasKeyset(e17){return!!e17&&this.keysets[e17]!==void 0}getAllKeys(){return this.assertInitialized(),Object.values(this.keysets).map(e17=>e17.toMintKeys()).filter(e17=>e17!==null)}getAllKeysetIds(){return this.assertInitialized(),Object.keys(this.keysets)}get cache(){let t2=Object.values(this.keysets),n=t2.map(e17=>e17.toMintKeyset()),r=t2.map(e17=>e17.toMintKeys()).filter(e17=>e17!==null),i=e14.mintToCacheDTO(this.mint.mintUrl,n,r);return i.savedAt=this.savedAt,i}};var qa=class{constructor(e17,t2,n){this.amountValue=A.from(e17),this.B_=t2,this.id=n}get amount(){return this.amountValue}getSerializedBlindedMessage(){return{amount:this.amountValue,B_:this.B_.toHex(true),id:this.id}}};var Ja=new Set(["locktime","pubkeys","n_sigs","refund","n_sigs_refund","sigflag"]);function Ya(e17){if(!e17||typeof e17!="string")throw new b("tag key must be a non empty string");if(Ja.has(e17))throw new b(`additionalTags must not use reserved key "${e17}"`)}var Z=class e15{constructor(e17,t2,n,r){if(typeof t2!="bigint"||t2<=0n)throw new b("OutputData: blindingFactor must be a positive bigint");if(!(n instanceof Uint8Array)||n.length===0)throw new b("OutputData: secret must be a non-empty Uint8Array");if(e17==null)throw new b("OutputData: blindedMessage is required");this.secret=n,this.blindingFactor=t2,this.blindedMessage=e17,this.ephemeralE=r}toProof(e17,n){if(e17==null)throw new b("Mint response is missing a signature for one of the outputs. Inputs may already be spent; if the wallet is seeded, try restoring (NUT-09) to recover.");if(!this.blindedMessage.amount.isZero()&&e17.id!==this.blindedMessage.id)throw new b(`Mint signature keyset id ${e17.id} does not match output ${this.blindedMessage.id}`);let i;e17.dleq&&(i={s:hexToBytes2(e17.dleq.s),e:hexToBytes2(e17.dleq.e),r:this.blindingFactor});let a=e17.amount.toString(),o=P(n.keys[a]);if(i){let t2=P(this.blindedMessage.B_),n2=P(e17.C_);if(!tr(i,t2,n2,o))throw new b("DLEQ verification failed on mint response")}let s=Ut({id:e17.id,C_:P(e17.C_)},this.blindingFactor,this.secret,o),c={id:e17.id,amount:e17.amount,C:s.C.toHex(true),secret:xe(s.secret),...i&&{dleq:{s:bytesToHex2(i.s),e:bytesToHex2(i.e),r:Ti(this.blindingFactor)}}};return this.ephemeralE&&(c.p2pk_e=this.ephemeralE),c}static createP2PKData(e17,t2,n,r){let i=B(t2,n.keys,r),a=e17.blindKeys&&e17.sigFlag==="SIG_ALL"?Rt():void 0;return i.map(t3=>this.createSingleP2PKData(e17,t3,n.id,a))}static createSingleP2PKData(n,r,a,o){let s=A.from(r),c=On(n),l=Array.isArray(c.pubkey)?c.pubkey:[c.pubkey],u=c.refundKeys??[],d=c.requiredSignatures??1,f=c.requiredRefundSignatures??1,p=c.hashlock,m=typeof p=="string"&&p.length>0,h=m?p:l[0],g=m?l:l.slice(1),_=u,v;if(n.blindKeys){let{blinded:e17,Ehex:t2}=vn([...l,...u],o,!m);m?g=e17.slice(0,l.length):(h=e17[0],g=e17.slice(1,l.length)),_=e17.slice(l.length),v=t2}let y=[],x2=c.locktime??NaN;if(Number.isSafeInteger(x2)&&x2>=0&&y.push(["locktime",String(x2)]),g.length>0&&(y.push(["pubkeys",...g]),d>1&&y.push(["n_sigs",String(d)])),_.length>0&&(y.push(["refund",..._]),f>1&&y.push(["n_sigs_refund",String(f)])),c.sigFlag=="SIG_ALL"&&y.push(["sigflag","SIG_ALL"]),c.additionalTags?.length){let e17=c.additionalTags.map(([e18,...t2])=>(Ya(e18),[e18,...t2.map(String)]));y.push(...e17)}let S2=[m?"HTLC":"P2PK",{nonce:bytesToHex2(randomBytes3(32)),data:h,tags:y}],C=JSON.stringify(S2),ee=[...C].length;if(ee>1024)throw new b(`Secret too long (${ee} characters), maximum is ${ce}`);let w=new TextEncoder().encode(C),{r:T2,B_:te2}=Vt(w);return new e15(new qa(s,te2,a).getSerializedBlindedMessage(),T2,w,v)}static createRandomData(e17,t2,n){return B(e17,t2.keys,n).map(e18=>this.createSingleRandomData(e18,t2.id))}static createSingleRandomData(n,r){let a=A.from(n),o=bytesToHex2(randomBytes3(32)),s=new TextEncoder().encode(o),{r:c,B_:l}=Vt(s);return new e15(new qa(a,l,r).getSerializedBlindedMessage(),c,s)}static createDeterministicData(e17,t2,n,r,i){let a=B(e17,r.keys,i),o=dr(t2,r.id);return a.map((e18,t3)=>Xa(e18,r.id,o(n+t3)))}static createSingleDeterministicData(e17,t2,n,r){return Xa(e17,r,ur(t2,r,n))}static sumOutputAmounts(e17){return A.sum(e17.map(e18=>e18.blindedMessage.amount))}static serialize(e17){return{blindedMessage:{amount:e17.blindedMessage.amount.toString(),B_:e17.blindedMessage.B_,id:e17.blindedMessage.id},blindingFactor:e17.blindingFactor.toString(),secret:bytesToHex2(e17.secret),...e17.ephemeralE&&{ephemeralE:e17.ephemeralE}}}static deserialize(t2){try{if(!/^(0|[1-9]\d*)$/.test(t2.blindingFactor))throw new b("blindingFactor must be a canonical decimal integer");let n=hexToBytes2(t2.secret);xe(n);let i=BigInt(t2.blindingFactor),{B_:a}=Vt(n,i);if(a.toHex(true)!==t2.blindedMessage.B_.toLowerCase())throw new b("stored output does not match its secret. Inputs may already be spent; if the wallet is seeded, try restoring (NUT-09) to recover.");return new e15({amount:A.from(t2.blindedMessage.amount),B_:t2.blindedMessage.B_,id:t2.blindedMessage.id},i,n,t2.ephemeralE?P(t2.ephemeralE).toHex(true):void 0)}catch(e17){throw new b(`Invalid SerializedOutputData: ${e17 instanceof Error?e17.message:String(e17)}`,{cause:e17})}}};function Xa(e17,n,r){let i=A.from(e17),a=bytesToHex2(r.secret),o=new TextEncoder().encode(a),{r:s,B_:c}=Vt(o,bytesToNumberBE(r.blindingFactor));return new Z(new qa(i,c,n).getSerializedBlindedMessage(),s,o)}var Mo;var No=class e16{constructor(e17,t2){this.tokenGeneration=0,this.sessionGeneration=0,this.tokens={},this.pool=[],this.desiredPoolSize=100,this.maxPerMint=100,this.mintUrl=e17,this.req=t2?.request??Fa,this.logger=t2?.logger??E;let n=Math.max(1,t2?.desiredPoolSize??this.desiredPoolSize),r=Math.max(1,t2?.maxPerMint??this.maxPerMint);this.desiredPoolSize=Math.min(n,100),this.maxPerMint=Math.min(r,100),this.desiredPoolSize!==n&&this.logger.warn("AuthManager: desiredPoolSize exceeds internal cap and was clamped",{configured:n,clampedTo:this.desiredPoolSize}),this.maxPerMint!==r&&this.logger.warn("AuthManager: maxPerMint exceeds internal cap and was clamped",{configured:r,clampedTo:this.maxPerMint})}attachOIDC(e17){return this.oidc&&this.oidcListener&&this.oidc.removeTokenListener(this.oidcListener),this.oidc&&this.oidc!==e17&&(this.tokens={}),this.tokenGeneration++,this.sessionGeneration++,this.oidc=e17,this.bindOIDC(),this}bindOIDC(){let e17=this.oidc;if(!e17)return;this.oidcListener&&e17.removeTokenListener(this.oidcListener);let t2=(n,r)=>{if(this.oidc!==e17||r==="refresh"&&this.oidcListener!==t2){this.logger.warn("AuthManager: ignoring tokens from an earlier session");return}this.updateFromOIDC(n,r)};this.oidcListener=t2,e17.addTokenListener(t2)}get poolSize(){return this.pool.length}get poolTarget(){return this.desiredPoolSize}get activeAuthKeysetId(){try{return this.keychain?.getCheapestKeyset().id}catch{return}}get hasCAT(){return!!this.tokens.accessToken}getCAT(){return this.tokens.accessToken}setCAT(e17){this.tokenGeneration++,this.sessionGeneration++,this.tokens={accessToken:e17},this.bindOIDC()}async ensureCAT(e17){if(this.validForAtLeast(e17)||!this.oidc||!this.tokens.refreshToken)return this.tokens.accessToken;if(this.inflightRefresh?.session!==this.sessionGeneration){let e18=this.tokenGeneration,t3=this.sessionGeneration,n=(async()=>{try{let t4=await this.oidc.refresh(this.tokens.refreshToken);this.tokenGeneration===e18&&this.updateFromOIDC(t4,"refresh")}catch(e19){this.logger.warn("AuthManager: CAT refresh failed",{err:e19})}})();this.inflightRefresh={session:t3,promise:n}}let t2=this.inflightRefresh;try{await t2.promise}finally{this.inflightRefresh===t2&&(this.inflightRefresh=void 0)}return this.validForAtLeast(0)?this.tokens.accessToken:void 0}validForAtLeast(t2=e16.MIN_VALID_SECS){let{accessToken:n,expiresAt:r}=this.tokens;return n?r?Date.now()+t2*1e3<r:true:false}updateFromOIDC(e17,t2){if(!e17.access_token)return;this.tokenGeneration++,t2==="signin"&&this.sessionGeneration++;let n=Date.now();if(this.tokens.accessToken=e17.access_token,this.tokens.refreshToken=e17.refresh_token||(t2==="refresh"?this.tokens.refreshToken:void 0),typeof e17.expires_in=="number"&&e17.expires_in>0)this.tokens.expiresAt=n+e17.expires_in*1e3;else{let t3=this.parseJwtExpSec(e17.access_token);this.tokens.expiresAt=t3?t3*1e3:void 0}this.bindOIDC(),this.logger.debug("AuthManager: OIDC tokens updated",{expiresAt:this.tokens.expiresAt})}async ensure(e17){if(await this.init(),this.pool.length>=e17)return;let t2=Math.max(this.desiredPoolSize,e17),n=this.getBatMaxMint(),r=Math.min(t2-this.pool.length,n);r<=0||await this.topUp(r)}async getBlindAuthToken({method:e17,path:t2}){return this.info&&!this.info.requiresBlindAuthToken(e17,t2)&&this.logger.warn("Endpoint is not marked as protected by NUT-22; still issuing BAT",{method:e17,path:t2}),this.withLock(async()=>{if(await this.ensure(1),this.pool.length===0)throw new b("AuthManager: no BATs available and minting failed");let n=this.pool.pop();return this.logger.debug("AuthManager: BAT requested",{method:e17,path:t2,remaining:this.pool.length}),Po(n)})}importPool(e17,t2="replace"){t2==="replace"&&(this.pool=[]);let n=new Map(this.pool.map(e18=>[e18.secret,e18]));for(let t3 of e17)!t3||!t3.secret||!t3.C||!t3.id||n.has(t3.secret)||(this.pool.push(t3),n.set(t3.secret,t3))}exportPool(){return this.pool.map(e17=>({...e17,dleq:e17.dleq?{...e17.dleq}:void 0}))}parseJwtExpSec(e17){if(!e17)return;let t2=e17.split(".");if(t2.length===3)try{let e18=new TextDecoder("utf-8").decode(Xe(t2[1])),n=JSON.parse(e18),r=typeof n.exp=="number"?n.exp:Number(n.exp);if(Number.isFinite(r)&&r>0)return r}catch{this.logger.warn("JWT access token was malformed.")}}async withLock(e17){let t2=this.lockChain??Promise.resolve(),n,r=new Promise(e18=>{n=e18}),i=t2.then(()=>r);this.lockChain=i;try{return await t2,await e17()}finally{n(),this.lockChain===i&&(this.lockChain=void 0)}}async init(){if(!this.info){let e17=await this.req({endpoint:G(this.mintUrl,"/v1/info"),method:"GET",logger:this.logger});this.info=new Y(e17,this.logger)}if(!this.keychain){let[e17,t2]=await Promise.all([this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/keysets"),method:"GET",logger:this.logger}),this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/keys"),method:"GET",logger:this.logger})]);if(!Array.isArray(e17.keysets)||!Array.isArray(t2.keysets))throw new b("AuthManager: mint returned malformed auth keysets");if(e17.keysets.length>1e4||t2.keysets.length>1e4)throw new b("AuthManager: mint returned more auth keysets than can be processed");let n=e17.keysets.map(e18=>ra(e18)),r=t2.keysets.map(e18=>ia(e18)),i=Ka.mintToCacheDTO(this.mintUrl,n,r);this.keychain=Ka.fromCache(this.mintUrl,"auth",i),this.keychain.getCheapestKeyset()}}getBatMaxMint(){if(!this.info)throw new b("AuthManager: mint info not loaded");let e17=this.info.nuts[22],t2=J(e17?.bat_max_mint,"nuts.22.bat_max_mint",this.maxPerMint);return Math.max(1,Math.min(this.maxPerMint,t2))}getActiveKeys(){if(!this.keychain)throw new b("AuthManager: keyset not loaded for active keyset");return this.keychain.getCheapestKeyset()}async topUp(e17){if(!this.info)throw new b("AuthManager: mint info not loaded");let t2=this.info.requiresClearAuthToken("POST","/v1/auth/blind/mint"),n=this.sessionGeneration,r;if(t2){if(r=await this.ensureCAT(),this.sessionGeneration!==n)throw new b("AuthManager: session changed while obtaining BATs");if(!r)throw new b("AuthManager: Clear-auth token required for /v1/auth/blind/mint but not available. Authenticate with the mint to obtain a CAT first.")}let i=this.getActiveKeys(),a=Z.createRandomData(e17,i),o={outputs:a.map(e18=>e18.blindedMessage)},s={};r&&(s["Clear-auth"]=r);let c=await this.req({endpoint:G(this.mintUrl,"/v1/auth/blind/mint"),method:"POST",headers:s,...r?{redirect:"error"}:{},requestBody:o,logger:this.logger});if(!Array.isArray(c?.signatures)||c.signatures.length!==a.length)throw new b("AuthManager: bad BAT mint response");let l=c.signatures.map(e18=>({...e18,amount:A.from(e18.amount)})),u=a.map((e18,t3)=>e18.toProof(l[t3],i));for(let e18 of u)if(!Ji(e18,i))throw new b("AuthManager: mint returned BAT with invalid DLEQ");if(t2&&this.sessionGeneration!==n)throw new b("AuthManager: session changed while obtaining BATs");this.pool.push(...u),this.logger.debug("AuthManager: performed topUp",{minted:u.length,pool:this.pool.length})}};Mo=No,Mo.MIN_VALID_SECS=30;function Po(e17){return`authA${Ye(utf8ToBytes3(JSON.stringify({id:e17.id,secret:e17.secret,C:e17.C})))}`}import{computeSmartDenominations,getWallet,lookupInvoice,maxKeysetDenomination,missingDleqRecoveryDiagnostic,NwcError,parseNwcUri,payInvoiceReconciling,prepareP2PKMint,restoreProofsFromOutputs,serializeOutputData,swapFeeForProofs,toLockPubkey as toLockPubkey2}from"@dvmkit/sdk/internal";import{DRAIN_DELIVERY_RESERVE_SATS}from"@dvmkit/sdk/server";init_defaults();import{appendFileSync as appendFileSync2,chmodSync as chmodSync6,existsSync as existsSync11,mkdirSync as mkdirSync9,readFileSync as readFileSync11,writeFileSync as writeFileSync9}from"fs";function appendSwapped(entry){appendEntry2({...entry,kind:"swapped",ts:Date.now()})}function appendParked(entry){appendEntry2({...entry,kind:"parked",ts:Date.now()})}function appendMintQuote(entry){appendEntry2({...entry,kind:"mint_quote",ts:Date.now()})}function appendMintPaid(entry){appendEntry2({...entry,kind:"mint_paid",ts:Date.now()})}function appendMintPaymentAttempt(entry){appendEntry2({...entry,kind:"mint_payment_attempt",ts:Date.now()})}function appendMintPaymentRejected(entry){appendEntry2({...entry,kind:"mint_payment_rejected",ts:Date.now()})}function appendMintOutputs(entry){appendEntry2({...entry,kind:"mint_outputs",ts:Date.now()})}function appendMintAbandoned(entry){appendEntry2({...entry,kind:"mint_abandoned",ts:Date.now()})}function readUnparked(dvmId){return scan(dvmId).unparked}function readOpenMintQuotes(dvmId){return scan(dvmId).openQuotes}function scan(dvmId){if(!existsSync11(DRAIN_PARK_LOG_FILE))return{unparked:[],openQuotes:[]};const raw2=readFileSync11(DRAIN_PARK_LOG_FILE,"utf-8");const swapped=new Map;const parked=new Set;const quotes=new Map;const paid=new Map;const paymentRejected=new Map;const outputs=new Map;const abandoned=new Set;for(const line of raw2.split("\n")){if(!line)continue;let entry;try{entry=JSON.parse(line)}catch{continue}if(entry.dvm_id!==dvmId)continue;const key=`${entry.dvm_id}|${entry.credit_id}|${entry.drain_id}`;switch(entry.kind){case"swapped":swapped.set(key,entry);break;case"parked":parked.add(key);break;case"mint_quote":quotes.set(key,entry);break;case"mint_paid":paid.set(key,entry);break;case"mint_payment_attempt":if(paymentRejected.get(key)?.quote_id===entry.quote_id)paymentRejected.delete(key);break;case"mint_payment_rejected":paymentRejected.set(key,entry);break;case"mint_outputs":outputs.set(key,entry);break;case"mint_abandoned":abandoned.add(`${key}|${entry.quote_id}`);break}}const openQuotes=[];for(const[key,quote]of quotes){if(parked.has(key)||swapped.has(key)||abandoned.has(`${key}|${quote.quote_id}`))continue;const paidEntry=paid.get(key);const paymentRejectedEntry=paymentRejected.get(key);const outputsEntry=outputs.get(key);openQuotes.push({quote,...paidEntry?.quote_id===quote.quote_id?{paid:paidEntry}:{},...paymentRejectedEntry?.quote_id===quote.quote_id?{paymentRejected:paymentRejectedEntry}:{},...outputsEntry?.quote_id===quote.quote_id?{outputs:outputsEntry}:{}})}return{unparked:[...swapped.entries()].filter(([k2])=>!parked.has(k2)).map(([,e17])=>e17),openQuotes}}function appendEntry2(entry){if(!existsSync11(BUILDER_CONFIG_DIR)){mkdirSync9(BUILDER_CONFIG_DIR,{recursive:true,mode:448})}if(!existsSync11(DRAIN_PARK_LOG_FILE)){writeFileSync9(DRAIN_PARK_LOG_FILE,"",{mode:384})}else{chmodSync6(DRAIN_PARK_LOG_FILE,384)}appendFileSync2(DRAIN_PARK_LOG_FILE,JSON.stringify(entry)+"\n")}function bolt11AmountMsats(bolt11){const trimmed=bolt11.trim();const separator=trimmed.lastIndexOf("1");if(separator<=0)return null;const match=/^ln(?:bcrt|bc|tbs|tb|sb)(\d+)([munp])?$/i.exec(trimmed.slice(0,separator));if(!match)return null;const digits=Number(match[1]);if(!Number.isFinite(digits))return null;const multiplier=match[2]?.toLowerCase()??"";switch(multiplier){case"m":return digits*1e8;case"u":return digits*1e5;case"n":return digits*100;case"p":return Math.round(digits/10);default:return digits*1e11}}async function serviceDrains(ctx){const summary={fetched:false,drains_seen:0,drains_parked:0,drains_jit_minted:0,drains_failed:0,drains_queued:0,drains_manual:0,drains_channel:0,recovered:0,cashu_liability_sats_after:0};const recovery=new Map;summary.recovered+=await recoverParks(ctx,summary,recovery);await recoverMintQuotes(ctx,summary,recovery);let pending3;try{pending3=await ctx.client.getPendingDrains()}catch(err){rethrowIfBadSignature(err);rethrowIfAdminStateUnavailable(err);if(isMissingDrainSurface(err)){return{...summary,fetched:true}}logDrain(ctx,"drain_pending_fetch_failed",{error:err instanceof Error?err.message:String(err)});return summary}summary.fetched=true;summary.drains_seen=pending3.drains.length;const stillPending=new Set((await refreshedPendingIds(ctx,pending3.drains,summary)).map(d=>`${d.credit_id}|${d.drain_id}`));let cashuOutstandingSats=0;for(const drain of pending3.drains){if(!stillPending.has(`${drain.credit_id}|${drain.drain_id}`))continue;if(drain.method==="cashu"){const key=`${drain.credit_id}|${drain.drain_id}`;const recovered=recovery.get(key);if(recovered==="resolved"){continue}if(recovered){if(recovered==="failed")summary.drains_failed++;else summary.drains_queued++;cashuOutstandingSats+=drain.amount_sats??0;continue}const outcome=await parkOne(ctx,drain,pending3.mints??[]);if(outcome==="parked"||outcome==="jit_minted"){summary.drains_parked++;if(outcome==="jit_minted")summary.drains_jit_minted++}else{if(outcome==="queued")summary.drains_queued++;else summary.drains_failed++;cashuOutstandingSats+=drain.amount_sats??0}}else if(drain.settlement==="channel"){summary.drains_channel++;logDrain(ctx,"drain_channel_settlement_pending",{credit_id:drain.credit_id,drain_id:drain.drain_id,method:drain.method,channel_id:drain.channel_id,amount_micro:drain.amount_micro,hint:"Usually nothing to do \u2014 the caller's channel close settles this one, and their next drain poll finishes the reconciliation. It is not on `dvmctl credit drains-owed`. The exception is a Tempo close that landed on chain and refunded less than the ledger extinguished: that one never clears on a poll, and `dvmctl credit tempo-wedged` is where it shows up (tracked change)."})}else{summary.drains_manual++;logDrain(ctx,"drain_manual_settlement_required",{credit_id:drain.credit_id,drain_id:drain.drain_id,method:drain.method,payout:drain.payout,amount_micro:drain.amount_micro,amount_native:drain.amount_native,hint:`No builder-held on-chain send keys exist; send this refund yourself and record it with \`dvmctl credit drain-settle ${ctx.handle} ${drain.credit_id} ${drain.drain_id} --tx <hash>\`. \`dvmctl credit drains-owed ${ctx.handle}\` lists everything outstanding.`})}}summary.cashu_liability_sats_after=cashuOutstandingSats;return summary}async function recoverParks(ctx,summary,recovery){const unparked=readUnparked(ctx.dvmId);let recovered=0;for(const entry of unparked){const key=`${entry.credit_id}|${entry.drain_id}`;recovery.set(key,"queued");try{await ctx.client.parkDrain({credit_id:entry.credit_id,drain_id:entry.drain_id,token:entry.token,consumed_row_ids:entry.consumed_row_ids,keep_proofs:entry.keep_proofs,...entry.mint_evidence?{mint_evidence:entry.mint_evidence}:{}});appendParked({dvm_id:ctx.dvmId,credit_id:entry.credit_id,drain_id:entry.drain_id});recovered++;recovery.set(key,"resolved");if(entry.mint_evidence)summary.drains_jit_minted++;logDrain(ctx,"drain_park_recovered",{credit_id:entry.credit_id,drain_id:entry.drain_id})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_recovery_failed",{credit_id:entry.credit_id,drain_id:entry.drain_id,error:err instanceof Error?err.message:String(err)})}}return recovered}async function refreshedPendingIds(ctx,drains,summary){if(summary.recovered===0)return drains;try{const fresh=await ctx.client.getPendingDrains();return fresh.drains}catch(err){rethrowIfAdminStateUnavailable(err);return drains}}async function parkOne(ctx,drain,mints){const amountSats=drain.amount_sats;const refundPubkey=drain.payout.refund_pubkey;if(!amountSats||amountSats<=0||!refundPubkey){logDrain(ctx,"drain_park_unpriceable",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats});return"failed"}let batches;try{batches=(await ctx.client.getPendingBatches()).batches}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_batches_fetch_failed",{error:err instanceof Error?err.message:String(err)});return"failed"}const jitAvailable=ctx.refundNwc!==void 0;const selected=await selectCoveringBatch(ctx,drain,amountSats,batches,jitAvailable);if(!selected){return jitMintOne(ctx,drain,amountSats,refundPubkey,mints)}const{batch,wallet,inputProofs,keypair}=selected;let send;let keep;try{const result=await wallet.ops.send(amountSats,inputProofs).asLocked({pubkey:refundPubkey}).keepAsLocked({pubkey:batch.lock_pubkey}).privkey(keypair.privkey).includeFees(false).run();send=result.send;keep=result.keep}catch(err){const recoveryDiagnostic=missingDleqRecoveryDiagnostic(inputProofs,err);logDrain(ctx,"drain_swap_failed",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,error:err instanceof Error?err.message:String(err),...recoveryDiagnostic?{recovery:recoveryDiagnostic}:{}});return"failed"}const inputIndexBySecret=new Map(batch.proofs.map((p,i)=>[p.secret,i]));const untouchedInputs=new Set;const change=[];for(const p of keep){const index=inputIndexBySecret.get(p.secret);if(index===void 0)change.push(p);else untouchedInputs.add(index)}const consumedRowIds=batch.row_ids.filter((_,i)=>!untouchedInputs.has(i));const keepProofs=change.map(p=>({id:p.id,amount:amountForJson(p.amount),secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));if(consumedRowIds.length===0){logDrain(ctx,"drain_park_no_consumed_rows",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,batch_rows:batch.row_ids.length});return"failed"}const encoded=ki({mint:batch.mint_url,proofs:rehydrateLocalProofs(send)});appendSwapped({dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id,token:encoded,consumed_row_ids:consumedRowIds,keep_proofs:keepProofs});try{await ctx.client.parkDrain({credit_id:drain.credit_id,drain_id:drain.drain_id,token:encoded,consumed_row_ids:consumedRowIds,keep_proofs:keepProofs})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_post_failed",{credit_id:drain.credit_id,drain_id:drain.drain_id,error:err instanceof Error?err.message:String(err)});return"failed"}appendParked({dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id});logDrain(ctx,"drain_parked",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,mint_url:batch.mint_url});return"parked"}async function jitMintOne(ctx,drain,amountSats,refundPubkey,mints){const ids={credit_id:drain.credit_id,drain_id:drain.drain_id};if(!ctx.refundNwc){logDrain(ctx,"drain_jit_unconfigured",{...ids,amount_sats:amountSats,display:"This refund needs sats bought from the hub, and no send wallet is configured to buy them.",hint:"Pass `--refund-nwc <wallet connection URI>` (or set DVMKIT_REFUND_NWC_URL) with a send-capable NWC connection on the wallet holding your Bitcoin deposits. The refund waits, visible in `dvmctl credit drains-owed`, until one is wired."});return"queued"}const keypair=ctx.currentKeypair;if(!keypair){logDrain(ctx,"drain_jit_no_lock_key",{...ids,display:"This refund needs to be minted, and no current builder lock key was resolved to sign the mint quote.",hint:"The mint quote is locked to your builder key (NUT-20) so nobody else can redeem the invoice you pay for. Check `~/.dvmkit/builder.mnemonic` is present and that this DVM is registered in `~/.dvmkit/builder.json`."});return"queued"}let conn;try{conn=parseNwcUri(ctx.refundNwc.uri)}catch(err){logDrain(ctx,"drain_jit_nwc_invalid",{...ids,error:err instanceof Error?err.message:String(err),display:"The refund send wallet's connection string could not be read, so nothing was paid.",hint:"Re-copy the `wallet connection URI` URI from the wallet that holds your Bitcoin deposits. The refund stays owed and retries on the next tick."});return"queued"}const tried=[];for(const mintUrl of mints){tried.push(mintUrl);let wallet;let quote;try{wallet=await(ctx.walletProvider??getWallet)(mintUrl);quote=await wallet.createLockedMintQuote(amountSats,keypair.pubkey)}catch(err){logDrain(ctx,"drain_jit_quote_failed",{...ids,mint_url:mintUrl,amount_sats:amountSats,error:err instanceof Error?err.message:String(err)});continue}const invoiceMsats=bolt11AmountMsats(quote.request);const ceilingMsats=(amountSats+DRAIN_DELIVERY_RESERVE_SATS)*1e3;if(invoiceMsats===null||invoiceMsats>ceilingMsats){logDrain(ctx,"drain_jit_quote_refused",{...ids,mint_url:mintUrl,quote_id:quote.quote,reason:invoiceMsats===null?"invoice_unreadable":"above_ceiling",face_sats:amountSats,invoice_msats:invoiceMsats,ceiling_msats:ceilingMsats,display:invoiceMsats===null?`${mintUrl} asked for this refund's ecash with an invoice whose amount could not be read, so nothing was paid.`:`${mintUrl} wants more for this refund's ecash than the refund is worth, so nothing was paid.`,hint:"Nothing left the wallet, and the unpaid quote simply expires at the mint. Another accepted mint is tried next; if every one of them is refused this way, check the mints' own fee settings before adding one that is cheaper."});continue}const entry={dvm_id:ctx.dvmId,credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:mintUrl,quote_id:quote.quote,bolt11:quote.request,invoice_msats:invoiceMsats,face_sats:amountSats,reserve_sats:DRAIN_DELIVERY_RESERVE_SATS,expires_at:quote.expiry??null,refund_pubkey:refundPubkey};appendMintQuote(entry);logDrain(ctx,"drain_jit_quoted",{...ids,mint_url:mintUrl,quote_id:quote.quote,face_sats:amountSats,invoice_msats:entry.invoice_msats});return advanceMintQuote(ctx,conn,keypair,{quote:{...entry,kind:"mint_quote",ts:Date.now()}})}logDrain(ctx,"drain_jit_no_mint",{...ids,amount_sats:amountSats,mints_tried:tried,display:tried.length===0?"This refund has to be bought at a Cashu mint and this DVM has none configured, so nothing could be minted.":"No accepted mint would quote for this refund, so nothing was bought and nothing was paid.",hint:tried.length===0?"Set DVMKIT_CASHU_MINTS on the DVM. A non-channel Bitcoin refund is P2PK-locked ecash and nothing else, so without a mint there is no shape it can take.":`Each mint's own error is on the \`drain_jit_quote_failed\` line above it. The refund stays owed and retries every tick \u2014 nothing here needs undoing, and no payment was attempted.`});return"queued"}async function advanceMintQuote(ctx,conn,keypair,open){const entry=open.quote;const refundPubkey=entry.refund_pubkey;const ids={credit_id:entry.credit_id,drain_id:entry.drain_id};const base3={...ids,mint_url:entry.mint_url,quote_id:entry.quote_id};let wallet;try{wallet=await(ctx.walletProvider??getWallet)(entry.mint_url)}catch(err){logDrain(ctx,"drain_jit_mint_unreachable",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint holding this refund's quote is not answering, so the refund is still on its way.",hint:"Nothing is lost and nothing needs undoing \u2014 the quote is recorded and the next tick picks it up where it left off. If the mint stays down, its own status page is the place to look."});return"queued"}let paid=Boolean(open.paid);let feesPaidMsats=open.paid?.fees_paid_msats??null;let checked;if(!paid){if(quoteDead(entry)){appendMintAbandoned({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,reason:"quote_expired_unpaid"});logDrain(ctx,"drain_jit_quote_abandoned",{...base3,reason:"quote_expired_unpaid",display:"The mint's quote for this refund ran out before it could be paid, so it was dropped unpaid.",hint:"Nothing left the wallet \u2014 the refund is still owed and the next tick asks for a fresh quote. If this repeats, the wallet is the place to look: an exhausted budget or an empty balance keeps a quote from being paid until it expires."});return"queued"}appendMintPaymentAttempt({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id});try{const outcome=await(ctx.payInvoice??payInvoiceReconciling)(conn,entry.bolt11);paid=true;feesPaidMsats=outcome.feesPaidMsats??null;appendMintPaid({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,preimage:outcome.preimage,...outcome.feesPaidMsats!==void 0?{fees_paid_msats:outcome.feesPaidMsats}:{},...outcome.outcome==="paid"?{}:{recovered:true}});logDrain(ctx,"drain_jit_invoice_paid",{...base3,pay_outcome:outcome.outcome,invoice_msats:entry.invoice_msats,fees_paid_msats:outcome.feesPaidMsats??null})}catch(err){const code=err instanceof NwcError?err.code:"pay_failed";const definitivelyRejected=err instanceof NwcError&&err.method==="pay_invoice"&&code.toLowerCase()!=="payment_unknown";if(definitivelyRejected){appendMintPaymentRejected({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,error_code:code})}logDrain(ctx,code==="payment_unknown"?"drain_jit_payment_unknown":"drain_jit_payment_failed",{...base3,error_code:code,error:err instanceof Error?err.message:String(err),display:code==="payment_unknown"?"The wallet buying this refund never answered, so we cannot yet say whether the payment went out.":"The wallet could not pay for this refund's ecash, so no money left and the refund is still owed.",hint:code==="payment_unknown"?"Nothing is retried until this resolves \u2014 the next tick asks the wallet about this exact invoice, so it cannot be paid twice. Check the wallet is online and reachable over its relays.":"The wallet's own error is on this line. A balance too low or a budget exhausted are the usual two; the same invoice is retried on the next tick once the wallet can pay it."});return"queued"}}try{checked=await wallet.checkMintQuoteBolt11(entry.quote_id)}catch(err){logDrain(ctx,"drain_jit_quote_state_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint would not say whether it has been paid for this refund yet, so it stays in flight.",hint:"The payment is recorded, so nothing is re-sent. The next tick asks again."});return"queued"}let proofs;if(checked.state===sa.ISSUED){if(!open.outputs){logDrain(ctx,"drain_jit_issued_unrecoverable",{...base3,display:"This refund's ecash was already issued by the mint, but this machine has no record of the outputs needed to collect it.",hint:"Do not re-run the refund \u2014 the drain is deliberately held so it cannot be bought twice. This means another host minted the same quote, or `~/.dvmkit/drain-park.log` lost a line. Check for a second worker against this DVM before doing anything else."});return"failed"}try{proofs=await restoreProofsFromOutputs(wallet,open.outputs.keyset_id,open.outputs.outputs)}catch(err){logDrain(ctx,"drain_jit_restore_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has this refund's ecash and would not hand back the signatures for it this time.",hint:"The outputs are on disk, so this is recoverable \u2014 the next tick asks the mint again. Nothing was paid twice."});return"queued"}logDrain(ctx,"drain_jit_restored",{...base3,proofs:proofs.length})}else if(checked.state===sa.PAID){const denominations=computeSmartDenominations(entry.face_sats,{maxDenomination:maxKeysetDenomination(wallet),includeTinyTail:false});let preview;try{preview=await prepareP2PKMint(wallet,entry.face_sats,checked,keypair.privkey,refundPubkey,denominations)}catch(err){logDrain(ctx,"drain_jit_prepare_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has been paid for this refund but would not prepare the notes this time.",hint:"The payment stands and is not repeated \u2014 the next tick prepares them again."});return"queued"}const previewKeysetId=preview.outputData[0]?.blindedMessage.id;if(!previewKeysetId||preview.outputData.some(output=>output.blindedMessage.id!==previewKeysetId)){logDrain(ctx,"drain_jit_prepare_failed",{...base3,error:"The prepared Cashu outputs do not share one keyset.",display:"The mint has been paid for this refund but returned malformed note metadata this time.",hint:"The payment stands and is not repeated \u2014 the next tick prepares the notes again."});return"queued"}appendMintOutputs({dvm_id:ctx.dvmId,...ids,quote_id:entry.quote_id,keyset_id:previewKeysetId,outputs:serializeOutputData(preview.outputData)});try{proofs=await wallet.completeMint(preview)}catch(err){logDrain(ctx,"drain_jit_mint_failed",{...base3,error:err instanceof Error?err.message:String(err),display:"The mint has been paid for this refund and did not return the notes, so it is still in flight.",hint:"The blinded outputs are recorded, so the next tick can collect the same notes from the mint \u2014 nothing is bought twice."});return"queued"}}else{if(quoteExpired(checked)){logDrain(ctx,"drain_jit_quote_expired_paid",{...base3,invoice_msats:entry.invoice_msats,display:"The hub paid for this refund's ecash and the mint's quote expired before it credited the payment.",hint:`Do not re-run the refund \u2014 the drain is held so it cannot be bought twice. Ask ${entry.mint_url} about quote ${entry.quote_id}; the payment either sits with them or was returned by Lightning. Settle it there, then remove this quote's lines from \`~/.dvmkit/drain-park.log\` to release the drain.`});return"failed"}logDrain(ctx,"drain_jit_awaiting_credit",{...base3,quote_state:checked.state,display:"The hub has paid for this refund's ecash and the mint has not credited the payment yet.",hint:"Ordinary for a moment after a Lightning payment. The next tick collects the notes; nothing is paid again in the meantime."});return"queued"}const evidence={source:"jit_mint",mint_url:entry.mint_url,quote_id:entry.quote_id,face_sats:entry.face_sats,reserve_sats:entry.reserve_sats,invoice_msats:entry.invoice_msats,routing_fee_msats:feesPaidMsats};const encoded=ki({mint:entry.mint_url,proofs:rehydrateLocalProofs(proofs)});appendSwapped({dvm_id:ctx.dvmId,...ids,token:encoded,consumed_row_ids:[],keep_proofs:[],mint_evidence:evidence});try{await ctx.client.parkDrain({...ids,token:encoded,consumed_row_ids:[],keep_proofs:[],mint_evidence:evidence})}catch(err){rethrowIfAdminStateUnavailable(err);logDrain(ctx,"drain_park_post_failed",{...base3,error:err instanceof Error?err.message:String(err)});return"queued"}appendParked({dvm_id:ctx.dvmId,...ids});logDrain(ctx,"drain_jit_parked",{...base3,face_sats:entry.face_sats,reserve_sats:entry.reserve_sats,invoice_msats:entry.invoice_msats,routing_fee_msats:feesPaidMsats,reserve_remainder_msats:entry.reserve_sats*1e3-deliveryCostMsats(entry,feesPaidMsats)});return"jit_minted"}var MINT_QUOTE_STALE_MS=24*60*60*1e3;function quoteDead(entry){if(entry.expires_at!==null)return entry.expires_at*1e3<Date.now();return Date.now()-entry.ts>MINT_QUOTE_STALE_MS}function quoteExpired(quote){return typeof quote.expiry==="number"&&quote.expiry*1e3<Date.now()}function deliveryCostMsats(entry,routingFeeMsats){return entry.invoice_msats-entry.face_sats*1e3+(routingFeeMsats??0)}async function recoverMintQuotes(ctx,summary,recovery){const open=readOpenMintQuotes(ctx.dvmId);if(open.length===0)return;const keypair=ctx.currentKeypair;let conn;if(ctx.refundNwc){try{conn=parseNwcUri(ctx.refundNwc.uri)}catch{conn=void 0}}for(const item of open){const key=`${item.quote.credit_id}|${item.quote.drain_id}`;recovery.set(key,"queued");if(!conn||!keypair){logDrain(ctx,"drain_jit_recovery_unconfigured",{credit_id:item.quote.credit_id,drain_id:item.quote.drain_id,quote_id:item.quote.quote_id,display:"A refund purchase from a previous run is unfinished and cannot be checked without the send wallet.",hint:"Re-wire `--refund-nwc` (or DVMKIT_REFUND_NWC_URL) before the next tick. The refund is deliberately held until then, because the earlier payment may already have gone out."});continue}let carried=item;if(!item.paid){const settled=await reconcileQuotePayment(ctx,conn,item.quote,item.paymentRejected);if(settled==="unknown")continue;if(settled!=="unpaid")carried={...item,paid:settled}}const outcome=await advanceMintQuote(ctx,conn,keypair,carried);if(outcome==="jit_minted"){summary.recovered++;summary.drains_jit_minted++;recovery.set(key,"resolved")}else if(outcome==="failed"){recovery.set(key,"failed")}}}async function reconcileQuotePayment(ctx,conn,entry,paymentRejected){const base3={credit_id:entry.credit_id,drain_id:entry.drain_id,quote_id:entry.quote_id,mint_url:entry.mint_url};let tx;try{tx=await(ctx.lookupInvoiceImpl??lookupInvoice)(conn,{invoice:entry.bolt11})}catch(err){const code=err instanceof NwcError?err.code:"lookup_failed";if(code==="NOT_FOUND"){logDrain(ctx,"drain_jit_payment_never_sent",base3);return"unpaid"}logDrain(ctx,"drain_jit_recovery_lookup_failed",{...base3,error_code:code,error:err instanceof Error?err.message:String(err),display:"A refund purchase from a previous run could not be checked, so it is being left alone.",hint:"Nothing is re-sent while this is unanswered \u2014 that is what stops a refund being bought twice. The next tick asks the wallet again."});return"unknown"}if(tx.settledAt){const paid={dvm_id:ctx.dvmId,credit_id:entry.credit_id,drain_id:entry.drain_id,quote_id:entry.quote_id,preimage:tx.preimage??"",...tx.feesPaidMsats!==void 0?{fees_paid_msats:tx.feesPaidMsats}:{},recovered:true};appendMintPaid(paid);logDrain(ctx,"drain_jit_payment_recovered",{...base3,fees_paid_msats:tx.feesPaidMsats??null});return{...paid,kind:"mint_paid",ts:Date.now()}}if(tx.expiresAt&&tx.expiresAt*1e3<Date.now()){logDrain(ctx,"drain_jit_payment_expired_unpaid",base3);return"unpaid"}if(paymentRejected){logDrain(ctx,"drain_jit_payment_retrying",{...base3,error_code:paymentRejected.error_code,display:"The wallet rejected the previous attempt and confirms this refund invoice is still unpaid, so the same invoice is being retried.",hint:"No new mint quote was opened. If the wallet rejects this attempt too, its error is reported and the same safe check runs again next tick."});return"unpaid"}logDrain(ctx,"drain_jit_payment_in_flight",{...base3,display:"A refund purchase from a previous run is still in flight, so nothing new was attempted for it.",hint:"The refund is held until the wallet reports one way or the other \u2014 that is what keeps it from being paid for twice."});return"unknown"}function rehydrateLocalProofs(proofs){return proofs.map(proof=>({...proof,amount:localAmount(proof.amount)}))}function amountForJson(amount2){return localAmount(amount2).toNumber()}function localAmount(amount2){if(typeof amount2==="number"||typeof amount2==="bigint"||typeof amount2==="string"){return A.from(amount2)}if(amount2&&typeof amount2==="object"&&"toBigInt"in amount2){const toBigInt3=amount2.toBigInt;if(typeof toBigInt3==="function"){const value=toBigInt3.call(amount2);if(typeof value==="bigint")return A.from(value)}}throw new TypeError("Cashu proof amount must be a primitive AmountLike or expose toBigInt()")}async function selectCoveringBatch(ctx,drain,amountSats,batches,jitAvailable){const nextStep=jitAvailable?"The refund is minted just in time from the hub instead \u2014 see the `drain_jit_*` lines below.":"";let closestShortfall;const unpriceableMints=new Set;for(const batch of batches){const keypair=ctx.keypairsByPubkey.get(toLockPubkey2(batch.lock_pubkey));if(!keypair)continue;if(batch.total_sats<amountSats)continue;let wallet;let inputProofs;let reserveSats;try{wallet=await(ctx.walletProvider??getWallet)(batch.mint_url);inputProofs=batch.proofs.map(p=>({id:p.id,amount:p.amount,secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));reserveSats=swapFeeForProofs(wallet,inputProofs)}catch(err){unpriceableMints.add(batch.mint_url);logDrain(ctx,"drain_park_batch_unpriceable",{credit_id:drain.credit_id,drain_id:drain.drain_id,mint_url:batch.mint_url,error:err instanceof Error?err.message:String(err)});continue}const requiredSats=amountSats+reserveSats;if(batch.total_sats>=requiredSats)return{batch,wallet,inputProofs,keypair};if(!closestShortfall||requiredSats-batch.total_sats<closestShortfall.requiredSats-closestShortfall.batch.total_sats){closestShortfall={batch,requiredSats}}}if(closestShortfall){const{batch,requiredSats}=closestShortfall;const shortBySats=requiredSats-batch.total_sats;logDrain(ctx,"drain_park_batch_below_swap_cost",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,mint_url:batch.mint_url,total_sats:batch.total_sats,required_sats:requiredSats,short_by_sats:shortBySats,batches_seen:batches.length,unpriceable_mints:[...unpriceableMints],hint:`A batch at ${batch.mint_url} holds ${batch.total_sats} sats, which covers the ${amountSats}-sat refund but not the ${requiredSats-amountSats} sats that mint charges to swap it. ${nextStep||`It parks automatically once ${shortBySats} more sats land on that lock key; to intervene, fund the DVM or settle manually.`}`});return void 0}if(unpriceableMints.size>0){const mints=[...unpriceableMints];logDrain(ctx,"drain_park_no_priceable_batch",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,batches_seen:batches.length,unpriceable_mints:mints,hint:`No accumulator batch could be priced: ${mints.join(", ")} would not answer for its keyset, so the refund's swap cost is unknown. This is a mint problem, not a funding one${nextStep?`. ${nextStep}`:" \u2014 the drain retries on the next tick"}, and the per-batch \`drain_park_batch_unpriceable\` lines carry each mint's error.`});return void 0}logDrain(ctx,"drain_park_insufficient_batch",{credit_id:drain.credit_id,drain_id:drain.drain_id,amount_sats:amountSats,batches_seen:batches.length,hint:`No single accumulator batch covers this refund yet. ${nextStep||"It parks automatically once enough value accumulates on one lock key at one mint; to intervene, fund the DVM or settle manually."}`});return void 0}function isMissingDrainSurface(err){return err instanceof AdminApiError&&err.status===404}function logDrain(ctx,level,fields){console.log(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,...fields}))}var DEFAULT_INTERVAL_SECONDS=60;var MIN_INTERVAL_SECONDS=10;var MAX_INTERVAL_SECONDS=3600;var DEFAULT_HOLD_ALARM_AFTER="24h";async function meltPending(handle2,opts){const ctx=await resolveContext(handle2,opts);if(!opts.watch){const summary=await runTick(ctx);emitTickSummary(summary,!!opts.human,ctx.handle);const failure=runFailure(ctx,summary);if(failure)throw failure;recordCleanRun(ctx,summary);return}await runWatchLoop(ctx,opts)}async function resolveContext(handle2,opts){const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);const{canonicalDvmId,currentKeypair,keypairsByPubkey}=selected?resolveBuilderLockKeyring(selected.dvmId):await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);const endpoint=await resolveEndpoint(handle2,opts.endpoint,selected);const drainsOnly=!!opts.drainsOnly;const payoutLnAddress=opts.payout??getBuilderDvm(canonicalDvmId)?.payout_ln_address;const payoutNwc=parsePayoutNwc(opts.payoutNwc??process.env.DVMKIT_MELT_PAYOUT_NWC_URL);if(!payoutLnAddress&&!payoutNwc&&!drainsOnly){throw new DvmError27("no_payout_destination","No payout destination configured.","Pass `--payout <ln-address>` or set a default with `dvmctl set-payout "+handle2+" <ln-address>`. To melt into a wallet reachable over NWC instead, pass `--payout-nwc <wallet connection URI>` or set DVMKIT_MELT_PAYOUT_NWC_URL.")}const refundNwcUri=opts.refundNwc??process.env.DVMKIT_REFUND_NWC_URL;const client=new AdminCashuClient({endpoint,dvmId:canonicalDvmId,privkeyHex:currentKeypair.privkey});return{handle:handle2,dvmId:canonicalDvmId,endpoint,currentKeypair,keypairsByPubkey,payoutLnAddress:payoutLnAddress??"",...payoutNwc?{payoutNwc}:{},drainsOnly,holdAlarmAfterMs:resolveHoldAlarmAfterMs(opts.holdAlarmAfter),client,drainCtx:{dvmId:canonicalDvmId,handle:handle2,client,keypairsByPubkey,currentKeypair,...refundNwcUri?{refundNwc:{uri:refundNwcUri}}:{}},fetchImpl:globalThis.fetch.bind(globalThis),human:!!opts.human}}function parsePayoutNwc(uri){if(!uri)return void 0;try{return parseNwcUri2(uri)}catch(err){throw new DvmError27("invalid_payout_nwc",`Melt payout NWC connection could not be read: ${err instanceof Error?err.message:String(err)}`,"Re-copy the `wallet connection URI` URI from the wallet the melt should land in. Nothing was melted \u2014 the run refuses rather than paying the Lightning address you did not name.")}}async function payoutInvoice(ctx,msats){if(ctx.payoutNwc){const invoice=await makeInvoice(ctx.payoutNwc,{amountMsats:msats,description:`dvmkit melt \xB7 ${ctx.handle}`});if(!invoice.invoice){throw new DvmError27("payout_invoice_missing","The payout wallet returned no bolt11 for the melt.","The wallet answered `make_invoice` without an invoice. Check the connection permits `make_invoice`, then re-run \u2014 nothing has melted.")}return invoice.invoice}return fetchLnurlPayInvoice(ctx.payoutLnAddress,msats)}function resolveHoldAlarmAfterMs(input){try{return parseDurationMs(input??DEFAULT_HOLD_ALARM_AFTER)}catch(err){throw new DvmError27("invalid_duration",err instanceof Error?err.message:String(err),"`--hold-alarm-after` takes one count and one unit \u2014 e.g. `6h`, `2d`. Raise it to give a flaky DVM more rope; lower it to hear about a stalled melt sooner.")}}async function resolveEndpoint(handle2,override,selected){if(override)return override.replace(/\/+$/,"");const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,selected?.dvmId??handle2);if(!dvm.url){throw new DvmError27("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for the deploy to finish. Otherwise pass `--endpoint <url>` (e.g. for self-hosted DVMs).")}return dvm.url.replace(/\/+$/,"")}async function runTick(ctx){const recovered=await runRecovery(ctx);const drains=await serviceDrains(ctx.drainCtx);const meltHeld=!drains.fetched||drains.cashu_liability_sats_after>0;const unknownSince=recordDrainState(ctx.dvmId,drains.fetched);const heldForMs=drainHoldElapsedMs(unknownSince);if(heldForMs!==null&&heldForMs>=ctx.holdAlarmAfterMs){logErr(ctx,"melt_pending_drain_hold_sustained",new Error(`Pending drains have been unreadable since ${unknownSince}; the payout melt has been held ever since.`),{drain_state_unknown_since:unknownSince,hold_alarm_after_ms:ctx.holdAlarmAfterMs})}const summaryBase={tick:new Date().toISOString(),dvm_id:ctx.dvmId,endpoint:ctx.endpoint,recovered,drains_seen:drains.drains_seen,drains_parked:drains.drains_parked,drains_jit_minted:drains.drains_jit_minted,drains_failed:drains.drains_failed,drains_queued:drains.drains_queued,drains_manual:drains.drains_manual,drains_channel:drains.drains_channel,drains_recovered:drains.recovered,drain_state_known:drains.fetched,drain_state_unknown_since:unknownSince,melt_held:meltHeld};const noBatches={batches_seen:0,batches_melted:0,batches_skipped:0,batches_failed:0,sats_melted:0};if(ctx.drainsOnly){logEvent(ctx,"melt_pending_skipped_drains_only",{drain_liability_sats:drains.cashu_liability_sats_after,drain_state_known:drains.fetched});return{...summaryBase,...noBatches,melt_skipped:"drains_only"}}if(meltHeld){logEvent(ctx,"melt_pending_held_for_drains",{drain_liability_sats:drains.cashu_liability_sats_after,drain_state_known:drains.fetched});return{...summaryBase,...noBatches,melt_skipped:null}}let res;try{res=await ctx.client.getPendingBatches()}catch(err){rethrowIfBadSignature(err);rethrowIfAdminStateUnavailable(err);throw new DvmError27("pending_batches_fetch_failed","Failed to fetch pending batches from the DVM admin endpoint.",err instanceof Error?err.message:String(err))}let melted=0;let skipped=0;let failed=0;let satsMelted=0;for(const batch of res.batches){const outcome=await meltBatch(ctx,batch);if(outcome==="melted"){melted++;satsMelted+=batch.total_sats}else if(outcome==="skipped")skipped++;else failed++}return{...summaryBase,batches_seen:res.batches.length,batches_melted:melted,batches_skipped:skipped,batches_failed:failed,sats_melted:satsMelted,melt_skipped:null}}async function meltBatch(ctx,batch){const batchKeypair=ctx.keypairsByPubkey.get(toLockPubkey3(batch.lock_pubkey));if(!batchKeypair){logEvent(ctx,"melt_pending_skip_unknown_lock",{mint_url:batch.mint_url,lock_pubkey:batch.lock_pubkey,row_ids:batch.row_ids,total_sats:batch.total_sats});return"skipped"}let inputFees;try{inputFees=await getInputFeesForProofs(batch.mint_url,batch.proofs)}catch(err){logErr(ctx,"melt_pending_input_fee_lookup_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<=inputFees){logEvent(ctx,"melt_pending_input_fees_exceed_batch",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,input_fees:inputFees});return"skipped"}let bolt11;try{bolt11=await payoutInvoice(ctx,(batch.total_sats-inputFees)*1e3)}catch(err){logErr(ctx,"melt_pending_payout_invoice_failed",err,{destination:ctx.payoutNwc?"nwc":"lnurl",mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,input_fees:inputFees});return"failed"}let quote;try{quote=await fetchMeltQuote(batch.mint_url,bolt11)}catch(err){logErr(ctx,"melt_pending_quote_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<quote.amountSats+quote.feeReserveSats+inputFees){const target=batch.total_sats-quote.feeReserveSats-inputFees;if(target<=0){logEvent(ctx,"melt_pending_fee_reserve_exceeds_batch",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,fee_reserve_sats:quote.feeReserveSats,input_fees:inputFees});return"skipped"}try{bolt11=await payoutInvoice(ctx,target*1e3)}catch(err){logErr(ctx,"melt_pending_payout_invoice_failed",err,{destination:ctx.payoutNwc?"nwc":"lnurl",mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,target_sats:target});return"failed"}try{quote=await fetchMeltQuote(batch.mint_url,bolt11)}catch(err){logErr(ctx,"melt_pending_quote_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids});return"failed"}if(batch.total_sats<quote.amountSats+quote.feeReserveSats+inputFees){logEvent(ctx,"melt_pending_fee_reserve_too_high",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,amount_sats:quote.amountSats,fee_reserve_sats:quote.feeReserveSats,input_fees:inputFees,retry:true});return"skipped"}}const proofs=batch.proofs.map(p=>({id:p.id,amount:p.amount,secret:p.secret,C:p.C,...p.dleq?{dleq:{...p.dleq}}:{}}));appendPreMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,mint_url:batch.mint_url,quote_id:quote.rawQuote.quote,lock_pubkey:batch.lock_pubkey,total_sats:batch.total_sats});let preimage;try{const meltResult=await meltProofsWithQuoteSigned(batch.mint_url,proofs,quote,batchKeypair.privkey);preimage=meltResult.preimage}catch(err){const recoveryDiagnostic=missingDleqRecoveryDiagnostic2(proofs,err);appendPostMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,status:"failed",error:err instanceof Error?err.message:String(err)});logErr(ctx,"melt_pending_melt_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,...recoveryDiagnostic?{recovery:recoveryDiagnostic}:{}});return"failed"}if(!preimage){logErr(ctx,"melt_pending_missing_preimage_after_success",new Error("mint returned PAID without payment_preimage"),{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote});return"failed"}try{await ctx.client.markMelted({row_ids:batch.row_ids,melt_quote_id:quote.rawQuote.quote,payment_preimage:preimage})}catch(err){rethrowIfAdminStateUnavailable(err);logErr(ctx,"melt_pending_mark_melted_failed",err,{mint_url:batch.mint_url,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote});return"failed"}appendPostMelt({dvm_id:ctx.dvmId,row_ids:batch.row_ids,quote_id:quote.rawQuote.quote,status:"success",preimage});logEvent(ctx,"melt_pending_batch_melted",{mint_url:batch.mint_url,row_ids:batch.row_ids,total_sats:batch.total_sats,quote_id:quote.rawQuote.quote});return"melted"}async function meltProofsWithQuoteSigned(mintUrl,proofs,quote,privkeyHex){const{getWallet:getWallet2}=await import("@dvmkit/sdk/internal");const wallet=await getWallet2(mintUrl);const{quote:melted}=await wallet.meltProofsBolt11(quote.rawQuote,proofs,{privkey:privkeyHex});return{preimage:melted.payment_preimage??null}}async function runRecovery(ctx){const inFlight=readInFlight(ctx.dvmId);if(inFlight.length===0)return 0;let recovered=0;for(const entry of inFlight){const result=await recoverOne(ctx,entry);if(result==="recovered")recovered++}return recovered}async function recoverOne(ctx,entry){let quote;try{quote=await checkMeltQuoteState(entry.mintUrl,entry.quoteId)}catch(err){logErr(ctx,"melt_pending_recovery_check_failed",err,{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}if(quote.state==="PENDING"){logEvent(ctx,"melt_pending_recovery_pending",{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}if(quote.state==="UNPAID"){appendPostMelt({dvm_id:ctx.dvmId,row_ids:entry.rowIds,quote_id:entry.quoteId,status:"failed",error:"ln_unpaid_on_recovery"});logEvent(ctx,"melt_pending_recovery_abandoned",{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"abandoned"}const preimage=quote.payment_preimage;if(!preimage){logErr(ctx,"melt_pending_recovery_paid_no_preimage",new Error("mint returned PAID without payment_preimage"),{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}try{await ctx.client.markMelted({row_ids:entry.rowIds,melt_quote_id:entry.quoteId,payment_preimage:preimage})}catch(err){rethrowIfAdminStateUnavailable(err);logErr(ctx,"melt_pending_recovery_mark_melted_failed",err,{mint_url:entry.mintUrl,quote_id:entry.quoteId});return"pending"}appendPostMelt({dvm_id:ctx.dvmId,row_ids:entry.rowIds,quote_id:entry.quoteId,status:"success",preimage});logEvent(ctx,"melt_pending_recovery_marked_melted",{mint_url:entry.mintUrl,quote_id:entry.quoteId,row_ids:entry.rowIds});return"recovered"}async function runWatchLoop(ctx,opts){const requestedInterval=opts.interval??DEFAULT_INTERVAL_SECONDS;const intervalSeconds=Math.min(Math.max(MIN_INTERVAL_SECONDS,requestedInterval),MAX_INTERVAL_SECONDS);let stopping=false;let inFlight=null;const fatalHolder={err:null};logEvent(ctx,"melt_pending_watch_started",{interval_seconds:intervalSeconds,endpoint:ctx.endpoint});async function tick(){if(stopping)return;if(inFlight){logEvent(ctx,"melt_pending_tick_skipped_in_flight",{});return}inFlight=(async()=>{try{const summary=await runTick(ctx);emitTickSummary(summary,ctx.human,ctx.handle);const failure=runFailure(ctx,summary);if(failure){logErr(ctx,"melt_pending_tick_not_recorded",failure)}else{recordCleanRun(ctx,summary)}}catch(err){if(err instanceof DvmError27&&err.code==="admin_bad_signature"){fatalHolder.err=err;shutdown();return}logErr(ctx,"melt_pending_tick_error",err)}finally{inFlight=null}})();await inFlight}const timer=setInterval(()=>{void tick()},intervalSeconds*1e3);const shutdown=()=>{if(stopping)return;stopping=true;clearInterval(timer);logEvent(ctx,"melt_pending_watch_stopping",{})};process.on("SIGTERM",shutdown);process.on("SIGINT",shutdown);await tick();await new Promise(resolve8=>{const poll=setInterval(()=>{if(stopping&&!inFlight){clearInterval(poll);resolve8()}},100);if(typeof poll.unref==="function")poll.unref()});if(fatalHolder.err)throw fatalHolder.err}function runFailure(ctx,summary){if(summary.batches_failed>0){return new DvmError27("batches_failed",`${summary.batches_failed} batch(es) failed during this sweep.`,"Inspect the per-batch logs above. Transient failures retry on next run.")}if(ctx.drainsOnly)return drainsUnsettled(summary);return holdSustained(summary,ctx.holdAlarmAfterMs)}function recordCleanRun(ctx,summary){recordMeltSuccess(ctx.dvmId,ctx.drainsOnly?"drains_only":"sweep",{handle:ctx.handle,batches_melted:summary.batches_melted,sats_melted:summary.sats_melted,drains_parked:summary.drains_parked})}function drainsUnsettled(summary){if(!summary.drain_state_known){return new DvmError27("drain_state_unknown",`Could not read this DVM's pending credit drains, so ${settledBeforeFetchFailed(summary)} and this run cannot say what it still owes.`,"See `drain_pending_fetch_failed` above for the underlying error, then check the DVM is reachable and still advertises the lock pubkey this mnemonic derives. Every drain counter except `drains_recovered` reads zero for want of an answer, not for want of work.")}const unsettled=summary.drains_failed+summary.drains_manual+summary.drains_queued;if(unsettled===0)return null;return new DvmError27("drains_unsettled",`${unsettled} credit drain(s) went unsettled (failed=${summary.drains_failed}, manual=${summary.drains_manual}, queued=${summary.drains_queued}).`,unsettledDrainHint(summary))}function holdSustained(summary,holdAlarmAfterMs){const heldForMs=drainHoldElapsedMs(summary.drain_state_unknown_since);if(heldForMs===null||heldForMs<holdAlarmAfterMs)return null;return new DvmError27("drain_state_unknown_sustained",`This DVM's pending credit drains have been unreadable since ${summary.drain_state_unknown_since} (${Math.floor(heldForMs/36e5)}h), so the payout melt has been held that whole time and no owed refund has parked.`,"See `drain_pending_fetch_failed` above for the underlying error. Check the DVM is up and its admin endpoint reachable \u2014 nothing melts and no caller gets refunded until it answers. Raise `--hold-alarm-after` if this DVM is expected to be offline this long.")}function settledBeforeFetchFailed(summary){if(summary.drains_recovered===0)return"nothing was settled";return`only the ${summary.drains_recovered} park(s) replayed from the local log were settled`}function unsettledDrainHint(summary){const hints=[];if(summary.drains_failed>0){hints.push("`failed`: inspect the per-drain logs above \u2014 `drain_park_insufficient_batch` means no single accumulator batch covers the refund yet, so fund the DVM or wait for accumulation and it parks on a later run.")}if(summary.drains_manual>0){hints.push("`manual`: x402/tempo refunds have no builder-held send keys \u2014 list them with `dvmctl credit drains-owed <handle>`, send each one yourself, and record it with `dvmctl credit drain-settle <handle> <credit-id> <drain-id> --tx <hash>`.")}if(summary.drains_queued>0){hints.push("`queued`: nothing was attempted for these \u2014 the `drain_jit_*` lines above say why. `drain_jit_unconfigured` means no send wallet is wired to buy the refund's ecash; `drain_jit_no_mint` means no accepted mint would quote; anything naming a payment in flight is being held on purpose so the refund cannot be bought twice.")}return hints.join(" ")}function emitTickSummary(summary,human,slug){if(human){info(`[${summary.tick}] dvm=${summary.dvm_id} seen=${summary.batches_seen} melted=${summary.batches_melted} skipped=${summary.batches_skipped} failed=${summary.batches_failed} sats=${summary.sats_melted} recovered=${summary.recovered} drains=${summary.drains_seen} parked=${summary.drains_parked} drain_failed=${summary.drains_failed} drain_queued=${summary.drains_queued} drain_manual=${summary.drains_manual}${drainStateNote(summary)}${meltNote(summary)}`)}else{json({...summary,dvmId:summary.dvm_id,slug})}}function drainStateNote(summary){if(summary.drain_state_known)return"";if(!summary.drain_state_unknown_since)return" (drain state unknown)";return` (drain state unknown since ${summary.drain_state_unknown_since})`}function meltNote(summary){if(summary.melt_skipped==="drains_only")return" (melt skipped: --drains-only)";return summary.melt_held?" (melt held for drains)":""}function logEvent(ctx,level,fields){console.log(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,...fields}))}function logErr(ctx,level,err,fields={}){console.error(JSON.stringify({level,ts:new Date().toISOString(),dvm_id:ctx.dvmId,handle:ctx.handle,error:err instanceof Error?err.message:String(err),...err instanceof DvmError27?{code:err.code,...err.hint?{hint:err.hint}:{},...err.display?{display:err.display}:{}}:err instanceof AdminApiError?{status:err.status,reason:err.body.reason,code:err.body.error}:{},...fields}))}import{DvmError as DvmError28}from"@dvmkit/sdk/internal";init_builder_store();init_defaults();init_output();function meltStatus(handle2,opts){const maxAgeMs=resolveMaxAgeMs(opts.maxAge);const records=readMeltSuccesses();const nowMs=Date.now();const except=validatedExcept(opts.except??[]);const configured=configuredTargets();const all=targets(handle2,configured);refuseAmbiguousExcept(all,except);const watched=all.filter(target=>!except.has(target.handle));const excepted=all.filter(target=>except.has(target.handle)).map(t2=>t2.handle);const configuredIds=new Set(configured.map(target=>target.dvmId));const orphaned=[...records.keys()].filter(dvmId=>!configuredIds.has(dvmId)).sort();const dvms=all.map(target=>{const record2=records.get(target.dvmId)??null;return entryFor(target,record2,except.has(target.handle)?null:maxAgeMs,nowMs)});emit(dvms,{maxAgeMs,excepted,orphaned,human:!!opts.human});if(maxAgeMs!==null&&watched.length===0)nothingToWatch(all.length>0);const stale=dvms.filter(entry=>entry.stale);if(stale.length>0)throw staleError(stale,opts.maxAge??"")}function entryFor(target,record2,maxAgeMs,nowMs){const sweep=record2?.sweep??null;const drainsOnly=record2?.drains_only??null;const sweepAgeMs=meltRunAgeMs(sweep,nowMs);const drainsOnlyAgeMs=meltRunAgeMs(drainsOnly,nowMs);return{handle:target.handle,dvm_id:target.dvmId,last_success_at:sweep?.at??null,age_seconds:sweepAgeMs===null?null:Math.floor(sweepAgeMs/1e3),stale:maxAgeMs!==null&&(sweepAgeMs===null||sweepAgeMs>maxAgeMs),batches_melted:sweep?.batches_melted??null,sats_melted:sweep?.sats_melted??null,drains_parked:sweep?.drains_parked??null,last_drains_only_at:drainsOnly?.at??null,drains_only_age_seconds:drainsOnlyAgeMs===null?null:Math.floor(drainsOnlyAgeMs/1e3)}}var HANDLE_PATTERN=/^[a-z0-9][a-z0-9-]{0,62}$/;function validatedExcept(handles){for(const handle2 of handles){if(HANDLE_PATTERN.test(handle2))continue;throw new DvmError28("invalid_except_handle",`\`--except ${handle2||"<empty>"}\` is not a DVM handle.`,"A handle is lowercase letters, digits and hyphens. This most often means the value was swallowed from the next flag \u2014 `--except --max-age 30h` reads `--max-age` as the handle and leaves no window, which turns the alarm into a plain read that always exits 0. Put a handle after every `--except`, and repeat the flag for each one.")}return new Set(handles)}function refuseAmbiguousExcept(all,except){for(const handle2 of except){const matches2=all.filter(target=>target.handle===handle2);if(matches2.length<=1)continue;throw new DvmError28("ambiguous_dvm_handle",`${matches2.length} builder.json entries claim the handle "${handle2}", so \`--except ${handle2}\` cannot pick between them.`,`Excepting by handle would drop every one of them from the alarm, including a DVM that is still melting. Candidates: ${matches2.map(target=>target.dvmId).join(", ")}. Remove the entry you no longer operate from ${BUILDER_FILE}, then except the handle.`)}}function targets(handle2,configured){if(handle2){const entry=requireBuilderDvmByHandle(handle2);return[{handle:entry.handle,dvmId:entry.canonical_dvm_id}]}return configured}function configuredTargets(){return Object.values(loadBuilderStore().dvms).map(entry=>({handle:entry.handle,dvmId:entry.canonical_dvm_id})).sort((a,b2)=>a.handle.localeCompare(b2.handle))}function nothingToWatch(allExcepted){throw new DvmError28("no_dvms_recorded",allExcepted?"Every DVM on this machine was excluded by `--except`, so `--max-age` has nothing to check.":`No DVMs are recorded in ${BUILDER_FILE}, so \`--max-age\` has nothing to check.`,allExcepted?"Exiting 0 here would be an alarm that can never fire, so this refuses instead. Drop an `--except` flag, or drop `--max-age` if you only wanted to read the record.":"Exiting 0 here would be an alarm that can never fire, so this refuses instead. The usual cause is the command running against the wrong state directory rather than a machine with no DVMs \u2014 a cron or launchd job runs in a minimal environment, so check `HOME` is the account that deployed these DVMs, or set `DVMKIT_CONFIG_DIR` explicitly in the job.")}function resolveMaxAgeMs(input){if(!input)return null;try{return parseDurationMs(input)}catch(err){throw new DvmError28("invalid_duration",err instanceof Error?err.message:String(err),"`--max-age` takes one count and one unit \u2014 e.g. `30h`, `2d`. Set it a little longer than your melt cadence so a single late run is not an alarm.")}}function staleError(stale,maxAge){const named=stale.map(entry=>entry.last_success_at?`${entry.handle} (last swept ${entry.last_success_at})`:`${entry.handle} (never swept on this machine)`).join(", ");return new DvmError28("melt_stale",`${stale.length} DVM(s) have not melted successfully within ${maxAge}: ${named}.`,"Nothing is being swept to your payout destination and no caller refund is parking for these DVMs. Check the scheduled `dvmctl melt-pending` is still running at all \u2014 a cron that stopped firing or a `--watch` daemon that was killed is the usual cause \u2014 then run it by hand and read the error it gives you. A `--drains-only` run does not count here: it settles refunds and sweeps nothing. If one of these DVMs has no Cashu rail and is never meant to melt, exclude it with `--except <handle>`.")}function emit(dvms,ctx){if(!ctx.human){json({dvms,max_age_seconds:ctx.maxAgeMs===null?null:Math.floor(ctx.maxAgeMs/1e3),excepted:ctx.excepted,orphaned:ctx.orphaned,state_file:MELT_SUCCESS_FILE});return}if(dvms.length===0){info("No configured DVMs to report on.")}else{for(const entry of dvms){info(`${entry.handle}: ${entry.last_success_at?`last clean sweep ${entry.last_success_at} (${describeAge(entry.age_seconds)} ago)`:"no clean sweep recorded on this machine"}${entry.stale?" \u2014 STALE":""}${entry.last_drains_only_at?` \xB7 last drains-only ${describeAge(entry.drains_only_age_seconds)} ago`:""}`)}}if(ctx.excepted.length>0)info(`Excepted: ${ctx.excepted.join(", ")}`);if(ctx.orphaned.length>0){info(`Orphaned melt records (not watched): ${ctx.orphaned.join(", ")}`)}info(`Record: ${MELT_SUCCESS_FILE}`)}function describeAge(seconds2){if(seconds2===null)return"unknown";if(seconds2<3600)return`${Math.floor(seconds2/60)}m`;if(seconds2<86400)return`${Math.floor(seconds2/3600)}h`;return`${Math.floor(seconds2/86400)}d`}init_output();async function metrics(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const hours2=opts.hours?Number(opts.hours):24;const result=await getMetrics(platformUrl,token,dvmId,hours2);if(opts.human){if(result.length===0){info(`No metrics for "${slug}" in the last ${hours2} hours.`);return}let total=0;let ok=0;let clientErr=0;let serverErr=0;let latencyMs=0;for(const m of result){total+=m.totalRequests;ok+=m.status2xx;clientErr+=m.status4xx;serverErr+=m.status5xx;latencyMs+=m.totalLatencyMs}const pct=n=>total>0?(n/total*100).toFixed(1):"0.0";const avgLatency=total>0?Math.round(latencyMs/total):0;info(` Last ${hours2} hours:`);info(` Total requests: ${total.toLocaleString()}`);info(` Success (2xx): ${ok.toLocaleString()} (${pct(ok)}%)`);info(` Client err (4xx): ${clientErr.toLocaleString()} (${pct(clientErr)}%)`);info(` Server err (5xx): ${serverErr.toLocaleString()} (${pct(serverErr)}%)`);info(` Avg latency: ${avgLatency}ms`)}else{json({dvmId,slug,hours:hours2,metrics:result})}}init_output();async function orgs(opts){const{token,platformUrl}=requireAuth();const activeOrg=loadActiveOrg();const memberships=await listBuilderOrgs(platformUrl,token);if(opts.human){printHuman(memberships,activeOrg);return}const sharedCount=memberships.filter(membership=>!membership.isPersonal).length;json({activeOrg,memberships:memberships.map(membership=>outputMembership(membership,activeOrg)),display:sharedCount===0?`No shared org memberships. Active context: ${activeOrg??"personal"}.`:`${sharedCount} shared org membership${sharedCount===1?"":"s"}. Active context: ${activeOrg??"personal"}.`,hint:sharedCount===0?"Create or join a shared org in the dashboard. After you join, run `dvmctl switch <handle>`.":"Run `dvmctl switch <handle>` to choose a shared org, or `dvmctl switch --personal` for your personal context."})}function outputMembership(membership,activeOrg){const active=membership.isPersonal?activeOrg===null:membership.handle===activeOrg;if(membership.isPersonal){return{displayName:membership.displayName,role:membership.role,personal:true,active}}return{handle:membership.handle,displayName:membership.displayName,role:membership.role,personal:false,active}}function printHuman(memberships,activeOrg){info(`Active context: ${activeOrg??"personal"}`);const shared=memberships.filter(membership=>!membership.isPersonal);if(shared.length===0)info("No shared org memberships.");if(memberships.length===0)return;const rows=memberships.map(membership=>({handle:membership.isPersonal?"\u2014":membership.handle,name:membership.isPersonal?`${membership.displayName??"Personal context"} (personal)`:membership.displayName??"\u2014",role:membership.role,active:membership.isPersonal?activeOrg===null:membership.handle===activeOrg}));const handleW=Math.max(6,...rows.map(row=>row.handle.length));const nameW=Math.max(4,...rows.map(row=>row.name.length));const roleW=Math.max(4,...rows.map(row=>row.role.length));info("");info(` ${"HANDLE".padEnd(handleW)} ${"NAME".padEnd(nameW)} ${"ROLE".padEnd(roleW)} ACTIVE`);for(const row of rows){info(` ${row.handle.padEnd(handleW)} ${row.name.padEnd(nameW)} ${row.role.padEnd(roleW)} ${row.active?"yes":"no"}`)}}init_output();async function pause(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await pauseDvm(platformUrl,token,dvmId);if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Paused "${slug}". It's no longer serving traffic and its machines are stopping. Data, secrets and database are untouched \u2014 run \`dvmctl resume ${slug}\` to bring it back.`)}else{json({...result,dvmId,slug})}}init_output();async function restart(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await restartMachines(platformUrl,token,dvmId,{machine:opts.machine});if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Restarted ${result.restarted} machine${result.restarted===1?"":"s"}.`)}else{json({...result,dvmId,slug})}}init_output();async function resume(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await resumeDvm(platformUrl,token,dvmId);if(opts.human){for(const m of result.machines){info(`${m.id}: ${m.state}`)}info(`Resumed "${slug}". It's serving traffic again on the same image.`)}else{json({...result,dvmId,slug})}}import{writeFileSync as writeFileSync10}from"fs";init_output();async function revenueCsv(opts){const{token,platformUrl}=requireAuth();const{csv,org}=await getRevenueCsv(platformUrl,token,{from:opts.from,to:opts.to,currency:opts.currency,rail:opts.rail,org:resolveOrgScope(opts)});info(` Org: ${org}`);if(opts.out){writeFileSync10(opts.out,csv,"utf-8");info(` CSV written to ${opts.out}`)}else{process.stdout.write(csv)}}init_output();async function revenueEvents(opts){const{token,platformUrl}=requireAuth();const result=await getRevenueEvents(platformUrl,token,{from:opts.from,to:opts.to,rail:opts.rail,limit:opts.limit!==void 0?Number(opts.limit):void 0,org:resolveOrgScope(opts)});if(opts.human){info(` Org: ${scopeLabel(result.org)}`);if(result.events.length===0){info(` No revenue events found for ${scopeLabel(result.org)}.`);return}info(` ${result.total} total events (showing ${result.events.length})`);info("");const dateW=10;const railW=Math.max(4,...result.events.map(e17=>e17.rail.length));const typeW=Math.max(4,...result.events.map(e17=>e17.source_type.length));const slugW=Math.max(3,...result.events.map(e17=>e17.dvm_slug.length));info(` ${"DATE".padEnd(dateW)} ${"RAIL".padEnd(railW)} ${"TYPE".padEnd(typeW)} ${"DVM".padEnd(slugW)} ${"AMOUNT".padStart(12)} DESCRIPTION`);for(const e17 of result.events){const cur=e17.fiat_currency.toUpperCase();info(` ${e17.date.padEnd(dateW)} ${e17.rail.padEnd(railW)} ${e17.source_type.padEnd(typeW)} ${e17.dvm_slug.padEnd(slugW)} ${formatMinor(e17.net_fiat_minor,cur).padStart(12)} ${e17.line_description}`)}}else{json(result)}}function formatMinor(minor,currency){const major=(minor/100).toFixed(2);const symbols={GBP:"\xA3",EUR:"\u20AC",USD:"$",CHF:"CHF "};const sym=symbols[currency]??`${currency} `;return`${sym}${major}`}init_output();async function revenueSummary(opts){const{token,platformUrl}=requireAuth();const result=await getRevenueSeries(platformUrl,token,{from:opts.from,to:opts.to,currency:opts.currency,org:resolveOrgScope(opts)});if(opts.human){const s=result.summary;const cur=s.fiat_currency.toUpperCase();info(` Revenue summary for ${scopeLabel(result.org)} (${s.period.from} to ${s.period.to})`);info("");info(` Total: ${formatMinor2(s.total_gross_fiat_minor,cur)} (${s.total_count} events)`);info("");if(s.by_source_type_rail.length>0){const typeW=Math.max(4,...s.by_source_type_rail.map(r=>r.source_type.length));const railW=Math.max(4,...s.by_source_type_rail.map(r=>r.rail.length));info(` ${"TYPE".padEnd(typeW)} ${"RAIL".padEnd(railW)} ${"AMOUNT".padStart(12)} COUNT`);for(const row of s.by_source_type_rail){info(` ${row.source_type.padEnd(typeW)} ${row.rail.padEnd(railW)} ${formatMinor2(row.gross_fiat_minor,cur).padStart(12)} ${row.count}`)}}if(s.cross_rate_note){info("");info(` Note: ${s.cross_rate_note}`)}for(const w of result.stale_fx_warnings){info(` ! ${w}`)}}else{json(result)}}function formatMinor2(minor,currency){const major=(minor/100).toFixed(2);const symbols={GBP:"\xA3",EUR:"\u20AC",USD:"$",CHF:"CHF "};const sym=symbols[currency]??`${currency} `;return`${sym}${major}`}init_output();async function rollback(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const{deployId,rolledBackTo}=await rollbackDvm(platformUrl,token,dvmId,{to:opts.to});if(opts.human){info(`Rolling back ${slug} to deploy ${rolledBackTo}...`);info(` Deploy queued: ${deployId}`)}else{progress({event:"queued",deploy_id:deployId,rolled_back_to:rolledBackTo})}const result=await streamDeployLogs(platformUrl,token,deployId,{onLog:line=>{if(opts.human)info(` ${line}`);else progress({event:"log",line})},onPhase:({status:status2,elapsedMs})=>{if(opts.human)info(` ${status2}... (${fmtElapsed(elapsedMs)})`);else progress({event:"phase",phase:status2,elapsed_ms:elapsedMs})}});if(opts.human){info(`
2432
2432
  Rolled back to ${result.url}`);if(result.dvmkitUrl)info(` Public URL: ${result.dvmkitUrl}`);info("")}else{json({status:"deployed",url:result.url,dvmkitUrl:result.dvmkitUrl,slug,dvmId,deployId,rolledBackTo})}}import{deriveBuilderLockKeypair as deriveBuilderLockKeypair3,DvmError as DvmError29}from"@dvmkit/sdk/internal";init_builder_mnemonic();init_builder_lock_identity();init_builder_store();init_output();async function rotate(handle2,opts){const mnemonic=readBuilderMnemonic();if(!mnemonic){throw new DvmError29("no_builder_mnemonic","No builder mnemonic found.","Run `dvmctl lock create` first to generate or import a mnemonic.")}const selected=opts.endpoint?void 0:await resolveDvm(handle2,opts);let directKeyring;let builderEntry;if(selected){builderEntry=requireSelectedBuilderDvm(selected.dvmId)}else{directKeyring=await resolveBuilderLockKeyringByHandle(handle2,opts.endpoint);builderEntry=requireSelectedBuilderDvm(directKeyring.canonicalDvmId)}const currentIndex=builderEntry.current_rotation_index;const nextIndex=currentIndex+1;const nextKeypair=deriveBuilderLockKeypair3(mnemonic,builderEntry.canonical_dvm_id,nextIndex);const target=await resolveRotationTarget(handle2,builderEntry.canonical_dvm_id,opts.endpoint,!!builderEntry.self_hosted,selected);let response;let resyncedFrom409=false;let platformSyncWarning;if(target.kind==="platform-isolate"){try{const platformResponse=await rotateDvmLockPubkey(target.platformUrl,target.token,builderEntry.canonical_dvm_id,nextKeypair.pubkey);response={current:platformResponse.current,retired:platformResponse.retired};if(platformResponse.lock_pubkey_sync_warning){platformSyncWarning=platformResponse.lock_pubkey_sync_warning}}catch(err){if(err instanceof DvmError29&&err.code==="no_op_rotation"){response={current:nextKeypair.pubkey,retired:[]};resyncedFrom409=true}else{throw err}}}else{const currentKeypair=directKeyring?.currentKeypair??deriveBuilderLockKeypair3(mnemonic,builderEntry.canonical_dvm_id,currentIndex);const client=new AdminCashuClient({endpoint:target.endpoint,dvmId:builderEntry.canonical_dvm_id,privkeyHex:currentKeypair.privkey});try{response=await client.rotateLockPubkey({new_pubkey:nextKeypair.pubkey})}catch(err){if(err instanceof AdminApiError&&err.isBadSignature){throw new DvmError29("admin_bad_signature","DVM admin endpoint rejected our signed challenge (bad_signature).","The DVM's currently-published lock pubkey does not match the privkey derived from this mnemonic + current rotation_index. Confirm `~/.dvmkit/builder.json` is in sync with the DVM.")}if(err instanceof AdminApiError&&err.status===409){response={current:nextKeypair.pubkey,retired:[]};resyncedFrom409=true}else{throw err}}if(!builderEntry.self_hosted){try{const{token,platformUrl}=requireAuth();await setDvmLockPubkey(platformUrl,token,builderEntry.canonical_dvm_id,nextKeypair.pubkey)}catch(err){throw new DvmError29("platform_lock_sync_failed",`Rotated "${handle2}" on the DVM, but could not update the platform's record of its lock pubkey: ${err instanceof Error?err.message:String(err)}`,`The rotation itself landed and builder.json is at index ${nextIndex}. Re-run this command from a machine with platform auth, or run \`dvmctl rotate ${handle2}\` without \`--endpoint\`, before the next \`dvmctl deploy\` \u2014 a deploy against the stale record re-injects the old pubkey and every melt then fails admin_bad_signature.`)}}}patchBuilderDvm(builderEntry.canonical_dvm_id,{current_rotation_index:nextIndex});if(opts.human){info(resyncedFrom409?`Resynced builder.json \u2014 DVM was already at rotation_index ${nextIndex} (prior rotate landed without bumping the local index).`:`Rotated lock pubkey for "${handle2}".`);info(` rotation_index: ${currentIndex} \u2192 ${nextIndex}`);info(` new pubkey: ${response.current}`);if(response.retired.length>0){info(` retired pubkeys in grace window:`);for(const r of response.retired){info(` ${r.pubkey} (retired ${new Date(r.retired_at_ms).toISOString()})`)}}if(platformSyncWarning){info(` Warning: platform bookkeeping sync failed \u2014 ${platformSyncWarning}`);info(" Re-run `dvmctl rotate` once the platform is reachable, or update via dashboard.")}}else{json({status:resyncedFrom409?"resynced":"rotated",handle:handle2,dvmId:builderEntry.canonical_dvm_id,slug:handle2,canonical_dvm_id:builderEntry.canonical_dvm_id,previous_rotation_index:currentIndex,current_rotation_index:nextIndex,current_pubkey:response.current,retired:response.retired,platform_sync_warning:platformSyncWarning})}}async function resolveRotationTarget(handle2,dvmId,override,selfHosted,selected){if(override){return{kind:"sdk-admin",endpoint:override.replace(/\/+$/,"")}}if(selfHosted){throw new DvmError29("endpoint_required",`Self-hosted DVM "${handle2}" needs an explicit \`--endpoint <url>\`.`,"Platform lookup is skipped for self-hosted DVMs \u2014 pass the DVM's public URL.")}if(!resolveAuth()){throw new DvmError29("auth_required","Not authenticated.","Set DVMKIT_API_KEY or run `dvmctl auth` so the CLI can resolve the DVM endpoint via the platform.")}const{token,platformUrl}=selected??requireAuth();const{dvm}=await getDvm(platformUrl,token,dvmId);if(dvm.runtime==="isolate"){return{kind:"platform-isolate",platformUrl,token}}if(!dvm.url){throw new DvmError29("dvm_url_unset",`DVM "${handle2}" has no platform URL yet.`,"If the DVM is still deploying, wait for it to finish. Otherwise pass `--endpoint <url>`.")}return{kind:"sdk-admin",endpoint:dvm.url.replace(/\/+$/,"")}}import{DvmError as DvmError30}from"@dvmkit/sdk/internal";init_output();async function scale(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);if(opts.count===void 0&&opts.size===void 0){throw new DvmError30("bad_request","Nothing to scale.",`Pass --count <n> and/or --size <vm-size>, e.g. dvmctl scale ${slug} --count 2.`)}let count;if(opts.count!==void 0){count=Number(opts.count);if(!Number.isInteger(count)||count<1||count>8){throw new DvmError30("bad_request","--count must be an integer between 1 and 8.")}}const result=await scaleDvm(platformUrl,token,dvmId,{count,size:opts.size});if(opts.human){const{before,after}=result;info(`Scaled ${result.slug}:`);if(before.maxMachines!==after.maxMachines){info(` machines: ${before.maxMachines} \u2192 ${after.maxMachines}`)}const sizeBefore=`${before.cpuCount}\xD7 ${before.cpuKind} CPU, ${before.memoryMb} MB`;const sizeAfter=`${after.cpuCount}\xD7 ${after.cpuKind} CPU, ${after.memoryMb} MB`;if(sizeBefore!==sizeAfter){info(` size: ${sizeBefore} \u2192 ${sizeAfter}`)}}else{json({...result,dvmId,slug})}}init_output();async function secretsList(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const secrets=await listSecrets(platformUrl,token,dvmId);if(opts.human){if(secrets.length===0){info("No secrets set.")}else{for(const s of secrets){info(` ${s.name} (applied ${s.last_applied_at})`)}}}else{json({dvmId,slug,secrets})}}init_builder_store();init_output();import{DvmError as DvmError31,validateLnAddress}from"@dvmkit/sdk/internal";async function setPayout(handle2,lnAddress,opts){if(opts.selfHosted&&opts.org){throw new DvmError31("invalid_scope","--self-hosted cannot be combined with --org.","Use --self-hosted for the local DVM created by `dvmctl init`, or omit it to select a platform DVM with --org.")}const selected=opts.selfHosted?void 0:await resolveDvm(handle2,opts);const existing=selected?requireSelectedBuilderDvm(selected.dvmId):requireBuilderDvmByHandle(handle2);const dvmId=selected?.dvmId??existing.canonical_dvm_id;if(!opts.skipPreflight){await validateLnAddress(lnAddress)}patchBuilderDvm(dvmId,{payout_ln_address:lnAddress});if(opts.human){info(`Payout address for "${handle2}" set to ${lnAddress}.`);info("\n`dvmctl melt-pending "+handle2+"` now melts to this address by default. Override per-run with `--payout <addr>`.")}else{json({status:"set",handle:handle2,dvmId,slug:handle2,payout_ln_address:lnAddress})}}init_output();async function ssh(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);if(!opts.command){const code=await openSshSession(platformUrl,token,dvmId,{machine:opts.machine});process.exit(code)}const result=await execCommand(platformUrl,token,dvmId,opts.command,{machine:opts.machine});if(opts.human){if(result.stdout)raw(result.stdout.replace(/\n$/,""));if(result.stderr)info(result.stderr.replace(/\n$/,""));if(result.exit_code!==0)info(`(exit code ${result.exit_code})`)}else{json({...result,dvmId,slug})}process.exit(result.exit_code)}init_output();async function status(slug,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const result=await getMachineStatus(platformUrl,token,dvmId);if(opts.human){if(result.suspensionReason==="builder_pause"){info(`"${slug}" is paused. Run \`dvmctl resume ${slug}\` to bring it back online.`)}else if(result.suspensionReason==="free_tier_idle"){info(`"${slug}" is idle \u2014 the platform stopped its machines after a quiet spell. The next request wakes it automatically; nothing to fix. Run \`dvmctl resume ${slug}\` to bring it back now.`)}else if(result.status==="suspended"){const reason=result.suspensionReason??"operator";const pause2=result.paused?` Your pause is still on record \u2014 "${slug}" returns to paused once the suspension is lifted.`:"";info(`"${slug}" is suspended by the platform (${reason}); \`dvmctl resume\` won't lift it.${pause2}`)}if(result.machines.length===0){info("No machines (DVM is not currently running any).")}for(const m of result.machines){info("");info(` Machine: ${m.id}`);info(` State: ${m.state}`);info(` Region: ${m.region??"\u2014"}`);info(` Image: ${m.image.version??"\u2014"}`);info(` Created: ${fmtDate5(m.created)}`);info(` Started: ${fmtDate5(m.started)}`);if(m.checks.length>0){info(" Checks:");for(const check2 of m.checks){const endpoint=check2.endpoint?` (${check2.endpoint})`:"";info(` ${check2.name??"?"}: ${check2.status??"?"}${endpoint}`)}}}info("");const vm=result.scale.vm_size;const size4=vm?`${vm.cpus??"?"}\xD7 ${vm.cpu_kind??"?"}, ${vm.memory_mb??"?"}MB`:"\u2014";info(` Scale: ${result.scale.count} machine${result.scale.count===1?"":"s"} (${size4})`)}else{json({...result,dvmId,slug})}}function fmtDate5(iso){if(!iso)return"\u2014";const ms=Date.parse(iso);if(Number.isNaN(ms))return iso;return new Date(ms).toISOString().replace("T"," ").replace(/\.\d+Z$/," UTC")}import{DvmError as DvmError32}from"@dvmkit/sdk/internal";init_output();async function switchCmd(handle2,opts){if(opts.personal&&handle2!=null){throw new DvmError32("bad_arguments","Cannot pass both <handle> and --personal.","Use `dvmctl switch <handle>` to set, or `dvmctl switch --personal` to clear.")}if(opts.personal){clearActiveOrg();if(opts.human){info("Switched to personal context.")}else{json({activeOrg:null})}return}if(handle2==null){const active=loadActiveOrg();if(opts.human){info(active?`Active org: ${active}`:"Active context: personal")}else{json({activeOrg:active})}return}const creds=resolveAuth();if(!creds){throw new DvmError32("auth_required","Not authenticated.","Set DVMKIT_API_KEY or run `dvmctl auth`.")}const platformUrl=process.env.DVMKIT_PLATFORM_URL??"https://api.dvmkit.dev";const membership=await getOrgMembership(platformUrl,creds.token,handle2);saveActiveOrg(membership.handle);if(opts.human){info(`Switched to ${membership.handle} (${membership.role}).`);info("Subsequent `dvmctl` commands read from this org, and deploys create DVMs under it.")}else{json({activeOrg:membership.handle,role:membership.role})}}init_output();import{DvmError as DvmError33}from"@dvmkit/sdk/internal";var MAX_LIMIT2=50;async function tempoLosses(handle2,opts){const limit=resolveLimit2(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getTempoCreditLosses({limit}));if(opts.human){if(res.losses.length===0){info("No Tempo-backed credits have been closed for lost collection.");return}for(const loss of res.losses){raw(`${loss.credit_id} ${loss.currency} former=${String(loss.former_balance_micro)} micro settled=${loss.settled_on_chain_native} voucher=${loss.highest_voucher_native} channel=${loss.channel_id}`)}info("These are audit records. The credits expose no drawable or reclaimable balance.");return}json({op:"credit_tempo_losses",handle:handle2,dvmId,slug:handle2,losses:res.losses,loss_count:res.losses.length,display:res.losses.length===0?"No Tempo-backed credits have been closed for lost collection.":`${res.losses.length} Tempo-backed credit loss record(s). These credits are terminal and expose no drawable or reclaimable balance.`,hint:"This is a read-only audit surface; there is no refund, retry, or mark-sent action."})}async function tempoWedged(handle2,opts){const limit=resolveLimit2(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getTempoDrains({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{},...opts.minAgeMs===void 0?{}:{minAgeMs:opts.minAgeMs},...opts.evidence===false?{evidence:false}:{}}));const unresolved=res.drains.filter(d=>d.written_off_at===null);const awaitingClose=unresolved.filter(d=>d.chain?.chain==="unconfirmed");const wedged=unresolved.filter(d=>d.chain?.chain!=="unconfirmed");const repairable=wedged.filter(d=>coversDebit(d));const short=wedged.filter(d=>isShortRefund(d));if(opts.human){if(res.drains.length===0){info("No Tempo closes are stuck between chain and ledger. Nothing to repair.");return}for(const drain of res.drains){raw(`${drain.credit_id} ${drain.drain_id} ${drain.status} ${describeChain(drain)} owed=${drain.amount_native??"-"} native (${String(drain.amount_micro)} ${drain.currency} micro)`+(drain.written_off_at===null?"":` written-off${noteSuffix(drain.write_off_note)}`))}info(`${wedged.length} awaiting a decision, ${repairable.length} whose on-chain refund covers the debit. Finish one with \`dvmctl credit tempo-reconcile ${handle2} <credit-id> <drain-id>\`.`);if(short.length>0){info(`${short.length} refunded the caller less than the ledger extinguished. A reconcile refuses those; \`dvmctl credit tempo-write-off\` is how one is closed.`)}if(awaitingClose.length>0){info(`${awaitingClose.length} more have no confirmed close on chain yet \u2014 those are the caller's to finish, not yours, and are not counted above.`)}if(!opts.includeResolved){info("A written-off close still holds its caller's debited balance, and is not on this page \u2014 `--include-resolved` lists those too.")}if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_tempo_wedged",handle:handle2,dvmId,slug:handle2,drains:res.drains,wedged_count:wedged.length,repairable_count:repairable.length,short_refund_count:short.length,awaiting_close_count:awaitingClose.length,...res.next_after?{next_after:res.next_after}:{},display:wedged.length===0?awaitingClose.length===0?"No Tempo channel closes are waiting on an operator decision.":`No Tempo close is waiting on an operator decision. ${awaitingClose.length} pending drain(s) have no confirmed close on chain yet, which is the caller's to finish rather than yours.`:`${wedged.length} Tempo close(s) landed on chain without the caller's credit balance being booked as paid. ${repairable.length} paid enough to cover the debit and can be finished.`+(short.length>0?` ${short.length} refunded the caller less than was extinguished \u2014 those cannot be reconciled and need a decision.`:"")+(awaitingClose.length>0?` A further ${awaitingClose.length} pending drain(s) have no confirmed close on chain yet and are not counted here.`:""),hint:"Finish one with `dvmctl credit tempo-reconcile <handle> <credit-id> <drain-id>` \u2014 the amount comes off the close receipt, not from you. If the refund was genuinely short, close it with `dvmctl credit tempo-write-off`."+(opts.includeResolved?"":" Drains you have already written off are not counted here; re-run with `--include-resolved` to see those.")})}async function tempoReconcile(handle2,creditId,drainId,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileTempoDrain({credit_id:creditId,drain_id:drainId}));if(opts.human){info(res.replayed?`Already booked (drain ${res.drain_id} on ${res.credit_id}) \u2014 nothing moved.`:`Booked the ${res.settled_amount??"on-chain"} refund against drain ${res.drain_id} on ${res.credit_id}.`);return}json({op:"credit_tempo_reconcile",handle:handle2,dvmId,slug:handle2,credit_id:res.credit_id,drain_id:res.drain_id,channel_id:res.channel_id,transaction:res.transaction,settled_amount:res.settled_amount,amount_micro:res.amount_micro,amount_native:res.amount_native,currency:res.currency,replayed:res.replayed,display:res.replayed?`This drain was already booked. Nothing moved.`:`Booked drain ${res.drain_id} on ${res.credit_id} against the refund the channel close already paid (${res.transaction??"no transaction recorded"}).`,hint:"The reclaim is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function tempoWriteOff(handle2,creditId,drainId,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffTempoDrain({credit_id:creditId,drain_id:drainId,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (drain ${res.drain_id} on ${res.credit_id})${noteSuffix(res.note)}. Nothing changed.`:`Wrote off drain ${res.drain_id} on ${res.credit_id}. Nothing was booked.`);return}json({op:"credit_tempo_write_off",handle:handle2,dvmId,slug:handle2,credit_id:res.credit_id,drain_id:res.drain_id,channel_id:res.channel_id,amount_micro:res.amount_micro,amount_native:res.amount_native,currency:res.currency,note:res.note,replayed:res.replayed,display:res.replayed?`This drain was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:"Closed this drain without booking it. No ledger row moved: the caller keeps the on-chain refund the close paid them, and the shortfall against what was extinguished is not expressible here \u2014 make it good out of band if you owe it.",hint:"Reversible: `dvmctl credit tempo-reconcile` still accepts a written-off drain if you change your mind."})}function coversDebit(drain){return compareRefund(drain)==="covers"}function isShortRefund(drain){return compareRefund(drain)==="short"}function compareRefund(drain){if(drain.chain?.chain!=="found"||drain.amount_native===null)return"incomparable";try{return BigInt(drain.chain.refundedToPayer)>=BigInt(drain.amount_native)?"covers":"short"}catch{return"incomparable"}}function describeChain(drain){const evidence=drain.chain;if(!evidence)return"chain=not-checked";switch(evidence.chain){case"found":return`chain=refunded:${evidence.refundedToPayer}${isShortRefund(drain)?" (short)":""}`;case"unreported":return"chain=no-receipt";case"unconfirmed":return"chain=no-close";default:return"chain=unreachable"}}function noteSuffix(note){return note?` \u2014 note on record: "${note}"`:""}function resolveLimit2(limit){if(limit===void 0)return 20;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT2){throw new DvmError33("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT2} (got ${limit}).`,"Leave it unset for the default page of 20, then follow `next_after` for the rest. The ceiling is low because every row on the page may cost a chain read.")}return limit}function renderTrace(trace,opts){const lines=[];const c=opts.color?ansi:noColor;const statusLabel=trace.status==="error"?c.red("ERROR"):c.green("OK");const dimParts=[];for(const key of["builder_id","dvm_id","dvm_slug","rail"]){const val=trace.dimensions[key];if(val!=null)dimParts.push(`${key.replace("_id","")} ${stringify2(val)}`)}lines.push(`trace ${trace.trace_id} duration ${fmtMs(trace.duration_ms)} status ${statusLabel}`+(dimParts.length>0?` ${dimParts.join(" ")}`:""));lines.push("");if(trace.truncated){lines.push(c.yellow("WARNING: trace contains more than 10000 spans; showing first 10000 ordered by start time."));lines.push(c.yellow("This usually means a runaway span emitter (e.g. a tight loop logging)."));lines.push("")}const childrenOf=new Map;for(const span of trace.spans){const key=span.parent_span_id;let list2=childrenOf.get(key);if(!list2){list2=[];childrenOf.set(key,list2)}list2.push(span)}for(const list2 of childrenOf.values()){list2.sort((a,b2)=>a.started_at_ms-b2.started_at_ms)}const flat=[];function walk3(parentId,prefixStack){const children=childrenOf.get(parentId);if(!children)return;for(let i=0;i<children.length;i++){const span=children[i];const isLast=i===children.length-1;const hasChildren=childrenOf.has(span.span_id);let glyph;if(prefixStack.length===0){glyph=hasChildren?"\u2500\u252C\u2500 ":"\u2500\u2500\u2500 "}else{const connector=isLast?"\u2514":"\u251C";const branch=hasChildren?"\u2500\u252C\u2500 ":"\u2500\u2500 ";glyph=connector+branch}const prefix=prefixStack.join("")+glyph;flat.push({span,prefix});const continuation=prefixStack.length===0?"":isLast?" ":"\u2502 ";walk3(span.span_id,[...prefixStack,continuation])}}walk3(null,[]);const rendered=new Set(flat.map(e17=>e17.span.span_id));for(const span of trace.spans){if(!rendered.has(span.span_id)){flat.push({span,prefix:"\u2500\u2500\u2500 "})}}const traceStart=trace.started_at_ms;for(const{span,prefix}of flat){const offset=span.started_at_ms-traceStart;const offsetStr=c.dim(`${String(offset).padStart(5)}ms `);const isLog=span.duration_ms===0;let line;if(isLog){const icon=levelIcon(span.level,c);const attrs=inlineKV(span.dimensions,span.attributes);line=`${offsetStr}${c.dim(prefix)}${icon} ${span.name}${attrs}`}else{const dur=`[${fmtMs(span.duration_ms).padStart(7)}]`;const statusWord=span.status==="error"?c.red("error"):span.status==="ok"?c.green("ok"):"";const attrs=inlineKV(span.dimensions,span.attributes);line=`${offsetStr}${c.dim(prefix)}${span.name}${attrs} ${dur} ${statusWord}`}lines.push(line)}if(Object.keys(trace.dimensions).length>0){lines.push("");lines.push("attributes:");const maxKeyLen=Math.max(...Object.keys(trace.dimensions).map(k2=>k2.length));for(const[k2,v]of Object.entries(trace.dimensions)){lines.push(` ${k2.padEnd(maxKeyLen+2)}${String(v)}`)}}return lines.join("\n")}function fmtMs(ms){if(ms<1e3)return`${ms}ms`;if(ms<6e4)return`${(ms/1e3).toFixed(1)}s`;return`${(ms/6e4).toFixed(1)}m`}function levelIcon(level,c){switch(level){case"error":return c.red("\u2716");case"warn":return c.yellow("\u26A0");default:return c.blue("\u2139")}}function inlineKV(dims,attrs){const headerKeys=new Set(["builder_id","dvm_id","dvm_slug","rail"]);const merged=[];for(const[k2,v]of Object.entries(dims)){if(!headerKeys.has(k2))merged.push([k2,v])}for(const[k2,v]of Object.entries(attrs)){merged.push([k2,v])}if(merged.length===0)return"";const parts=merged.map(([k2,v])=>{const s=stringify2(v);return`${k2}: ${s.length>60?s.slice(0,57)+"...":s}`});return` {${parts.join(", ")}}`}function stringify2(val){if(typeof val==="object"&&val!==null)return JSON.stringify(val);return String(val)}var ansi={red:s=>`\x1B[31m${s}\x1B[0m`,green:s=>`\x1B[32m${s}\x1B[0m`,yellow:s=>`\x1B[33m${s}\x1B[0m`,blue:s=>`\x1B[34m${s}\x1B[0m`,dim:s=>`\x1B[2m${s}\x1B[0m`};var noColor={red:s=>s,green:s=>s,yellow:s=>s,blue:s=>s,dim:s=>s};init_output();async function traceGet(traceId,opts){const{token,platformUrl}=requireAuth();const result=await getBuilderTrace(platformUrl,token,traceId,{org:resolveOrgScope(opts)});if(opts.human){const useColor=opts.color!==false&&process.stdout.isTTY;info(renderTrace(result,{color:useColor}))}else{json(result)}}import{DvmError as DvmError34}from"@dvmkit/sdk/internal";init_output();async function transfer(slug,to,opts){const{token,platformUrl,dvmId}=await resolveDvm(slug,opts);const body=opts.createOrg?{to:{create:{handle:to,displayName:opts.displayName}}}:{to};const result=await transferDvm(platformUrl,token,dvmId,body);if(result.status==="noop"){throw new DvmError34("noop",`DVM "${slug}" is already in the target destination.`)}if(opts.human){if(result.org){info(`Created org "${result.org.handle}" and transferred ${slug} into it.`)}else{info(`Transferred ${slug} \u2192 ${result.to}`)}if(result.treasuryRekeyPending){info("Warning: Treasury re-key pending \u2014 redeploy the DVM (`dvmctl deploy`) to update the Cashu lock pubkey under the new org.")}}else{json({...result,dvmId,slug})}}init_builder_store();init_output();async function whoami(opts){const{token,platformUrl}=requireAuth();const activeOrg=loadActiveOrg();const identity=readLocalIdentity();const[builder,memberships]=await Promise.all([getAuthenticatedBuilderProfile(platformUrl,token),listBuilderOrgs(platformUrl,token)]);if(opts.human){printHuman2(builder,memberships,activeOrg,identity);return}json({identity_pubkey:identity.pubkey,identity_status:identity.status,...identity.status==="unreadable"?{identity_detail:identity.detail}:{},builder,activeOrg,memberships:memberships.map(membership=>outputMembership2(membership,activeOrg)),display:`Builder ${builder.email??builder.handle??builder.id}. Active context: ${activeOrg??"personal"}.`,hint:identity.status==="missing"?"Run `dvmctl identity create` before a cloud or Connect deploy.":identity.status==="unreadable"?"Repair the local builder configuration before a cloud or Connect deploy.":"Every listed membership can deploy a new DVM. The role shows its existing access level."})}function readLocalIdentity(){try{const pubkey=loadIdentityPubkey();if(pubkey===null)return{status:"missing",pubkey:null};if(!/^[0-9a-f]{64}$/.test(pubkey)){return{status:"unreadable",pubkey:null,detail:"Local builder identity pubkey is not a 64-character hexadecimal key."}}return{status:"available",pubkey}}catch(error51){return{status:"unreadable",pubkey:null,detail:error51 instanceof Error?error51.message:String(error51)}}}function outputMembership2(membership,activeOrg){const active=membership.isPersonal?activeOrg===null:membership.handle===activeOrg;if(membership.isPersonal){return{displayName:membership.displayName,role:membership.role,personal:true,deployable:true,active}}return{handle:membership.handle,displayName:membership.displayName,role:membership.role,personal:false,deployable:true,active}}function printHuman2(builder,memberships,activeOrg,identity){info(`Builder: ${builder.displayName??builder.email??builder.handle??builder.id}`);info(` id: ${builder.id}`);info(` email: ${builder.email??"\u2014"}`);info(` handle: ${builder.handle??"\u2014"}`);info(` identity pubkey: ${identity.pubkey??"\u2014"} (${identity.status})`);if(identity.status==="unreadable")info(` identity detail: ${identity.detail}`);info(`Active context: ${activeOrg??"personal"}`);if(memberships.length===0){info("No org memberships found.");return}const rows=memberships.map(membership=>({handle:membership.isPersonal?"\u2014":membership.handle,name:membership.isPersonal?`${membership.displayName??"Personal context"} (personal)`:membership.displayName??"\u2014",role:membership.role,active:membership.isPersonal?activeOrg===null:membership.handle===activeOrg}));const handleW=Math.max(6,...rows.map(row=>row.handle.length));const nameW=Math.max(4,...rows.map(row=>row.name.length));const roleW=Math.max(4,...rows.map(row=>row.role.length));info("");info(` ${"HANDLE".padEnd(handleW)} ${"NAME".padEnd(nameW)} ${"ROLE".padEnd(roleW)} DEPLOY ACTIVE`);for(const row of rows){info(` ${row.handle.padEnd(handleW)} ${row.name.padEnd(nameW)} ${row.role.padEnd(roleW)} yes ${row.active?"yes":"no"}`)}}init_output();async function x402ExactWedged(handle2,opts){const limit=opts.limit??20;if(!Number.isSafeInteger(limit)||limit<1||limit>50){throw new Error("limit must be an integer in 1..50")}const{client,dvmId}=await connectAdmin(handle2,opts);const response=await adminCall(()=>client.getX402ExactSettlements({limit,...opts.includeResolved?{includeResolved:true}:{}}));if(opts.human){if(response.settlements.length===0){info("No exact x402 payments are waiting for recovery.");return}for(const settlement of response.settlements){raw(`${settlement.settlement_intent_id} ${settlement.status} tx=${settlement.transaction??"unknown"} credit=${settlement.credit_id??"-"} job=${settlement.job_id??"-"}`)}info(`Finish a chain-evidenced payment with \`dvmctl credit x402-exact-reconcile ${handle2} <settlement-intent-id>\`.`);return}json({op:"credit_x402_exact_wedged",handle:handle2,dvmId,slug:handle2,settlements:response.settlements,wedged_count:response.settlements.filter(settlement=>settlement.status!=="complete"&&settlement.status!=="rejected").length,display:response.settlements.length===0?"No exact x402 payments are waiting for recovery.":`${response.settlements.length} exact x402 payment(s) need an operator decision or local finalization.`,hint:"Run `dvmctl credit x402-exact-reconcile <handle> <settlement-intent-id>` only after this queue shows the authorization; the DVM derives the transaction and amount from its own evidence."})}async function x402ExactReconcile(handle2,settlementIntentId,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const response=await adminCall(()=>client.reconcileX402ExactSettlement(settlementIntentId));const admissionPending=response.settlement_status==="local_committed";if(opts.human){info(admissionPending?`Recorded settlement ${response.transaction??response.settlement_intent_id}; its original job will be admitted when the caller retries the same request.`:response.replayed?`Settlement ${response.settlement_intent_id} was already recorded locally. Nothing changed.`:`Recorded settlement ${response.transaction??response.settlement_intent_id} against its original local effect.`);return}json({op:"credit_x402_exact_reconcile",handle:handle2,dvmId,slug:handle2,...response,display:admissionPending?`Recorded exact x402 transaction ${response.transaction??"from the DVM's chain evidence"}; its original job is still waiting for admission.`:response.replayed?"This exact x402 payment was already finalized locally. Nothing changed.":`Finalized the original local effect for exact x402 transaction ${response.transaction??"recorded by the DVM"}.`,hint:admissionPending?"Have the caller retry the identical signed request and x402 authorization. The DVM will reuse the settled payment and original job id; do not create a new authorization.":response.job_id?"The caller can retry or query its original request; no new payment authorization is needed.":"The original credit funding is now recorded; the caller can read that credit without paying again."})}init_output();import{DvmError as DvmError35}from"@dvmkit/sdk/internal";var MAX_LIMIT3=50;async function x402Wedged(handle2,opts){const limit=resolveLimit3(opts.limit);const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.getX402Settlements({limit,...opts.after===void 0?{}:{after:opts.after},...opts.includeResolved?{includeResolved:true}:{},...opts.minAgeMs===void 0?{}:{minAgeMs:opts.minAgeMs},...opts.evidence===false?{evidence:false}:{}}));const unresolved=res.settlements.filter(s=>s.status!=="written_off");const repairable=unresolved.filter(s=>s.chain?.chain==="found");const held=res.settlements.filter(s=>s.spending_blocked&&(s.credit_balance_micro??0)>0);if(opts.human){if(res.settlements.length===0){info("No x402 settlements are stuck between chain and ledger. Nothing to repair.");return}for(const settlement of res.settlements){raw(`${settlement.settlement_id} ${settlement.status} ${describeChain2(settlement)} credit=${settlement.credit_id??"-"} drain=${settlement.drain_id??"-"} ${describeHold(settlement)}`)}info(`${unresolved.length} awaiting a decision, ${repairable.length} with a matching refund on chain. Finish one with \`dvmctl credit x402-reconcile ${handle2} <settlement-id>\`.`);if(held.length>0){info(`${held.length} of these hold a caller's balance: it can be reclaimed but not spent until the settlement is closed out.`)}if(!opts.includeResolved){info("A written-off settlement keeps holding its caller's balance for good, and is not on this page \u2014 `--include-resolved` lists those too.")}if(res.next_after)info(`More rows: re-run with \`--after ${res.next_after}\`.`);return}json({op:"credit_x402_wedged",handle:handle2,dvmId,slug:handle2,settlements:res.settlements,wedged_count:unresolved.length,repairable_count:repairable.length,holding_balance_count:held.length,...res.next_after?{next_after:res.next_after}:{},display:unresolved.length===0?"No x402 refunds are waiting on an operator decision.":`${unresolved.length} x402 refund(s) settled on chain without the caller's credit balance being cleared. ${repairable.length} of them have a matching refund the chain can confirm.`+(held.length>0?` ${held.length} are holding a caller's balance, which they can reclaim but not spend until the settlement is closed out.`:""),hint:"Finish one with `dvmctl credit x402-reconcile <handle> <settlement-id>` \u2014 the amount comes off the chain, not from you. If the chain and the ledger disagree, close it with `dvmctl credit x402-write-off`."+(opts.includeResolved?"":" A written-off settlement holds its caller's balance for good and is not counted here; re-run with `--include-resolved` to see those.")})}async function x402Reconcile(handle2,settlementId,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.reconcileX402Settlement({settlement_id:settlementId}));if(opts.human){info(res.replayed?`Already booked (drain ${res.drain_id} on ${res.credit_id}) \u2014 nothing moved.`:`Booked the ${res.settled_amount??"on-chain"} refund against drain ${res.drain_id} on ${res.credit_id}.`);return}json({op:"credit_x402_reconcile",handle:handle2,dvmId,slug:handle2,settlement_id:res.settlement_id,channel_id:res.channel_id,credit_id:res.credit_id,drain_id:res.drain_id,transaction:res.transaction,settled_amount:res.settled_amount,amount_micro:res.amount_micro,currency:res.currency,replayed:res.replayed,display:res.replayed?`This settlement was already booked against drain ${res.drain_id}. Nothing moved.`:`Cleared the caller's balance on ${res.credit_id} against the refund the chain already paid (${res.transaction??"no transaction recorded"}).`,hint:"The reclaim is reported to the platform on the DVM's own retry loop, so its liability view catches up without another command."})}async function x402WriteOff(handle2,settlementId,opts){const{client,dvmId}=await connectAdmin(handle2,opts);const res=await adminCall(()=>client.writeOffX402Settlement({settlement_id:settlementId,...opts.note===void 0?{}:{note:opts.note}}));if(opts.human){info(res.replayed?`Already written off (${res.settlement_id})${res.note?` \u2014 note on record: "${res.note}"`:""}. Nothing changed.`:`Wrote off settlement ${res.settlement_id}. Nothing was booked.`);return}json({op:"credit_x402_write_off",handle:handle2,dvmId,slug:handle2,settlement_id:res.settlement_id,channel_id:res.channel_id,effect_id:res.effect_id,note:res.note,replayed:res.replayed,display:res.replayed?`This settlement was already written off${res.note?`, with the note "${res.note}"`:""}. Nothing changed, and your note was not recorded over it.`:"Closed this settlement without booking it. No ledger row moved: if the refund did reach the chain, the caller still holds the matching credit balance here and can reclaim it with a new drain.",hint:"Reversible: `dvmctl credit x402-reconcile` still accepts a written-off settlement if you change your mind."})}function describeChain2(settlement){const evidence=settlement.chain;if(!evidence)return"chain=not-checked";switch(evidence.chain){case"found":return`chain=paid:${evidence.amount}`;case"absent":return"chain=nothing-found";case"unbookmarked":return"chain=no-bookmark";default:return"chain=unreachable"}}function describeHold(settlement){const balance=settlement.credit_balance_micro;if(balance===null)return"balance=-";const amount2=`${String(balance)}${settlement.credit_currency?` ${settlement.credit_currency}`:""} micro`;return settlement.spending_blocked?`balance=${amount2} (spend-blocked)`:`balance=${amount2}`}function resolveLimit3(limit){if(limit===void 0)return 20;if(!Number.isSafeInteger(limit)||limit<1||limit>MAX_LIMIT3){throw new DvmError35("invalid_limit",`--limit must be an integer in 1..${MAX_LIMIT3} (got ${limit}).`,"Leave it unset for the default page of 20, then follow `next_after` for the rest. The ceiling is low because every row on the page may cost a chain log scan.")}return limit}init_output();import{asProviderText as asProviderText2,DvmError as DvmError36}from"@dvmkit/sdk/internal";init_output();import{formatFiat}from"@dvmkit/sdk";import{formatUsd,fundingRailLabel,shellQuoteArg,x402NetworkByCaip2,x402NetworkLabel}from"@dvmkit/sdk/internal";function takeCreditNoticeFields(){const taken={...pending};pending={};return taken}var pending={};init_output();import{shellQuoteArg as shellQuoteArg2}from"@dvmkit/sdk/internal";function takeFeedbackNoticeFields(){const taken={...pending2};pending2={};return taken}var pending2={};function catchErrors(fn,human){try{const result=fn();if(result instanceof Promise){result.then(()=>process.exit(process.exitCode??0),err=>handleError(err,human))}}catch(err){handleError(err,human)}}function handleError(err,human){if(err instanceof DvmError36){if(human){const dataDisplay=err.data?.display;const display=typeof dataDisplay==="string"?dataDisplay:err.display;const dataNextStep=err.data?.next_step;const nextStep=typeof dataNextStep==="string"?dataNextStep:void 0;info([`Error: ${err.message}`,display,nextStep,err.hint].filter(Boolean).join("\n"))}else{const error51={code:err.code,message:err.message,...err.display?{display:err.display}:{},...err.hint?{hint:err.hint}:{},...err.data??{}};for(const field of["message","display","hint"]){const value=error51[field];if(err.proseSources?.[field]==="provider"&&typeof value==="string"){error51[field]=asProviderText2(value)}}json({error:error51,...takeCreditNoticeFields(),...takeFeedbackNoticeFields()})}process.exit(1)}const diagnostic=unknownErrorDiagnostic(err);const message=`An unexpected internal error occurred (${diagnostic.label}).`;if(human){info(`Error: ${message}`)}else{json({error:{code:"internal",message,error_name:diagnostic.name,...diagnostic.code?{error_code:diagnostic.code}:{}},...takeCreditNoticeFields(),...takeFeedbackNoticeFields()})}process.exit(1)}function unknownErrorDiagnostic(err){const name=unknownErrorName(err);const code=unknownErrorCode(err);return{name,...code?{code}:{},label:code?`${name}:${code}`:name}}function unknownErrorName(err){if(!(err instanceof Error))return`thrown_${typeof err}`;try{const prototype=Object.getPrototypeOf(err);const constructor=prototype?.constructor;const name=typeof constructor==="function"?constructor.name:"";return/^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/.test(name)?name:"Error"}catch{return"Error"}}function unknownErrorCode(err){if(typeof err!=="object"&&typeof err!=="function"||err===null)return void 0;try{const code=err.code;return typeof code==="string"&&/^E[A-Z0-9_]{1,31}$/.test(code)?code:void 0}catch{return void 0}}init_http();init_output();function buildProgram(){const program2=new Command;program2.name("dvmctl").description("Builder CLI for creating and deploying DVMs").version(CLI_VERSION);program2.command("create <name>").description("Scaffold a new DVM project").option("--price <price>","Static price (default: $0.01)").option("--name <name>","DVM display name (default: derived from directory)").option("--description <text>","DVM description").action((dir,opts)=>{catchErrors(()=>create(dir,opts))});program2.command("init <handle>").description("Bootstrap a self-hosted DVM: generate a canonical_dvm_id and persist it to ~/.dvmkit/builder.json").option("--self-hosted","Mark the DVM as self-hosted (required \u2014 platform-hosted DVMs get their canonical_dvm_id at deploy time)").option("--human","Human-readable output instead of JSON").action((handle2,opts)=>{catchErrors(()=>{init(handle2,opts)},opts.human)});program2.command("dev <handler>").description("Start a local development server for a DVM handler").option("--port <port>","HTTP port (default: PORT from .env or 3000)").option("--no-reload","Disable hot reload on file changes").action((handler,opts)=>{catchErrors(async()=>{const{dev:dev2}=await Promise.resolve().then(()=>(init_dev(),dev_exports));await dev2(handler,opts)})});program2.command("deploy").description("Deploy a DVM project").option("--target <target>","Deploy target: cloud, self-host, or local").option("--name <slug>","DVM slug for cloud deploy (default: from package.json)").option("--runtime <runtime>","Runtime tier: container or isolate (default: auto-detect)").option("--endpoint-url <url>","Connect-tier upstream URL (required on plans where the platform doesn't host compute)").option("--local","Register a locally-running DVM into the local platform (no Fly), pointing at its localhost URL").option("--url <url>","Localhost URL the DVM is running at, for --local (default http://localhost:<PORT or 3000>)").option("--dry-run","Validate the project (slug, package.json, vmConfig) and build the Dockerfile locally without contacting the platform").option("--yes","Skip the SDK-staleness confirmation prompt and proceed automatically").option("--allow-dirty","Allow a cloud image to include uncommitted packaged files").option("--org <handle>","Deploy under this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>deploy(opts),opts.human)});program2.command("deploy-status <deployId>").description("Show the current status of a deploy by ID (recovery handle for `dvmctl deploy`)").option("--human","Human-readable output instead of JSON").action((deployId,opts)=>{catchErrors(()=>deployStatus(deployId,opts),opts.human)});program2.command("validate").description("Validate `package.json#dvmkit` locally (vmConfig + inheritSecrets) without contacting the platform").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>{validate4(opts)},opts.human)});program2.command("doctor").description("Diagnose builder-side local state").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>{builderDoctor(opts)},opts.human)});program2.command("serve <handler>").description("Start a production DVM server").option("--port <port>","HTTP port (default: PORT env var or 8080)").option("--dvm <handle>","Serve under the builder-store lock identity for <handle> \u2014 advertises the lock pubkey derived from your recovery mnemonic, so `dvmctl melt-pending <handle>` and `dvmctl rotate <handle>` can sign for this DVM (self-hosted deploys)").action((handler,opts)=>{catchErrors(async()=>{const{builderServe:builderServe2}=await Promise.resolve().then(()=>(init_serve(),serve_exports));await builderServe2(handler,opts)})});program2.command("list").description("List the deployed DVMs in your current org context").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>list(opts),opts.human)});program2.command("info <slug>").description("Show details for a deployed DVM").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>dvmInfo(slug,opts),opts.human)});program2.command("status <slug>").description("Show a deployed DVM's live Fly runtime state (machines, region, image, health)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>status(slug,opts),opts.human)});program2.command("destroy <slug>").description("Destroy a deployed DVM and its paired Postgres cluster").option("--confirm","Skip confirmation prompt (required in non-interactive mode)").option("--keep-db","Preserve the Postgres cluster for re-attachment on redeploy").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>destroy(slug,opts),opts.human)});program2.command("metrics <slug>").description("Show request metrics for a deployed DVM").option("--hours <hours>","Number of hours to look back (default: 24)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>metrics(slug,opts),opts.human)});program2.command("events <slug>").description("Show recent events for a deployed DVM (deploys, restarts, scale, env, machine state)").option("--since <duration>","Look-back window (e.g. 30s, 5m, 2h, 7d; default: 7d)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>events(slug,opts),opts.human)});const feedbackCmd=program2.command("feedback").description("View caller-submitted feedback and toggle collection per DVM");feedbackCmd.command("list <slug>").description("List caller-submitted feedback for one of your DVMs (newest-first)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--since <duration>","Look-back window (e.g. 24h, 7d, ISO 8601)").option("--cursor <c>","Opaque cursor from a prior page's next_cursor").option("--limit <n>","Max rows per page (1-100, default 50)").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>feedbackList(slug,opts),opts.human)});feedbackCmd.command("enable <slug>").description("Enable caller-feedback collection for one of your DVMs").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>feedbackEnable(slug,opts),opts.human)});feedbackCmd.command("disable <slug>").description("Disable caller-feedback collection for one of your DVMs").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>feedbackDisable(slug,opts),opts.human)});const traceCmd=program2.command("trace").description("Inspect request traces for your DVMs");traceCmd.command("get <traceId>").description("Fetch a trace by ID (isolate-dataset spans only)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--human","Human-readable trace waterfall instead of JSON").option("--no-color","Disable ANSI colors in human output").action((traceId,opts)=>{catchErrors(()=>traceGet(traceId,opts),opts.human)});program2.command("env <slug>").description("View or update environment variables for a deployed DVM").option("--set <KEY=value...>","Set environment variables (repeatable)").option("--unset <KEY...>","Unset environment variables (repeatable)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>env(slug,opts),opts.human)});program2.command("logs <slug>").description("View or stream logs for a deployed DVM").option("--tail","Stream live logs until interrupted (Ctrl-C)").option("--since <duration>","Only show logs newer than this (e.g. 30s, 5m, 2h, 1d)").option("--lines <count>","Max recent lines to show in non-tail mode (default: 100)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").action((slug,opts)=>{catchErrors(()=>logs(slug,opts))});program2.command("ssh <slug>").description("Open an interactive shell (or run a one-shot --command) on a deployed DVM's Fly machine").option("--command <cmd>","Run a one-shot command instead of opening an interactive shell").option("--machine <id>","Target a specific machine (default: first running machine)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON (one-shot --command only)").action((slug,opts)=>{catchErrors(()=>ssh(slug,opts),opts.human)});program2.command("restart <slug>").description("Restart a deployed DVM's Fly machines without a redeploy").option("--machine <id>","Restart a single machine (default: all running machines)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>restart(slug,opts),opts.human)});program2.command("pause <slug>").description("Take a DVM offline \u2014 stops its machines, keeps its slug, data and database").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>pause(slug,opts),opts.human)});program2.command("resume <slug>").description("Bring a paused or idle DVM back online on the same image (no redeploy)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>resume(slug,opts),opts.human)});const dbCmd=program2.command("db").description("Connect to a DVM's per-DVM database");dbCmd.command("connect <slug>").description("Open an interactive psql session against the DVM's database (requires local psql)").option("--machine <id>","Broker through a specific machine (default: first running machine)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Print connection status to stderr").action((slug,opts)=>{catchErrors(()=>dbConnect(slug,opts),opts.human)});dbCmd.command("proxy <slug>").description("Bind a local port to the DVM's database so any Postgres client can connect (pg_dump, drizzle-kit, GUI clients)").option("--port <n>","Local port to bind (default: an OS-assigned free port)").option("--machine <id>","Broker through a specific machine (default: first running machine)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>dbProxy(slug,opts),opts.human)});program2.command("rollback <slug>").description("Revert a deployed DVM to a previous successful deploy's image").option("--to <deploy-id>","Roll back to a specific deploy_id (default: previous deploy)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>rollback(slug,opts),opts.human)});program2.command("scale <slug>").description("Change a deployed DVM's machine count and/or VM size (no redeploy)").option("--count <n>","Number of machines to run (1-8)").option("--size <vm-size>","Fly VM size, e.g. shared-cpu-1x, shared-cpu-2x, performance-1x").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>scale(slug,opts),opts.human)});program2.command("switch [handle]").description("Set the active org context for reads and deploys. Omit handle to show current; --personal to clear.").option("--personal","Clear org context (read and deploy as personal builder)").option("--human","Human-readable output instead of JSON").action((handle2,opts)=>{catchErrors(()=>switchCmd(handle2,opts),opts.human)});program2.command("orgs").description("List your personal and shared org memberships").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>orgs(opts),opts.human)});program2.command("whoami").description("Show your local identity, builder profile, and org access").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>whoami(opts),opts.human)});program2.command("transfer <slug> <to>").description("Transfer a DVM to an org (by handle) or back to personal").option("--create-org","Create the target org if it doesn't exist (to = desired handle)").option("--display-name <name>","Display name for the new org (with --create-org)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,to,opts)=>{catchErrors(()=>transfer(slug,to,opts),opts.human)});program2.command("auth").description("Authenticate with the dvmkit platform").option("--status","Show current auth status").option("--logout","Remove stored credentials").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>auth(opts),opts.human)});const handleCmd=program2.command("handle").description("Manage your builder handle for *.dvmkit.ai subdomains");handleCmd.command("set").argument("<handle>","Handle to claim (2-20 chars, lowercase, no '--')").option("--human","Human-readable output instead of JSON").action((h,opts)=>{catchErrors(()=>handle(h,opts),opts.human)});handleCmd.command("check").argument("<handle>","Handle to check availability for").option("--human","Human-readable output instead of JSON").action((h,opts)=>{catchErrors(()=>handleCheck(h,opts),opts.human)});const identityCmd=program2.command("identity").description("Manage your builder signing identity (deploy attestations, /v1/info#builder)");identityCmd.command("create").description("Generate a per-workstation secp256k1 identity keypair (tracked change). Persists pubkey to ~/.dvmkit/builder.json and secret to ~/.dvmkit/builder.key (0600).").option("--force","Overwrite an existing identity. Any DVMs deployed under the old key will need re-deploy to refresh their /v1/info attestation.").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>{identityCreate(opts)},opts.human)});identityCmd.command("show").description("Print the builder identity pubkey from ~/.dvmkit/builder.json").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>{identityShow(opts)},opts.human)});const lockCmd=program2.command("lock").description("Manage your builder Cashu P2PK lock keypair (BIP-39 mnemonic)");lockCmd.command("create").description("Generate a BIP-39 mnemonic for builder Cashu lock keypairs").option("--import <mnemonic>","Import an existing 12-word BIP-39 mnemonic").option("--force","Replace an existing mnemonic with a freshly generated one. The old words are copied to a timestamped .bak sibling; proofs locked to lock pubkeys derived from them stop being spendable.").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>{lockCreate(opts)},opts.human)});const domainsCmd=program2.command("domains").description("Manage custom domains for deployed DVMs");domainsCmd.command("add").argument("<slug>","DVM slug").argument("<domain>","Custom domain (e.g. api.example.com)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,domain2,opts)=>{catchErrors(()=>domains("add",slug,{domain:domain2,human:opts.human,org:opts.org}),opts.human)});domainsCmd.command("remove").argument("<slug>","DVM slug").argument("<domain>","Custom domain to remove").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,domain2,opts)=>{catchErrors(()=>domains("remove",slug,{domain:domain2,human:opts.human,org:opts.org}),opts.human)});domainsCmd.command("verify").argument("<slug>","DVM slug").argument("<domain>","Custom domain to verify").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,domain2,opts)=>{catchErrors(()=>domains("verify",slug,{domain:domain2,human:opts.human,org:opts.org}),opts.human)});domainsCmd.command("list").argument("<slug>","DVM slug").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>domains("list",slug,{human:opts.human,org:opts.org}),opts.human)});const secretsCmd=program2.command("secrets").description("Inspect and set secrets for a DVM");secretsCmd.command("list").argument("<slug>","DVM slug").description("List secret names + last-applied timestamps for a DVM. Never returns values; platform-injected keys are excluded.").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((slug,opts)=>{catchErrors(()=>secretsList(slug,opts),opts.human)});const revenueCmd=program2.command("revenue").description("View revenue data across your DVMs");revenueCmd.command("summary").description("Show revenue summary with totals and by-rail breakdown").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").requiredOption("--from <date>","Start date (YYYY-MM-DD)").requiredOption("--to <date>","End date (YYYY-MM-DD)").option("--currency <c>","Reporting currency (gbp, eur, usd, chf)").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>revenueSummary(opts),opts.human)});revenueCmd.command("events").description("List individual revenue events (requires admin role in a shared org)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").option("--from <date>","Start date (YYYY-MM-DD)").option("--to <date>","End date (YYYY-MM-DD)").option("--rail <r>","Filter by payment rail (cashu, lightning, tempo, x402)").option("--limit <n>","Max events to return (default: 50)").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>revenueEvents(opts),opts.human)});revenueCmd.command("csv").description("Export revenue data as CSV (requires admin role in a shared org)").option("--org <handle>","Read as this org, overriding the `dvmctl switch` context").requiredOption("--from <date>","Start date (YYYY-MM-DD)").requiredOption("--to <date>","End date (YYYY-MM-DD)").option("--currency <c>","Reporting currency (gbp, eur, usd, chf)").option("--rail <r>","Filter by payment rail").option("--out <path>","Write CSV to file instead of stdout").action(opts=>{catchErrors(()=>revenueCsv(opts))});const apiKeysCmd=program2.command("api-keys").description("Manage builder API keys");apiKeysCmd.command("list").description("List your API keys").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>apiKeysList(opts),opts.human)});apiKeysCmd.command("create").description("Create a new API key").option("--label <text>","Label for the key").option("--human","Human-readable output instead of JSON").action(opts=>{catchErrors(()=>apiKeysCreate(opts),opts.human)});apiKeysCmd.command("revoke <id>").description("Revoke (delete) an API key").option("--human","Human-readable output instead of JSON").action((id,opts)=>{catchErrors(()=>apiKeysRevoke(id,opts),opts.human)});program2.command("rotate <handle>").description("Rotate the per-DVM Cashu lock pubkey \u2014 derives the next BIP-32 child, publishes it to the DVM, demotes the prior pubkey to retired (tracked change)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; required for self-hosted DVMs)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handle2,opts)=>{catchErrors(()=>rotate(handle2,opts),opts.human)});program2.command("melt-pending <handle>").description("Drain a DVM's pending Cashu accumulator batches \u2014 derives the per-DVM lock privkey locally, melts at the mint, posts mark-melted to the DVM admin endpoint").option("--payout <ln-address>","Lightning address to melt to (overrides any default set via `dvmctl set-payout`)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--watch","Run as a long-lived daemon polling every --interval seconds").option("--interval <seconds>","Poll cadence under --watch (default: 60, range 10\u20133600)",v=>Number(v)).option("--drains-only","Service the credit refunds you owe callers and stop \u2014 never sweep accumulator batches to the payout address. No --payout needed. Use when settling refunds out of band from the scheduled melt").option("--hold-alarm-after <duration>","How long this DVM's drain surface may stay unreadable before a one-shot sweep exits non-zero instead of holding the melt again (default: 24h). Nothing melts and no refund parks while it is held, so this is the ceiling on a silent stall").option("--refund-nwc <uri>","Send-capable NWC connection used to buy a caller's refund when no accumulator batch covers it \u2014 the notes are minted just in time and paid for from this wallet (env: DVMKIT_REFUND_NWC_URL). It only ever pays invoices this command obtained from your own accepted mints").option("--payout-nwc <uri>","Mint the payout invoice from this NWC wallet instead of resolving --payout over LNURL (env: DVMKIT_MELT_PAYOUT_NWC_URL). Use when the melt destination is a node with no Lightning address of its own").option("--human","Human-readable output instead of JSON").action((handle2,opts)=>{catchErrors(()=>meltPending(handle2,opts),opts.human)});program2.command("melt-status [handle]").description("Report when `dvmctl melt-pending` last completed cleanly for each DVM on this machine \u2014 the liveness signal a scheduled melt leaves behind, readable with no platform involved").option("--max-age <duration>","Exit non-zero when a DVM has not swept successfully within this window (e.g. `30h`, `2d`). Cron this and the exit code alarms on a melt job that stopped running at all. Every DVM is watched, and one that has never swept counts as stale").option("--except <handle>","Do not alarm on this DVM under --max-age (repeatable). For a DVM with no Cashu rail, which never melts and would otherwise hold the alarm red forever",(value,previous)=>[...previous,value],[]).option("--human","Human-readable output instead of JSON").action((handle2,opts)=>{catchErrors(()=>{meltStatus(handle2,opts)},opts.human)});const creditCmd=program2.command("credit").description("Reconcile prepaid-credit money your DVM could not apply on its own");creditCmd.command("blocked <handle>").description("List Lightning top-ups your DVM received but could never credit \u2014 the queue behind every `lightning_settlement_blocked` log line (tracked change)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--limit <n>","Page size (default: 50, max 200)",v=>Number(v)).option("--after <cursor>","Continue from a previous run's `next_after` value").option("--include-resolved","Also list rows you already reconciled or wrote off \u2014 the audit view").option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>blockedInvoices(handleArg,opts),opts.human)});creditCmd.command("reconcile <handle> <payment-hash>").description("Credit a blocked Lightning payment onto a credit the caller holds. The amount, currency and caller all come off the invoice your DVM recorded; you name only the credit. Idempotent").option("--credit-id <id>","Credit the payment should land on (required)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,paymentHash,opts)=>{catchErrors(()=>reconcileInvoice(handleArg,paymentHash,opts),opts.human)});creditCmd.command("write-off <handle> <payment-hash>").description("Record that you reviewed a blocked Lightning payment and are not crediting it. Moves no money and is reversible \u2014 the row just leaves the queue").option("--note <text>","Why you are not crediting it. Recorded on the invoice row").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,paymentHash,opts)=>{catchErrors(()=>writeOffInvoice(handleArg,paymentHash,opts),opts.human)});creditCmd.command("drains-owed <handle>").description("List the stablecoin refunds your DVM owes callers that nothing will ever pay on its own \u2014 one-payment x402 and Tempo reclaims, the queue behind every `drain_manual_settlement_required` log line (tracked change)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>drainsOwed(handleArg,opts),opts.human)});creditCmd.command("liability <handles...>").description("Show the satoshis your DVMs owe callers \u2014 undrawn Bitcoin deposits plus reclaims in flight \u2014 and how much of your hub balance is earnings you can sweep. Name every DVM the hub backs (tracked change)").option("--hub-sats <sats>","Your hub's spendable balance, in sats. Supply it to get the sweepable figure directly").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArgs,opts)=>{catchErrors(()=>creditLiability(handleArgs,opts),opts.human)});creditCmd.command("drain-settle <handle> <credit-id> <drain-id>").description("Record that you settled an owed stablecoin refund on chain. Moves no money \u2014 the ledger catches up with a payout you already made, the caller's poll sees it settled, and the reclaim stops counting against your liability. Idempotent").option("--tx <hash>","On-chain transaction that paid the refund (required)").option("--note <text>","Recorded alongside the transaction, e.g. which wallet sent it").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,creditId,drainId,opts)=>{catchErrors(()=>drainSettle(handleArg,creditId,drainId,opts),opts.human)});creditCmd.command("x402-wedged <handle>").description("List x402 cooperative refunds that settled on chain but never cleared the caller's balance, with what the chain says about each (tracked change)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--limit <n>","Page size (default: 20, max 50)",v=>Number(v)).option("--after <cursor>","Continue from a previous run's `next_after` value").option("--include-resolved","Also list settlements you already wrote off \u2014 the audit view").option("--min-age-ms <ms>","How long a settlement must have sat before it counts as stuck (default: 300000)",v=>Number(v)).option("--no-evidence","Skip the per-row chain lookups \u2014 a fast read of the queue itself").option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>x402Wedged(handleArg,opts),opts.human)});creditCmd.command("x402-reconcile <handle> <settlement-id>").description("Finish the ledger leg of an x402 refund the chain already paid. The amount is re-derived from chain evidence, never sent from here; a chain/ledger disagreement refuses. Idempotent").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,settlementId,opts)=>{catchErrors(()=>x402Reconcile(handleArg,settlementId,opts),opts.human)});creditCmd.command("x402-write-off <handle> <settlement-id>").description("Record that you reviewed a wedged x402 settlement and are not booking it. Books nothing and is reversible \u2014 the row just leaves the queue").option("--note <text>","Why you are not booking it. Recorded on the settlement row").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,settlementId,opts)=>{catchErrors(()=>x402WriteOff(handleArg,settlementId,opts),opts.human)});creditCmd.command("x402-exact-wedged <handle>").description("List exact x402 authorizations whose chain settlement has not finished its original local credit or job effect").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--limit <n>","Rows to return (default: 20, max 50)",v=>Number(v)).option("--include-resolved","Also list completed and rejected intents").option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>x402ExactWedged(handleArg,opts),opts.human)});creditCmd.command("x402-exact-reconcile <handle> <settlement-intent-id>").description("Finish the original local effect of a chain-evidenced exact x402 payment. The transaction and amount come from the DVM, never this command").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,settlementIntentId,opts)=>{catchErrors(()=>x402ExactReconcile(handleArg,settlementIntentId,opts),opts.human)});creditCmd.command("tempo-losses <handle>").description("List Tempo-backed credits terminalized after their channel finalized before the DVM collected accepted value").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--limit <n>","Rows to return (default: 20, max 50)",v=>Number(v)).option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>tempoLosses(handleArg,opts),opts.human)});creditCmd.command("tempo-wedged <handle>").description("List Tempo cooperative closes that landed on chain but never booked the caller's drain, with what the close receipt says about each (tracked change)").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--limit <n>","Page size (default: 20, max 50)",v=>Number(v)).option("--after <cursor>","Continue from a previous run's `next_after` value").option("--include-resolved","Also list drains you already wrote off \u2014 the audit view").option("--min-age-ms <ms>","How long a drain must have sat before it counts as stuck (default: 300000)",v=>Number(v)).option("--no-evidence","Skip the per-row chain lookups \u2014 a fast read of the queue itself").option("--human","Human-readable output instead of JSON").action((handleArg,opts)=>{catchErrors(()=>tempoWedged(handleArg,opts),opts.human)});creditCmd.command("tempo-reconcile <handle> <credit-id> <drain-id>").description("Book a Tempo drain whose channel close already paid. The amount is re-read from the close receipt, never sent from here; a refund short of the debit refuses. Idempotent").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,creditId,drainId,opts)=>{catchErrors(()=>tempoReconcile(handleArg,creditId,drainId,opts),opts.human)});creditCmd.command("tempo-write-off <handle> <credit-id> <drain-id>").description("Record that you reviewed a wedged Tempo close and are not booking it. Books nothing and is reversible \u2014 the row just leaves the queue").option("--note <text>","Why you are not booking it. Recorded on the drain row").option("--endpoint <url>","DVM admin endpoint URL (skips the platform getDvm lookup; use for self-hosted DVMs or local dev)").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handleArg,creditId,drainId,opts)=>{catchErrors(()=>tempoWriteOff(handleArg,creditId,drainId,opts),opts.human)});program2.command("set-payout <handle> <ln-address>").description("Persist the default Lightning payout address for `dvmctl melt-pending <handle>` (pre-flights the LNURL-pay endpoint)").option("--skip-preflight","Skip the LNURL pre-flight check (use when the provider is temporarily down)").option("--self-hosted","Resolve this handle from local builder.json state instead of asking the platform which DVM it is. Named for the self-hosted case it was built for, but it reaches any entry recorded on this machine \u2014 including one a `dvmctl deploy` wrote, whose payout address it will rewrite").option("--org <handle>","Target this org, overriding the `dvmctl switch` context").option("--human","Human-readable output instead of JSON").action((handle2,lnAddress,opts)=>{catchErrors(()=>setPayout(handle2,lnAddress,opts),opts.human)});return program2}if(process.argv[1]&&realpathSync2(process.argv[1])===fileURLToPath2(import.meta.url)){installGlobalAcceptEncoding();setOutputTraceId(randomTraceId());buildProgram().parse()}export{buildProgram};
2433
2433
  /*! Bundled license information:
2434
2434
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvmkit/dvmctl",
3
- "version": "0.2.0-rc.2",
3
+ "version": "0.2.1-rc.1",
4
4
  "description": "Builder CLI for creating, testing, and deploying Digital Vending Machines",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "verify": "npm run build && npm run lint && npm run typecheck && npm test"
37
37
  },
38
38
  "dependencies": {
39
- "@dvmkit/sdk": "0.1.4-rc.7"
39
+ "@dvmkit/sdk": "0.1.5-rc.8"
40
40
  },
41
41
  "optionalDependencies": {
42
42
  "pg": "8.20.0",