@bsv/wallet-toolbox 1.1.62 → 1.2.1
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/docs/client.md +2339 -182
- package/docs/wallet.md +2339 -182
- package/out/src/CWIStyleWalletManager.d.ts +417 -0
- package/out/src/CWIStyleWalletManager.d.ts.map +1 -0
- package/out/src/CWIStyleWalletManager.js +1153 -0
- package/out/src/CWIStyleWalletManager.js.map +1 -0
- package/out/src/SimpleWalletManager.d.ts +169 -0
- package/out/src/SimpleWalletManager.d.ts.map +1 -0
- package/out/src/SimpleWalletManager.js +315 -0
- package/out/src/SimpleWalletManager.js.map +1 -0
- package/out/src/Wallet.d.ts +6 -1
- package/out/src/Wallet.d.ts.map +1 -1
- package/out/src/Wallet.js +39 -7
- package/out/src/Wallet.js.map +1 -1
- package/out/src/WalletAuthenticationManager.d.ts +33 -0
- package/out/src/WalletAuthenticationManager.d.ts.map +1 -0
- package/out/src/WalletAuthenticationManager.js +110 -0
- package/out/src/WalletAuthenticationManager.js.map +1 -0
- package/out/src/WalletPermissionsManager.d.ts +575 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -0
- package/out/src/WalletPermissionsManager.js +1789 -0
- package/out/src/WalletPermissionsManager.js.map +1 -0
- package/out/src/WalletSettingsManager.d.ts +59 -0
- package/out/src/WalletSettingsManager.d.ts.map +1 -0
- package/out/src/WalletSettingsManager.js +189 -0
- package/out/src/WalletSettingsManager.js.map +1 -0
- package/out/src/__tests/CWIStyleWalletManager.test.d.ts +2 -0
- package/out/src/__tests/CWIStyleWalletManager.test.d.ts.map +1 -0
- package/out/src/__tests/CWIStyleWalletManager.test.js +471 -0
- package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.js +239 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.checks.test.js +637 -0
- package/out/src/__tests/WalletPermissionsManager.checks.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.encryption.test.js +295 -0
- package/out/src/__tests/WalletPermissionsManager.encryption.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts +83 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.js +261 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.js +377 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.initialization.test.js +227 -0
- package/out/src/__tests/WalletPermissionsManager.initialization.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.proxying.test.js +566 -0
- package/out/src/__tests/WalletPermissionsManager.proxying.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js +454 -0
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -0
- package/out/src/index.all.d.ts +9 -0
- package/out/src/index.all.d.ts.map +1 -1
- package/out/src/index.all.js +9 -0
- package/out/src/index.all.js.map +1 -1
- package/out/src/index.client.d.ts +9 -0
- package/out/src/index.client.d.ts.map +1 -1
- package/out/src/index.client.js +9 -0
- package/out/src/index.client.js.map +1 -1
- package/out/src/sdk/CertOpsWallet.d.ts +7 -0
- package/out/src/sdk/CertOpsWallet.d.ts.map +1 -0
- package/out/src/sdk/CertOpsWallet.js +3 -0
- package/out/src/sdk/CertOpsWallet.js.map +1 -0
- package/out/src/sdk/__test/CertificateLifeCycle.test.js +19 -82
- package/out/src/sdk/__test/CertificateLifeCycle.test.js.map +1 -1
- package/out/src/sdk/index.d.ts +1 -1
- package/out/src/sdk/index.d.ts.map +1 -1
- package/out/src/sdk/index.js +1 -1
- package/out/src/sdk/index.js.map +1 -1
- package/out/src/sdk/validationHelpers.d.ts.map +1 -1
- package/out/src/sdk/validationHelpers.js +13 -12
- package/out/src/sdk/validationHelpers.js.map +1 -1
- package/out/src/services/__tests/bitrails.test.js +7 -2
- package/out/src/services/__tests/bitrails.test.js.map +1 -1
- package/out/src/services/providers/__tests/WhatsOnChain.test.js +3 -3
- package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -1
- package/out/src/signer/methods/proveCertificate.d.ts.map +1 -1
- package/out/src/signer/methods/proveCertificate.js +3 -19
- package/out/src/signer/methods/proveCertificate.js.map +1 -1
- package/out/src/storage/__test/WalletStorageManager.test.js +1 -1
- package/out/src/storage/__test/WalletStorageManager.test.js.map +1 -1
- package/out/src/storage/remoting/StorageClient.d.ts +2 -2
- package/out/src/storage/remoting/StorageClient.d.ts.map +1 -1
- package/out/src/storage/remoting/StorageClient.js +1 -1
- package/out/src/storage/remoting/StorageClient.js.map +1 -1
- package/out/src/utility/identityUtils.d.ts +31 -0
- package/out/src/utility/identityUtils.d.ts.map +1 -0
- package/out/src/utility/identityUtils.js +116 -0
- package/out/src/utility/identityUtils.js.map +1 -0
- package/out/src/wab-client/WABClient.d.ts +49 -0
- package/out/src/wab-client/WABClient.d.ts.map +1 -0
- package/out/src/wab-client/WABClient.js +83 -0
- package/out/src/wab-client/WABClient.js.map +1 -0
- package/out/src/wab-client/__tests/WABClient.man.test.d.ts +2 -0
- package/out/src/wab-client/__tests/WABClient.man.test.d.ts.map +1 -0
- package/out/src/wab-client/__tests/WABClient.man.test.js +52 -0
- package/out/src/wab-client/__tests/WABClient.man.test.js.map +1 -0
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts +34 -0
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts.map +1 -0
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js +16 -0
- package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js.map +1 -0
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts +7 -0
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts.map +1 -0
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js +36 -0
- package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js.map +1 -0
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts +28 -0
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts.map +1 -0
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js +69 -0
- package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js.map +1 -0
- package/out/test/Wallet/action/internalizeAction.a.test.js +1 -1
- package/out/test/Wallet/action/internalizeAction.a.test.js.map +1 -1
- package/out/test/Wallet/certificate/acquireCertificate.test.js +26 -29
- package/out/test/Wallet/certificate/acquireCertificate.test.js.map +1 -1
- package/out/test/storage/KnexMigrations.test.js +1 -1
- package/out/test/storage/KnexMigrations.test.js.map +1 -1
- package/out/test/storage/update.test.js +1 -1
- package/out/test/storage/update.test.js.map +1 -1
- package/out/test/utils/TestUtilsWalletStorage.d.ts +9 -5
- package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
- package/out/test/utils/TestUtilsWalletStorage.js +15 -9
- package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
- package/out/test/wallet/action/internalizeAction.test.js +1 -1
- package/out/test/wallet/action/internalizeAction.test.js.map +1 -1
- package/out/test/wallet/list/listActions2.test.js +1 -1
- package/out/test/wallet/list/listActions2.test.js.map +1 -1
- package/out/test/wallet/sync/Wallet.sync.test.js +1 -1
- package/out/test/wallet/sync/Wallet.sync.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/src/CWIStyleWalletManager.ts +1738 -0
- package/src/SimpleWalletManager.ts +526 -0
- package/src/Wallet.ts +70 -7
- package/src/WalletAuthenticationManager.ts +150 -0
- package/src/WalletPermissionsManager.ts +2424 -0
- package/src/WalletSettingsManager.ts +243 -0
- package/src/__tests/CWIStyleWalletManager.test.ts +604 -0
- package/src/__tests/WalletPermissionsManager.callbacks.test.ts +323 -0
- package/src/__tests/WalletPermissionsManager.checks.test.ts +839 -0
- package/src/__tests/WalletPermissionsManager.encryption.test.ts +370 -0
- package/src/__tests/WalletPermissionsManager.fixtures.ts +284 -0
- package/src/__tests/WalletPermissionsManager.flows.test.ts +457 -0
- package/src/__tests/WalletPermissionsManager.initialization.test.ts +300 -0
- package/src/__tests/WalletPermissionsManager.proxying.test.ts +706 -0
- package/src/__tests/WalletPermissionsManager.tokens.test.ts +546 -0
- package/src/index.all.ts +9 -0
- package/src/index.client.ts +9 -0
- package/src/sdk/CertOpsWallet.ts +18 -0
- package/src/sdk/__test/CertificateLifeCycle.test.ts +66 -113
- package/src/sdk/index.ts +1 -1
- package/src/sdk/validationHelpers.ts +12 -11
- package/src/services/__tests/bitrails.test.ts +7 -2
- package/src/services/providers/__tests/WhatsOnChain.test.ts +3 -3
- package/src/signer/methods/proveCertificate.ts +14 -21
- package/src/storage/__test/WalletStorageManager.test.ts +1 -1
- package/src/storage/remoting/StorageClient.ts +4 -4
- package/src/utility/identityUtils.ts +159 -0
- package/src/wab-client/WABClient.ts +94 -0
- package/src/wab-client/__tests/WABClient.man.test.ts +59 -0
- package/src/wab-client/auth-method-interactors/AuthMethodInteractor.ts +47 -0
- package/src/wab-client/auth-method-interactors/PersonaIDInteractor.ts +35 -0
- package/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.ts +72 -0
- package/test/Wallet/action/internalizeAction.a.test.ts +1 -1
- package/test/Wallet/certificate/acquireCertificate.test.ts +89 -30
- package/test/storage/KnexMigrations.test.ts +1 -1
- package/test/storage/update.test.ts +1 -1
- package/test/utils/TestUtilsWalletStorage.ts +24 -13
- package/test/wallet/action/internalizeAction.test.ts +1 -1
- package/test/wallet/list/listActions2.test.ts +1 -1
- package/test/wallet/sync/Wallet.sync.test.ts +1 -1
- package/out/src/sdk/CertOps.d.ts +0 -66
- package/out/src/sdk/CertOps.d.ts.map +0 -1
- package/out/src/sdk/CertOps.js +0 -190
- package/out/src/sdk/CertOps.js.map +0 -1
- package/src/sdk/CertOps.ts +0 -274
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { mockUnderlyingWallet, MockedBSV_SDK } from './WalletPermissionsManager.fixtures'
|
|
2
|
+
import { WalletPermissionsManager } from '../WalletPermissionsManager'
|
|
3
|
+
import { jest } from '@jest/globals'
|
|
4
|
+
|
|
5
|
+
jest.mock('@bsv/sdk', () => MockedBSV_SDK)
|
|
6
|
+
|
|
7
|
+
describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
|
|
8
|
+
let underlying: ReturnType<typeof mockUnderlyingWallet>
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Create a fresh underlying mock wallet before each test
|
|
12
|
+
underlying = mockUnderlyingWallet()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.clearAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('Unit Tests for metadata encryption helpers', () => {
|
|
20
|
+
it('should call underlying.encrypt() with the correct protocol and key when encryptWalletMetadata=true', async () => {
|
|
21
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
22
|
+
encryptWalletMetadata: true
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const plaintext = 'Hello, world!'
|
|
26
|
+
await manager['maybeEncryptMetadata'](plaintext)
|
|
27
|
+
|
|
28
|
+
// We expect underlying.encrypt() to have been called exactly once
|
|
29
|
+
expect(underlying.encrypt).toHaveBeenCalledTimes(1)
|
|
30
|
+
|
|
31
|
+
// Check that the call was with the correct protocol ID and key
|
|
32
|
+
expect(underlying.encrypt).toHaveBeenCalledWith(
|
|
33
|
+
{
|
|
34
|
+
plaintext: expect.any(Array), // byte array version of 'Hello, world!'
|
|
35
|
+
protocolID: [2, 'admin metadata encryption'],
|
|
36
|
+
keyID: '1'
|
|
37
|
+
},
|
|
38
|
+
'admin.domain.com'
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should NOT call underlying.encrypt() if encryptWalletMetadata=false', async () => {
|
|
43
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
44
|
+
encryptWalletMetadata: false
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const plaintext = 'No encryption needed!'
|
|
48
|
+
const result = await manager['maybeEncryptMetadata'](plaintext)
|
|
49
|
+
|
|
50
|
+
expect(result).toBe(plaintext)
|
|
51
|
+
expect(underlying.encrypt).not.toHaveBeenCalled()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should call underlying.decrypt() with correct protocol and key, returning plaintext on success', async () => {
|
|
55
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
56
|
+
encryptWalletMetadata: true
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Underlying decrypt mock returns { plaintext: [42, 42] } by default
|
|
60
|
+
// which would become "**" if using our ASCII interpretation
|
|
61
|
+
;(underlying.decrypt as any).mockResolvedValueOnce({
|
|
62
|
+
plaintext: [72, 105] // 'Hi'
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const ciphertext = 'random-string-representing-ciphertext'
|
|
66
|
+
const result = await manager['maybeDecryptMetadata'](ciphertext)
|
|
67
|
+
|
|
68
|
+
// We expect underlying.decrypt() to have been called
|
|
69
|
+
expect(underlying.decrypt).toHaveBeenCalledTimes(1)
|
|
70
|
+
expect(underlying.decrypt).toHaveBeenCalledWith(
|
|
71
|
+
{
|
|
72
|
+
ciphertext: expect.any(Array), // byte array version of ciphertext
|
|
73
|
+
protocolID: [2, 'admin metadata encryption'],
|
|
74
|
+
keyID: '1'
|
|
75
|
+
},
|
|
76
|
+
'admin.domain.com'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// The manager returns the decrypted UTF-8 string
|
|
80
|
+
expect(result).toBe('Hi')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should fallback to original string if underlying.decrypt() fails', async () => {
|
|
84
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
85
|
+
encryptWalletMetadata: true
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Make underlying.decrypt() throw an error to simulate failure
|
|
89
|
+
;(underlying.decrypt as any).mockImplementationOnce(() => {
|
|
90
|
+
throw new Error('Decryption error')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const ciphertext = 'this-was-not-valid-for-decryption'
|
|
94
|
+
const result = await manager['maybeDecryptMetadata'](ciphertext)
|
|
95
|
+
|
|
96
|
+
// The manager should return the original ciphertext if decryption throws
|
|
97
|
+
expect(result).toBe(ciphertext)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('Integration Tests for createAction + listActions (round-trip encryption)', () => {
|
|
102
|
+
it('should encrypt metadata fields in createAction when encryptWalletMetadata=true, then decrypt them in listActions', async () => {
|
|
103
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
104
|
+
encryptWalletMetadata: true
|
|
105
|
+
})
|
|
106
|
+
manager.bindCallback('onSpendingAuthorizationRequested', x => {
|
|
107
|
+
manager.grantPermission({ requestID: x.requestID, ephemeral: true })
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// We prepare an action with multiple metadata fields
|
|
111
|
+
const actionDescription = 'User Action #1: Doing something important'
|
|
112
|
+
const inputDesc = 'Some input desc'
|
|
113
|
+
const outputDesc = 'Some output desc'
|
|
114
|
+
const customInstr = 'Some custom instructions'
|
|
115
|
+
|
|
116
|
+
// Our createAction call
|
|
117
|
+
await manager.createAction(
|
|
118
|
+
{
|
|
119
|
+
description: actionDescription,
|
|
120
|
+
inputs: [
|
|
121
|
+
{
|
|
122
|
+
outpoint: '0231.0',
|
|
123
|
+
unlockingScriptLength: 73,
|
|
124
|
+
inputDescription: inputDesc
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
outputs: [
|
|
128
|
+
{
|
|
129
|
+
lockingScript: '561234',
|
|
130
|
+
satoshis: 500,
|
|
131
|
+
outputDescription: outputDesc,
|
|
132
|
+
customInstructions: customInstr
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
'nonadmin.com'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
// 1) Confirm underlying.encrypt() was called for each field that is non-empty:
|
|
140
|
+
// - description
|
|
141
|
+
// - inputDescription
|
|
142
|
+
// - outputDescription
|
|
143
|
+
// - customInstructions
|
|
144
|
+
// (We can't be certain how many times exactly, but we can check that it was at least 4.)
|
|
145
|
+
expect(underlying.encrypt).toHaveBeenCalledTimes(4)
|
|
146
|
+
|
|
147
|
+
// 2) Now we simulate listing actions. We'll have the manager call underlying.listActions.
|
|
148
|
+
// Our mock underlying wallet returns an empty array by default, so let's override it
|
|
149
|
+
// to return the "encrypted" data that the manager gave it.
|
|
150
|
+
// But the manager doesn't store that data in the underlying wallet mock automatically.
|
|
151
|
+
// We'll just pretend that the wallet returns some data, and ensure the manager tries to decrypt it.
|
|
152
|
+
;(underlying.listActions as any).mockResolvedValueOnce({
|
|
153
|
+
totalActions: 1,
|
|
154
|
+
actions: [
|
|
155
|
+
{
|
|
156
|
+
description: 'fake-encrypted-string-for-description',
|
|
157
|
+
inputs: [
|
|
158
|
+
{
|
|
159
|
+
outpoint: 'txid1.0',
|
|
160
|
+
inputDescription: 'fake-encrypted-string-for-inputDesc'
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
outputs: [
|
|
164
|
+
{
|
|
165
|
+
lockingScript: 'OP_RETURN 1234',
|
|
166
|
+
satoshis: 500,
|
|
167
|
+
outputDescription: 'fake-encrypted-string-for-outputDesc',
|
|
168
|
+
customInstructions: 'fake-encrypted-string-for-customInstr'
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Also mock decrypt calls to simulate a correct round-trip
|
|
176
|
+
const decryptMock = underlying.decrypt as any
|
|
177
|
+
decryptMock.mockResolvedValueOnce({
|
|
178
|
+
plaintext: Array.from(actionDescription).map(c => c.charCodeAt(0))
|
|
179
|
+
})
|
|
180
|
+
decryptMock.mockResolvedValueOnce({
|
|
181
|
+
plaintext: Array.from(inputDesc).map(c => c.charCodeAt(0))
|
|
182
|
+
})
|
|
183
|
+
decryptMock.mockResolvedValueOnce({
|
|
184
|
+
plaintext: Array.from(outputDesc).map(c => c.charCodeAt(0))
|
|
185
|
+
})
|
|
186
|
+
decryptMock.mockResolvedValueOnce({
|
|
187
|
+
plaintext: Array.from(customInstr).map(c => c.charCodeAt(0))
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const result = await (manager as any).listActions({}, 'nonadmin.com')
|
|
191
|
+
|
|
192
|
+
// We should get exactly 1 action
|
|
193
|
+
expect(result.actions.length).toBe(1)
|
|
194
|
+
const action = result.actions[0]
|
|
195
|
+
|
|
196
|
+
// The manager is expected to have decrypted each field
|
|
197
|
+
expect(action.description).toBe(actionDescription)
|
|
198
|
+
expect(action.inputs[0].inputDescription).toBe(inputDesc)
|
|
199
|
+
expect(action.outputs[0].outputDescription).toBe(outputDesc)
|
|
200
|
+
expect(action.outputs[0].customInstructions).toBe(customInstr)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should not encrypt metadata if encryptWalletMetadata=false, storing and retrieving plaintext', async () => {
|
|
204
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
205
|
+
encryptWalletMetadata: false
|
|
206
|
+
})
|
|
207
|
+
manager.bindCallback('onSpendingAuthorizationRequested', x => {
|
|
208
|
+
manager.grantPermission({ requestID: x.requestID, ephemeral: true })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
const actionDescription = 'Plaintext action description'
|
|
212
|
+
const inputDesc = 'Plaintext input desc'
|
|
213
|
+
const outputDesc = 'Plaintext output desc'
|
|
214
|
+
const customInstr = 'Plaintext instructions'
|
|
215
|
+
await manager.createAction(
|
|
216
|
+
{
|
|
217
|
+
description: actionDescription,
|
|
218
|
+
inputs: [
|
|
219
|
+
{
|
|
220
|
+
outpoint: '9876.0',
|
|
221
|
+
unlockingScriptLength: 73,
|
|
222
|
+
inputDescription: inputDesc
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
outputs: [
|
|
226
|
+
{
|
|
227
|
+
lockingScript: 'ABCD',
|
|
228
|
+
satoshis: 123,
|
|
229
|
+
outputDescription: outputDesc,
|
|
230
|
+
customInstructions: customInstr
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
'nonadmin.com'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Because encryption is disabled, underlying.encrypt() is not called
|
|
238
|
+
expect(underlying.encrypt).not.toHaveBeenCalled()
|
|
239
|
+
|
|
240
|
+
// Simulate that the wallet actually stored them in plaintext and is returning them as-is
|
|
241
|
+
;(underlying.listActions as any).mockResolvedValue({
|
|
242
|
+
totalActions: 1,
|
|
243
|
+
actions: [
|
|
244
|
+
{
|
|
245
|
+
description: actionDescription,
|
|
246
|
+
inputs: [
|
|
247
|
+
{
|
|
248
|
+
outpoint: '0123.0',
|
|
249
|
+
inputDescription: inputDesc
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
outputs: [
|
|
253
|
+
{
|
|
254
|
+
lockingScript: 'ABCD',
|
|
255
|
+
satoshis: 123,
|
|
256
|
+
outputDescription: outputDesc,
|
|
257
|
+
customInstructions: customInstr
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Decrypt is still called, because we try to decrypt regardless of whether encryption is enabled.
|
|
265
|
+
// This allows us to disable it on a wallet that had it in the past. The result is that when not encrypted,
|
|
266
|
+
// the plaintext is returned if decryption fails. If it was encrypted from metadata encryption being enabled in
|
|
267
|
+
// the past (even when not enabled now), we will still decrypt and see the correct plaintext rather than garbage.
|
|
268
|
+
// To simulate, we make decryption pass through.
|
|
269
|
+
underlying.decrypt.mockImplementation(x => x)
|
|
270
|
+
const listResult = await (manager as any).listActions({}, 'nonadmin.com')
|
|
271
|
+
expect(underlying.decrypt).toHaveBeenCalledTimes(4)
|
|
272
|
+
|
|
273
|
+
// Confirm the returned data is the same as originally provided (plaintext)
|
|
274
|
+
const [first] = listResult.actions
|
|
275
|
+
expect(first.description).toBe(actionDescription)
|
|
276
|
+
expect(first.inputs[0].inputDescription).toBe(inputDesc)
|
|
277
|
+
expect(first.outputs[0].outputDescription).toBe(outputDesc)
|
|
278
|
+
expect(first.outputs[0].customInstructions).toBe(customInstr)
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
describe('Integration Test for listOutputs decryption', () => {
|
|
283
|
+
it('should decrypt customInstructions in listOutputs if encryptWalletMetadata=true', async () => {
|
|
284
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
285
|
+
encryptWalletMetadata: true
|
|
286
|
+
})
|
|
287
|
+
manager.bindCallback('onBasketAccessRequested', x => {
|
|
288
|
+
manager.grantPermission({ requestID: x.requestID, ephemeral: true })
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// Suppose we have an output with custom instructions that was stored encrypted
|
|
292
|
+
;(underlying.listOutputs as any).mockResolvedValue({
|
|
293
|
+
totalOutputs: 1,
|
|
294
|
+
outputs: [
|
|
295
|
+
{
|
|
296
|
+
outpoint: 'fakeTxid.0',
|
|
297
|
+
satoshis: 999,
|
|
298
|
+
lockingScript: 'OP_RETURN something',
|
|
299
|
+
basket: 'some-basket',
|
|
300
|
+
customInstructions: 'fake-encrypted-instructions-string'
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const originalInstr = 'Please do not reveal this data.'
|
|
306
|
+
// We'll mock decrypt() to interpret 'fake-encrypted-instructions-string' as a success
|
|
307
|
+
;(underlying.decrypt as any).mockResolvedValueOnce({
|
|
308
|
+
plaintext: Array.from(originalInstr).map(ch => ch.charCodeAt(0))
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
const outputsResult = await manager.listOutputs(
|
|
312
|
+
{
|
|
313
|
+
basket: 'some-basket'
|
|
314
|
+
},
|
|
315
|
+
'some-origin.com'
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
expect(outputsResult.outputs.length).toBe(1)
|
|
319
|
+
expect(outputsResult.outputs[0].customInstructions).toBe(originalInstr)
|
|
320
|
+
|
|
321
|
+
// Confirm we tried to decrypt
|
|
322
|
+
expect(underlying.decrypt).toHaveBeenCalledTimes(1)
|
|
323
|
+
expect(underlying.decrypt).toHaveBeenCalledWith(
|
|
324
|
+
{
|
|
325
|
+
ciphertext: expect.any(Array),
|
|
326
|
+
protocolID: [2, 'admin metadata encryption'],
|
|
327
|
+
keyID: '1'
|
|
328
|
+
},
|
|
329
|
+
'admin.domain.com'
|
|
330
|
+
)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should fallback to the original ciphertext if decrypt fails in listOutputs', async () => {
|
|
334
|
+
const manager = new WalletPermissionsManager(underlying, 'admin.domain.com', {
|
|
335
|
+
encryptWalletMetadata: true
|
|
336
|
+
})
|
|
337
|
+
manager.bindCallback('onBasketAccessRequested', x => {
|
|
338
|
+
manager.grantPermission({ requestID: x.requestID, ephemeral: true })
|
|
339
|
+
})
|
|
340
|
+
;(underlying.listOutputs as any).mockResolvedValue({
|
|
341
|
+
totalOutputs: 1,
|
|
342
|
+
outputs: [
|
|
343
|
+
{
|
|
344
|
+
outpoint: 'fakeTxid.1',
|
|
345
|
+
satoshis: 500,
|
|
346
|
+
lockingScript: 'OP_RETURN something',
|
|
347
|
+
basket: 'some-basket',
|
|
348
|
+
customInstructions: 'bad-ciphertext-of-some-kind'
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Force an error from decrypt
|
|
354
|
+
;(underlying.decrypt as any).mockImplementationOnce(() => {
|
|
355
|
+
throw new Error('Failed to decrypt')
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const outputsResult = await manager.listOutputs(
|
|
359
|
+
{
|
|
360
|
+
basket: 'some-basket'
|
|
361
|
+
},
|
|
362
|
+
'some-origin.com'
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
expect(outputsResult.outputs.length).toBe(1)
|
|
366
|
+
// Should fall back to the original 'bad-ciphertext-of-some-kind'
|
|
367
|
+
expect(outputsResult.outputs[0].customInstructions).toBe('bad-ciphertext-of-some-kind')
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
})
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A permissions manager testing mock/stub file for:
|
|
3
|
+
* 1) The `@bsv/sdk` library: Transaction, LockingScript, PushDrop, Utils, Random, etc.
|
|
4
|
+
* 2) A BRC-100 `WalletInterface` (the underlying wallet).
|
|
5
|
+
*
|
|
6
|
+
* This file bypasses real validation/logic in `@bsv/sdk`, returning placeholders and
|
|
7
|
+
* stubs to prevent test-time errors such as "Invalid Atomic BEEF prefix."
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* ---------------------------------------------------------------------------
|
|
11
|
+
* 1) Partial Mocks for @bsv/sdk
|
|
12
|
+
* -------------------------------------------------------------------------*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A minimal mock for `Transaction` that won't throw "Invalid Atomic BEEF prefix."
|
|
16
|
+
* We override the static methods so they do not do real parsing/validation.
|
|
17
|
+
*/
|
|
18
|
+
export class MockTransaction {
|
|
19
|
+
public inputs: any[] = [{}]
|
|
20
|
+
public outputs: any[] = []
|
|
21
|
+
public fee: number = 0
|
|
22
|
+
|
|
23
|
+
constructor() {}
|
|
24
|
+
static fromAtomicBEEF() {
|
|
25
|
+
// Mocked below
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static fromBEEF(beef: number[]): MockTransaction {
|
|
29
|
+
// Same approach as above
|
|
30
|
+
return new MockTransaction()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getFee(): number {
|
|
34
|
+
return this.fee
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
;(MockTransaction as any).fromAtomicBEEF = jest.fn(() => {
|
|
39
|
+
// We skip real validation, returning a MockTransaction with minimal structure.
|
|
40
|
+
const tx = new MockTransaction()
|
|
41
|
+
return tx
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Mocks for `LockingScript`. If your code calls e.g. LockingScript.fromHex, we can just
|
|
46
|
+
* store the hex and do nothing else.
|
|
47
|
+
*/
|
|
48
|
+
export class MockLockingScript {
|
|
49
|
+
hex: string
|
|
50
|
+
constructor(hex: string) {
|
|
51
|
+
this.hex = hex
|
|
52
|
+
}
|
|
53
|
+
public toHex(): string {
|
|
54
|
+
return this.hex
|
|
55
|
+
}
|
|
56
|
+
static fromHex(hex: string): MockLockingScript {
|
|
57
|
+
return new MockLockingScript(hex)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* We stub out all methods: `decode()`, `lock()`, `unlock()`.
|
|
63
|
+
*/
|
|
64
|
+
export class MockPushDrop {
|
|
65
|
+
// Typically we might store the wallet reference, but we can skip for now.
|
|
66
|
+
constructor() {
|
|
67
|
+
//
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Decodes a LockingScript into some {fields: number[][], protocol...} or undefined
|
|
71
|
+
static decode(script: MockLockingScript): { fields: number[][] } | undefined {
|
|
72
|
+
// If you rely on a real format, parse or store a pattern.
|
|
73
|
+
// For now, returning a minimal stub: empty fields
|
|
74
|
+
if (!script || !script.hex) return undefined
|
|
75
|
+
if (script.hex.includes('some script')) {
|
|
76
|
+
// When needed, return some fields
|
|
77
|
+
return {
|
|
78
|
+
fields: [
|
|
79
|
+
[],
|
|
80
|
+
[],
|
|
81
|
+
[],
|
|
82
|
+
[],
|
|
83
|
+
[],
|
|
84
|
+
[],
|
|
85
|
+
[] // 7 fields should always be enough...
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Just pretend we always decode to a single empty field array
|
|
90
|
+
return { fields: [] }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
lock(
|
|
94
|
+
fields: number[][],
|
|
95
|
+
protocolID: [number, string],
|
|
96
|
+
keyID: string,
|
|
97
|
+
counterparty: string,
|
|
98
|
+
singleSignature: boolean,
|
|
99
|
+
anyoneCanPay: boolean
|
|
100
|
+
): MockLockingScript {
|
|
101
|
+
return new MockLockingScript('deadbeef')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unlock(
|
|
105
|
+
protocolID: [number, string],
|
|
106
|
+
keyID: string,
|
|
107
|
+
counterparty: string,
|
|
108
|
+
sighashType: string,
|
|
109
|
+
enforceReplayProtection: boolean,
|
|
110
|
+
sigSize: number,
|
|
111
|
+
lockingScript: MockLockingScript
|
|
112
|
+
): {
|
|
113
|
+
sign: (tx: MockTransaction, vin: number) => Promise<MockLockingScript>
|
|
114
|
+
} {
|
|
115
|
+
// In real usage, it would handle signature logic. We'll return a minimal stub.
|
|
116
|
+
return {
|
|
117
|
+
sign: async (tx: MockTransaction, vin: number) => {
|
|
118
|
+
// produce a minimal unlocking script
|
|
119
|
+
return new MockLockingScript('mockUnlockingScript')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Mocks for Utils, e.g. toHex, toUTF8, fromUTF8, etc.
|
|
127
|
+
* We can provide minimal stubs that won't break your code.
|
|
128
|
+
*/
|
|
129
|
+
export const MockUtils = {
|
|
130
|
+
toHex: (data: number[]) => {
|
|
131
|
+
// Converts an array of numbers to a hexadecimal string.
|
|
132
|
+
return data.map(num => num.toString(16).padStart(2, '0')).join('')
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
toArray: (str: string, encoding = 'utf8') => {
|
|
136
|
+
// Converts a string to an array of numbers based on the encoding.
|
|
137
|
+
if (encoding === 'hex') {
|
|
138
|
+
const arr: number[] = []
|
|
139
|
+
for (let i = 0; i < str.length; i += 2) {
|
|
140
|
+
arr.push(parseInt(str.substr(i, 2), 16))
|
|
141
|
+
}
|
|
142
|
+
return arr
|
|
143
|
+
} else if (encoding === 'base64') {
|
|
144
|
+
const binaryStr = atob(str)
|
|
145
|
+
return Array.from(binaryStr, char => char.charCodeAt(0))
|
|
146
|
+
} else if (encoding === 'utf8') {
|
|
147
|
+
return Array.from(str, char => char.charCodeAt(0))
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error('Unsupported encoding: ' + encoding)
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
toUTF8: (arr: number[]) => {
|
|
154
|
+
// Converts an array of numbers to a UTF-8 string.
|
|
155
|
+
return String.fromCharCode(...arr)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Mocks for Random
|
|
161
|
+
*/
|
|
162
|
+
export const MockRandom = (size: number): number[] => {
|
|
163
|
+
return [...require('crypto').randomBytes(size)]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Overriding the real classes with our mocks.
|
|
168
|
+
*/
|
|
169
|
+
export const MockedBSV_SDK = {
|
|
170
|
+
Transaction: MockTransaction,
|
|
171
|
+
LockingScript: MockLockingScript,
|
|
172
|
+
PushDrop: MockPushDrop,
|
|
173
|
+
Utils: MockUtils,
|
|
174
|
+
Random: MockRandom,
|
|
175
|
+
Certificate: null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ---------------------------------------------------------------------------
|
|
179
|
+
* 2) A full mock for the BRC-100 WalletInterface
|
|
180
|
+
* -------------------------------------------------------------------------*/
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A helper function returning a Jest-mocked `WalletInterface`.
|
|
184
|
+
* This ensures all required methods exist and return plausible values.
|
|
185
|
+
*
|
|
186
|
+
* - By default, `createAction` returns a signableTransaction with empty arrays,
|
|
187
|
+
* so that the manager can call `Transaction.fromAtomicBEEF([])` without throwing.
|
|
188
|
+
* - You can override or chain .mockResolvedValueOnce(...) inside individual tests
|
|
189
|
+
* if you want more specific behavior in certain test steps.
|
|
190
|
+
*/
|
|
191
|
+
export function mockUnderlyingWallet(): jest.Mocked<any> {
|
|
192
|
+
return {
|
|
193
|
+
getPublicKey: jest.fn().mockResolvedValue({ publicKey: '029999...' }),
|
|
194
|
+
revealCounterpartyKeyLinkage: jest.fn().mockResolvedValue({
|
|
195
|
+
encryptedLinkage: [1, 2, 3],
|
|
196
|
+
encryptedLinkageProof: [4, 5, 6],
|
|
197
|
+
prover: '02abcdef...',
|
|
198
|
+
verifier: '02cccccc...',
|
|
199
|
+
counterparty: '02bbbbbb...',
|
|
200
|
+
revelationTime: new Date().toISOString()
|
|
201
|
+
}),
|
|
202
|
+
revealSpecificKeyLinkage: jest.fn().mockResolvedValue({
|
|
203
|
+
encryptedLinkage: [1, 2, 3],
|
|
204
|
+
encryptedLinkageProof: [4, 5, 6],
|
|
205
|
+
prover: '02abcdef...',
|
|
206
|
+
verifier: '02cccccc...',
|
|
207
|
+
counterparty: '02bbbbbb...',
|
|
208
|
+
protocolID: [1, 'test-protocol'],
|
|
209
|
+
keyID: 'testKey',
|
|
210
|
+
proofType: 1
|
|
211
|
+
}),
|
|
212
|
+
encrypt: jest.fn().mockResolvedValue({ ciphertext: [42, 42, 42, 42, 42, 42, 42] }),
|
|
213
|
+
decrypt: jest.fn().mockResolvedValue({ plaintext: [42, 42, 42, 42, 42] }),
|
|
214
|
+
createHmac: jest.fn().mockResolvedValue({ hmac: [0xaa] }),
|
|
215
|
+
verifyHmac: jest.fn().mockResolvedValue({ valid: true }),
|
|
216
|
+
createSignature: jest.fn().mockResolvedValue({ signature: [0x30, 0x44] }),
|
|
217
|
+
verifySignature: jest.fn().mockResolvedValue({ valid: true }),
|
|
218
|
+
|
|
219
|
+
createAction: jest.fn(async x => {
|
|
220
|
+
if (x.options && x.options.signAndProcess === true) {
|
|
221
|
+
return {
|
|
222
|
+
tx: []
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
signableTransaction: {
|
|
227
|
+
tx: [],
|
|
228
|
+
reference: 'mockReference'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}),
|
|
232
|
+
signAction: jest.fn().mockResolvedValue({
|
|
233
|
+
txid: 'fake-txid',
|
|
234
|
+
tx: []
|
|
235
|
+
}),
|
|
236
|
+
abortAction: jest.fn().mockResolvedValue({ aborted: true }),
|
|
237
|
+
listActions: jest.fn().mockResolvedValue({
|
|
238
|
+
totalActions: 0,
|
|
239
|
+
actions: []
|
|
240
|
+
}),
|
|
241
|
+
internalizeAction: jest.fn().mockResolvedValue({ accepted: true }),
|
|
242
|
+
listOutputs: jest.fn().mockResolvedValue({
|
|
243
|
+
totalOutputs: 0,
|
|
244
|
+
outputs: []
|
|
245
|
+
}),
|
|
246
|
+
relinquishOutput: jest.fn().mockResolvedValue({ relinquished: true }),
|
|
247
|
+
|
|
248
|
+
acquireCertificate: jest.fn().mockResolvedValue({
|
|
249
|
+
type: 'some-cert-type',
|
|
250
|
+
subject: '02aaaaaaaaaa...',
|
|
251
|
+
serialNumber: 'serial123',
|
|
252
|
+
certifier: '02ccccccccccc...',
|
|
253
|
+
revocationOutpoint: 'some.txid.1',
|
|
254
|
+
signature: 'deadbeef',
|
|
255
|
+
fields: { name: 'Alice', dob: '1990-01-01' }
|
|
256
|
+
}),
|
|
257
|
+
listCertificates: jest.fn().mockResolvedValue({
|
|
258
|
+
totalCertificates: 0,
|
|
259
|
+
certificates: []
|
|
260
|
+
}),
|
|
261
|
+
proveCertificate: jest.fn().mockResolvedValue({
|
|
262
|
+
keyringForVerifier: {},
|
|
263
|
+
certificate: undefined,
|
|
264
|
+
verifier: undefined
|
|
265
|
+
}),
|
|
266
|
+
relinquishCertificate: jest.fn().mockResolvedValue({ relinquished: true }),
|
|
267
|
+
discoverByIdentityKey: jest.fn().mockResolvedValue({
|
|
268
|
+
totalCertificates: 0,
|
|
269
|
+
certificates: []
|
|
270
|
+
}),
|
|
271
|
+
discoverByAttributes: jest.fn().mockResolvedValue({
|
|
272
|
+
totalCertificates: 0,
|
|
273
|
+
certificates: []
|
|
274
|
+
}),
|
|
275
|
+
isAuthenticated: jest.fn().mockResolvedValue({ authenticated: true }),
|
|
276
|
+
waitForAuthentication: jest.fn().mockResolvedValue({ authenticated: true }),
|
|
277
|
+
getHeight: jest.fn().mockResolvedValue({ height: 777777 }),
|
|
278
|
+
getHeaderForHeight: jest.fn().mockResolvedValue({
|
|
279
|
+
header: '000000000000abc...'
|
|
280
|
+
}),
|
|
281
|
+
getNetwork: jest.fn().mockResolvedValue({ network: 'testnet' }),
|
|
282
|
+
getVersion: jest.fn().mockResolvedValue({ version: 'vendor-1.0.0' })
|
|
283
|
+
}
|
|
284
|
+
}
|