@bsv/wallet-toolbox 1.3.17 → 1.3.19

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.3.17",
3
+ "version": "1.3.19",
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",
@@ -168,8 +168,8 @@ export abstract class StorageReaderWriter extends StorageReader {
168
168
  basketId: 0,
169
169
  userId: user.userId,
170
170
  name: 'default',
171
- numberOfDesiredUTXOs: 32,
172
- minimumDesiredUTXOValue: 1000,
171
+ numberOfDesiredUTXOs: 144,
172
+ minimumDesiredUTXOValue: 32,
173
173
  isDeleted: false
174
174
  })
175
175
  break
@@ -174,7 +174,7 @@ describe('generateChange tests', () => {
174
174
 
175
175
  const r = await generateChangeSdk(params, allocateChangeInput, releaseChangeInput)
176
176
  expect(JSON.stringify(r)).toBe(
177
- '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false},{"satoshis":1000,"outputId":15013,"spendable":false}],"changeOutputs":[{"satoshis":688,"lockingScriptLength":25},{"satoshis":1000,"lockingScriptLength":25}],"size":39658,"fee":80,"satsPerKb":2}'
177
+ '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false}],"changeOutputs":[{"satoshis":689,"lockingScriptLength":25}],"size":39476,"fee":79,"satsPerKb":2}'
178
178
  )
179
179
  expectTransactionSize(params, r)
180
180
  })
@@ -195,7 +195,7 @@ describe('generateChange tests', () => {
195
195
 
196
196
  const r = await generateChangeSdk(params, allocateChangeInput, releaseChangeInput)
197
197
  expect(JSON.stringify(r)).toBe(
198
- '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false},{"satoshis":1000,"outputId":15013,"spendable":false}],"changeOutputs":[{"satoshis":569,"lockingScriptLength":25},{"satoshis":1000,"lockingScriptLength":25}],"size":39658,"fee":199,"satsPerKb":5}'
198
+ '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false}],"changeOutputs":[{"satoshis":570,"lockingScriptLength":25}],"size":39476,"fee":198,"satsPerKb":5}'
199
199
  )
200
200
  expectTransactionSize(params, r)
201
201
  })
@@ -216,7 +216,7 @@ describe('generateChange tests', () => {
216
216
 
217
217
  const r = await generateChangeSdk(params, allocateChangeInput, releaseChangeInput)
218
218
  expect(JSON.stringify(r)).toBe(
219
- '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false},{"satoshis":1000,"outputId":15013,"spendable":false}],"changeOutputs":[{"satoshis":728,"lockingScriptLength":25},{"satoshis":1000,"lockingScriptLength":25}],"size":39658,"fee":40,"satsPerKb":1}'
219
+ '{"allocatedChangeInputs":[{"satoshis":1004,"outputId":15011,"spendable":false},{"satoshis":1000,"outputId":15017,"spendable":false}],"changeOutputs":[{"satoshis":728,"lockingScriptLength":25}],"size":39476,"fee":40,"satsPerKb":1}'
220
220
  )
221
221
  expectTransactionSize(params, r)
222
222
  })
@@ -887,55 +887,87 @@ describe('generateChange tests', () => {
887
887
  { satoshis: 1034, outputId: 18492 }
888
888
  ]
889
889
  }
890
+ const d14: TestCase = {
891
+ p: {
892
+ fixedInputs: [],
893
+ fixedOutputs: [{ satoshis: 3, lockingScriptLength: 141 }],
894
+ feeModel: { model: 'sat/kb', value: 2 },
895
+ changeInitialSatoshis: 1000,
896
+ changeFirstSatoshis: 285,
897
+ changeLockingScriptLength: 25,
898
+ changeUnlockingScriptLength: 107,
899
+ targetNetCount: 22
900
+ },
901
+ availableChange: [
902
+ { satoshis: 995, outputId: 16190 },
903
+ { satoshis: 1000, outputId: 18487 },
904
+ { satoshis: 1000, outputId: 18488 },
905
+ { satoshis: 1000, outputId: 18489 },
906
+ { satoshis: 1000, outputId: 18490 },
907
+ { satoshis: 1000, outputId: 18491 },
908
+ { satoshis: 1000, outputId: 18492 },
909
+ { satoshis: 1000, outputId: 18493 },
910
+ { satoshis: 1000, outputId: 18494 },
911
+ { satoshis: 1000, outputId: 18495 },
912
+ { satoshis: 100, outputId: 18496 }
913
+ ]
914
+ }
915
+
916
+ const test8Cases = [
917
+ {
918
+ n: 14,
919
+ d: d14,
920
+ er: '{"allocatedChangeInputs":[{"satoshis":1000,"outputId":18495,"spendable":false}],"changeOutputs":[{"satoshis":996,"lockingScriptLength":25}],"size":342,"fee":1,"satsPerKb":2}'
921
+ },
922
+ {
923
+ n: 13,
924
+ d: d13,
925
+ er: '{"allocatedChangeInputs":[{"satoshis":1000,"outputId":18489,"spendable":false}],"changeOutputs":[{"satoshis":497,"lockingScriptLength":25}],"size":429,"fee":1,"satsPerKb":2}'
926
+ },
927
+ {
928
+ n: 12,
929
+ d: d12,
930
+ er: '{"allocatedChangeInputs":[{"satoshis":14030,"outputId":21194,"spendable":false}],"changeOutputs":[{"satoshis":13800,"lockingScriptLength":25}],"size":260,"fee":29,"satsPerKb":110}'
931
+ },
932
+ {
933
+ n: 11,
934
+ d: d11,
935
+ er: '{"allocatedChangeInputs":[{"satoshis":285,"outputId":21319,"spendable":false},{"satoshis":1000,"outputId":21309,"spendable":false}],"changeOutputs":[{"satoshis":1039,"lockingScriptLength":25}],"size":408,"fee":45,"satsPerKb":110}'
936
+ },
937
+ {
938
+ n: 10,
939
+ d: d10,
940
+ er: '{"allocatedChangeInputs":[{"satoshis":14030,"outputId":21194,"spendable":false}],"changeOutputs":[{"satoshis":13800,"lockingScriptLength":25}],"size":260,"fee":29,"satsPerKb":110}'
941
+ },
942
+ {
943
+ n: 9,
944
+ d: d9,
945
+ er: '{"allocatedChangeInputs":[{"satoshis":4571,"outputId":15626,"spendable":false}],"changeOutputs":[{"satoshis":3368,"lockingScriptLength":25}],"size":1373,"fee":3,"satsPerKb":2}'
946
+ },
947
+ {
948
+ n: 8,
949
+ d: d8,
950
+ er: '{"allocatedChangeInputs":[{"satoshis":1817,"outputId":15658,"spendable":false}],"changeOutputs":[{"satoshis":52,"lockingScriptLength":25}],"size":260,"fee":1,"satsPerKb":2}'
951
+ },
952
+ {
953
+ n: 7,
954
+ d: d7,
955
+ er: '{"allocatedChangeInputs":[{"satoshis":1817,"outputId":15658,"spendable":false}],"changeOutputs":[{"satoshis":46,"lockingScriptLength":25}],"size":260,"fee":1,"satsPerKb":2}'
956
+ },
957
+ {
958
+ n: 6,
959
+ d: d6,
960
+ er: '{"allocatedChangeInputs":[{"satoshis":1000,"outputId":16346,"spendable":false}],"changeOutputs":[{"satoshis":298,"lockingScriptLength":25}],"size":673,"fee":2,"satsPerKb":2}'
961
+ },
962
+ {
963
+ n: 5,
964
+ d: d5,
965
+ er: '{"allocatedChangeInputs":[],"changeOutputs":[{"satoshis":799,"lockingScriptLength":25}],"size":191,"fee":1,"satsPerKb":2}'
966
+ }
967
+ ]
890
968
 
891
- test('8 paramsText d5 d6 d7 d8 d9 d10 d11 d12 d13', async () => {
892
- for (const { n, d, er } of [
893
- {
894
- n: 13,
895
- d: d13,
896
- er: '{"allocatedChangeInputs":[{"satoshis":1000,"outputId":18489,"spendable":false}],"changeOutputs":[{"satoshis":497,"lockingScriptLength":25}],"size":429,"fee":1,"satsPerKb":2}'
897
- },
898
- {
899
- n: 12,
900
- d: d12,
901
- er: '{"allocatedChangeInputs":[{"satoshis":14030,"outputId":21194,"spendable":false}],"changeOutputs":[{"satoshis":13800,"lockingScriptLength":25}],"size":260,"fee":29,"satsPerKb":110}'
902
- },
903
- {
904
- n: 11,
905
- d: d11,
906
- er: '{"allocatedChangeInputs":[{"satoshis":285,"outputId":21319,"spendable":false},{"satoshis":1000,"outputId":21309,"spendable":false}],"changeOutputs":[{"satoshis":1039,"lockingScriptLength":25}],"size":408,"fee":45,"satsPerKb":110}'
907
- },
908
- {
909
- n: 10,
910
- d: d10,
911
- er: '{"allocatedChangeInputs":[{"satoshis":14030,"outputId":21194,"spendable":false}],"changeOutputs":[{"satoshis":13800,"lockingScriptLength":25}],"size":260,"fee":29,"satsPerKb":110}'
912
- },
913
- {
914
- n: 9,
915
- d: d9,
916
- er: '{"allocatedChangeInputs":[{"satoshis":4571,"outputId":15626,"spendable":false}],"changeOutputs":[{"satoshis":3368,"lockingScriptLength":25}],"size":1373,"fee":3,"satsPerKb":2}'
917
- },
918
- {
919
- n: 8,
920
- d: d8,
921
- er: '{"allocatedChangeInputs":[{"satoshis":1817,"outputId":15658,"spendable":false}],"changeOutputs":[{"satoshis":52,"lockingScriptLength":25}],"size":260,"fee":1,"satsPerKb":2}'
922
- },
923
- {
924
- n: 7,
925
- d: d7,
926
- er: '{"allocatedChangeInputs":[{"satoshis":1817,"outputId":15658,"spendable":false}],"changeOutputs":[{"satoshis":46,"lockingScriptLength":25}],"size":260,"fee":1,"satsPerKb":2}'
927
- },
928
- {
929
- n: 6,
930
- d: d6,
931
- er: '{"allocatedChangeInputs":[{"satoshis":1000,"outputId":16346,"spendable":false}],"changeOutputs":[{"satoshis":298,"lockingScriptLength":25}],"size":673,"fee":2,"satsPerKb":2}'
932
- },
933
- {
934
- n: 5,
935
- d: d5,
936
- er: '{"allocatedChangeInputs":[],"changeOutputs":[{"satoshis":799,"lockingScriptLength":25}],"size":191,"fee":1,"satsPerKb":2}'
937
- }
938
- ]) {
969
+ test('8 paramsText d5 d6 d7 d8 d9 d10 d11 d12 d13 d14', async () => {
970
+ for (const { n, d, er } of test8Cases) {
939
971
  const params: GenerateChangeSdkParams = { ...d.p }
940
972
  const availableChange: GenerateChangeSdkChangeInput[] = [...d.availableChange]
941
973
 
@@ -236,6 +236,17 @@ export async function generateChangeSdk(
236
236
  if (feeExcess() < 0)
237
237
  // Not enough available funding even if no change outputs
238
238
  break
239
+ // At this point we have a funded transaction, but there may be change outputs that are each costing as change input,
240
+ // resulting in pointless churn of change outputs.
241
+ // And remove change inputs that funded only a single change output (along with that output)...
242
+ const changeInputs = [...r.allocatedChangeInputs]
243
+ while (changeInputs.length > 1 && r.changeOutputs.length > 1) {
244
+ const lastOutput = r.changeOutputs.slice(-1)[0]
245
+ const i = changeInputs.findIndex(ci => ci.satoshis <= lastOutput.satoshis)
246
+ if (i < 0) break
247
+ r.changeOutputs.pop()
248
+ changeInputs.splice(i, 1)
249
+ }
239
250
  // and try again...
240
251
  }
241
252
  }
@@ -1,5 +1,15 @@
1
- import { WalletOutput } from '@bsv/sdk'
2
- import { sdk, Services, Setup, StorageKnex, TableOutput, TableUser, verifyOne, verifyOneOrNone } from '../../../src'
1
+ import { Transaction, WalletOutput } from '@bsv/sdk'
2
+ import {
3
+ sdk,
4
+ Services,
5
+ Setup,
6
+ StorageKnex,
7
+ TableOutput,
8
+ TableTransaction,
9
+ TableUser,
10
+ verifyOne,
11
+ verifyOneOrNone
12
+ } from '../../../src'
3
13
  import { _tu, TuEnv } from '../../utils/TestUtilsWalletStorage'
4
14
  import { specOpInvalidChange, ValidListOutputsArgs } from '../../../src/sdk'
5
15
  import { LocalWalletTestOptions } from '../../utils/localWalletMethods'
@@ -194,6 +204,29 @@ describe('operations.man tests', () => {
194
204
  */
195
205
  await storage.destroy()
196
206
  })
207
+ test('9 review recent transaction change use', async () => {
208
+ const { env, storage, services } = await createMainReviewSetup()
209
+ const countTxs = await storage.countTransactions({
210
+ partial: { userId: 311 },
211
+ status: ['completed', 'unproven', 'failed']
212
+ })
213
+ const txs = await storage.findTransactions({
214
+ partial: { userId: 311 },
215
+ status: ['unproven', 'completed', 'failed'],
216
+ paged: { limit: 100, offset: Math.max(0, countTxs - 100) }
217
+ })
218
+ for (const tx of txs) {
219
+ const ls = await toLogString(tx, storage)
220
+ console.log(ls)
221
+ }
222
+ const countReqs = await storage.countProvenTxReqs({ partial: {}, status: ['completed', 'unmined'] })
223
+ const reqs = await storage.findProvenTxReqs({
224
+ partial: {},
225
+ status: ['unmined', 'completed'],
226
+ paged: { limit: 100, offset: countReqs - 100 }
227
+ })
228
+ await storage.destroy()
229
+ })
197
230
  })
198
231
 
199
232
  async function createMainReviewSetup(): Promise<{
@@ -217,3 +250,33 @@ async function createMainReviewSetup(): Promise<{
217
250
  await storage.makeAvailable()
218
251
  return { env, storage, services }
219
252
  }
253
+
254
+ async function toLogString(tx: TableTransaction, storage: StorageKnex): Promise<string> {
255
+ const rawTx = await storage.getRawTxOfKnownValidTransaction(tx.txid)
256
+ const btx = Transaction.fromBinary(rawTx!)
257
+ let log = `tx ${tx.txid} ${tx.status} s:${tx.satoshis} uid:${tx.userId} tid:${tx.transactionId}\n`
258
+ for (let i = 0; i < Math.max(btx.inputs.length, btx.outputs.length); i++) {
259
+ let ilog: string = ''
260
+ let olog: string = ''
261
+ if (i < btx.inputs.length) {
262
+ const input = btx.inputs[i]
263
+ ilog = `${logTxid(input.sourceTXID!)}.${input.sourceOutputIndex}`
264
+ }
265
+ if (i < btx.outputs.length) {
266
+ const output = btx.outputs[i]
267
+ olog = `${ar('' + output.satoshis, 9)} ${output.lockingScript.toHex().length / 2}`
268
+ }
269
+ log += `${ar('' + i, 5)} ${al(ilog, 20)} ${olog}\n`
270
+ }
271
+ return log
272
+
273
+ function logTxid(txid: string): string {
274
+ return `${txid.slice(0, 6)}..${txid.slice(-6)}`
275
+ }
276
+ function al(v: string | number, w: number): string {
277
+ return v.toString().padEnd(w)
278
+ }
279
+ function ar(v: string | number, w: number): string {
280
+ return v.toString().padStart(w)
281
+ }
282
+ }