@fedimint/core 0.1.0
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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/dts/FedimintWallet.d.ts +51 -0
- package/dist/dts/FedimintWallet.d.ts.map +1 -0
- package/dist/dts/WalletDirector.d.ts +79 -0
- package/dist/dts/WalletDirector.d.ts.map +1 -0
- package/dist/dts/index.d.ts +5 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/services/BalanceService.d.ts +15 -0
- package/dist/dts/services/BalanceService.d.ts.map +1 -0
- package/dist/dts/services/FederationService.d.ts +13 -0
- package/dist/dts/services/FederationService.d.ts.map +1 -0
- package/dist/dts/services/LightningService.d.ts +48 -0
- package/dist/dts/services/LightningService.d.ts.map +1 -0
- package/dist/dts/services/MintService.d.ts +23 -0
- package/dist/dts/services/MintService.d.ts.map +1 -0
- package/dist/dts/services/RecoveryService.d.ts +13 -0
- package/dist/dts/services/RecoveryService.d.ts.map +1 -0
- package/dist/dts/services/WalletService.d.ts +13 -0
- package/dist/dts/services/WalletService.d.ts.map +1 -0
- package/dist/dts/services/index.d.ts +7 -0
- package/dist/dts/services/index.d.ts.map +1 -0
- package/dist/dts/transport/TransportClient.d.ts +55 -0
- package/dist/dts/transport/TransportClient.d.ts.map +1 -0
- package/dist/dts/transport/index.d.ts +2 -0
- package/dist/dts/transport/index.d.ts.map +1 -0
- package/dist/dts/types/index.d.ts +4 -0
- package/dist/dts/types/index.d.ts.map +1 -0
- package/dist/dts/types/transport.d.ts +2 -0
- package/dist/dts/types/transport.d.ts.map +1 -0
- package/dist/dts/types/utils.d.ts +21 -0
- package/dist/dts/types/utils.d.ts.map +1 -0
- package/dist/dts/types/wallet.d.ts +241 -0
- package/dist/dts/types/wallet.d.ts.map +1 -0
- package/dist/dts/utils/logger.d.ts +24 -0
- package/dist/dts/utils/logger.d.ts.map +1 -0
- package/dist/index.d.ts +578 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
- package/src/FedimintWallet.ts +119 -0
- package/src/WalletDirector.ts +119 -0
- package/src/index.ts +4 -0
- package/src/services/BalanceService.test.ts +26 -0
- package/src/services/BalanceService.ts +29 -0
- package/src/services/FederationService.test.ts +58 -0
- package/src/services/FederationService.ts +216 -0
- package/src/services/LightningService.test.ts +265 -0
- package/src/services/LightningService.ts +289 -0
- package/src/services/MintService.test.ts +74 -0
- package/src/services/MintService.ts +129 -0
- package/src/services/RecoveryService.ts +28 -0
- package/src/services/WalletService.test.ts +59 -0
- package/src/services/WalletService.ts +50 -0
- package/src/services/index.ts +6 -0
- package/src/transport/TransportClient.ts +254 -0
- package/src/transport/index.ts +1 -0
- package/src/types/index.ts +3 -0
- package/src/types/transport.ts +1 -0
- package/src/types/utils.ts +23 -0
- package/src/types/wallet.ts +298 -0
- package/src/utils/logger.ts +69 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { keyPair } from '../test/crypto'
|
|
3
|
+
import { walletTest } from '../test/fixtures'
|
|
4
|
+
|
|
5
|
+
walletTest(
|
|
6
|
+
'createInvoice should create a bolt11 invoice',
|
|
7
|
+
async ({ wallet }) => {
|
|
8
|
+
expect(wallet).toBeDefined()
|
|
9
|
+
expect(wallet.isOpen()).toBe(true)
|
|
10
|
+
|
|
11
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
12
|
+
const invoice = await wallet.lightning.createInvoice(100, 'test')
|
|
13
|
+
expect(invoice).toBeDefined()
|
|
14
|
+
expect(invoice).toMatchObject({
|
|
15
|
+
invoice: expect.any(String),
|
|
16
|
+
operation_id: expect.any(String),
|
|
17
|
+
})
|
|
18
|
+
// 3 requests were made, one for the invoice, one for refreshing the
|
|
19
|
+
// gateway cache, one for getting the gateway info
|
|
20
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 3)
|
|
21
|
+
|
|
22
|
+
// Test with expiry time
|
|
23
|
+
await expect(
|
|
24
|
+
wallet.lightning.createInvoice(100, 'test', 1000),
|
|
25
|
+
).resolves.toBeDefined()
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
walletTest('createInvoice with expiry', async ({ wallet }) => {
|
|
30
|
+
expect(wallet).toBeDefined()
|
|
31
|
+
expect(wallet.isOpen()).toBe(true)
|
|
32
|
+
|
|
33
|
+
const invoice = await wallet.lightning.createInvoice(100, 'test', 1000)
|
|
34
|
+
expect(invoice).toBeDefined()
|
|
35
|
+
expect(invoice).toMatchObject({
|
|
36
|
+
invoice: expect.any(String),
|
|
37
|
+
operation_id: expect.any(String),
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
walletTest(
|
|
42
|
+
'listGateways should return a list of gateways',
|
|
43
|
+
async ({ wallet }) => {
|
|
44
|
+
expect(wallet).toBeDefined()
|
|
45
|
+
expect(wallet.isOpen()).toBe(true)
|
|
46
|
+
|
|
47
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
48
|
+
const gateways = await wallet.lightning.listGateways()
|
|
49
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
50
|
+
expect(gateways).toBeDefined()
|
|
51
|
+
expect(gateways).toMatchObject(expect.any(Array))
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
walletTest(
|
|
56
|
+
'updateGatewayCache should update the gateway cache',
|
|
57
|
+
async ({ wallet }) => {
|
|
58
|
+
expect(wallet).toBeDefined()
|
|
59
|
+
expect(wallet.isOpen()).toBe(true)
|
|
60
|
+
|
|
61
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
62
|
+
await expect(wallet.lightning.updateGatewayCache()).resolves.toBeDefined()
|
|
63
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
walletTest('getGateway should return a gateway', async ({ wallet }) => {
|
|
68
|
+
expect(wallet).toBeDefined()
|
|
69
|
+
expect(wallet.isOpen()).toBe(true)
|
|
70
|
+
|
|
71
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
72
|
+
const gateway = await wallet.lightning.getGateway()
|
|
73
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
74
|
+
expect(gateway).toMatchObject({
|
|
75
|
+
api: expect.any(String),
|
|
76
|
+
fees: expect.any(Object),
|
|
77
|
+
gateway_id: expect.any(String),
|
|
78
|
+
gateway_redeem_key: expect.any(String),
|
|
79
|
+
lightning_alias: expect.any(String),
|
|
80
|
+
mint_channel_id: expect.any(Number),
|
|
81
|
+
node_pub_key: expect.any(String),
|
|
82
|
+
route_hints: expect.any(Array),
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
walletTest(
|
|
87
|
+
'payInvoice should throw on insufficient funds',
|
|
88
|
+
async ({ wallet }) => {
|
|
89
|
+
expect(wallet).toBeDefined()
|
|
90
|
+
expect(wallet.isOpen()).toBe(true)
|
|
91
|
+
|
|
92
|
+
const invoice = await wallet.lightning.createInvoice(100, 'test')
|
|
93
|
+
expect(invoice).toBeDefined()
|
|
94
|
+
expect(invoice).toMatchObject({
|
|
95
|
+
invoice: expect.any(String),
|
|
96
|
+
operation_id: expect.any(String),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
100
|
+
// Insufficient funds
|
|
101
|
+
try {
|
|
102
|
+
await wallet.lightning.payInvoice(invoice.invoice)
|
|
103
|
+
expect.unreachable('Should throw error')
|
|
104
|
+
} catch (error) {
|
|
105
|
+
expect(error).toBeDefined()
|
|
106
|
+
}
|
|
107
|
+
// 3 requests were made, one for paying the invoice, one for refreshing the
|
|
108
|
+
// gateway cache, one for getting the gateway info
|
|
109
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 3)
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
walletTest(
|
|
114
|
+
'payInvoice should pay a bolt11 invoice',
|
|
115
|
+
{ timeout: 20_000 },
|
|
116
|
+
async ({ fundedWallet }) => {
|
|
117
|
+
expect(fundedWallet).toBeDefined()
|
|
118
|
+
expect(fundedWallet.isOpen()).toBe(true)
|
|
119
|
+
const initialBalance = await fundedWallet.balance.getBalance()
|
|
120
|
+
expect(initialBalance).toBeGreaterThan(0)
|
|
121
|
+
const externalInvoice = await fundedWallet.testing.createFaucetInvoice(1)
|
|
122
|
+
const gatewayInfo = await fundedWallet.testing.getFaucetGatewayInfo()
|
|
123
|
+
const payment = await fundedWallet.lightning.payInvoice(
|
|
124
|
+
externalInvoice,
|
|
125
|
+
gatewayInfo,
|
|
126
|
+
)
|
|
127
|
+
expect(payment).toMatchObject({
|
|
128
|
+
contract_id: expect.any(String),
|
|
129
|
+
fee: expect.any(Number),
|
|
130
|
+
payment_type: expect.any(Object),
|
|
131
|
+
})
|
|
132
|
+
const finalBalance = await fundedWallet.balance.getBalance()
|
|
133
|
+
expect(finalBalance).toBeLessThan(initialBalance)
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
walletTest(
|
|
138
|
+
'createInvoiceTweaked should create a bolt11 invoice with a tweaked public key',
|
|
139
|
+
async ({ wallet }) => {
|
|
140
|
+
expect(wallet).toBeDefined()
|
|
141
|
+
expect(wallet.isOpen()).toBe(true)
|
|
142
|
+
|
|
143
|
+
// Make an ephemeral key pair
|
|
144
|
+
const { publicKey, secretKey } = keyPair()
|
|
145
|
+
const tweak = 1
|
|
146
|
+
|
|
147
|
+
// Create an invoice paying to the tweaked public key
|
|
148
|
+
const invoice = await wallet.lightning.createInvoiceTweaked(
|
|
149
|
+
1000,
|
|
150
|
+
'test tweaked',
|
|
151
|
+
publicKey,
|
|
152
|
+
tweak,
|
|
153
|
+
)
|
|
154
|
+
expect(invoice).toBeDefined()
|
|
155
|
+
expect(invoice).toMatchObject({
|
|
156
|
+
invoice: expect.any(String),
|
|
157
|
+
operation_id: expect.any(String),
|
|
158
|
+
})
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
walletTest(
|
|
163
|
+
'scanReceivesForTweaks should return the operation id',
|
|
164
|
+
async ({ wallet }) => {
|
|
165
|
+
expect(wallet).toBeDefined()
|
|
166
|
+
expect(wallet.isOpen()).toBe(true)
|
|
167
|
+
|
|
168
|
+
// Make an ephemeral key pair
|
|
169
|
+
const { publicKey, secretKey } = keyPair()
|
|
170
|
+
const tweak = 1
|
|
171
|
+
|
|
172
|
+
const gatewayInfo = await wallet.testing.getFaucetGatewayInfo()
|
|
173
|
+
|
|
174
|
+
// Create an invoice paying to the tweaked public key
|
|
175
|
+
const invoice = await wallet.lightning.createInvoiceTweaked(
|
|
176
|
+
1000,
|
|
177
|
+
'test tweaked',
|
|
178
|
+
publicKey,
|
|
179
|
+
tweak,
|
|
180
|
+
undefined,
|
|
181
|
+
gatewayInfo,
|
|
182
|
+
)
|
|
183
|
+
await expect(
|
|
184
|
+
wallet.testing.payFaucetInvoice(invoice.invoice),
|
|
185
|
+
).resolves.toBeDefined()
|
|
186
|
+
|
|
187
|
+
// Scan for the receive
|
|
188
|
+
const operationIds = await wallet.lightning.scanReceivesForTweaks(
|
|
189
|
+
secretKey,
|
|
190
|
+
[tweak],
|
|
191
|
+
{},
|
|
192
|
+
)
|
|
193
|
+
expect(operationIds).toBeDefined()
|
|
194
|
+
expect(operationIds).toHaveLength(1)
|
|
195
|
+
|
|
196
|
+
// Subscribe to claiming the receive
|
|
197
|
+
const subscription = await wallet.lightning.subscribeLnClaim(
|
|
198
|
+
operationIds[0],
|
|
199
|
+
(state) => {
|
|
200
|
+
expect(state).toBeDefined()
|
|
201
|
+
expect(state).toMatchObject({
|
|
202
|
+
state: 'claimed',
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
expect(subscription).toBeDefined()
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
walletTest(
|
|
211
|
+
'subscribe_internal_pay should return state',
|
|
212
|
+
async ({ fundedWallet }) => {
|
|
213
|
+
expect(fundedWallet).toBeDefined()
|
|
214
|
+
expect(fundedWallet.isOpen()).toBe(true)
|
|
215
|
+
const initialBalance = await fundedWallet.balance.getBalance()
|
|
216
|
+
expect(initialBalance).toBeGreaterThan(0)
|
|
217
|
+
const externalInvoice = (
|
|
218
|
+
await fundedWallet.lightning.createInvoice(1, 'test invoice')
|
|
219
|
+
).invoice
|
|
220
|
+
const payment = await fundedWallet.lightning.payInvoice(externalInvoice)
|
|
221
|
+
expect(payment).toMatchObject({
|
|
222
|
+
contract_id: expect.any(String),
|
|
223
|
+
fee: expect.any(Number),
|
|
224
|
+
payment_type: expect.any(Object),
|
|
225
|
+
})
|
|
226
|
+
const finalBalance = await fundedWallet.balance.getBalance()
|
|
227
|
+
expect(finalBalance).toBeLessThan(initialBalance)
|
|
228
|
+
expect(payment.payment_type).toHaveProperty('internal')
|
|
229
|
+
if ('internal' in payment.payment_type) {
|
|
230
|
+
const id = payment.payment_type.internal
|
|
231
|
+
await new Promise<void>((resolve, reject) => {
|
|
232
|
+
const unsubscribe = fundedWallet.lightning.subscribeInternalPayment(
|
|
233
|
+
id,
|
|
234
|
+
(state) => {
|
|
235
|
+
try {
|
|
236
|
+
expect(state).toBeDefined()
|
|
237
|
+
expect(state).toBe('funding')
|
|
238
|
+
unsubscribe()
|
|
239
|
+
resolve()
|
|
240
|
+
} catch (err) {
|
|
241
|
+
reject(err)
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
} else {
|
|
247
|
+
const id = payment.payment_type.lightning
|
|
248
|
+
await new Promise<void>((resolve, reject) => {
|
|
249
|
+
const unsubscribe = fundedWallet.lightning.subscribeLnPay(
|
|
250
|
+
id,
|
|
251
|
+
(state) => {
|
|
252
|
+
try {
|
|
253
|
+
expect(state).toBeDefined()
|
|
254
|
+
expect(state).toBe('created')
|
|
255
|
+
unsubscribe()
|
|
256
|
+
resolve()
|
|
257
|
+
} catch (err) {
|
|
258
|
+
reject(err)
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { TransportClient } from '../transport'
|
|
2
|
+
import type {
|
|
3
|
+
CreateBolt11Response,
|
|
4
|
+
GatewayInfo,
|
|
5
|
+
JSONObject,
|
|
6
|
+
LightningGateway,
|
|
7
|
+
LnInternalPayState,
|
|
8
|
+
LnPayState,
|
|
9
|
+
LnReceiveState,
|
|
10
|
+
OutgoingLightningPayment,
|
|
11
|
+
} from '../types'
|
|
12
|
+
|
|
13
|
+
export class LightningService {
|
|
14
|
+
constructor(private client: TransportClient) {}
|
|
15
|
+
|
|
16
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
|
|
17
|
+
async createInvoice(
|
|
18
|
+
amountMsats: number,
|
|
19
|
+
description: string,
|
|
20
|
+
expiryTime?: number, // in seconds
|
|
21
|
+
gatewayInfo?: GatewayInfo,
|
|
22
|
+
extraMeta?: JSONObject,
|
|
23
|
+
) {
|
|
24
|
+
const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
|
|
25
|
+
return await this.client.rpcSingle<CreateBolt11Response>(
|
|
26
|
+
'ln',
|
|
27
|
+
'create_bolt11_invoice',
|
|
28
|
+
{
|
|
29
|
+
amount: amountMsats,
|
|
30
|
+
description,
|
|
31
|
+
expiry_time: expiryTime ?? null,
|
|
32
|
+
extra_meta: extraMeta ?? {},
|
|
33
|
+
gateway,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async createInvoiceTweaked(
|
|
39
|
+
amountMsats: number,
|
|
40
|
+
description: string,
|
|
41
|
+
tweakKey: string,
|
|
42
|
+
index: number,
|
|
43
|
+
expiryTime?: number, // in seconds
|
|
44
|
+
gatewayInfo?: GatewayInfo,
|
|
45
|
+
extraMeta?: JSONObject,
|
|
46
|
+
) {
|
|
47
|
+
const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
|
|
48
|
+
return await this.client.rpcSingle<CreateBolt11Response>(
|
|
49
|
+
'ln',
|
|
50
|
+
'create_bolt11_invoice_for_user_tweaked',
|
|
51
|
+
{
|
|
52
|
+
amount: amountMsats,
|
|
53
|
+
description,
|
|
54
|
+
expiry_time: expiryTime ?? null,
|
|
55
|
+
user_key: tweakKey,
|
|
56
|
+
index,
|
|
57
|
+
extra_meta: extraMeta ?? {},
|
|
58
|
+
gateway,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Returns the operation ids of payments received to the tweaks of the user secret key
|
|
64
|
+
async scanReceivesForTweaks(
|
|
65
|
+
tweakKey: string,
|
|
66
|
+
indices: number[],
|
|
67
|
+
extraMeta?: JSONObject,
|
|
68
|
+
) {
|
|
69
|
+
return await this.client.rpcSingle<string[]>(
|
|
70
|
+
'ln',
|
|
71
|
+
'scan_receive_for_user_tweaked',
|
|
72
|
+
{
|
|
73
|
+
user_key: tweakKey,
|
|
74
|
+
indices,
|
|
75
|
+
extra_meta: extraMeta ?? {},
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async _getDefaultGatewayInfo() {
|
|
81
|
+
await this.updateGatewayCache()
|
|
82
|
+
const gateways = await this.listGateways()
|
|
83
|
+
return gateways[0]?.info
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
|
|
87
|
+
async payInvoice(
|
|
88
|
+
invoice: string,
|
|
89
|
+
gatewayInfo?: GatewayInfo,
|
|
90
|
+
extraMeta?: JSONObject,
|
|
91
|
+
) {
|
|
92
|
+
const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
|
|
93
|
+
return await this.client.rpcSingle<OutgoingLightningPayment>(
|
|
94
|
+
'ln',
|
|
95
|
+
'pay_bolt11_invoice',
|
|
96
|
+
{
|
|
97
|
+
maybe_gateway: gateway,
|
|
98
|
+
invoice,
|
|
99
|
+
extra_meta: extraMeta ?? {},
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoicesync-invoice-string */
|
|
105
|
+
async payInvoiceSync(
|
|
106
|
+
invoice: string,
|
|
107
|
+
timeoutMs: number = 10000,
|
|
108
|
+
gatewayInfo?: GatewayInfo,
|
|
109
|
+
extraMeta?: JSONObject,
|
|
110
|
+
) {
|
|
111
|
+
return new Promise<
|
|
112
|
+
| { success: false; error?: string }
|
|
113
|
+
| {
|
|
114
|
+
success: true
|
|
115
|
+
data: { feeMsats: number; preimage: string }
|
|
116
|
+
}
|
|
117
|
+
>(async (resolve, reject) => {
|
|
118
|
+
const { contract_id, fee } = await this.payInvoice(
|
|
119
|
+
invoice,
|
|
120
|
+
gatewayInfo,
|
|
121
|
+
extraMeta,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
// TODO: handle error handling for other subscription statuses
|
|
125
|
+
const unsubscribe = this.subscribeLnPay(contract_id, (res) => {
|
|
126
|
+
if (typeof res !== 'string' && 'success' in res) {
|
|
127
|
+
clearTimeout(timeoutId)
|
|
128
|
+
unsubscribe()
|
|
129
|
+
resolve({
|
|
130
|
+
success: true,
|
|
131
|
+
data: { feeMsats: fee, preimage: res.success.preimage },
|
|
132
|
+
})
|
|
133
|
+
} else if (typeof res !== 'string' && 'unexpected_error' in res) {
|
|
134
|
+
reject(new Error(res.unexpected_error.error_message))
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const timeoutId = setTimeout(() => {
|
|
139
|
+
unsubscribe()
|
|
140
|
+
resolve({ success: false, error: 'Payment timeout' })
|
|
141
|
+
}, timeoutMs)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
subscribeInternalPayment(
|
|
146
|
+
operation_id: string,
|
|
147
|
+
onSuccess: (state: LnInternalPayState) => void = () => {},
|
|
148
|
+
onError: (error: string) => void = () => {},
|
|
149
|
+
) {
|
|
150
|
+
return this.client.rpcStream(
|
|
151
|
+
'ln',
|
|
152
|
+
'subscribe_internal_pay',
|
|
153
|
+
{ operation_id: operation_id },
|
|
154
|
+
onSuccess,
|
|
155
|
+
onError,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// TODO: Document
|
|
160
|
+
subscribeLnClaim(
|
|
161
|
+
operationId: string,
|
|
162
|
+
onSuccess: (state: LnReceiveState) => void = () => {},
|
|
163
|
+
onError: (error: string) => void = () => {},
|
|
164
|
+
) {
|
|
165
|
+
return this.client.rpcStream(
|
|
166
|
+
'ln',
|
|
167
|
+
'subscribe_ln_claim',
|
|
168
|
+
{ operation_id: operationId },
|
|
169
|
+
onSuccess,
|
|
170
|
+
onError,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// TODO: Document (for external payments only)
|
|
175
|
+
// TODO: Make this work for BOTH internal and external payments
|
|
176
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
|
|
177
|
+
subscribeLnPay(
|
|
178
|
+
operationId: string,
|
|
179
|
+
onSuccess: (state: LnPayState) => void = () => {},
|
|
180
|
+
onError: (error: string) => void = () => {},
|
|
181
|
+
) {
|
|
182
|
+
return this.client.rpcStream(
|
|
183
|
+
'ln',
|
|
184
|
+
'subscribe_ln_pay',
|
|
185
|
+
{ operation_id: operationId },
|
|
186
|
+
onSuccess,
|
|
187
|
+
onError,
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
|
|
192
|
+
async waitForPay(operationId: string) {
|
|
193
|
+
return new Promise<
|
|
194
|
+
| { success: false; error?: string }
|
|
195
|
+
| { success: true; data: { preimage: string } }
|
|
196
|
+
>((resolve, reject) => {
|
|
197
|
+
let unsubscribe: () => void
|
|
198
|
+
const timeoutId = setTimeout(() => {
|
|
199
|
+
resolve({ success: false, error: 'Waiting for receive timeout' })
|
|
200
|
+
}, 15000)
|
|
201
|
+
|
|
202
|
+
unsubscribe = this.subscribeLnPay(
|
|
203
|
+
operationId,
|
|
204
|
+
(res) => {
|
|
205
|
+
if (typeof res !== 'string' && 'success' in res) {
|
|
206
|
+
clearTimeout(timeoutId)
|
|
207
|
+
unsubscribe()
|
|
208
|
+
resolve({
|
|
209
|
+
success: true,
|
|
210
|
+
data: { preimage: res.success.preimage },
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
(error) => {
|
|
215
|
+
clearTimeout(timeoutId)
|
|
216
|
+
unsubscribe()
|
|
217
|
+
reject(error)
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
|
|
224
|
+
subscribeLnReceive(
|
|
225
|
+
operationId: string,
|
|
226
|
+
onSuccess: (state: LnReceiveState) => void = () => {},
|
|
227
|
+
onError: (error: string) => void = () => {},
|
|
228
|
+
) {
|
|
229
|
+
return this.client.rpcStream(
|
|
230
|
+
'ln',
|
|
231
|
+
'subscribe_ln_receive',
|
|
232
|
+
{ operation_id: operationId },
|
|
233
|
+
onSuccess,
|
|
234
|
+
onError,
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
|
|
239
|
+
async waitForReceive(operationId: string, timeoutMs: number = 15000) {
|
|
240
|
+
return new Promise<LnReceiveState>((resolve, reject) => {
|
|
241
|
+
let unsubscribe: () => void
|
|
242
|
+
const timeoutId = setTimeout(() => {
|
|
243
|
+
reject(new Error('Timeout waiting for receive'))
|
|
244
|
+
}, timeoutMs)
|
|
245
|
+
|
|
246
|
+
unsubscribe = this.subscribeLnReceive(
|
|
247
|
+
operationId,
|
|
248
|
+
(res) => {
|
|
249
|
+
if (res === 'claimed') {
|
|
250
|
+
clearTimeout(timeoutId)
|
|
251
|
+
unsubscribe()
|
|
252
|
+
resolve(res)
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
(error) => {
|
|
256
|
+
clearTimeout(timeoutId)
|
|
257
|
+
unsubscribe()
|
|
258
|
+
reject(error)
|
|
259
|
+
},
|
|
260
|
+
)
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async getGateway(
|
|
265
|
+
gatewayId: string | null = null,
|
|
266
|
+
forceInternal: boolean = false,
|
|
267
|
+
) {
|
|
268
|
+
return await this.client.rpcSingle<LightningGateway | null>(
|
|
269
|
+
'ln',
|
|
270
|
+
'get_gateway',
|
|
271
|
+
{
|
|
272
|
+
gateway_id: gatewayId,
|
|
273
|
+
force_internal: forceInternal,
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async listGateways() {
|
|
279
|
+
return await this.client.rpcSingle<LightningGateway[]>(
|
|
280
|
+
'ln',
|
|
281
|
+
'list_gateways',
|
|
282
|
+
{},
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async updateGatewayCache() {
|
|
287
|
+
return await this.client.rpcSingle('ln', 'update_gateway_cache', {})
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { walletTest } from '../test/fixtures'
|
|
3
|
+
|
|
4
|
+
walletTest('redeemEcash should error on invalid ecash', async ({ wallet }) => {
|
|
5
|
+
expect(wallet).toBeDefined()
|
|
6
|
+
expect(wallet.isOpen()).toBe(true)
|
|
7
|
+
|
|
8
|
+
await expect(wallet.mint.redeemEcash('test')).rejects.toThrow()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
walletTest(
|
|
12
|
+
'reissueExternalNotes should throw if wallet is empty',
|
|
13
|
+
async ({ wallet }) => {
|
|
14
|
+
expect(wallet).toBeDefined()
|
|
15
|
+
expect(wallet.isOpen()).toBe(true)
|
|
16
|
+
|
|
17
|
+
await expect(wallet.mint.reissueExternalNotes('test')).rejects.toThrow()
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
walletTest('spendNotes should throw if wallet is empty', async ({ wallet }) => {
|
|
22
|
+
expect(wallet).toBeDefined()
|
|
23
|
+
expect(wallet.isOpen()).toBe(true)
|
|
24
|
+
|
|
25
|
+
await expect(wallet.mint.spendNotes(100)).rejects.toThrow()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
walletTest('parseNotes should parse notes', async ({ wallet }) => {
|
|
29
|
+
expect(wallet).toBeDefined()
|
|
30
|
+
expect(wallet.isOpen()).toBe(true)
|
|
31
|
+
|
|
32
|
+
await expect(wallet.mint.reissueExternalNotes('test')).rejects.toThrow()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
walletTest(
|
|
36
|
+
'getNotesByDenomination should return empty object if wallet is empty',
|
|
37
|
+
async ({ wallet }) => {
|
|
38
|
+
expect(wallet).toBeDefined()
|
|
39
|
+
expect(wallet.isOpen()).toBe(true)
|
|
40
|
+
|
|
41
|
+
const notes = await wallet.mint.getNotesByDenomination()
|
|
42
|
+
const balance = await wallet.balance.getBalance()
|
|
43
|
+
expect(balance).toEqual(0)
|
|
44
|
+
expect(notes).toBeDefined()
|
|
45
|
+
expect(notes).toEqual({})
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
walletTest(
|
|
50
|
+
'getNotesByDenomination should get notes by denomination',
|
|
51
|
+
async ({ fundedWallet }) => {
|
|
52
|
+
expect(fundedWallet).toBeDefined()
|
|
53
|
+
expect(fundedWallet.isOpen()).toBe(true)
|
|
54
|
+
|
|
55
|
+
const notes = await fundedWallet.mint.getNotesByDenomination()
|
|
56
|
+
const balance = await fundedWallet.balance.getBalance()
|
|
57
|
+
expect(balance).toEqual(10000)
|
|
58
|
+
expect(notes).toBeDefined()
|
|
59
|
+
expect(notes).toEqual({
|
|
60
|
+
'1': 2,
|
|
61
|
+
'1024': 3,
|
|
62
|
+
'128': 2,
|
|
63
|
+
'16': 3,
|
|
64
|
+
'2': 3,
|
|
65
|
+
'2048': 2,
|
|
66
|
+
'256': 3,
|
|
67
|
+
'32': 2,
|
|
68
|
+
'4': 2,
|
|
69
|
+
'512': 3,
|
|
70
|
+
'64': 2,
|
|
71
|
+
'8': 2,
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
)
|