@bsv/wallet-toolbox 1.2.22 → 1.2.24

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.22",
3
+ "version": "1.2.24",
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",
package/src/Wallet.ts CHANGED
@@ -848,21 +848,6 @@ export class Wallet implements WalletInterface, ProtoWallet {
848
848
  })
849
849
  }
850
850
 
851
- /**
852
- * Uses `listOutputs` special operation to compute the total value (of satoshis) for
853
- * all spendable outputs in a basket (which defaults to the change 'default' basket).
854
- *
855
- * @param {string} basket - Optional. Defaults to 'default', the wallet change basket.
856
- * @returns {number} sum of output satoshis
857
- */
858
- async balance(basket: string = 'default'): Promise<number> {
859
- const args: ListOutputsArgs = {
860
- basket: specOpWalletBalance
861
- }
862
- const r = await this.listOutputs(args)
863
- return r.totalOutputs
864
- }
865
-
866
851
  /**
867
852
  * Uses `listOutputs` to iterate over chunks of up to 1000 outputs to
868
853
  * compute the sum of output satoshis.
@@ -889,6 +874,20 @@ export class Wallet implements WalletInterface, ProtoWallet {
889
874
  return r
890
875
  }
891
876
 
877
+ /**
878
+ * Uses `listOutputs` special operation to compute the total value (of satoshis) for
879
+ * all spendable outputs in the 'default' basket.
880
+ *
881
+ * @returns {number} sum of output satoshis
882
+ */
883
+ async balance(): Promise<number> {
884
+ const args: ListOutputsArgs = {
885
+ basket: specOpWalletBalance
886
+ }
887
+ const r = await this.listOutputs(args)
888
+ return r.totalOutputs
889
+ }
890
+
892
891
  /**
893
892
  * Uses `listOutputs` special operation to review the spendability via `Services` of
894
893
  * outputs currently considered spendable. Returns the outputs that fail to verify.
@@ -79,7 +79,7 @@ async function postBeefTest(services: Services) {
79
79
  } else {
80
80
  expect(tr!.status).toBe('error')
81
81
  expect(tr!.doubleSpend).toBe(true)
82
- if (tr!.competingTxs?.length) expect(tr!.competingTxs).toEqual([c.txidUndo])
82
+ if (tr!.competingTxs !== undefined) expect(tr!.competingTxs).toEqual([c.txidUndo])
83
83
  }
84
84
  }
85
85
  }
@@ -50,20 +50,31 @@ async function validateReqsAndMergeBeefs(
50
50
  const vreqs: PostReqsToNetworkDetails[] = []
51
51
 
52
52
  for (const req of reqs) {
53
- const noRawTx = !req.rawTx
54
- const noTxIds = !req.notify.transactionIds || req.notify.transactionIds.length < 1
55
- const noInputBEEF = !req.inputBEEF
56
- if (noRawTx || noTxIds || noInputBEEF) {
57
- // This should have happened earlier...
58
- req.addHistoryNote({ when: new Date().toISOString(), what: 'validateReqFailed', noRawTx, noTxIds, noInputBEEF })
59
- req.status = 'invalid'
53
+ try {
54
+ const noRawTx = !req.rawTx
55
+ const noTxIds = !req.notify.transactionIds || req.notify.transactionIds.length < 1
56
+ const noInputBEEF = !req.inputBEEF
57
+ if (noRawTx || noTxIds || noInputBEEF) {
58
+ // This should have happened earlier...
59
+ req.addHistoryNote({ when: new Date().toISOString(), what: 'validateReqFailed', noRawTx, noTxIds, noInputBEEF })
60
+ req.status = 'invalid'
61
+ await req.updateStorageDynamicProperties(storage, trx)
62
+ r.details.push({ txid: req.txid, req, status: 'invalid' })
63
+ } else {
64
+ const vreq: PostReqsToNetworkDetails = { txid: req.txid, req, status: 'unknown' }
65
+ await storage.mergeReqToBeefToShareExternally(req.api, r.beef, [], trx)
66
+ vreqs.push(vreq)
67
+ r.details.push(vreq)
68
+ }
69
+ } catch (eu: unknown) {
70
+ const { code, message } = sdk.WalletError.fromUnknown(eu)
71
+ req.addHistoryNote({ when: new Date().toISOString(), what: 'validateReqError', txid: req.txid, code, message })
72
+ req.attempts++
73
+ if (req.attempts > 6) {
74
+ req.status = 'invalid'
75
+ r.details.push({ txid: req.txid, req, status: 'invalid' })
76
+ }
60
77
  await req.updateStorageDynamicProperties(storage, trx)
61
- r.details.push({ txid: req.txid, req, status: 'invalid' })
62
- } else {
63
- const vreq: PostReqsToNetworkDetails = { txid: req.txid, req, status: 'unknown' }
64
- vreqs.push(vreq)
65
- r.details.push(vreq)
66
- await storage.mergeReqToBeefToShareExternally(req.api, r.beef, [], trx)
67
78
  }
68
79
  }
69
80
  return { r, vreqs, txids: vreqs.map(r => r.txid) }
@@ -11,12 +11,12 @@ import {
11
11
  } from './localWalletMethods'
12
12
  import { abort } from 'process'
13
13
 
14
- const chain: sdk.Chain = 'test'
14
+ const chain: sdk.Chain = 'main'
15
15
 
16
16
  const options: LocalWalletTestOptions = {
17
17
  setActiveClient: true,
18
18
  useMySQLConnectionForClient: true,
19
- useTestIdentityKey: true,
19
+ useTestIdentityKey: false,
20
20
  useIdentityKey2: false
21
21
  }
22
22
 
@@ -41,6 +41,13 @@ describe('localWallet2 tests', () => {
41
41
  await setup.wallet.destroy()
42
42
  })
43
43
 
44
+ test('2 create 1 sat delayed', async () => {
45
+ const setup = await createSetup(chain, options)
46
+ const car = await createOneSatTestOutput(setup, {}, 1)
47
+ //await trackReqByTxid(setup, car.txid!)
48
+ await setup.wallet.destroy()
49
+ })
50
+
44
51
  test('2a create 1 sat immediate', async () => {
45
52
  const setup = await createSetup(chain, options)
46
53
  const car = await createOneSatTestOutput(setup, { acceptDelayedBroadcast: false }, 1)
@@ -0,0 +1,129 @@
1
+ import {
2
+ Beef,
3
+ BigNumber,
4
+ Hash,
5
+ P2PKH,
6
+ PublicKey,
7
+ Signature,
8
+ Transaction,
9
+ TransactionSignature,
10
+ UnlockingScript,
11
+ Utils
12
+ } from '@bsv/sdk'
13
+ import { Setup, verifyInteger, verifyTruthy, Wallet } from '../../../src'
14
+ import { parseWalletOutpoint } from '../../../src/sdk'
15
+
16
+ describe('mountaintop tests', () => {
17
+ jest.setTimeout(99999999)
18
+
19
+ test('0 signature validity', async () => {
20
+ // obtain the secrets environment for the testnet network.
21
+ const env = Setup.getEnv('main')
22
+ const setup = await Setup.createWalletClient({ env })
23
+
24
+ const privKey = await setup.wallet.keyDeriver.derivePrivateKey([1, 'mountaintops'], '1', 'anyone')
25
+ const a2 = privKey.toAddress()
26
+
27
+ const { publicKey } = await setup.wallet.getPublicKey({
28
+ protocolID: [1, 'mountaintops'],
29
+ keyID: '1',
30
+ counterparty: 'anyone',
31
+ forSelf: true
32
+ })
33
+ const address = PublicKey.fromString(publicKey).toAddress()
34
+ expect(address).toBe('1BSMQ1PxMbzMqjB47EYaSNBAD7Qme1dXuk')
35
+ expect(a2).toBe(address)
36
+
37
+ const msg = [
38
+ 111, 16, 29, 104, 166, 199, 108, 36, 242, 153, 242, 104, 10, 198, 151, 176, 170, 69, 98, 209, 88, 105, 113, 199,
39
+ 124, 56, 189, 3, 104, 214, 94, 76
40
+ ]
41
+ const msgHash = new BigNumber(msg, 16)
42
+ const h1 = msgHash.toHex()
43
+ const msgHash2 = new BigNumber(msg)
44
+ const h2 = msgHash2.toHex()
45
+
46
+ const beef = Beef.fromString(beef0, 'hex')
47
+ const o = parseWalletOutpoint(outpoints[0])
48
+ const tx = new Transaction()
49
+ tx.addInput({
50
+ sourceOutputIndex: o.vout,
51
+ sourceTXID: o.txid,
52
+ sourceTransaction: beef.findAtomicTransaction(o.txid)
53
+ })
54
+
55
+ const p2pkh = new P2PKH().unlock(privKey, 'all')
56
+ tx.inputs[0].unlockingScript = await p2pkh.sign(tx, 0)
57
+ const ok2 = await tx.verify('scripts only')
58
+ expect(ok2).toBe(true)
59
+
60
+ tx.inputs[0].unlockingScript = await sign(setup.wallet, tx, 0)
61
+ const ok = await tx.verify('scripts only')
62
+ expect(ok).toBe(true)
63
+ })
64
+ })
65
+
66
+ const outpoints = [
67
+ '30c83f8b84864fd4497414980faadf074192e7193602ef96ca18705876ce74b1.0',
68
+ '797bd197f2328931231f2834c5dc8036fe9990981c368df27d3b55fa926863be.0',
69
+ '08d47d844e81d751691f6b4a39ce378e9d0f70a3a0606c87995f0f28399552e2.0'
70
+ ]
71
+ const beef0 =
72
+ '0200beef01fe068d0d0008027a001b501758910c83d8e2c839cfc133245510f5ddbbd28202c331bb9feccc261c287b02b174ce76587018ca96ef023619e7924107dfaa0f98147449d44f86848b3fc830013c006174b69497f770d46604b177a98ff8b8a693a5cec19cd145b3b32abab71676f8011f001f86947779e8e749fd439f037d93733c2ea0734a17cdf1c32f87278b80c7ff72010e00161e280d8481978b9d2696c58d634beda36265ceef9faaa351566afc2c8ab2f0010600e250ce168ac74d432a14df5669f337cd44a8c2cfc8709b955174dd57e2354399010200fd976461d8c0ed097e32ae79afefb35e89daa7289daf7b01bde8bb1481762f590100005fb474d7ddaf5a299509a165cabfe0b6dbea56ed56d1f0d3acf1d3d89531a21e01010029f89d48414f66b9bfd8d711f51d6db0a712cfff2d641f66f83dd2e5e452e5c601010001000000014289ced528197deb6980d634200d333d9983c45ef46893affd027914fdc02cf7000000006a473044022019e70e4325f95b3d5f9f0569123b23c6bff7ef4197fcb15fa50ac3537b8546de0220100d181429245e0349903a0784ad61bfa022d864fca8ce6ba13b0de99fd39eb641210327c7cb8afcd1adce5b26055d70cad9fb1045976a6f99b2ee61ed36295d5802a7ffffffff020a000000000000001976a914727caee3e1178da2ca0b48786171f23695a4ccd088ac1d000000000000001976a9148419faaf7a5e97dcc62002e2415cb51bdb91937e88ac00000000'
73
+
74
+ async function sign(client: Wallet, tx: Transaction, inputIndex: number): Promise<UnlockingScript> {
75
+ let signatureScope = TransactionSignature.SIGHASH_FORKID
76
+ signatureScope |= TransactionSignature.SIGHASH_ALL
77
+ const input = tx.inputs[inputIndex]
78
+ const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex)
79
+ const sourceTXID = input.sourceTXID ?? input.sourceTransaction?.id('hex')
80
+ if (sourceTXID == null || sourceTXID === undefined) {
81
+ throw new Error('The input sourceTXID or sourceTransaction is required for transaction signing.')
82
+ }
83
+ if (sourceTXID === '') {
84
+ throw new Error('The input sourceTXID or sourceTransaction is required for transaction signing.')
85
+ }
86
+ const sourceSatoshis = input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
87
+ if (sourceSatoshis == null || sourceSatoshis === undefined) {
88
+ throw new Error('The sourceSatoshis or input sourceTransaction is required for transaction signing.')
89
+ }
90
+ const lockingScript = input.sourceTransaction?.outputs[input.sourceOutputIndex].lockingScript
91
+ if (lockingScript == null) {
92
+ throw new Error('The lockingScript or input sourceTransaction is required for transaction signing.')
93
+ }
94
+
95
+ const preimage = TransactionSignature.format({
96
+ sourceTXID,
97
+ sourceOutputIndex: verifyInteger(input.sourceOutputIndex),
98
+ sourceSatoshis,
99
+ transactionVersion: tx.version,
100
+ otherInputs,
101
+ inputIndex,
102
+ outputs: tx.outputs,
103
+ inputSequence: verifyTruthy(input.sequence),
104
+ subscript: lockingScript,
105
+ lockTime: tx.lockTime,
106
+ scope: signatureScope
107
+ })
108
+
109
+ const { signature } = await client.createSignature({
110
+ hashToDirectlySign: Hash.sha256(Hash.sha256(preimage)),
111
+ protocolID: [1, 'mountaintops'],
112
+ keyID: '1',
113
+ counterparty: 'anyone'
114
+ })
115
+ const rawSignature = Signature.fromDER(signature)
116
+ const sig = new TransactionSignature(rawSignature.r, rawSignature.s, signatureScope)
117
+ const sigForScript = sig.toChecksigFormat()
118
+ const { publicKey } = await client.getPublicKey({
119
+ protocolID: [1, 'mountaintops'],
120
+ keyID: '1',
121
+ counterparty: 'anyone',
122
+ forSelf: true
123
+ })
124
+ const pubkeyForScript = Utils.toArray(publicKey, 'hex')
125
+ return new UnlockingScript([
126
+ { op: sigForScript.length, data: sigForScript },
127
+ { op: pubkeyForScript.length, data: pubkeyForScript }
128
+ ])
129
+ }
@@ -23,7 +23,7 @@ describe('specOps tests', () => {
23
23
  test('0a wallet balance method', async () => {
24
24
  const setup = await createSetup('test')
25
25
 
26
- const r = await setup.wallet.balance('default')
26
+ const r = await setup.wallet.balance()
27
27
 
28
28
  expect(r > 0).toBe(true)
29
29