@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.
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/dts/FedimintWallet.d.ts +114 -0
- package/dist/dts/FedimintWallet.d.ts.map +1 -0
- package/dist/dts/index.d.ts +3 -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 +11 -0
- package/dist/dts/services/FederationService.d.ts.map +1 -0
- package/dist/dts/services/LightningService.d.ts +47 -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 +8 -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/types/index.d.ts +4 -0
- package/dist/dts/types/index.d.ts.map +1 -0
- package/dist/dts/types/utils.d.ts +23 -0
- package/dist/dts/types/utils.d.ts.map +1 -0
- package/dist/dts/types/wallet.d.ts +102 -0
- package/dist/dts/types/wallet.d.ts.map +1 -0
- package/dist/dts/types/worker.d.ts +4 -0
- package/dist/dts/types/worker.d.ts.map +1 -0
- package/dist/dts/utils/logger.d.ts +17 -0
- package/dist/dts/utils/logger.d.ts.map +1 -0
- package/dist/dts/worker/WorkerClient.d.ts +44 -0
- package/dist/dts/worker/WorkerClient.d.ts.map +1 -0
- package/dist/dts/worker/index.d.ts +2 -0
- package/dist/dts/worker/index.d.ts.map +1 -0
- package/dist/index.d.ts +392 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.js +2 -0
- package/dist/worker.js.map +1 -0
- package/package.json +43 -0
- package/src/FedimintWallet.test.ts +73 -0
- package/src/FedimintWallet.ts +215 -0
- package/src/index.ts +2 -0
- package/src/services/BalanceService.test.ts +50 -0
- package/src/services/BalanceService.ts +29 -0
- package/src/services/FederationService.test.ts +58 -0
- package/src/services/FederationService.ts +24 -0
- package/src/services/LightningService.test.ts +208 -0
- package/src/services/LightningService.ts +274 -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 +24 -0
- package/src/services/WalletService.ts +10 -0
- package/src/services/index.ts +6 -0
- package/src/test/TestFedimintWallet.ts +26 -0
- package/src/test/TestingService.ts +79 -0
- package/src/test/crypto.ts +44 -0
- package/src/test/fixtures.test.ts +18 -0
- package/src/test/fixtures.ts +70 -0
- package/src/types/index.ts +3 -0
- package/src/types/utils.ts +29 -0
- package/src/types/wallet.ts +141 -0
- package/src/types/worker.ts +16 -0
- package/src/utils/logger.ts +61 -0
- package/src/worker/WorkerClient.test.ts +6 -0
- package/src/worker/WorkerClient.ts +236 -0
- package/src/worker/index.ts +1 -0
- package/src/worker/worker.js +167 -0
- package/src/worker/worker.test.ts +90 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { WorkerClient } from './worker'
|
|
2
|
+
import {
|
|
3
|
+
BalanceService,
|
|
4
|
+
MintService,
|
|
5
|
+
LightningService,
|
|
6
|
+
FederationService,
|
|
7
|
+
RecoveryService,
|
|
8
|
+
WalletService,
|
|
9
|
+
} from './services'
|
|
10
|
+
import { logger, type LogLevel } from './utils/logger'
|
|
11
|
+
import { FederationConfig, JSONValue } from './types'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_CLIENT_NAME = 'fm-default' as const
|
|
14
|
+
|
|
15
|
+
export class FedimintWallet {
|
|
16
|
+
private _client: WorkerClient
|
|
17
|
+
|
|
18
|
+
public balance: BalanceService
|
|
19
|
+
public mint: MintService
|
|
20
|
+
public lightning: LightningService
|
|
21
|
+
public federation: FederationService
|
|
22
|
+
public recovery: RecoveryService
|
|
23
|
+
public wallet: WalletService
|
|
24
|
+
|
|
25
|
+
private _openPromise: Promise<void> | undefined = undefined
|
|
26
|
+
private _resolveOpen: () => void = () => {}
|
|
27
|
+
private _isOpen: boolean = false
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new instance of FedimintWallet.
|
|
31
|
+
*
|
|
32
|
+
* This constructor initializes a FedimintWallet instance, which manages communication
|
|
33
|
+
* with a Web Worker. The Web Worker is responsible for running WebAssembly code that
|
|
34
|
+
* handles the core Fedimint Client operations.
|
|
35
|
+
*
|
|
36
|
+
* (default) When not in lazy mode, the constructor immediately initializes the
|
|
37
|
+
* Web Worker and begins loading the WebAssembly module in the background. This
|
|
38
|
+
* allows for faster subsequent operations but may increase initial load time.
|
|
39
|
+
*
|
|
40
|
+
* In lazy mode, the Web Worker and WebAssembly initialization are deferred until
|
|
41
|
+
* the first operation that requires them, reducing initial overhead at the cost
|
|
42
|
+
* of a slight delay on the first operation.
|
|
43
|
+
*
|
|
44
|
+
* @param {boolean} lazy - If true, delays Web Worker and WebAssembly initialization
|
|
45
|
+
* until needed. Default is false.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Create a wallet with immediate initialization
|
|
49
|
+
* const wallet = new FedimintWallet();
|
|
50
|
+
* wallet.open();
|
|
51
|
+
*
|
|
52
|
+
* // Create a wallet with lazy initialization
|
|
53
|
+
* const lazyWallet = new FedimintWallet(true);
|
|
54
|
+
* // Some time later...
|
|
55
|
+
* lazyWallet.initialize();
|
|
56
|
+
* lazyWallet.open();
|
|
57
|
+
*/
|
|
58
|
+
constructor(lazy: boolean = false) {
|
|
59
|
+
this._openPromise = new Promise((resolve) => {
|
|
60
|
+
this._resolveOpen = resolve
|
|
61
|
+
})
|
|
62
|
+
this._client = new WorkerClient()
|
|
63
|
+
this.mint = new MintService(this._client)
|
|
64
|
+
this.lightning = new LightningService(this._client)
|
|
65
|
+
this.balance = new BalanceService(this._client)
|
|
66
|
+
this.federation = new FederationService(this._client)
|
|
67
|
+
this.recovery = new RecoveryService(this._client)
|
|
68
|
+
this.wallet = new WalletService(this._client)
|
|
69
|
+
|
|
70
|
+
logger.info('FedimintWallet instantiated')
|
|
71
|
+
|
|
72
|
+
if (!lazy) {
|
|
73
|
+
this.initialize()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async initialize() {
|
|
78
|
+
logger.info('Initializing WorkerClient')
|
|
79
|
+
await this._client.initialize()
|
|
80
|
+
logger.info('WorkerClient initialized')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async waitForOpen() {
|
|
84
|
+
if (this._isOpen) return Promise.resolve()
|
|
85
|
+
return this._openPromise
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async open(clientName: string = DEFAULT_CLIENT_NAME) {
|
|
89
|
+
await this._client.initialize()
|
|
90
|
+
// TODO: Determine if this should be safe or throw
|
|
91
|
+
if (this._isOpen) throw new Error('The FedimintWallet is already open.')
|
|
92
|
+
const { success } = await this._client.sendSingleMessage<{
|
|
93
|
+
success: boolean
|
|
94
|
+
}>('open', { clientName })
|
|
95
|
+
if (success) {
|
|
96
|
+
this._isOpen = !!success
|
|
97
|
+
this._resolveOpen()
|
|
98
|
+
}
|
|
99
|
+
return success
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async joinFederation(
|
|
103
|
+
inviteCode: string,
|
|
104
|
+
clientName: string = DEFAULT_CLIENT_NAME,
|
|
105
|
+
) {
|
|
106
|
+
await this._client.initialize()
|
|
107
|
+
// TODO: Determine if this should be safe or throw
|
|
108
|
+
if (this._isOpen)
|
|
109
|
+
throw new Error(
|
|
110
|
+
'The FedimintWallet is already open. You can only call `joinFederation` on closed clients.',
|
|
111
|
+
)
|
|
112
|
+
try {
|
|
113
|
+
const response = await this._client.sendSingleMessage<{
|
|
114
|
+
success: boolean
|
|
115
|
+
}>('join', { inviteCode, clientName })
|
|
116
|
+
if (response.success) {
|
|
117
|
+
this._isOpen = true
|
|
118
|
+
this._resolveOpen()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return response.success
|
|
122
|
+
} catch (e) {
|
|
123
|
+
logger.error('Error joining federation', e)
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* This should ONLY be called when UNLOADING the wallet client.
|
|
130
|
+
* After this call, the FedimintWallet instance should be discarded.
|
|
131
|
+
*/
|
|
132
|
+
async cleanup() {
|
|
133
|
+
this._openPromise = undefined
|
|
134
|
+
this._isOpen = false
|
|
135
|
+
await this._client.cleanup()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
isOpen() {
|
|
139
|
+
return this._isOpen
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async previewFederation(inviteCode: string) {
|
|
143
|
+
const response = this._client.sendSingleMessage<{
|
|
144
|
+
config: FederationConfig
|
|
145
|
+
federation_id: string
|
|
146
|
+
}>('previewFederation', { inviteCode })
|
|
147
|
+
return response
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets the log level for the library.
|
|
152
|
+
* @param level The desired log level ('DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE').
|
|
153
|
+
*/
|
|
154
|
+
setLogLevel(level: LogLevel) {
|
|
155
|
+
logger.setLevel(level)
|
|
156
|
+
logger.info(`Log level set to ${level}.`)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parses a federation invite code and retrieves its details.
|
|
161
|
+
*
|
|
162
|
+
* This method sends the provided invite code to the WorkerClient for parsing.
|
|
163
|
+
* The response includes the federation_id and url.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} inviteCode - The invite code to be parsed.
|
|
166
|
+
* @returns {Promise<{ federation_id: string, url: string}>}
|
|
167
|
+
* A promise that resolves to an object containing:
|
|
168
|
+
* - `federation_id`: The id of the feder.
|
|
169
|
+
* - `url`: One of the apipoints to connect to the federation
|
|
170
|
+
*
|
|
171
|
+
* @throws {Error} If the WorkerClient encounters an issue during the parsing process.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* const inviteCode = "example-invite-code";
|
|
175
|
+
* const parsedCode = await wallet.parseInviteCode(inviteCode);
|
|
176
|
+
* console.log(parsedCode.federation_id, parsedCode.url);
|
|
177
|
+
*/
|
|
178
|
+
async parseInviteCode(inviteCode: string) {
|
|
179
|
+
const response = await this._client.sendSingleMessage<{
|
|
180
|
+
type: string
|
|
181
|
+
data: JSONValue
|
|
182
|
+
requestId: number
|
|
183
|
+
}>('parseInviteCode', { inviteCode })
|
|
184
|
+
return response
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Parses a BOLT11 Lightning invoice and retrieves its details.
|
|
189
|
+
*
|
|
190
|
+
* This method sends the provided invoice string to the WorkerClient for parsing.
|
|
191
|
+
* The response includes details such as the amount, expiry, and memo.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} invoiceStr - The BOLT11 invoice string to be parsed.
|
|
194
|
+
* @returns {Promise<{ amount: string, expiry: number, memo: string }>}
|
|
195
|
+
* A promise that resolves to an object containing:
|
|
196
|
+
* - `amount`: The amount specified in the invoice.
|
|
197
|
+
* - `expiry`: The expiry time of the invoice in seconds.
|
|
198
|
+
* - `memo`: A description or memo attached to the invoice.
|
|
199
|
+
*
|
|
200
|
+
* @throws {Error} If the WorkerClient encounters an issue during the parsing process.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* const invoiceStr = "lnbc1...";
|
|
204
|
+
* const parsedInvoice = await wallet.parseBolt11Invoice(invoiceStr);
|
|
205
|
+
* console.log(parsedInvoice.amount, parsedInvoice.expiry, parsedInvoice.memo);
|
|
206
|
+
*/
|
|
207
|
+
async parseBolt11Invoice(invoiceStr: string) {
|
|
208
|
+
const response = await this._client.sendSingleMessage<{
|
|
209
|
+
type: string
|
|
210
|
+
data: JSONValue
|
|
211
|
+
requestId: number
|
|
212
|
+
}>('parseBolt11Invoice', { invoiceStr })
|
|
213
|
+
return response
|
|
214
|
+
}
|
|
215
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { test, expect } from 'vitest'
|
|
2
|
+
import { TestFedimintWallet } from '../test/TestFedimintWallet'
|
|
3
|
+
import { beforeAll } from 'vitest'
|
|
4
|
+
|
|
5
|
+
let randomTestingId: string
|
|
6
|
+
let wallet: TestFedimintWallet
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
randomTestingId = Math.random().toString(36).substring(2, 15)
|
|
10
|
+
wallet = new TestFedimintWallet()
|
|
11
|
+
expect(wallet).toBeDefined()
|
|
12
|
+
await expect(
|
|
13
|
+
wallet.joinFederation(wallet.testing.TESTING_INVITE, randomTestingId),
|
|
14
|
+
).resolves.toBe(true)
|
|
15
|
+
expect(wallet.isOpen()).toBe(true)
|
|
16
|
+
|
|
17
|
+
// Cleanup after all tests
|
|
18
|
+
return async () => {
|
|
19
|
+
// clear up browser resources
|
|
20
|
+
await wallet.cleanup()
|
|
21
|
+
// remove the wallet db
|
|
22
|
+
indexedDB.deleteDatabase(randomTestingId)
|
|
23
|
+
// swap out the randomTestingId for a new one, to avoid raciness
|
|
24
|
+
randomTestingId = Math.random().toString(36).substring(2, 15)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('getBalance should be initially zero', async () => {
|
|
29
|
+
expect(wallet).toBeDefined()
|
|
30
|
+
expect(wallet.isOpen()).toBe(true)
|
|
31
|
+
const beforeGetBalance = wallet.testing.getRequestCounter()
|
|
32
|
+
await expect(wallet.balance.getBalance()).resolves.toEqual(0)
|
|
33
|
+
expect(wallet.testing.getRequestCounter()).toBe(beforeGetBalance + 1)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('subscribe balance', async () => {
|
|
37
|
+
expect(wallet).toBeDefined()
|
|
38
|
+
expect(wallet.isOpen()).toBe(true)
|
|
39
|
+
|
|
40
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
41
|
+
const callbacksBefore = wallet.testing.getRequestCallbackMap().size
|
|
42
|
+
const unsubscribe = await wallet.balance.subscribeBalance((balance) => {
|
|
43
|
+
expect(balance).toEqual(0)
|
|
44
|
+
})
|
|
45
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
46
|
+
expect(wallet.testing.getRequestCallbackMap().size).toBe(callbacksBefore + 1)
|
|
47
|
+
|
|
48
|
+
await expect(wallet.balance.getBalance()).resolves.toEqual(0)
|
|
49
|
+
expect(unsubscribe()).toBeUndefined()
|
|
50
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { WorkerClient } from '../worker'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Balance Service
|
|
5
|
+
*
|
|
6
|
+
* The Balance Service provides methods to interact with the balance of a Fedimint wallet.
|
|
7
|
+
*/
|
|
8
|
+
export class BalanceService {
|
|
9
|
+
constructor(private client: WorkerClient) {}
|
|
10
|
+
|
|
11
|
+
/** https://web.fedimint.org/core/FedimintWallet/BalanceService/getBalance */
|
|
12
|
+
async getBalance() {
|
|
13
|
+
return await this.client.rpcSingle<number>('', 'get_balance', {})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** https://web.fedimint.org/core/FedimintWallet/BalanceService/subscribeBalance */
|
|
17
|
+
subscribeBalance(
|
|
18
|
+
onSuccess: (balanceMsats: number) => void = () => {},
|
|
19
|
+
onError: (error: string) => void = () => {},
|
|
20
|
+
) {
|
|
21
|
+
return this.client.rpcStream<string>(
|
|
22
|
+
'',
|
|
23
|
+
'subscribe_balance_changes',
|
|
24
|
+
{},
|
|
25
|
+
(res) => onSuccess(parseInt(res)),
|
|
26
|
+
onError,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { walletTest } from '../test/fixtures'
|
|
3
|
+
|
|
4
|
+
walletTest(
|
|
5
|
+
'getConfig should return the federation config',
|
|
6
|
+
async ({ wallet }) => {
|
|
7
|
+
expect(wallet).toBeDefined()
|
|
8
|
+
expect(wallet.isOpen()).toBe(true)
|
|
9
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
10
|
+
await expect(wallet.federation.getConfig()).resolves.toMatchObject({
|
|
11
|
+
api_endpoints: expect.any(Object),
|
|
12
|
+
broadcast_public_keys: expect.any(Object),
|
|
13
|
+
consensus_version: expect.any(Object),
|
|
14
|
+
meta: expect.any(Object),
|
|
15
|
+
modules: expect.any(Object),
|
|
16
|
+
})
|
|
17
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
walletTest(
|
|
22
|
+
'getFederationId should return the federation id',
|
|
23
|
+
async ({ wallet }) => {
|
|
24
|
+
expect(wallet).toBeDefined()
|
|
25
|
+
expect(wallet.isOpen()).toBe(true)
|
|
26
|
+
|
|
27
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
28
|
+
const federationId = await wallet.federation.getFederationId()
|
|
29
|
+
expect(federationId).toBeTypeOf('string')
|
|
30
|
+
expect(federationId).toHaveLength(64)
|
|
31
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
walletTest(
|
|
36
|
+
'getInviteCode should return the invite code',
|
|
37
|
+
async ({ wallet }) => {
|
|
38
|
+
expect(wallet).toBeDefined()
|
|
39
|
+
expect(wallet.isOpen()).toBe(true)
|
|
40
|
+
|
|
41
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
42
|
+
const inviteCode = await wallet.federation.getInviteCode(0)
|
|
43
|
+
expect(inviteCode).toBeTypeOf('string')
|
|
44
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
walletTest(
|
|
49
|
+
'listOperations should return the list of operations',
|
|
50
|
+
async ({ wallet }) => {
|
|
51
|
+
expect(wallet).toBeDefined()
|
|
52
|
+
expect(wallet.isOpen()).toBe(true)
|
|
53
|
+
|
|
54
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
55
|
+
await expect(wallet.federation.listOperations()).resolves.toMatchObject([])
|
|
56
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
57
|
+
},
|
|
58
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JSONValue } from '../types'
|
|
2
|
+
import { WorkerClient } from '../worker'
|
|
3
|
+
|
|
4
|
+
export class FederationService {
|
|
5
|
+
constructor(private client: WorkerClient) {}
|
|
6
|
+
|
|
7
|
+
async getConfig() {
|
|
8
|
+
return await this.client.rpcSingle('', 'get_config', {})
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async getFederationId() {
|
|
12
|
+
return await this.client.rpcSingle<string>('', 'get_federation_id', {})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getInviteCode(peer: number = 0) {
|
|
16
|
+
return await this.client.rpcSingle<string | null>('', 'get_invite_code', {
|
|
17
|
+
peer,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async listOperations() {
|
|
22
|
+
return await this.client.rpcSingle<JSONValue[]>('', 'list_operations', {})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
)
|