mirror of
https://github.com/tompro/sattle.git
synced 2026-10-26 01:49:58 +00:00
Formatting
This commit is contained in:
@@ -4,25 +4,25 @@
|
||||
// results go OUT through deps.applyChangeset - this engine never touches
|
||||
// wallet state itself.
|
||||
|
||||
import {noteK1, sameInvoice} from 'lnurlcash-kit'
|
||||
import { noteK1, sameInvoice } from 'lnurlcash-kit';
|
||||
|
||||
import type {Bearer} from '../types'
|
||||
import type {PreparedMint} from '../ops'
|
||||
import {prepareMint} from '../ops'
|
||||
import {PollAbortedError} from '../ops/shared'
|
||||
import type { Bearer } from '../types';
|
||||
import type { PreparedMint } from '../ops';
|
||||
import { prepareMint } from '../ops';
|
||||
import { PollAbortedError } from '../ops/shared';
|
||||
|
||||
import type {PendingInvoice, RequestContext} from './context'
|
||||
import {invoiceResult, invoiceRetireAfter, resolvePaymentHash, settleAndClaim} from './invoices'
|
||||
import {handlePayInvoice} from './pay'
|
||||
import type {NwcRequest, NwcResponse} from './protocol'
|
||||
import {NWC_METHODS, errResult, okResult} from './protocol'
|
||||
import type { PendingInvoice, RequestContext } from './context';
|
||||
import { invoiceResult, invoiceRetireAfter, resolvePaymentHash, settleAndClaim } from './invoices';
|
||||
import { handlePayInvoice } from './pay';
|
||||
import type { NwcRequest, NwcResponse } from './protocol';
|
||||
import { NWC_METHODS, errResult, okResult } from './protocol';
|
||||
|
||||
const MAX_PENDING_MINT_OUTPUTS = 20
|
||||
const MAX_PENDING_MINT_OUTPUTS = 20;
|
||||
|
||||
// the same eligibility carve applies - the balance answers "what could
|
||||
// this wallet actually pay with right now"
|
||||
const spendable = (bearer: Bearer): boolean =>
|
||||
!bearer.spent && bearer.callback !== '' && !bearer.deviceId && !!noteK1(bearer.url)
|
||||
!bearer.spent && bearer.callback !== '' && !bearer.deviceId && !!noteK1(bearer.url);
|
||||
|
||||
const handleGetInfo = (ctx: RequestContext): NwcResponse =>
|
||||
okResult('get_info', {
|
||||
@@ -35,7 +35,7 @@ const handleGetInfo = (ctx: RequestContext): NwcResponse =>
|
||||
network: 'mainnet',
|
||||
methods: [...NWC_METHODS],
|
||||
notifications: [],
|
||||
})
|
||||
});
|
||||
|
||||
const handleGetBalance = (ctx: RequestContext): NwcResponse =>
|
||||
okResult('get_balance', {
|
||||
@@ -43,21 +43,21 @@ const handleGetBalance = (ctx: RequestContext): NwcResponse =>
|
||||
.getBearers()
|
||||
.filter(spendable)
|
||||
.reduce((sum, b) => sum + b.amount, 0),
|
||||
})
|
||||
});
|
||||
|
||||
const handleMakeInvoice = async (
|
||||
ctx: RequestContext,
|
||||
params: Record<string, unknown>,
|
||||
): Promise<NwcResponse> => {
|
||||
const amountMsat = Number(params.amount)
|
||||
const amountMsat = Number(params.amount);
|
||||
if (!Number.isInteger(amountMsat) || amountMsat <= 0) {
|
||||
return errResult('make_invoice', 'OTHER', 'Amount must be a positive whole number of msat.')
|
||||
return errResult('make_invoice', 'OTHER', 'Amount must be a positive whole number of msat.');
|
||||
}
|
||||
const mint = ctx.deps.getDefaultMint()
|
||||
const mint = ctx.deps.getDefaultMint();
|
||||
if (!mint) {
|
||||
return errResult('make_invoice', 'INTERNAL', 'No default mint is configured.')
|
||||
return errResult('make_invoice', 'INTERNAL', 'No default mint is configured.');
|
||||
}
|
||||
const now = ctx.nowSeconds()
|
||||
const now = ctx.nowSeconds();
|
||||
if (
|
||||
ctx.deps
|
||||
.getBearers()
|
||||
@@ -68,16 +68,16 @@ const handleMakeInvoice = async (
|
||||
bearer.pendingMint.retireAfter <= now,
|
||||
)
|
||||
) {
|
||||
await ctx.deps.recoverPendingMints(ctx.assertOwner)
|
||||
await ctx.deps.recoverPendingMints(ctx.assertOwner);
|
||||
}
|
||||
const pendingOutputs = ctx.deps
|
||||
.getBearers()
|
||||
.filter((bearer) => bearer.pendingMint && !bearer.spent).length
|
||||
.filter((bearer) => bearer.pendingMint && !bearer.spent).length;
|
||||
if (pendingOutputs >= MAX_PENDING_MINT_OUTPUTS) {
|
||||
return errResult('make_invoice', 'QUOTA_EXCEEDED', 'Too many unpaid invoices are pending.')
|
||||
return errResult('make_invoice', 'QUOTA_EXCEEDED', 'Too many unpaid invoices are pending.');
|
||||
}
|
||||
let prepared: PreparedMint
|
||||
let stagedOutput: Bearer | undefined
|
||||
let prepared: PreparedMint;
|
||||
let stagedOutput: Bearer | undefined;
|
||||
try {
|
||||
prepared = await prepareMint(mint, amountMsat, {
|
||||
...(ctx.deps.kit ?? {}),
|
||||
@@ -85,49 +85,45 @@ const handleMakeInvoice = async (
|
||||
allocateOutputSecrets: (server, count) =>
|
||||
ctx.deps.allocateOutputSecrets(server, count, ctx.assertOwner),
|
||||
persistOutput: async (note) => {
|
||||
stagedOutput = await ctx.deps.persistMintOutput(note, ctx.assertOwner)
|
||||
stagedOutput = await ctx.deps.persistMintOutput(note, ctx.assertOwner);
|
||||
},
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
if (stagedOutput) {
|
||||
try {
|
||||
await ctx.deps.discardMintOutput(stagedOutput, ctx.assertOwner)
|
||||
await ctx.deps.discardMintOutput(stagedOutput, ctx.assertOwner);
|
||||
} catch (cleanupError) {
|
||||
return errResult(
|
||||
'make_invoice',
|
||||
'INTERNAL',
|
||||
cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return errResult('make_invoice', 'INTERNAL', err instanceof Error ? err.message : String(err))
|
||||
return errResult('make_invoice', 'INTERNAL', err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
if (!stagedOutput) {
|
||||
return errResult('make_invoice', 'INTERNAL', 'The pending mint output was not persisted.')
|
||||
return errResult('make_invoice', 'INTERNAL', 'The pending mint output was not persisted.');
|
||||
}
|
||||
const retireAfter = invoiceRetireAfter(prepared.invoice)
|
||||
const retireAfter = invoiceRetireAfter(prepared.invoice);
|
||||
if (retireAfter !== null) {
|
||||
try {
|
||||
await ctx.deps.setMintOutputRetirement(
|
||||
stagedOutput,
|
||||
retireAfter,
|
||||
ctx.assertOwner,
|
||||
)
|
||||
await ctx.deps.setMintOutputRetirement(stagedOutput, retireAfter, ctx.assertOwner);
|
||||
} catch (error) {
|
||||
try {
|
||||
await ctx.deps.discardMintOutput(stagedOutput, ctx.assertOwner)
|
||||
await ctx.deps.discardMintOutput(stagedOutput, ctx.assertOwner);
|
||||
} catch (cleanupError) {
|
||||
return errResult(
|
||||
'make_invoice',
|
||||
'INTERNAL',
|
||||
cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
||||
)
|
||||
);
|
||||
}
|
||||
return errResult(
|
||||
'make_invoice',
|
||||
'INTERNAL',
|
||||
error instanceof Error ? error.message : String(error),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
const entry: PendingInvoice = {
|
||||
@@ -138,68 +134,68 @@ const handleMakeInvoice = async (
|
||||
prepared,
|
||||
stagedOutput,
|
||||
state: 'pending',
|
||||
}
|
||||
};
|
||||
if (typeof params.description === 'string' && params.description) {
|
||||
entry.description = params.description
|
||||
entry.description = params.description;
|
||||
}
|
||||
const expiry = Number(params.expiry)
|
||||
const expiry = Number(params.expiry);
|
||||
if (Number.isInteger(expiry) && expiry > 0) {
|
||||
entry.expiresAt = entry.createdAt + expiry
|
||||
entry.expiresAt = entry.createdAt + expiry;
|
||||
}
|
||||
ctx.invoices.set(entry.paymentHash, entry)
|
||||
ctx.invoices.set(entry.paymentHash, entry);
|
||||
// phase two runs in the background; the invoice goes out now and
|
||||
// lookup_invoice reports the settlement the service-owned claim observes
|
||||
const started = ctx.startBackground(() =>
|
||||
settleAndClaim(ctx, entry).catch((err) => {
|
||||
entry.state = 'failed'
|
||||
entry.state = 'failed';
|
||||
// an interrupted claim poll is normal service teardown (stop
|
||||
// aborted it), not a background failure worth surfacing
|
||||
if (err instanceof PollAbortedError) return
|
||||
ctx.deps.onError?.(err, ctx.connection())
|
||||
if (err instanceof PollAbortedError) return;
|
||||
ctx.deps.onError?.(err, ctx.connection());
|
||||
}),
|
||||
)
|
||||
);
|
||||
if (!started) {
|
||||
entry.state = 'failed'
|
||||
return errResult('make_invoice', 'INTERNAL', 'The wallet service is stopping.')
|
||||
entry.state = 'failed';
|
||||
return errResult('make_invoice', 'INTERNAL', 'The wallet service is stopping.');
|
||||
}
|
||||
return okResult('make_invoice', invoiceResult(entry))
|
||||
}
|
||||
return okResult('make_invoice', invoiceResult(entry));
|
||||
};
|
||||
|
||||
const handleLookupInvoice = (ctx: RequestContext, params: Record<string, unknown>): NwcResponse => {
|
||||
const invoiceParam = typeof params.invoice === 'string' ? params.invoice : undefined
|
||||
const invoiceParam = typeof params.invoice === 'string' ? params.invoice : undefined;
|
||||
const hashParam =
|
||||
typeof params.payment_hash === 'string' ? params.payment_hash.toLowerCase() : undefined
|
||||
typeof params.payment_hash === 'string' ? params.payment_hash.toLowerCase() : undefined;
|
||||
if (!invoiceParam && !hashParam) {
|
||||
return errResult('lookup_invoice', 'OTHER', 'Provide an invoice or a payment hash.')
|
||||
return errResult('lookup_invoice', 'OTHER', 'Provide an invoice or a payment hash.');
|
||||
}
|
||||
let entry = hashParam ? ctx.invoices.get(hashParam) : undefined
|
||||
let entry = hashParam ? ctx.invoices.get(hashParam) : undefined;
|
||||
if (!entry && invoiceParam) {
|
||||
for (const candidate of ctx.invoices.values()) {
|
||||
if (sameInvoice(candidate.invoice, invoiceParam)) {
|
||||
entry = candidate
|
||||
break
|
||||
entry = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!entry) {
|
||||
return errResult('lookup_invoice', 'NOT_FOUND', 'Unknown invoice.')
|
||||
return errResult('lookup_invoice', 'NOT_FOUND', 'Unknown invoice.');
|
||||
}
|
||||
return okResult('lookup_invoice', invoiceResult(entry))
|
||||
}
|
||||
return okResult('lookup_invoice', invoiceResult(entry));
|
||||
};
|
||||
|
||||
export const dispatch = async (ctx: RequestContext, request: NwcRequest): Promise<NwcResponse> => {
|
||||
switch (request.method) {
|
||||
case 'get_info':
|
||||
return handleGetInfo(ctx)
|
||||
return handleGetInfo(ctx);
|
||||
case 'get_balance':
|
||||
return handleGetBalance(ctx)
|
||||
return handleGetBalance(ctx);
|
||||
case 'make_invoice':
|
||||
return await handleMakeInvoice(ctx, request.params)
|
||||
return await handleMakeInvoice(ctx, request.params);
|
||||
case 'pay_invoice':
|
||||
return await handlePayInvoice(ctx, request.params)
|
||||
return await handlePayInvoice(ctx, request.params);
|
||||
case 'lookup_invoice':
|
||||
return handleLookupInvoice(ctx, request.params)
|
||||
return handleLookupInvoice(ctx, request.params);
|
||||
default:
|
||||
return errResult(request.method, 'NOT_IMPLEMENTED', `Unknown method: ${request.method}.`)
|
||||
return errResult(request.method, 'NOT_IMPLEMENTED', `Unknown method: ${request.method}.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user