@fedimint/core-web 0.0.4 → 0.0.6
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/README.md +4 -4
- package/dist/FedimintWallet.d.ts +9 -4
- package/dist/FedimintWallet.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/BalanceService.d.ts +26 -0
- package/dist/services/BalanceService.d.ts.map +1 -1
- package/dist/services/FederationService.d.ts +0 -1
- package/dist/services/FederationService.d.ts.map +1 -1
- package/dist/services/LightningService.d.ts +6 -5
- package/dist/services/LightningService.d.ts.map +1 -1
- package/dist/services/MintService.d.ts +3 -3
- package/dist/services/MintService.d.ts.map +1 -1
- package/dist/types/wallet.d.ts +24 -11
- package/dist/types/wallet.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/worker/WorkerClient.d.ts +3 -0
- package/dist/worker/WorkerClient.d.ts.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +4 -6
- package/src/FedimintWallet.test.ts +12 -9
- package/src/FedimintWallet.ts +35 -22
- package/src/services/BalanceService.test.ts +50 -0
- package/src/services/BalanceService.ts +26 -0
- package/src/services/FederationService.test.ts +58 -0
- package/src/services/FederationService.ts +0 -10
- package/src/services/LightningService.test.ts +168 -0
- package/src/services/LightningService.ts +21 -5
- package/src/services/MintService.test.ts +19 -0
- package/src/services/MintService.ts +38 -18
- package/src/test/TestFedimintWallet.ts +17 -0
- package/src/test/TestingService.ts +59 -0
- package/src/test/setupTests.ts +43 -0
- package/src/types/wallet.ts +32 -13
- package/src/utils/logger.ts +61 -0
- package/src/worker/WorkerClient.ts +41 -2
- package/src/worker/worker.js +67 -56
- package/src/worker/worker.test.ts +90 -0
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm.d.ts +0 -49
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm.js +0 -4
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm_bg.js +0 -1411
- package/node_modules/@fedimint/fedimint-client-wasm/fedimint_client_wasm_bg.wasm +0 -0
- package/node_modules/@fedimint/fedimint-client-wasm/package.json +0 -23
package/src/FedimintWallet.ts
CHANGED
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
FederationService,
|
|
7
7
|
RecoveryService,
|
|
8
8
|
} from './services'
|
|
9
|
+
import { logger, type LogLevel } from './utils/logger'
|
|
9
10
|
|
|
10
11
|
const DEFAULT_CLIENT_NAME = 'fm-default' as const
|
|
11
12
|
|
|
12
13
|
export class FedimintWallet {
|
|
13
|
-
private
|
|
14
|
+
private _client: WorkerClient
|
|
14
15
|
|
|
15
16
|
public balance: BalanceService
|
|
16
17
|
public mint: MintService
|
|
@@ -18,14 +19,13 @@ export class FedimintWallet {
|
|
|
18
19
|
public federation: FederationService
|
|
19
20
|
public recovery: RecoveryService
|
|
20
21
|
|
|
21
|
-
private
|
|
22
|
-
private
|
|
22
|
+
private _openPromise: Promise<void> | null = null
|
|
23
|
+
private _resolveOpen: () => void = () => {}
|
|
23
24
|
private _isOpen: boolean = false
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Creates a new instance of FedimintWallet.
|
|
27
28
|
*
|
|
28
|
-
* @description
|
|
29
29
|
* This constructor initializes a FedimintWallet instance, which manages communication
|
|
30
30
|
* with a Web Worker. The Web Worker is responsible for running WebAssembly code that
|
|
31
31
|
* handles the core Fedimint Client operations.
|
|
@@ -53,15 +53,17 @@ export class FedimintWallet {
|
|
|
53
53
|
* lazyWallet.open();
|
|
54
54
|
*/
|
|
55
55
|
constructor(lazy: boolean = false) {
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
56
|
+
this._openPromise = new Promise((resolve) => {
|
|
57
|
+
this._resolveOpen = resolve
|
|
58
58
|
})
|
|
59
|
-
this.
|
|
60
|
-
this.mint = new MintService(this.
|
|
61
|
-
this.lightning = new LightningService(this.
|
|
62
|
-
this.balance = new BalanceService(this.
|
|
63
|
-
this.federation = new FederationService(this.
|
|
64
|
-
this.recovery = new RecoveryService(this.
|
|
59
|
+
this._client = new WorkerClient()
|
|
60
|
+
this.mint = new MintService(this._client)
|
|
61
|
+
this.lightning = new LightningService(this._client)
|
|
62
|
+
this.balance = new BalanceService(this._client)
|
|
63
|
+
this.federation = new FederationService(this._client)
|
|
64
|
+
this.recovery = new RecoveryService(this._client)
|
|
65
|
+
|
|
66
|
+
logger.info('FedimintWallet instantiated')
|
|
65
67
|
|
|
66
68
|
if (!lazy) {
|
|
67
69
|
this.initialize()
|
|
@@ -69,24 +71,26 @@ export class FedimintWallet {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
async initialize() {
|
|
72
|
-
|
|
74
|
+
logger.info('Initializing WorkerClient')
|
|
75
|
+
await this._client.initialize()
|
|
76
|
+
logger.info('WorkerClient initialized')
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
async waitForOpen() {
|
|
76
80
|
if (this._isOpen) return Promise.resolve()
|
|
77
|
-
return this.
|
|
81
|
+
return this._openPromise
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
async open(clientName: string = DEFAULT_CLIENT_NAME) {
|
|
81
|
-
await this.
|
|
85
|
+
await this._client.initialize()
|
|
82
86
|
// TODO: Determine if this should be safe or throw
|
|
83
87
|
if (this._isOpen) throw new Error('The FedimintWallet is already open.')
|
|
84
|
-
const { success } = await this.
|
|
88
|
+
const { success } = await this._client.sendSingleMessage('open', {
|
|
85
89
|
clientName,
|
|
86
90
|
})
|
|
87
91
|
if (success) {
|
|
88
92
|
this._isOpen = !!success
|
|
89
|
-
this.
|
|
93
|
+
this._resolveOpen()
|
|
90
94
|
}
|
|
91
95
|
return success
|
|
92
96
|
}
|
|
@@ -95,19 +99,19 @@ export class FedimintWallet {
|
|
|
95
99
|
inviteCode: string,
|
|
96
100
|
clientName: string = DEFAULT_CLIENT_NAME,
|
|
97
101
|
) {
|
|
98
|
-
await this.
|
|
102
|
+
await this._client.initialize()
|
|
99
103
|
// TODO: Determine if this should be safe or throw
|
|
100
104
|
if (this._isOpen)
|
|
101
105
|
throw new Error(
|
|
102
106
|
'The FedimintWallet is already open. You can only call `joinFederation` on closed clients.',
|
|
103
107
|
)
|
|
104
|
-
const response = await this.
|
|
108
|
+
const response = await this._client.sendSingleMessage('join', {
|
|
105
109
|
inviteCode,
|
|
106
110
|
clientName,
|
|
107
111
|
})
|
|
108
112
|
if (response.success) {
|
|
109
113
|
this._isOpen = true
|
|
110
|
-
this.
|
|
114
|
+
this._resolveOpen()
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -116,12 +120,21 @@ export class FedimintWallet {
|
|
|
116
120
|
* After this call, the FedimintWallet instance should be discarded.
|
|
117
121
|
*/
|
|
118
122
|
async cleanup() {
|
|
119
|
-
this.
|
|
123
|
+
this._openPromise = null
|
|
120
124
|
this._isOpen = false
|
|
121
|
-
this.
|
|
125
|
+
this._client.cleanup()
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
isOpen() {
|
|
125
129
|
return this._isOpen
|
|
126
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Sets the log level for the library.
|
|
134
|
+
* @param level The desired log level ('DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE').
|
|
135
|
+
*/
|
|
136
|
+
setLogLevel(level: LogLevel) {
|
|
137
|
+
logger.setLevel(level)
|
|
138
|
+
logger.info(`Log level set to ${level}.`)
|
|
139
|
+
}
|
|
127
140
|
}
|
|
@@ -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.toBeUndefined()
|
|
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
|
+
})
|
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import { WorkerClient } from '../worker'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Balance Service
|
|
5
|
+
*
|
|
6
|
+
* The Balance Service provides methods to interact with the balance of a Fedimint wallet.
|
|
7
|
+
*/
|
|
3
8
|
export class BalanceService {
|
|
4
9
|
constructor(private client: WorkerClient) {}
|
|
5
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Get the balance of the current wallet
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const balance = await wallet.balance.getBalance()
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
6
19
|
async getBalance(): Promise<number> {
|
|
7
20
|
return await this.client.rpcSingle('', 'get_balance', {})
|
|
8
21
|
}
|
|
9
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to the balance of the current wallet
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const unsubscribe = wallet.balance.subscribeBalance((balance) => {
|
|
29
|
+
* console.log(balance)
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* // ...Cleanup Later
|
|
33
|
+
* unsubscribe()
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
10
36
|
subscribeBalance(
|
|
11
37
|
onSuccess: (balance: number) => void = () => {},
|
|
12
38
|
onError: (error: string) => void = () => {},
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { walletTest } from '../test/setupTests'
|
|
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
|
+
)
|
|
@@ -16,16 +16,6 @@ export class FederationService {
|
|
|
16
16
|
return await this.client.rpcSingle('', 'get_invite_code', { peer })
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async joinFederation(inviteCode: string, clientName: string): Promise<void> {
|
|
20
|
-
const response = await this.client.sendSingleMessage('join', {
|
|
21
|
-
inviteCode,
|
|
22
|
-
clientName,
|
|
23
|
-
})
|
|
24
|
-
if (!response.success) {
|
|
25
|
-
throw new Error('Failed to join federation')
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
19
|
async listOperations(): Promise<JSONValue[]> {
|
|
30
20
|
return await this.client.rpcSingle('', 'list_operations', {})
|
|
31
21
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { walletTest } from '../test/setupTests'
|
|
3
|
+
|
|
4
|
+
walletTest(
|
|
5
|
+
'createInvoice should create a bolt11 invoice',
|
|
6
|
+
async ({ wallet }) => {
|
|
7
|
+
expect(wallet).toBeDefined()
|
|
8
|
+
expect(wallet.isOpen()).toBe(true)
|
|
9
|
+
|
|
10
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
11
|
+
const invoice = await wallet.lightning.createInvoice(100, 'test')
|
|
12
|
+
expect(invoice).toBeDefined()
|
|
13
|
+
expect(invoice).toMatchObject({
|
|
14
|
+
invoice: expect.any(String),
|
|
15
|
+
operation_id: expect.any(String),
|
|
16
|
+
})
|
|
17
|
+
// 3 requests were made, one for the invoice, one for refreshing the
|
|
18
|
+
// gateway cache, one for getting the gateway info
|
|
19
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 3)
|
|
20
|
+
|
|
21
|
+
// Test with expiry time
|
|
22
|
+
await expect(
|
|
23
|
+
wallet.lightning.createInvoice(100, 'test', 1000, {}),
|
|
24
|
+
).resolves.toBeDefined()
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
walletTest(
|
|
29
|
+
'listGateways should return a list of gateways',
|
|
30
|
+
async ({ wallet }) => {
|
|
31
|
+
expect(wallet).toBeDefined()
|
|
32
|
+
expect(wallet.isOpen()).toBe(true)
|
|
33
|
+
|
|
34
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
35
|
+
const gateways = await wallet.lightning.listGateways()
|
|
36
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
37
|
+
expect(gateways).toBeDefined()
|
|
38
|
+
expect(gateways).toMatchObject([
|
|
39
|
+
{
|
|
40
|
+
info: expect.any(Object),
|
|
41
|
+
},
|
|
42
|
+
])
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
walletTest(
|
|
47
|
+
'updateGatewayCache should update the gateway cache',
|
|
48
|
+
async ({ wallet }) => {
|
|
49
|
+
expect(wallet).toBeDefined()
|
|
50
|
+
expect(wallet.isOpen()).toBe(true)
|
|
51
|
+
|
|
52
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
53
|
+
await expect(wallet.lightning.updateGatewayCache()).resolves.toBeDefined()
|
|
54
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
walletTest('getGateway should return a gateway', async ({ wallet }) => {
|
|
59
|
+
expect(wallet).toBeDefined()
|
|
60
|
+
expect(wallet.isOpen()).toBe(true)
|
|
61
|
+
|
|
62
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
63
|
+
const gateway = await wallet.lightning.getGateway()
|
|
64
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
65
|
+
expect(gateway).toMatchObject({
|
|
66
|
+
api: expect.any(String),
|
|
67
|
+
fees: expect.any(Object),
|
|
68
|
+
gateway_id: expect.any(String),
|
|
69
|
+
gateway_redeem_key: expect.any(String),
|
|
70
|
+
lightning_alias: expect.any(String),
|
|
71
|
+
mint_channel_id: expect.any(Number),
|
|
72
|
+
node_pub_key: expect.any(String),
|
|
73
|
+
route_hints: expect.any(Array),
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
walletTest(
|
|
78
|
+
'createInvoiceWithGateway should create a bolt11 invoice with a gateway',
|
|
79
|
+
async ({ wallet }) => {
|
|
80
|
+
expect(wallet).toBeDefined()
|
|
81
|
+
expect(wallet.isOpen()).toBe(true)
|
|
82
|
+
|
|
83
|
+
const gateways = await wallet.lightning.listGateways()
|
|
84
|
+
const gateway = gateways[0]
|
|
85
|
+
expect(gateway).toBeDefined()
|
|
86
|
+
|
|
87
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
88
|
+
const invoice = await wallet.lightning.createInvoiceWithGateway(
|
|
89
|
+
100,
|
|
90
|
+
'test',
|
|
91
|
+
null,
|
|
92
|
+
{},
|
|
93
|
+
gateway.info,
|
|
94
|
+
)
|
|
95
|
+
expect(invoice).toBeDefined()
|
|
96
|
+
expect(invoice).toMatchObject({
|
|
97
|
+
invoice: expect.any(String),
|
|
98
|
+
operation_id: expect.any(String),
|
|
99
|
+
})
|
|
100
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 1)
|
|
101
|
+
await expect(
|
|
102
|
+
wallet.lightning.createInvoiceWithGateway(
|
|
103
|
+
100,
|
|
104
|
+
'test',
|
|
105
|
+
1000,
|
|
106
|
+
{},
|
|
107
|
+
gateway.info,
|
|
108
|
+
),
|
|
109
|
+
).resolves.toBeDefined()
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
walletTest(
|
|
114
|
+
'payInvoice should throw on insufficient funds',
|
|
115
|
+
async ({ wallet }) => {
|
|
116
|
+
expect(wallet).toBeDefined()
|
|
117
|
+
expect(wallet.isOpen()).toBe(true)
|
|
118
|
+
|
|
119
|
+
const invoice = await wallet.lightning.createInvoice(100, 'test')
|
|
120
|
+
expect(invoice).toBeDefined()
|
|
121
|
+
expect(invoice).toMatchObject({
|
|
122
|
+
invoice: expect.any(String),
|
|
123
|
+
operation_id: expect.any(String),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const counterBefore = wallet.testing.getRequestCounter()
|
|
127
|
+
// Insufficient funds
|
|
128
|
+
try {
|
|
129
|
+
await wallet.lightning.payInvoice(invoice.invoice, {})
|
|
130
|
+
expect.unreachable('Should throw error')
|
|
131
|
+
} catch (error) {
|
|
132
|
+
expect(error).toBeDefined()
|
|
133
|
+
}
|
|
134
|
+
// 3 requests were made, one for paying the invoice, one for refreshing the
|
|
135
|
+
// gateway cache, one for getting the gateway info
|
|
136
|
+
expect(wallet.testing.getRequestCounter()).toBe(counterBefore + 3)
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
walletTest(
|
|
141
|
+
'payInvoice should pay a bolt11 invoice',
|
|
142
|
+
{ timeout: 45000 },
|
|
143
|
+
async ({ wallet }) => {
|
|
144
|
+
expect(wallet).toBeDefined()
|
|
145
|
+
expect(wallet.isOpen()).toBe(true)
|
|
146
|
+
|
|
147
|
+
const gateways = await wallet.lightning.listGateways()
|
|
148
|
+
const gateway = gateways[0]
|
|
149
|
+
if (!gateway) {
|
|
150
|
+
expect.unreachable('Gateway not found')
|
|
151
|
+
}
|
|
152
|
+
const invoice = await wallet.lightning.createInvoice(10000, 'test')
|
|
153
|
+
await expect(
|
|
154
|
+
wallet.testing.payWithFaucet(invoice.invoice),
|
|
155
|
+
).resolves.toBeDefined()
|
|
156
|
+
await wallet.lightning.waitForReceive(invoice.operation_id)
|
|
157
|
+
// Wait for balance to fully update
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
159
|
+
const externalInvoice = await wallet.testing.getExternalInvoice(10)
|
|
160
|
+
const payment = await wallet.lightning.payInvoice(externalInvoice.pr)
|
|
161
|
+
expect(payment).toBeDefined()
|
|
162
|
+
expect(payment).toMatchObject({
|
|
163
|
+
contract_id: expect.any(String),
|
|
164
|
+
fee: expect.any(Number),
|
|
165
|
+
payment_type: expect.any(Object),
|
|
166
|
+
})
|
|
167
|
+
},
|
|
168
|
+
)
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
export class LightningService {
|
|
14
14
|
constructor(private client: WorkerClient) {}
|
|
15
15
|
|
|
16
|
-
async
|
|
16
|
+
async createInvoiceWithGateway(
|
|
17
17
|
amount: number,
|
|
18
18
|
description: string,
|
|
19
19
|
expiryTime: number | null = null,
|
|
@@ -29,7 +29,7 @@ export class LightningService {
|
|
|
29
29
|
})
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async
|
|
32
|
+
async createInvoice(
|
|
33
33
|
amount: number,
|
|
34
34
|
description: string,
|
|
35
35
|
expiryTime: number | null = null,
|
|
@@ -46,7 +46,7 @@ export class LightningService {
|
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async
|
|
49
|
+
async payInvoiceWithGateway(
|
|
50
50
|
invoice: string,
|
|
51
51
|
gatewayInfo: GatewayInfo,
|
|
52
52
|
extraMeta: JSONObject = {},
|
|
@@ -58,12 +58,12 @@ export class LightningService {
|
|
|
58
58
|
})
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async _getDefaultGatewayInfo(): Promise<LightningGateway> {
|
|
61
|
+
private async _getDefaultGatewayInfo(): Promise<LightningGateway> {
|
|
62
62
|
const gateways = await this.listGateways()
|
|
63
63
|
return gateways[0]
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
async
|
|
66
|
+
async payInvoice(
|
|
67
67
|
invoice: string,
|
|
68
68
|
extraMeta: JSONObject = {},
|
|
69
69
|
): Promise<OutgoingLightningPayment> {
|
|
@@ -108,6 +108,22 @@ export class LightningService {
|
|
|
108
108
|
return unsubscribe
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
async waitForReceive(operationId: string): Promise<LnReceiveState> {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const unsubscribe = this.subscribeLnReceive(
|
|
114
|
+
operationId,
|
|
115
|
+
(res) => {
|
|
116
|
+
if (res === 'claimed') resolve(res)
|
|
117
|
+
},
|
|
118
|
+
reject,
|
|
119
|
+
)
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
unsubscribe()
|
|
122
|
+
reject(new Error('Timeout waiting for receive'))
|
|
123
|
+
}, 10000)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
111
127
|
async getGateway(
|
|
112
128
|
gatewayId: string | null = null,
|
|
113
129
|
forceInternal: boolean = false,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
import { walletTest } from '../test/setupTests'
|
|
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 reissue external notes',
|
|
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
|
+
)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { WorkerClient } from '../worker'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Duration,
|
|
4
|
+
JSONObject,
|
|
5
|
+
JSONValue,
|
|
6
|
+
MintSpendNotesResponse,
|
|
7
|
+
ReissueExternalNotesState,
|
|
8
|
+
} from '../types/wallet'
|
|
3
9
|
|
|
4
10
|
export class MintService {
|
|
5
11
|
constructor(private client: WorkerClient) {}
|
|
@@ -13,7 +19,7 @@ export class MintService {
|
|
|
13
19
|
|
|
14
20
|
async reissueExternalNotes(
|
|
15
21
|
oobNotes: string,
|
|
16
|
-
extraMeta: JSONObject,
|
|
22
|
+
extraMeta: JSONObject = {},
|
|
17
23
|
): Promise<string> {
|
|
18
24
|
return await this.client.rpcSingle('mint', 'reissue_external_notes', {
|
|
19
25
|
oob_notes: oobNotes,
|
|
@@ -26,12 +32,6 @@ export class MintService {
|
|
|
26
32
|
onSuccess: (state: JSONValue) => void = () => {},
|
|
27
33
|
onError: (error: string) => void = () => {},
|
|
28
34
|
) {
|
|
29
|
-
type ReissueExternalNotesState =
|
|
30
|
-
| 'Created'
|
|
31
|
-
| 'Issuing'
|
|
32
|
-
| 'Done'
|
|
33
|
-
| { Failed: { error: string } }
|
|
34
|
-
|
|
35
35
|
const unsubscribe = this.client.rpcStream<ReissueExternalNotesState>(
|
|
36
36
|
'mint',
|
|
37
37
|
'subscribe_reissue_external_notes',
|
|
@@ -45,16 +45,36 @@ export class MintService {
|
|
|
45
45
|
|
|
46
46
|
async spendNotes(
|
|
47
47
|
minAmount: number,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
// Tells the wallet to automatically try to cancel the spend if it hasn't completed
|
|
49
|
+
// after the specified number of milliseconds.
|
|
50
|
+
// If the receiver has already redeemed the notes at this time,
|
|
51
|
+
// the notes will not be cancelled
|
|
52
|
+
tryCancelAfter: number | Duration = 0,
|
|
53
|
+
includeInvite: boolean = false,
|
|
54
|
+
extraMeta: JSONValue = {},
|
|
55
|
+
): Promise<MintSpendNotesResponse> {
|
|
56
|
+
const duration =
|
|
57
|
+
typeof tryCancelAfter === 'number'
|
|
58
|
+
? { nanos: 0, secs: tryCancelAfter }
|
|
59
|
+
: tryCancelAfter
|
|
60
|
+
|
|
61
|
+
const res = await this.client.rpcSingle<Array<string>>(
|
|
62
|
+
'mint',
|
|
63
|
+
'spend_notes',
|
|
64
|
+
{
|
|
65
|
+
min_amount: minAmount,
|
|
66
|
+
try_cancel_after: duration,
|
|
67
|
+
include_invite: includeInvite,
|
|
68
|
+
extra_meta: extraMeta,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
const notes = res[1]
|
|
72
|
+
const operationId = res[0]
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
notes,
|
|
76
|
+
operation_id: operationId,
|
|
77
|
+
}
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
async validateNotes(oobNotes: string): Promise<number> {
|
|
@@ -0,0 +1,17 @@
|
|
|
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())
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Method to expose the WorkerClient
|
|
14
|
+
getWorkerClient(): WorkerClient {
|
|
15
|
+
return this['_client']
|
|
16
|
+
}
|
|
17
|
+
}
|