Skip to content

Commit b5f8550

Browse files
martinsaposnicmdk-bot[bot]
authored andcommitted
feat(api-contract): nodeControl.getBalance schemas and contract definition [1/4] (#739)
* feat(api-contract): nodeControl.getBalance schemas and contract definition [1/4] Add GetBalanceResultSchema and the standalone getBalanceContract on contracts/node-control. Deliberately leave the `nodeControl` router unwired until the SDK handler ships in the implementation PR. The SDK core's `implement(nodeControl).router({...})` is exhaustive, so adding to `nodeControl` here without the matching handler in @moneydevkit/core would break the workspace build. Splitting the wire-up into the next commit keeps each PR independently green. Bump api-contract to 0.1.27. * fix(api-contract): re-export GetBalanceResult{,Schema} from package root Address Codex review on #739: the node-control GetBalanceResult type and schema were declared but not re-exported, so external consumers couldn't import them via @moneydevkit/api-contract. Aliased to NodeGetBalanceResult{,Schema} to avoid colliding with the checkout-side GetBalanceResult{,Schema} that ships in PR [2/4] (#740), mirroring the existing NodeProgrammaticPayoutInput alias. The standalone getBalanceContract itself is intentionally NOT re-exported: none of its node-control siblings (payoutContract, programmaticPayoutContract, invoiceCreateBolt11Contract, ...) are re-exported either. Consumers access the contract through the nodeControl namespace once PR [3/4] wires it in.
1 parent 6f9beae commit b5f8550

6 files changed

Lines changed: 88 additions & 1 deletion

File tree

packages/api-contract/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@moneydevkit/api-contract",
3-
"version": "0.1.28",
3+
"version": "0.1.29",
44
"description": "API Contract for moneydevkit",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

packages/api-contract/src/contracts/node-control.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { eventIterator, oc } from "@orpc/contract";
22
import { z } from "zod";
33
import {
4+
GetBalanceResultSchema,
45
InvoiceBolt11ResultSchema,
56
InvoiceBolt12OfferResultSchema,
67
InvoiceCreateBolt11InputSchema,
@@ -34,6 +35,18 @@ export const invoiceCreateBolt12OfferContract = oc
3435
.input(InvoiceCreateBolt12OfferInputSchema)
3536
.output(InvoiceBolt12OfferResultSchema);
3637

38+
/**
39+
* Read the merchant node's spendable (outbound) balance.
40+
*
41+
* Deliberately not yet wired into the `nodeControl` router export below: the
42+
* SDK's `implement(nodeControl).router({...})` is exhaustive, so wiring here
43+
* without the matching handler in @moneydevkit/core would break the workspace
44+
* build. The wire-up lands together with the handler in the implementation PR.
45+
*/
46+
export const getBalanceContract = oc
47+
.input(z.void())
48+
.output(GetBalanceResultSchema);
49+
3750
/** Server-pushed event stream. Single subscriber per session, buffered, FIFO. */
3851
export const nodeEventsContract = oc
3952
.input(z.void())

packages/api-contract/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export type {
198198
InvoiceCreateBolt12OfferInput,
199199
InvoiceBolt12OfferResult,
200200
NodeEvent,
201+
GetBalanceResult as NodeGetBalanceResult,
201202
} from "./schemas/node-control";
202203
export {
203204
PayoutInputSchema,
@@ -208,6 +209,7 @@ export {
208209
InvoiceCreateBolt12OfferInputSchema,
209210
InvoiceBolt12OfferResultSchema,
210211
NodeEventSchema,
212+
GetBalanceResultSchema as NodeGetBalanceResultSchema,
211213
} from "./schemas/node-control";
212214

213215
// SDK contract - only the methods the SDK router implements

packages/api-contract/src/schemas/node-control.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ export type InvoiceBolt12OfferResult = z.infer<
8383
typeof InvoiceBolt12OfferResultSchema
8484
>;
8585

86+
/**
87+
* Result of the getBalance command on a merchant node. Returns the merchant's
88+
* total outbound liquidity in sats (matches lightning-js `getBalance()` /
89+
* `getBalanceWhileRunning()` units, which sum `outbound_capacity_msat` across
90+
* channels and divide by 1000).
91+
*/
92+
export const GetBalanceResultSchema = z.object({
93+
balanceSats: z.number().int().nonnegative(),
94+
});
95+
export type GetBalanceResult = z.infer<typeof GetBalanceResultSchema>;
96+
8697
/**
8798
* Events pushed from the node to mdk.com over the events() AsyncIterable.
8899
*
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it } from 'node:test'
2+
import assert from 'node:assert/strict'
3+
import { getBalanceContract, nodeControl } from '../../src/contracts/node-control'
4+
5+
describe('node-control contracts', () => {
6+
describe('getBalanceContract', () => {
7+
it('is exported as an oRPC contract object', () => {
8+
assert.ok(getBalanceContract)
9+
assert.equal(typeof getBalanceContract, 'object')
10+
})
11+
12+
it('is intentionally NOT yet wired into the nodeControl router', () => {
13+
// Keep this assertion red until the implementation PR lands the SDK
14+
// handler. Adding `getBalance` to `nodeControl` without the matching
15+
// handler in @moneydevkit/core would break the workspace build via
16+
// implement(nodeControl).router(...). Flip this assertion in PR3.
17+
const router = nodeControl as Record<string, unknown>
18+
assert.equal('getBalance' in router, false)
19+
})
20+
})
21+
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it } from 'node:test'
2+
import assert from 'node:assert/strict'
3+
import { GetBalanceResultSchema } from '../../src/schemas/node-control'
4+
5+
describe('node-control schemas', () => {
6+
describe('GetBalanceResultSchema', () => {
7+
it('accepts a non-negative integer sat balance', () => {
8+
assert.equal(GetBalanceResultSchema.safeParse({ balanceSats: 0 }).success, true)
9+
assert.equal(
10+
GetBalanceResultSchema.safeParse({ balanceSats: 1_234_567 }).success,
11+
true,
12+
)
13+
})
14+
15+
it('rejects negative balances', () => {
16+
assert.equal(
17+
GetBalanceResultSchema.safeParse({ balanceSats: -1 }).success,
18+
false,
19+
)
20+
})
21+
22+
it('rejects fractional sats (lightning-js returns integer sats)', () => {
23+
assert.equal(
24+
GetBalanceResultSchema.safeParse({ balanceSats: 1.5 }).success,
25+
false,
26+
)
27+
})
28+
29+
it('rejects missing balanceSats', () => {
30+
assert.equal(GetBalanceResultSchema.safeParse({}).success, false)
31+
})
32+
33+
it('rejects non-number balanceSats', () => {
34+
assert.equal(
35+
GetBalanceResultSchema.safeParse({ balanceSats: '1000' }).success,
36+
false,
37+
)
38+
})
39+
})
40+
})

0 commit comments

Comments
 (0)