@bsv/wallet-toolbox 1.2.27 → 1.2.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.2.27",
3
+ "version": "1.2.30",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "homepage": "https://github.com/bitcoin-sv/wallet-toolbox#readme",
32
32
  "dependencies": {
33
- "@bsv/auth-express-middleware": "^1.0.13",
34
- "@bsv/payment-express-middleware": "^1.0.3",
35
- "@bsv/sdk": "^1.4.9",
33
+ "@bsv/auth-express-middleware": "^1.1.2",
34
+ "@bsv/payment-express-middleware": "^1.0.4",
35
+ "@bsv/sdk": "^1.4.12",
36
36
  "express": "^4.21.2",
37
37
  "knex": "^3.1.0",
38
38
  "mysql2": "^3.12.0",
@@ -1,4 +1,4 @@
1
- import { WalletInterface, Utils, PushDrop, LockingScript, Transaction } from '@bsv/sdk'
1
+ import { WalletInterface, Utils, PushDrop, LockingScript, Transaction, WalletProtocol, Base64String } from '@bsv/sdk'
2
2
  import { validateCreateActionArgs } from './sdk'
3
3
 
4
4
  ////// TODO: ADD SUPPORT FOR ADMIN COUNTERPARTIES BASED ON WALLET STORAGE
@@ -23,7 +23,7 @@ export interface PermissionRequest {
23
23
  type: 'protocol' | 'basket' | 'certificate' | 'spending'
24
24
  originator: string // The domain or FQDN of the requesting application
25
25
  privileged?: boolean // For "protocol" or "certificate" usage, indicating privileged key usage
26
- protocolID?: [0 | 1 | 2, string] // For type='protocol': BRC-43 style (securityLevel, protocolName)
26
+ protocolID?: WalletProtocol // For type='protocol': BRC-43 style (securityLevel, protocolName)
27
27
  counterparty?: string // For type='protocol': e.g. target public key or "self"/"anyone"
28
28
 
29
29
  basket?: string // For type='basket': the basket name being requested
@@ -509,7 +509,7 @@ export class WalletPermissionsManager implements WalletInterface {
509
509
  }: {
510
510
  originator: string
511
511
  privileged: boolean
512
- protocolID: [0 | 1 | 2, string]
512
+ protocolID: WalletProtocol
513
513
  counterparty: string
514
514
  reason?: string
515
515
  seekPermission?: boolean
@@ -939,7 +939,7 @@ export class WalletPermissionsManager implements WalletInterface {
939
939
  * @param plaintext The metadata to encrypt if configured to do so
940
940
  * @returns The encrypted metadata, or the original value if encryption was disabled.
941
941
  */
942
- private async maybeEncryptMetadata(plaintext: string): Promise<string> {
942
+ private async maybeEncryptMetadata(plaintext: string): Promise<Base64String> {
943
943
  if (!this.config.encryptWalletMetadata) {
944
944
  return plaintext
945
945
  }
@@ -951,7 +951,7 @@ export class WalletPermissionsManager implements WalletInterface {
951
951
  },
952
952
  this.adminOriginator
953
953
  )
954
- return Utils.toUTF8(ciphertext) // Still a string, but scrambled.
954
+ return Utils.toBase64(ciphertext)
955
955
  }
956
956
 
957
957
  /**
@@ -959,11 +959,11 @@ export class WalletPermissionsManager implements WalletInterface {
959
959
  * @param ciphertext The metadata to attempt decryption for.
960
960
  * @returns The decrypted metadata. If decryption fails, returns the original value instead.
961
961
  */
962
- private async maybeDecryptMetadata(ciphertext: string): Promise<string> {
962
+ private async maybeDecryptMetadata(ciphertext: Base64String): Promise<string> {
963
963
  try {
964
964
  const { plaintext } = await this.underlying.decrypt(
965
965
  {
966
- ciphertext: Utils.toArray(ciphertext, 'utf8'),
966
+ ciphertext: Utils.toArray(ciphertext, 'base64'),
967
967
  protocolID: WalletPermissionsManager.METADATA_ENCRYPTION_PROTOCOL,
968
968
  keyID: '1'
969
969
  },
@@ -985,7 +985,7 @@ export class WalletPermissionsManager implements WalletInterface {
985
985
  private async findProtocolToken(
986
986
  originator: string,
987
987
  privileged: boolean,
988
- protocolID: [0 | 1 | 2, string],
988
+ protocolID: WalletProtocol,
989
989
  counterparty: string,
990
990
  includeExpired: boolean
991
991
  ): Promise<PermissionToken | undefined> {
@@ -1489,7 +1489,7 @@ export class WalletPermissionsManager implements WalletInterface {
1489
1489
  public async hasProtocolPermission(params: {
1490
1490
  originator: string
1491
1491
  privileged: boolean
1492
- protocolID: [0 | 1 | 2, string]
1492
+ protocolID: WalletProtocol
1493
1493
  counterparty: string
1494
1494
  }): Promise<boolean> {
1495
1495
  try {
@@ -2369,7 +2369,7 @@ export class WalletPermissionsManager implements WalletInterface {
2369
2369
  *
2370
2370
  * If it violates these rules and the caller is not admin, we consider it "admin-only."
2371
2371
  */
2372
- private isAdminProtocol(proto: [0 | 1 | 2, string]): boolean {
2372
+ private isAdminProtocol(proto: WalletProtocol): boolean {
2373
2373
  const protocolName = proto[1]
2374
2374
  if (protocolName.startsWith('admin') || protocolName.startsWith('p ')) {
2375
2375
  return true
@@ -47,7 +47,7 @@ export const DEFAULT_SETTINGS = {
47
47
  name: 'Babbage Trust Services',
48
48
  description: 'Resolves identity information for Babbage-run APIs and Bitcoin infrastructure.',
49
49
  iconUrl: 'https://projectbabbage.com/favicon.ico',
50
- identityKey: '028703956178067ea7ca405111f1ca698290a0112a3d7cf3d843e195bf58a7cfa6',
50
+ identityKey: '03daf815fe38f83da0ad83b5bedc520aa488aef5cbc93a93c67a7fe60406cbffe8',
51
51
  trust: 4
52
52
  },
53
53
  {
@@ -62,7 +62,7 @@ export const DEFAULT_SETTINGS = {
62
62
  description: 'Certifies social media handles, phone numbers and emails',
63
63
  iconUrl: 'https://socialcert.net/favicon.ico',
64
64
  trust: 3,
65
- identityKey: '03285263f06139b66fb27f51cf8a92e9dd007c4c4b83876ad6c3e7028db450a4c2'
65
+ identityKey: '02cf6cdf466951d8dfc9e7c9367511d0007ed6fba35ed42d425cc412fd6cfd4a17'
66
66
  }
67
67
  ]
68
68
  },
@@ -107,10 +107,10 @@ export class WalletSettingsManager {
107
107
  */
108
108
  async get(): Promise<WalletSettings> {
109
109
  // List outputs in the 'wallet-settings' basket
110
+ // There should only be one settings token
110
111
  const results = await this.wallet.listOutputs({
111
112
  basket: SETTINGS_BASKET,
112
- include: 'locking scripts',
113
- limit: 1 // There should only be one settings token
113
+ include: 'locking scripts'
114
114
  })
115
115
 
116
116
  // Return defaults if no settings token is found
@@ -118,7 +118,9 @@ export class WalletSettingsManager {
118
118
  return this.config.defaultSettings
119
119
  }
120
120
 
121
- const { fields } = PushDrop.decode(LockingScript.fromHex(results.outputs[0].lockingScript!))
121
+ const { fields } = PushDrop.decode(
122
+ LockingScript.fromHex(results.outputs[results.outputs.length - 1].lockingScript!)
123
+ )
122
124
  // Parse and return settings token
123
125
  return JSON.parse(Utils.toUTF8(fields[0]))
124
126
  }
@@ -164,15 +166,12 @@ export class WalletSettingsManager {
164
166
  // 1. List the existing token UTXO(s) for the settings basket.
165
167
  const existingUtxos = await this.wallet.listOutputs({
166
168
  basket: SETTINGS_BASKET,
167
- include: 'entire transactions',
168
- limit: 1
169
+ include: 'entire transactions'
169
170
  })
170
171
 
171
172
  // This is the "create a new token" path — no signAction, just a new locking script.
172
173
  if (!existingUtxos.outputs.length) {
173
174
  if (!newLockingScript) {
174
- // The intention was to clear the token, but no tokn was found to clear.
175
- // Thus, we are done.
176
175
  return true
177
176
  }
178
177
  await this.wallet.createAction({
@@ -186,14 +185,15 @@ export class WalletSettingsManager {
186
185
  }
187
186
  ],
188
187
  options: {
189
- randomizeOutputs: false
188
+ randomizeOutputs: false,
189
+ acceptDelayedBroadcast: false
190
190
  }
191
191
  })
192
192
  return true
193
193
  }
194
194
 
195
195
  // 2. Prepare the token UTXO for consumption.
196
- const tokenOutput = existingUtxos.outputs[0]
196
+ const tokenOutput = existingUtxos.outputs[existingUtxos.outputs.length - 1]
197
197
  const inputToConsume: CreateActionInput = {
198
198
  outpoint: tokenOutput.outpoint,
199
199
  unlockingScriptLength: 73,
@@ -219,7 +219,8 @@ export class WalletSettingsManager {
219
219
  inputs: [inputToConsume], // input index 0
220
220
  outputs,
221
221
  options: {
222
- randomizeOutputs: false
222
+ randomizeOutputs: false,
223
+ acceptDelayedBroadcast: false
223
224
  }
224
225
  })
225
226
  const tx = Transaction.fromBEEF(signableTransaction!.tx)
@@ -1,6 +1,7 @@
1
1
  import { mockUnderlyingWallet, MockedBSV_SDK } from './WalletPermissionsManager.fixtures'
2
2
  import { WalletPermissionsManager } from '../WalletPermissionsManager'
3
3
  import { jest } from '@jest/globals'
4
+ import { Utils } from '@bsv/sdk'
4
5
 
5
6
  jest.mock('@bsv/sdk', () => MockedBSV_SDK)
6
7
 
@@ -62,7 +63,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
62
63
  plaintext: [72, 105] // 'Hi'
63
64
  })
64
65
 
65
- const ciphertext = 'random-string-representing-ciphertext'
66
+ const ciphertext = Utils.toBase64(Utils.toArray('random-string-representing-ciphertext'))
66
67
  const result = await manager['maybeDecryptMetadata'](ciphertext)
67
68
 
68
69
  // We expect underlying.decrypt() to have been called
@@ -153,19 +154,19 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
153
154
  totalActions: 1,
154
155
  actions: [
155
156
  {
156
- description: 'fake-encrypted-string-for-description',
157
+ description: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-description')),
157
158
  inputs: [
158
159
  {
159
160
  outpoint: 'txid1.0',
160
- inputDescription: 'fake-encrypted-string-for-inputDesc'
161
+ inputDescription: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-inputDesc'))
161
162
  }
162
163
  ],
163
164
  outputs: [
164
165
  {
165
166
  lockingScript: 'OP_RETURN 1234',
166
167
  satoshis: 500,
167
- outputDescription: 'fake-encrypted-string-for-outputDesc',
168
- customInstructions: 'fake-encrypted-string-for-customInstr'
168
+ outputDescription: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-outputDesc')),
169
+ customInstructions: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-customInstr'))
169
170
  }
170
171
  ]
171
172
  }
@@ -268,7 +269,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
268
269
  // To simulate, we make decryption pass through.
269
270
  underlying.decrypt.mockImplementation(x => x)
270
271
  const listResult = await (manager as any).listActions({}, 'nonadmin.com')
271
- expect(underlying.decrypt).toHaveBeenCalledTimes(4)
272
+ expect(underlying.decrypt).toHaveBeenCalledTimes(3)
272
273
 
273
274
  // Confirm the returned data is the same as originally provided (plaintext)
274
275
  const [first] = listResult.actions
@@ -297,7 +298,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
297
298
  satoshis: 999,
298
299
  lockingScript: 'OP_RETURN something',
299
300
  basket: 'some-basket',
300
- customInstructions: 'fake-encrypted-instructions-string'
301
+ customInstructions: Utils.toBase64(Utils.toArray('fake-encrypted-instructions-string'))
301
302
  }
302
303
  ]
303
304
  })
@@ -153,6 +153,12 @@ export const MockUtils = {
153
153
  toUTF8: (arr: number[]) => {
154
154
  // Converts an array of numbers to a UTF-8 string.
155
155
  return String.fromCharCode(...arr)
156
+ },
157
+
158
+ toBase64: (arr: number[]) => {
159
+ // Converts an array of numbers to a Base64 string.
160
+ const binaryStr = String.fromCharCode(...arr)
161
+ return btoa(binaryStr)
156
162
  }
157
163
  }
158
164
 
@@ -1,5 +1,5 @@
1
- import { EntitySyncState, sdk, Services, Setup, StorageKnex } from '../../../src'
2
- import { _tu } from '../../utils/TestUtilsWalletStorage'
1
+ import { EntitySyncState, sdk, Services, Setup, StorageKnex, TableOutput, TableUser } from '../../../src'
2
+ import { _tu, TuEnv } from '../../utils/TestUtilsWalletStorage'
3
3
  import { specOpInvalidChange, ValidListOutputsArgs, WERR_REVIEW_ACTIONS } from '../../../src/sdk'
4
4
  import {
5
5
  burnOneSatTestOutput,
@@ -12,6 +12,7 @@ import {
12
12
  import { abort } from 'process'
13
13
 
14
14
  import * as dotenv from 'dotenv'
15
+ import { WalletOutput } from '@bsv/sdk'
15
16
  dotenv.config()
16
17
 
17
18
  const chain: sdk.Chain = 'main'
@@ -97,44 +98,73 @@ describe('localWallet2 tests', () => {
97
98
  await setup.wallet.destroy()
98
99
  })
99
100
 
100
- test('5 cleanup change for userId', async () => {
101
- const env = _tu.getEnv('main')
102
- const knex = Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION!)
103
- const storage = new StorageKnex({
104
- chain: env.chain,
105
- knex: knex,
106
- commissionSatoshis: 0,
107
- commissionPubKeyHex: undefined,
108
- feeModel: { model: 'sat/kb', value: 1 }
109
- })
110
- const servicesOptions = Services.createDefaultOptions(env.chain)
111
- if (env.whatsonchainApiKey) servicesOptions.whatsOnChainApiKey = env.whatsonchainApiKey
112
- storage.setServices(new Services(servicesOptions))
113
- await storage.makeAvailable()
114
- for (const userId of [76, 48, 166, 94, 110, 111, 81]) {
101
+ test('5 review and release all production invalid change utxos', async () => {
102
+ const { env, storage } = await createMainReviewSetup()
103
+ const users = await storage.findUsers({ partial: {} })
104
+ const withInvalid: Record<number, { user: TableUser; outputs: WalletOutput[]; total: number }> = {}
105
+ // [76, 48, 166, 94, 110, 111, 81]
106
+ const vargs: ValidListOutputsArgs = {
107
+ basket: specOpInvalidChange,
108
+ tags: [],
109
+ tagQueryMode: 'all',
110
+ includeLockingScripts: false,
111
+ includeTransactions: false,
112
+ includeCustomInstructions: false,
113
+ includeTags: false,
114
+ includeLabels: false,
115
+ limit: 0,
116
+ offset: 0,
117
+ seekPermission: false,
118
+ knownTxids: []
119
+ }
120
+ for (const user of users) {
121
+ const { userId } = user
115
122
  const auth = { userId, identityKey: '' }
116
- const vargs: ValidListOutputsArgs = {
117
- basket: specOpInvalidChange,
118
- tags: [],
119
- tagQueryMode: 'all',
120
- includeLockingScripts: false,
121
- includeTransactions: false,
122
- includeCustomInstructions: false,
123
- includeTags: false,
124
- includeLabels: false,
125
- limit: 0,
126
- offset: 0,
127
- seekPermission: false,
128
- knownTxids: []
129
- }
130
123
  let r = await storage.listOutputs(auth, vargs)
131
124
  if (r.totalOutputs > 0) {
132
- console.log(`userId ${userId} releasing ${r.totalOutputs} unspendable utxos`)
133
- r = await storage.listOutputs(auth, { ...vargs, tags: ['release'] })
134
- r = await storage.listOutputs(auth, vargs)
125
+ const total: number = r.outputs.reduce((s, o) => (s += o.satoshis), 0)
126
+ console.log(`userId ${userId}: ${r.totalOutputs} unspendable utxos, total ${total}, ${user.identityKey}`)
127
+ withInvalid[userId] = { user, outputs: r.outputs, total }
128
+ }
129
+ }
130
+ if (Object.keys(withInvalid).length > 0) {
131
+ debugger
132
+ // Release invalids
133
+ for (const { user, outputs } of Object.values(withInvalid)) {
134
+ const { userId } = user
135
+ const auth = { userId, identityKey: '' }
136
+ await storage.listOutputs(auth, { ...vargs, tags: ['release'] })
137
+ }
138
+ // Verify
139
+ for (const { user, outputs } of Object.values(withInvalid)) {
140
+ const { userId } = user
141
+ const auth = { userId, identityKey: '' }
142
+ const r = await storage.listOutputs(auth, vargs)
143
+ expect(r.totalOutputs).toBe(0)
135
144
  }
136
- expect(r.totalOutputs).toBe(0)
137
145
  }
138
146
  await storage.destroy()
139
147
  })
140
148
  })
149
+
150
+ async function createMainReviewSetup(): Promise<{
151
+ env: TuEnv
152
+ storage: StorageKnex
153
+ services: Services
154
+ }> {
155
+ const env = _tu.getEnv('main')
156
+ const knex = Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION!)
157
+ const storage = new StorageKnex({
158
+ chain: env.chain,
159
+ knex: knex,
160
+ commissionSatoshis: 0,
161
+ commissionPubKeyHex: undefined,
162
+ feeModel: { model: 'sat/kb', value: 1 }
163
+ })
164
+ const servicesOptions = Services.createDefaultOptions(env.chain)
165
+ if (env.whatsonchainApiKey) servicesOptions.whatsOnChainApiKey = env.whatsonchainApiKey
166
+ const services = new Services(servicesOptions)
167
+ storage.setServices(services)
168
+ await storage.makeAvailable()
169
+ return { env, storage, services }
170
+ }