-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add nodeControl contract + checkout.mintInvoice #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { eventIterator, oc } from "@orpc/contract"; | ||
| import { z } from "zod"; | ||
| import { | ||
| InvoiceBolt11ResultSchema, | ||
| InvoiceBolt12OfferResultSchema, | ||
| InvoiceCreateBolt11InputSchema, | ||
| InvoiceCreateBolt12OfferInputSchema, | ||
| NodeEventSchema, | ||
| PayoutInputSchema, | ||
| PayoutResultSchema, | ||
| } from "../schemas/node-control"; | ||
|
|
||
| /** | ||
| * Node control contract used over a WebSocket between mdk.com (RPC client) and | ||
| * a merchant's running lightning-js node (RPC handler). | ||
| * | ||
| * The connection is initiated by the merchant function dialing OUT to mdk.com | ||
| * (Vercel does not support inbound WebSockets). mdk.com grants a single-active | ||
| * lease per appId via a DB row before the node is constructed. | ||
| */ | ||
| export const payoutContract = oc | ||
| .input(PayoutInputSchema) | ||
| .output(PayoutResultSchema); | ||
|
|
||
| export const invoiceCreateBolt11Contract = oc | ||
| .input(InvoiceCreateBolt11InputSchema) | ||
| .output(InvoiceBolt11ResultSchema); | ||
|
|
||
| export const invoiceCreateBolt12OfferContract = oc | ||
| .input(InvoiceCreateBolt12OfferInputSchema) | ||
| .output(InvoiceBolt12OfferResultSchema); | ||
|
|
||
| /** | ||
| * Server-pushed event stream. mdk.com calls this once per session and consumes | ||
| * the AsyncIterable for the lifetime of the connection. Single subscriber per | ||
| * session, buffered from session start, FIFO. | ||
| */ | ||
| export const nodeEventsContract = oc | ||
| .input(z.void()) | ||
| .output(eventIterator(NodeEventSchema)); | ||
|
|
||
| export const nodeControl = { | ||
| payout: payoutContract, | ||
| invoice: { | ||
| createBolt11: invoiceCreateBolt11Contract, | ||
| createBolt12Offer: invoiceCreateBolt12OfferContract, | ||
| }, | ||
| events: nodeEventsContract, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import { z } from "zod"; | ||
|
|
||
| /** | ||
| * Input for the payout command. Destination is NOT in the payload — the node-side | ||
| * handler reads it from process.env.WITHDRAWAL_DESTINATION. This means mdk.com cannot | ||
| * direct funds to an arbitrary destination even if the per-app key is compromised. | ||
| * | ||
| * amountMsat is required and positive; "drain entire balance" semantics are out of v1. | ||
| * If needed later, add a separate explicit command (e.g. payout.drainAll). | ||
| */ | ||
| export const PayoutInputSchema = z.object({ | ||
| amountMsat: z.number().int().positive(), | ||
| idempotencyKey: z.string(), | ||
| }); | ||
| export type PayoutInput = z.infer<typeof PayoutInputSchema>; | ||
|
|
||
| /** | ||
| * Result of a payout command. Returned synchronously after the underlying | ||
| * payWhileRunning(_, _, 0) fire-and-forget call. The final outcome (Sent or Failed) | ||
| * arrives later as a paymentSent or paymentFailed event over the events() iterator. | ||
| */ | ||
| export const PayoutResultSchema = z.object({ | ||
| accepted: z.literal(true), | ||
| paymentId: z.string(), | ||
| paymentHash: z.string().nullable(), | ||
| }); | ||
| export type PayoutResult = z.infer<typeof PayoutResultSchema>; | ||
|
|
||
| /** | ||
| * Input for createBolt11. amountMsat null means a variable-amount JIT invoice. | ||
| */ | ||
| export const InvoiceCreateBolt11InputSchema = z.object({ | ||
| amountMsat: z.number().int().positive().nullable(), | ||
| description: z.string(), | ||
| expirySecs: z.number().int().positive(), | ||
| idempotencyKey: z.string(), | ||
| }); | ||
| export type InvoiceCreateBolt11Input = z.infer< | ||
| typeof InvoiceCreateBolt11InputSchema | ||
| >; | ||
|
|
||
| /** | ||
| * Result of createBolt11. expiresAt is a unix timestamp in seconds (matches lightning-js). | ||
| */ | ||
| export const InvoiceBolt11ResultSchema = z.object({ | ||
| bolt11: z.string(), | ||
| paymentHash: z.string(), | ||
| expiresAt: z.number(), | ||
| scid: z.string(), | ||
| }); | ||
| export type InvoiceBolt11Result = z.infer<typeof InvoiceBolt11ResultSchema>; | ||
|
|
||
| /** | ||
| * Input for createBolt12Offer. amountMsat null means a variable-amount offer. | ||
| */ | ||
| export const InvoiceCreateBolt12OfferInputSchema = z.object({ | ||
| amountMsat: z.number().int().positive().nullable(), | ||
| description: z.string(), | ||
| expirySecs: z.number().int().positive().optional(), | ||
| idempotencyKey: z.string(), | ||
| }); | ||
| export type InvoiceCreateBolt12OfferInput = z.infer< | ||
| typeof InvoiceCreateBolt12OfferInputSchema | ||
| >; | ||
|
|
||
| export const InvoiceBolt12OfferResultSchema = z.object({ | ||
| offer: z.string(), | ||
| }); | ||
| export type InvoiceBolt12OfferResult = z.infer< | ||
| typeof InvoiceBolt12OfferResultSchema | ||
| >; | ||
|
|
||
| /** | ||
| * Events pushed from the node to mdk.com over the events() AsyncIterable. | ||
| * | ||
| * - ready: emitted once after node.startReceiving() + setupBolt12Receive() complete. | ||
| * mdk.com SHOULD wait for this before sending command RPCs (commands are gated on | ||
| * nodeReady server-side and reject with {error:'node-not-ready'} otherwise). | ||
| * | ||
| * - paymentSent / paymentFailed: outbound payment outcomes. Correlate by paymentId | ||
| * returned from the original payout RPC. paymentId is only present for outbound | ||
| * payments (per lightning-js PaymentEvent typing); inbound failures clear pending | ||
| * claims locally but do not surface here. | ||
| * | ||
| * - draining: emitted when the node enters its drain window (15s before the | ||
| * hardcoded 300s lifetime expires). New command RPCs reject after this. | ||
| * | ||
| * - leaseReleased: emitted right before the node initiates a graceful shutdown | ||
| * (60s of quiet + no in-flight outbound + no pending claims + empty queue). | ||
| * Followed by a graceful WS close. | ||
| * | ||
| * reason on paymentFailed is optional because lightning-js types it optional in | ||
| * PaymentEvent (index.d.ts:47); forcing a default would lose signal. | ||
| */ | ||
| export const NodeEventSchema = z.discriminatedUnion("type", [ | ||
| z.object({ type: z.literal("ready"), nodeId: z.string() }), | ||
| z.object({ | ||
| type: z.literal("paymentSent"), | ||
| paymentId: z.string(), | ||
| paymentHash: z.string(), | ||
| preimage: z.string(), | ||
| }), | ||
| z.object({ | ||
| type: z.literal("paymentFailed"), | ||
| paymentId: z.string(), | ||
| paymentHash: z.string(), | ||
| reason: z.string().optional(), | ||
| }), | ||
| z.object({ type: z.literal("draining") }), | ||
| z.object({ type: z.literal("leaseReleased") }), | ||
| ]); | ||
| export type NodeEvent = z.infer<typeof NodeEventSchema>; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds
checkout.mintInvoiceto the core contract, butsdkContract.checkoutstill exports onlyregisterInvoiceinsrc/index.ts(lines 205-210), so any SDK router/client that is typed fromsdkContractcannot adopt the new single-step mint flow and will stay pinned to the legacy path. That mismatch makes this feature unavailable in the SDK-facing contract surface even though it is now publicly defined.Useful? React with 👍 / 👎.