@fedimint/core-web 0.0.0-20250617190659

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/dts/FedimintWallet.d.ts +114 -0
  4. package/dist/dts/FedimintWallet.d.ts.map +1 -0
  5. package/dist/dts/index.d.ts +3 -0
  6. package/dist/dts/index.d.ts.map +1 -0
  7. package/dist/dts/services/BalanceService.d.ts +15 -0
  8. package/dist/dts/services/BalanceService.d.ts.map +1 -0
  9. package/dist/dts/services/FederationService.d.ts +11 -0
  10. package/dist/dts/services/FederationService.d.ts.map +1 -0
  11. package/dist/dts/services/LightningService.d.ts +47 -0
  12. package/dist/dts/services/LightningService.d.ts.map +1 -0
  13. package/dist/dts/services/MintService.d.ts +23 -0
  14. package/dist/dts/services/MintService.d.ts.map +1 -0
  15. package/dist/dts/services/RecoveryService.d.ts +13 -0
  16. package/dist/dts/services/RecoveryService.d.ts.map +1 -0
  17. package/dist/dts/services/WalletService.d.ts +8 -0
  18. package/dist/dts/services/WalletService.d.ts.map +1 -0
  19. package/dist/dts/services/index.d.ts +7 -0
  20. package/dist/dts/services/index.d.ts.map +1 -0
  21. package/dist/dts/types/index.d.ts +4 -0
  22. package/dist/dts/types/index.d.ts.map +1 -0
  23. package/dist/dts/types/utils.d.ts +23 -0
  24. package/dist/dts/types/utils.d.ts.map +1 -0
  25. package/dist/dts/types/wallet.d.ts +102 -0
  26. package/dist/dts/types/wallet.d.ts.map +1 -0
  27. package/dist/dts/types/worker.d.ts +4 -0
  28. package/dist/dts/types/worker.d.ts.map +1 -0
  29. package/dist/dts/utils/logger.d.ts +17 -0
  30. package/dist/dts/utils/logger.d.ts.map +1 -0
  31. package/dist/dts/worker/WorkerClient.d.ts +44 -0
  32. package/dist/dts/worker/WorkerClient.d.ts.map +1 -0
  33. package/dist/dts/worker/index.d.ts +2 -0
  34. package/dist/dts/worker/index.d.ts.map +1 -0
  35. package/dist/index.d.ts +392 -0
  36. package/dist/index.js +2 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/worker.js +2 -0
  39. package/dist/worker.js.map +1 -0
  40. package/package.json +43 -0
  41. package/src/FedimintWallet.test.ts +73 -0
  42. package/src/FedimintWallet.ts +215 -0
  43. package/src/index.ts +2 -0
  44. package/src/services/BalanceService.test.ts +50 -0
  45. package/src/services/BalanceService.ts +29 -0
  46. package/src/services/FederationService.test.ts +58 -0
  47. package/src/services/FederationService.ts +24 -0
  48. package/src/services/LightningService.test.ts +208 -0
  49. package/src/services/LightningService.ts +274 -0
  50. package/src/services/MintService.test.ts +74 -0
  51. package/src/services/MintService.ts +129 -0
  52. package/src/services/RecoveryService.ts +28 -0
  53. package/src/services/WalletService.test.ts +24 -0
  54. package/src/services/WalletService.ts +10 -0
  55. package/src/services/index.ts +6 -0
  56. package/src/test/TestFedimintWallet.ts +26 -0
  57. package/src/test/TestingService.ts +79 -0
  58. package/src/test/crypto.ts +44 -0
  59. package/src/test/fixtures.test.ts +18 -0
  60. package/src/test/fixtures.ts +70 -0
  61. package/src/types/index.ts +3 -0
  62. package/src/types/utils.ts +29 -0
  63. package/src/types/wallet.ts +141 -0
  64. package/src/types/worker.ts +16 -0
  65. package/src/utils/logger.ts +61 -0
  66. package/src/worker/WorkerClient.test.ts +6 -0
  67. package/src/worker/WorkerClient.ts +236 -0
  68. package/src/worker/index.ts +1 -0
  69. package/src/worker/worker.js +167 -0
  70. package/src/worker/worker.test.ts +90 -0
@@ -0,0 +1,274 @@
1
+ import { WorkerClient } from '../worker'
2
+ import type {
3
+ CreateBolt11Response,
4
+ GatewayInfo,
5
+ JSONObject,
6
+ LightningGateway,
7
+ LnPayState,
8
+ LnReceiveState,
9
+ OutgoingLightningPayment,
10
+ } from '../types'
11
+
12
+ export class LightningService {
13
+ constructor(private client: WorkerClient) {}
14
+
15
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
16
+ async createInvoice(
17
+ amountMsats: number,
18
+ description: string,
19
+ expiryTime?: number, // in seconds
20
+ gatewayInfo?: GatewayInfo,
21
+ extraMeta?: JSONObject,
22
+ ) {
23
+ const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
24
+ return await this.client.rpcSingle<CreateBolt11Response>(
25
+ 'ln',
26
+ 'create_bolt11_invoice',
27
+ {
28
+ amount: amountMsats,
29
+ description,
30
+ expiry_time: expiryTime ?? null,
31
+ extra_meta: extraMeta ?? {},
32
+ gateway,
33
+ },
34
+ )
35
+ }
36
+
37
+ async createInvoiceTweaked(
38
+ amountMsats: number,
39
+ description: string,
40
+ tweakKey: string,
41
+ index: number,
42
+ expiryTime?: number, // in seconds
43
+ gatewayInfo?: GatewayInfo,
44
+ extraMeta?: JSONObject,
45
+ ) {
46
+ const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
47
+ return await this.client.rpcSingle<CreateBolt11Response>(
48
+ 'ln',
49
+ 'create_bolt11_invoice_for_user_tweaked',
50
+ {
51
+ amount: amountMsats,
52
+ description,
53
+ expiry_time: expiryTime ?? null,
54
+ user_key: tweakKey,
55
+ index,
56
+ extra_meta: extraMeta ?? {},
57
+ gateway,
58
+ },
59
+ )
60
+ }
61
+
62
+ // Returns the operation ids of payments received to the tweaks of the user secret key
63
+ async scanReceivesForTweaks(
64
+ tweakKey: string,
65
+ indices: number[],
66
+ extraMeta?: JSONObject,
67
+ ) {
68
+ return await this.client.rpcSingle<string[]>(
69
+ 'ln',
70
+ 'scan_receive_for_user_tweaked',
71
+ {
72
+ user_key: tweakKey,
73
+ indices,
74
+ extra_meta: extraMeta ?? {},
75
+ },
76
+ )
77
+ }
78
+
79
+ private async _getDefaultGatewayInfo() {
80
+ await this.updateGatewayCache()
81
+ const gateways = await this.listGateways()
82
+ return gateways[0]?.info
83
+ }
84
+
85
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
86
+ async payInvoice(
87
+ invoice: string,
88
+ gatewayInfo?: GatewayInfo,
89
+ extraMeta?: JSONObject,
90
+ ) {
91
+ const gateway = gatewayInfo ?? (await this._getDefaultGatewayInfo())
92
+ return await this.client.rpcSingle<OutgoingLightningPayment>(
93
+ 'ln',
94
+ 'pay_bolt11_invoice',
95
+ {
96
+ maybe_gateway: gateway,
97
+ invoice,
98
+ extra_meta: extraMeta ?? {},
99
+ },
100
+ )
101
+ }
102
+
103
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoicesync-invoice-string */
104
+ async payInvoiceSync(
105
+ invoice: string,
106
+ timeoutMs: number = 10000,
107
+ gatewayInfo?: GatewayInfo,
108
+ extraMeta?: JSONObject,
109
+ ) {
110
+ return new Promise<
111
+ | { success: false; error?: string }
112
+ | {
113
+ success: true
114
+ data: { feeMsats: number; preimage: string }
115
+ }
116
+ >(async (resolve, reject) => {
117
+ const { contract_id, fee } = await this.payInvoice(
118
+ invoice,
119
+ gatewayInfo,
120
+ extraMeta,
121
+ )
122
+
123
+ // TODO: handle error handling for other subscription statuses
124
+ const unsubscribe = this.subscribeLnPay(contract_id, (res) => {
125
+ if (typeof res !== 'string' && 'success' in res) {
126
+ clearTimeout(timeoutId)
127
+ unsubscribe()
128
+ resolve({
129
+ success: true,
130
+ data: { feeMsats: fee, preimage: res.success.preimage },
131
+ })
132
+ } else if (typeof res !== 'string' && 'unexpected_error' in res) {
133
+ reject(new Error(res.unexpected_error.error_message))
134
+ }
135
+ })
136
+
137
+ const timeoutId = setTimeout(() => {
138
+ unsubscribe()
139
+ resolve({ success: false, error: 'Payment timeout' })
140
+ }, timeoutMs)
141
+ })
142
+ }
143
+
144
+ // TODO: Document
145
+ subscribeLnClaim(
146
+ operationId: string,
147
+ onSuccess: (state: LnReceiveState) => void = () => {},
148
+ onError: (error: string) => void = () => {},
149
+ ) {
150
+ return this.client.rpcStream(
151
+ 'ln',
152
+ 'subscribe_ln_claim',
153
+ { operation_id: operationId },
154
+ onSuccess,
155
+ onError,
156
+ )
157
+ }
158
+
159
+ // TODO: Document (for external payments only)
160
+ // TODO: Make this work for BOTH internal and external payments
161
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
162
+ subscribeLnPay(
163
+ operationId: string,
164
+ onSuccess: (state: LnPayState) => void = () => {},
165
+ onError: (error: string) => void = () => {},
166
+ ) {
167
+ return this.client.rpcStream(
168
+ 'ln',
169
+ 'subscribe_ln_pay',
170
+ { operation_id: operationId },
171
+ onSuccess,
172
+ onError,
173
+ )
174
+ }
175
+
176
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/payInvoice#lightning-payinvoice-invoice-string */
177
+ async waitForPay(operationId: string) {
178
+ return new Promise<
179
+ | { success: false; error?: string }
180
+ | { success: true; data: { preimage: string } }
181
+ >((resolve, reject) => {
182
+ let unsubscribe: () => void
183
+ const timeoutId = setTimeout(() => {
184
+ resolve({ success: false, error: 'Waiting for receive timeout' })
185
+ }, 15000)
186
+
187
+ unsubscribe = this.subscribeLnPay(
188
+ operationId,
189
+ (res) => {
190
+ if (typeof res !== 'string' && 'success' in res) {
191
+ clearTimeout(timeoutId)
192
+ unsubscribe()
193
+ resolve({
194
+ success: true,
195
+ data: { preimage: res.success.preimage },
196
+ })
197
+ }
198
+ },
199
+ (error) => {
200
+ clearTimeout(timeoutId)
201
+ unsubscribe()
202
+ reject(error)
203
+ },
204
+ )
205
+ })
206
+ }
207
+
208
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
209
+ subscribeLnReceive(
210
+ operationId: string,
211
+ onSuccess: (state: LnReceiveState) => void = () => {},
212
+ onError: (error: string) => void = () => {},
213
+ ) {
214
+ return this.client.rpcStream(
215
+ 'ln',
216
+ 'subscribe_ln_receive',
217
+ { operation_id: operationId },
218
+ onSuccess,
219
+ onError,
220
+ )
221
+ }
222
+
223
+ /** https://web.fedimint.org/core/FedimintWallet/LightningService/createInvoice#lightning-createinvoice */
224
+ async waitForReceive(operationId: string, timeoutMs: number = 15000) {
225
+ return new Promise<LnReceiveState>((resolve, reject) => {
226
+ let unsubscribe: () => void
227
+ const timeoutId = setTimeout(() => {
228
+ reject(new Error('Timeout waiting for receive'))
229
+ }, timeoutMs)
230
+
231
+ unsubscribe = this.subscribeLnReceive(
232
+ operationId,
233
+ (res) => {
234
+ if (res === 'claimed') {
235
+ clearTimeout(timeoutId)
236
+ unsubscribe()
237
+ resolve(res)
238
+ }
239
+ },
240
+ (error) => {
241
+ clearTimeout(timeoutId)
242
+ unsubscribe()
243
+ reject(error)
244
+ },
245
+ )
246
+ })
247
+ }
248
+
249
+ async getGateway(
250
+ gatewayId: string | null = null,
251
+ forceInternal: boolean = false,
252
+ ) {
253
+ return await this.client.rpcSingle<LightningGateway | null>(
254
+ 'ln',
255
+ 'get_gateway',
256
+ {
257
+ gateway_id: gatewayId,
258
+ force_internal: forceInternal,
259
+ },
260
+ )
261
+ }
262
+
263
+ async listGateways() {
264
+ return await this.client.rpcSingle<LightningGateway[]>(
265
+ 'ln',
266
+ 'list_gateways',
267
+ {},
268
+ )
269
+ }
270
+
271
+ async updateGatewayCache() {
272
+ return await this.client.rpcSingle('ln', 'update_gateway_cache', {})
273
+ }
274
+ }
@@ -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
+ )
@@ -0,0 +1,129 @@
1
+ import { WorkerClient } from '../worker'
2
+ import type {
3
+ Duration,
4
+ JSONObject,
5
+ JSONValue,
6
+ MintSpendNotesResponse,
7
+ MSats,
8
+ NoteCountByDenomination,
9
+ ReissueExternalNotesState,
10
+ SpendNotesState,
11
+ } from '../types'
12
+
13
+ export class MintService {
14
+ constructor(private client: WorkerClient) {}
15
+
16
+ /** https://web.fedimint.org/core/FedimintWallet/MintService/redeemEcash */
17
+ async redeemEcash(notes: string) {
18
+ return await this.client.rpcSingle<string>(
19
+ 'mint',
20
+ 'reissue_external_notes',
21
+ {
22
+ oob_notes: notes, // "out of band notes"
23
+ extra_meta: null,
24
+ },
25
+ )
26
+ }
27
+
28
+ async reissueExternalNotes(oobNotes: string, extraMeta: JSONObject = {}) {
29
+ return await this.client.rpcSingle<string>(
30
+ 'mint',
31
+ 'reissue_external_notes',
32
+ {
33
+ oob_notes: oobNotes,
34
+ extra_meta: extraMeta,
35
+ },
36
+ )
37
+ }
38
+
39
+ subscribeReissueExternalNotes(
40
+ operationId: string,
41
+ onSuccess: (state: ReissueExternalNotesState) => void = () => {},
42
+ onError: (error: string) => void = () => {},
43
+ ) {
44
+ const unsubscribe = this.client.rpcStream<ReissueExternalNotesState>(
45
+ 'mint',
46
+ 'subscribe_reissue_external_notes',
47
+ { operation_id: operationId },
48
+ onSuccess,
49
+ onError,
50
+ )
51
+
52
+ return unsubscribe
53
+ }
54
+
55
+ /** https://web.fedimint.org/core/FedimintWallet/MintService/spendNotes */
56
+ async spendNotes(
57
+ amountMsats: number,
58
+ // Tells the wallet to automatically try to cancel the spend if it hasn't completed
59
+ // after the specified number of seconds. If the receiver has already redeemed
60
+ // the notes at this time, the notes will not be cancelled.
61
+ tryCancelAfter: number | Duration = 3600 * 24, // defaults to 1 day
62
+ includeInvite: boolean = false,
63
+ extraMeta: JSONValue = {},
64
+ ) {
65
+ const duration =
66
+ typeof tryCancelAfter === 'number'
67
+ ? { nanos: 0, secs: tryCancelAfter }
68
+ : tryCancelAfter
69
+
70
+ const res = await this.client.rpcSingle<MintSpendNotesResponse>(
71
+ 'mint',
72
+ 'spend_notes',
73
+ {
74
+ amount: amountMsats,
75
+ try_cancel_after: duration,
76
+ include_invite: includeInvite,
77
+ extra_meta: extraMeta,
78
+ },
79
+ )
80
+ const notes = res[1]
81
+ const operationId = res[0]
82
+
83
+ return {
84
+ notes,
85
+ operation_id: operationId,
86
+ }
87
+ }
88
+
89
+ /** https://web.fedimint.org/core/FedimintWallet/MintService/parseEcash */
90
+ async parseNotes(oobNotes: string) {
91
+ return await this.client.rpcSingle<MSats>('mint', 'validate_notes', {
92
+ oob_notes: oobNotes,
93
+ })
94
+ }
95
+
96
+ async tryCancelSpendNotes(operationId: string) {
97
+ await this.client.rpcSingle('mint', 'try_cancel_spend_notes', {
98
+ operation_id: operationId,
99
+ })
100
+ }
101
+
102
+ subscribeSpendNotes(
103
+ operationId: string,
104
+ onSuccess: (state: SpendNotesState) => void = () => {},
105
+ onError: (error: string) => void = () => {},
106
+ ) {
107
+ return this.client.rpcStream<SpendNotesState>(
108
+ 'mint',
109
+ 'subscribe_spend_notes',
110
+ { operation_id: operationId },
111
+ (res) => onSuccess(res),
112
+ onError,
113
+ )
114
+ }
115
+
116
+ async awaitSpendOobRefund(operationId: string) {
117
+ return await this.client.rpcSingle('mint', 'await_spend_oob_refund', {
118
+ operation_id: operationId,
119
+ })
120
+ }
121
+
122
+ async getNotesByDenomination() {
123
+ return await this.client.rpcSingle<NoteCountByDenomination>(
124
+ 'mint',
125
+ 'note_counts_by_denomination',
126
+ {},
127
+ )
128
+ }
129
+ }
@@ -0,0 +1,28 @@
1
+ import type { JSONValue } from '../types'
2
+ import { WorkerClient } from '../worker'
3
+
4
+ export class RecoveryService {
5
+ constructor(private client: WorkerClient) {}
6
+
7
+ async hasPendingRecoveries() {
8
+ return await this.client.rpcSingle<boolean>(
9
+ '',
10
+ 'has_pending_recoveries',
11
+ {},
12
+ )
13
+ }
14
+
15
+ async waitForAllRecoveries() {
16
+ await this.client.rpcSingle('', 'wait_for_all_recoveries', {})
17
+ }
18
+
19
+ subscribeToRecoveryProgress(
20
+ onSuccess: (progress: { module_id: number; progress: JSONValue }) => void,
21
+ onError: (error: string) => void,
22
+ ) {
23
+ return this.client.rpcStream<{
24
+ module_id: number
25
+ progress: JSONValue
26
+ }>('', 'subscribe_to_recovery_progress', {}, onSuccess, onError)
27
+ }
28
+ }
@@ -0,0 +1,24 @@
1
+ import { expect } from 'vitest'
2
+ import { walletTest } from '../test/fixtures'
3
+ import { TxOutputSummary, WalletSummary } from '../types'
4
+
5
+ walletTest(
6
+ 'getWalletSummary should return empty object if wallet is empty',
7
+ async ({ wallet }) => {
8
+ expect(wallet).toBeDefined()
9
+ expect(wallet.isOpen()).toBe(true)
10
+
11
+ const balance = await wallet.balance.getBalance()
12
+ expect(balance).toEqual(0)
13
+
14
+ const summary = await wallet.wallet.getWalletSummary()
15
+ const expectedSummary = {
16
+ spendable_utxos: expect.any(Array<TxOutputSummary>),
17
+ unsigned_peg_out_txos: expect.any(Array<TxOutputSummary>),
18
+ unsigned_change_utxos: expect.any(Array<TxOutputSummary>),
19
+ unconfirmed_peg_out_txos: expect.any(Array<TxOutputSummary>),
20
+ unconfirmed_change_utxos: expect.any(Array<TxOutputSummary>),
21
+ } satisfies WalletSummary
22
+ expect(summary).toEqual(expect.objectContaining(expectedSummary))
23
+ },
24
+ )
@@ -0,0 +1,10 @@
1
+ import { WalletSummary } from '../types'
2
+ import { WorkerClient } from '../worker'
3
+
4
+ export class WalletService {
5
+ constructor(private client: WorkerClient) {}
6
+
7
+ async getWalletSummary(): Promise<WalletSummary> {
8
+ return await this.client.rpcSingle('wallet', 'get_wallet_summary', {})
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ export { MintService } from './MintService'
2
+ export { BalanceService } from './BalanceService'
3
+ export { LightningService } from './LightningService'
4
+ export { RecoveryService } from './RecoveryService'
5
+ export { FederationService } from './FederationService'
6
+ export { WalletService } from './WalletService'
@@ -0,0 +1,26 @@
1
+ import { FedimintWallet } from '../FedimintWallet'
2
+ import { WorkerClient } from '../worker/WorkerClient'
3
+ import { TestingService } from './TestingService'
4
+
5
+ export class TestFedimintWallet extends FedimintWallet {
6
+ public testing: TestingService
7
+
8
+ constructor() {
9
+ super()
10
+ this.testing = new TestingService(this.getWorkerClient(), this.lightning)
11
+ }
12
+
13
+ async fundWallet(amount: number) {
14
+ const info = await this.testing.getFaucetGatewayInfo()
15
+ const invoice = await this.lightning.createInvoice(amount, '', 1000, info)
16
+ await Promise.all([
17
+ this.testing.payFaucetInvoice(invoice.invoice),
18
+ this.lightning.waitForReceive(invoice.operation_id),
19
+ ])
20
+ }
21
+
22
+ // Method to expose the WorkerClient
23
+ getWorkerClient(): WorkerClient {
24
+ return this['_client']
25
+ }
26
+ }
@@ -0,0 +1,79 @@
1
+ import { LightningService } from '../services'
2
+ import { WorkerClient } from '../worker'
3
+
4
+ export const TESTING_INVITE =
5
+ 'fed11qgqrsdnhwden5te0v9cxjtt4dekxzamxw4kz6mmjvvkhydted9ukg6r9xfsnx7th0fhn26tf093juamwv4u8gtnpwpcz7qqpyz0e327ua8geceutfrcaezwt22mk6s2rdy09kg72jrcmncng2gn0kp2m5sk'
6
+
7
+ // This is a testing service that allows for inspecting the internals
8
+ // of the WorkerClient. It is not intended for use in production.
9
+ export class TestingService {
10
+ public TESTING_INVITE: string
11
+ constructor(
12
+ private client: WorkerClient,
13
+ private lightning: LightningService,
14
+ ) {
15
+ // Solo Mint on mutinynet
16
+ this.TESTING_INVITE = TESTING_INVITE
17
+ }
18
+
19
+ getRequestCounter() {
20
+ return this.client._getRequestCounter()
21
+ }
22
+
23
+ getRequestCallbackMap() {
24
+ return this.client._getRequestCallbackMap()
25
+ }
26
+
27
+ async getInviteCode() {
28
+ const res = await fetch(`${import.meta.env.FAUCET}/connect-string`)
29
+ if (res.ok) {
30
+ return await res.text()
31
+ } else {
32
+ throw new Error(`Failed to get invite code: ${await res.text()}`)
33
+ }
34
+ }
35
+
36
+ private async getFaucetGatewayApi() {
37
+ const res = await fetch(`${import.meta.env.FAUCET}/gateway-api`)
38
+ if (res.ok) {
39
+ return await res.text()
40
+ } else {
41
+ throw new Error(`Failed to get gateway: ${await res.text()}`)
42
+ }
43
+ }
44
+
45
+ async getFaucetGatewayInfo() {
46
+ await this.lightning.updateGatewayCache()
47
+ const gateways = await this.lightning.listGateways()
48
+ const api = await this.getFaucetGatewayApi()
49
+ const gateway = gateways.find((g) => g.info.api === api)
50
+ if (!gateway) {
51
+ throw new Error(`Gateway not found: ${api}`)
52
+ }
53
+ return gateway.info
54
+ }
55
+
56
+ async payFaucetInvoice(invoice: string) {
57
+ const res = await fetch(`${import.meta.env.FAUCET}/pay`, {
58
+ method: 'POST',
59
+ body: invoice,
60
+ })
61
+ if (res.ok) {
62
+ return await res.text()
63
+ } else {
64
+ throw new Error(`Failed to pay faucet invoice: ${await res.text()}`)
65
+ }
66
+ }
67
+
68
+ async createFaucetInvoice(amount: number) {
69
+ const res = await fetch(`${import.meta.env.FAUCET}/invoice`, {
70
+ method: 'POST',
71
+ body: amount.toString(),
72
+ })
73
+ if (res.ok) {
74
+ return await res.text()
75
+ } else {
76
+ throw new Error(`Failed to generate faucet invoice: ${await res.text()}`)
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,44 @@
1
+ import * as secp256k1 from 'secp256k1'
2
+
3
+ const randomBytes = (size: number): Uint8Array => {
4
+ const array = new Uint8Array(size)
5
+ window.crypto.getRandomValues(array)
6
+ return array
7
+ }
8
+
9
+ interface KeyPair {
10
+ secretKey: string
11
+ publicKey: string
12
+ }
13
+
14
+ export const keyPair = (secretKey?: Uint8Array): KeyPair => {
15
+ const privateKey: Uint8Array = secretKey
16
+ ? validatePrivateKey(secretKey)
17
+ : generatePrivateKey()
18
+
19
+ const publicKey = secp256k1.publicKeyCreate(privateKey)
20
+
21
+ return {
22
+ secretKey: Array.from(privateKey)
23
+ .map((b) => b.toString(16).padStart(2, '0'))
24
+ .join(''), // Convert Uint8Array to hex string
25
+ publicKey: Array.from(publicKey)
26
+ .map((b) => b.toString(16).padStart(2, '0'))
27
+ .join(''), // Convert Uint8Array to hex string
28
+ }
29
+ }
30
+
31
+ const validatePrivateKey = (key: Uint8Array): Uint8Array => {
32
+ if (!secp256k1.privateKeyVerify(key)) {
33
+ throw new Error('Invalid private key provided')
34
+ }
35
+ return key
36
+ }
37
+
38
+ const generatePrivateKey = (): Uint8Array => {
39
+ let key: Uint8Array
40
+ do {
41
+ key = randomBytes(32)
42
+ } while (!secp256k1.privateKeyVerify(key))
43
+ return key
44
+ }