@bsv/sdk 1.0.34 → 1.0.37

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.
Files changed (46) hide show
  1. package/dist/cjs/mod.js +1 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/src/primitives/PrivateKey.js +5 -3
  5. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  6. package/dist/cjs/src/primitives/PublicKey.js +14 -1
  7. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  8. package/dist/cjs/src/script/templates/P2PKH.js +1 -0
  9. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  10. package/dist/cjs/src/transaction/Transaction.js +38 -8
  11. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  12. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  13. package/dist/esm/mod.js +1 -0
  14. package/dist/esm/mod.js.map +1 -1
  15. package/dist/esm/src/primitives/PrivateKey.js +5 -3
  16. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  17. package/dist/esm/src/primitives/PublicKey.js +14 -1
  18. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  19. package/dist/esm/src/script/templates/P2PKH.js +1 -0
  20. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  21. package/dist/esm/src/transaction/Transaction.js +40 -10
  22. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  23. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  24. package/dist/types/mod.d.ts +1 -0
  25. package/dist/types/mod.d.ts.map +1 -1
  26. package/dist/types/src/primitives/PrivateKey.d.ts +6 -4
  27. package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
  28. package/dist/types/src/primitives/PublicKey.d.ts +4 -2
  29. package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
  30. package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
  31. package/dist/types/src/transaction/Transaction.d.ts +9 -2
  32. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  33. package/dist/types/src/transaction/TransactionInput.d.ts +4 -0
  34. package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
  35. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  36. package/docs/primitives.md +73 -67
  37. package/docs/script.md +13 -11
  38. package/docs/transaction.md +508 -39
  39. package/mod.ts +1 -0
  40. package/package.json +1 -1
  41. package/src/primitives/PrivateKey.ts +6 -4
  42. package/src/primitives/PublicKey.ts +13 -2
  43. package/src/script/templates/P2PKH.ts +1 -0
  44. package/src/transaction/Transaction.ts +41 -10
  45. package/src/transaction/TransactionInput.ts +4 -0
  46. package/src/transaction/__tests/Transaction.test.ts +61 -0
package/mod.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./src/script/templates/index.js"
4
4
  export * from "./src/transaction/index.js"
5
5
  export * from "./src/transaction/fee-models/index.js"
6
6
  export * from "./src/transaction/broadcasters/index.js"
7
+ export * from "./src/transaction/chaintrackers/index.js"
7
8
  export * from "./src/transaction/http/index.js"
8
9
  export * from "./src/messages/index.js"
9
10
  export * from "./src/compat/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.0.34",
3
+ "version": "1.0.37",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -209,15 +209,17 @@ export default class PrivateKey extends BigNumber {
209
209
  * Base58Check encodes the hash of the public key associated with this private key with a prefix to indicate locking script type.
210
210
  * Defaults to P2PKH for mainnet, otherwise known as a "Bitcoin Address".
211
211
  *
212
- * @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet.
212
+ * @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet or use the strings 'testnet' or 'mainnet'
213
213
  *
214
214
  * @returns Returns the address encoding associated with the hash of the public key associated with this private key.
215
215
  *
216
216
  * @example
217
- * const address = pubkey.toAddress()
218
- * const testnetAddress = pubkey.toAddress([0x6f])
217
+ * const address = privkey.toAddress()
218
+ * const address = privkey.toAddress('mainnet')
219
+ * const testnetAddress = privkey.toAddress([0x6f])
220
+ * const testnetAddress = privkey.toAddress('testnet')
219
221
  */
220
- toAddress (prefix: number[] = [0x00]): string {
222
+ toAddress (prefix: number[] | string = [0x00]): string {
221
223
  return this.toPublicKey().toAddress(prefix)
222
224
  }
223
225
 
@@ -145,15 +145,26 @@ export default class PublicKey extends Point {
145
145
  * Base58Check encodes the hash of the public key with a prefix to indicate locking script type.
146
146
  * Defaults to P2PKH for mainnet, otherwise known as a "Bitcoin Address".
147
147
  *
148
- * @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet.
148
+ * @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet or use the strings 'mainnet' or 'testnet'
149
149
  *
150
150
  * @returns Returns the address encoding associated with the hash of the public key.
151
151
  *
152
152
  * @example
153
153
  * const address = pubkey.toAddress()
154
+ * const address = pubkey.toAddress('mainnet')
154
155
  * const testnetAddress = pubkey.toAddress([0x6f])
156
+ * const testnetAddress = pubkey.toAddress('testnet')
155
157
  */
156
- toAddress (prefix: number[] = [0x00]): string {
158
+ toAddress (prefix: number[] | string = [0x00]): string {
159
+ if (typeof prefix === 'string') {
160
+ if (prefix === 'testnet' || prefix === 'test') {
161
+ prefix = [0x6f]
162
+ } else if (prefix === 'mainnet' || prefix === 'main') {
163
+ prefix = [0x00]
164
+ } else {
165
+ throw new Error(`Invalid prefix ${prefix}`)
166
+ }
167
+ }
157
168
  return toBase58Check(this.toHash() as number[], prefix)
158
169
  }
159
170
 
@@ -91,6 +91,7 @@ export default class P2PKH implements ScriptTemplate {
91
91
  )
92
92
  }
93
93
  sourceSatoshis ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
94
+ sourceSatoshis ||= input.sourceSatoshis
94
95
  if (!sourceSatoshis) {
95
96
  throw new Error(
96
97
  'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
@@ -10,8 +10,8 @@ import { Broadcaster, BroadcastResponse, BroadcastFailure } from './Broadcaster.
10
10
  import MerklePath from './MerklePath.js'
11
11
  import Spend from '../script/Spend.js'
12
12
  import ChainTracker from './ChainTracker.js'
13
- import {defaultBroadcaster} from "./broadcasters/DefaultBroadcaster.js";
14
- import {defaultChainTracker} from "./chaintrackers/DefaultChainTracker.js";
13
+ import { defaultBroadcaster } from './broadcasters/DefaultBroadcaster.js'
14
+ import { defaultChainTracker } from './chaintrackers/DefaultChainTracker.js'
15
15
 
16
16
  /**
17
17
  * Represents a complete Bitcoin transaction. This class encapsulates all the details
@@ -293,26 +293,35 @@ export default class Transaction {
293
293
  /**
294
294
  * Computes fees prior to signing.
295
295
  * If no fee model is provided, uses a SatoshisPerKilobyte fee model that pays 10 sat/kb.
296
+ * If fee is a number, the transaction uses that value as fee.
296
297
  *
297
- * @param model - The initialized fee model to use
298
+ * @param modelOrFee - The initialized fee model to use or fixed fee for the transaction
298
299
  * @param changeDistribution - Specifies how the change should be distributed
299
300
  * amongst the change outputs
300
301
  *
301
302
  * TODO: Benford's law change distribution.
302
303
  */
303
- async fee (model?: FeeModel, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
304
+ async fee (modelOrFee?: FeeModel | number, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
304
305
  this.cachedHash = undefined
305
- if (typeof model === 'undefined') {
306
- model = new SatoshisPerKilobyte(10)
306
+ if (typeof modelOrFee === 'undefined') {
307
+ modelOrFee = new SatoshisPerKilobyte(10)
307
308
  }
308
- const fee = await model.computeFee(this)
309
+ if (typeof modelOrFee === 'number') {
310
+ const sats = modelOrFee
311
+ modelOrFee = {
312
+ computeFee: async () => sats
313
+ }
314
+ }
315
+ const fee = await modelOrFee.computeFee(this)
309
316
  // change = inputs - fee - non-change outputs
310
317
  let change = 0
311
318
  for (const input of this.inputs) {
312
- if (typeof input.sourceTransaction !== 'object') {
313
- throw new Error('Source transactions are required for all inputs during fee computation')
319
+ if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
320
+ throw new Error('Source transactions or sourceSatoshis are required for all inputs during fee computation')
314
321
  }
315
- change += input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
322
+ change += input.sourceTransaction
323
+ ? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
324
+ : input.sourceSatoshis
316
325
  }
317
326
  change -= fee
318
327
  let changeCount = 0
@@ -350,6 +359,28 @@ export default class Transaction {
350
359
  }
351
360
  }
352
361
 
362
+ /**
363
+ * Utility method that returns the current fee based on inputs and outputs
364
+ *
365
+ * @returns The current transaction fee
366
+ */
367
+ getFee (): number {
368
+ let totalIn = 0
369
+ for (const input of this.inputs) {
370
+ if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
371
+ throw new Error('Source transactions or sourceSatoshis are required for all inputs to calculate fee')
372
+ }
373
+ totalIn += input.sourceTransaction
374
+ ? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
375
+ : input.sourceSatoshis || 0
376
+ }
377
+ let totalOut = 0
378
+ for (const output of this.outputs) {
379
+ totalOut += output.satoshis || 0
380
+ }
381
+ return totalIn - totalOut
382
+ }
383
+
353
384
  /**
354
385
  * Signs a transaction, hydrating all its unlocking scripts based on the provided script templates where they are available.
355
386
  */
@@ -16,6 +16,9 @@ import Transaction from './Transaction.js'
16
16
  * @property {number} sourceOutputIndex - The index of the output in the source transaction
17
17
  * that this input is spending. It is zero-based, indicating the position of the
18
18
  * output in the array of outputs of the source transaction.
19
+ * @property {number} [sourceSatoshis] - The amount of satoshis of the source transaction
20
+ * output that this input is spending, used for fee calculation and signing when
21
+ * source transaction is not present
19
22
  * @property {UnlockingScript} [unlockingScript] - Optional. The script that 'unlocks' the
20
23
  * source output for spending. This script typically contains signatures and
21
24
  * public keys that evidence the ownership of the output.
@@ -54,6 +57,7 @@ export default interface TransactionInput {
54
57
  sourceTransaction?: Transaction
55
58
  sourceTXID?: string
56
59
  sourceOutputIndex: number
60
+ sourceSatoshis?: number
57
61
  unlockingScript?: UnlockingScript
58
62
  unlockingScriptTemplate?: {
59
63
  sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
@@ -317,6 +317,32 @@ describe('Transaction', () => {
317
317
  // 4000 sats in - 1000 sats out - 1033 sats fee = expected 1967 sats change
318
318
  expect(spendTx.outputs[1].satoshis).toEqual(1967)
319
319
  })
320
+ it('Computes fee using FixedFee model', async () => {
321
+ const privateKey = new PrivateKey(1)
322
+ const publicKey = new Curve().g.mul(privateKey)
323
+ const publicKeyHash = hash160(publicKey.encode(true)) as number[]
324
+ const p2pkh = new P2PKH()
325
+ const sourceTx = new Transaction(1, [], [{
326
+ lockingScript: p2pkh.lock(publicKeyHash),
327
+ satoshis: 4000
328
+ }], 0)
329
+ const spendTx = new Transaction(1, [{
330
+ sourceTransaction: sourceTx,
331
+ sourceOutputIndex: 0,
332
+ unlockingScriptTemplate: p2pkh.unlock(privateKey),
333
+ sequence: 0xffffffff
334
+ }], [{
335
+ satoshis: 1000,
336
+ lockingScript: p2pkh.lock(publicKeyHash)
337
+ }, {
338
+ lockingScript: p2pkh.lock(publicKeyHash),
339
+ change: true
340
+ }], 0)
341
+ expect(spendTx.outputs[1].satoshis).not.toBeDefined()
342
+ await spendTx.fee(1033)
343
+ // 4000 sats in - 1000 sats out - 1033 sats fee = expected 1967 sats change
344
+ expect(spendTx.outputs[1].satoshis).toEqual(1967)
345
+ })
320
346
  it('Distributes change among multiple change outputs', async () => {
321
347
  const privateKey = new PrivateKey(1)
322
348
  const publicKey = new Curve().g.mul(privateKey)
@@ -352,6 +378,41 @@ describe('Transaction', () => {
352
378
  expect(spendTx.outputs[1].satoshis).toEqual(983)
353
379
  expect(spendTx.outputs[2].satoshis).toEqual(983)
354
380
  })
381
+ it('Calculates fee for utxo based transaction', async () => {
382
+ const utxos = [ // WoC format utxos
383
+ {
384
+ height: 1600000,
385
+ tx_pos: 0,
386
+ tx_hash: '672dd6a93fa5d7ba6794e0bdf8b479440b95a55ec10ad3d9e03585ecb5628d8d',
387
+ value: 10000
388
+ },
389
+ {
390
+ height: 1600000,
391
+ tx_pos: 0,
392
+ tx_hash: 'f33505acf37a7726cc37d391bc6f889b8684ac2a2d581c4be2a4b1c8b46609bc',
393
+ value: 10000
394
+ },
395
+ ]
396
+ const priv = PrivateKey.fromRandom()
397
+ const tx = new Transaction()
398
+ utxos.forEach(utxo => {
399
+ const script = new P2PKH().lock(priv.toPublicKey().toHash())
400
+ tx.addInput({
401
+ sourceTXID: utxo.tx_hash,
402
+ sourceOutputIndex: utxo.tx_pos,
403
+ sourceSatoshis: utxo.value,
404
+ unlockingScriptTemplate: new P2PKH()
405
+ .unlock(priv, 'all', false, utxo.value, script)
406
+ })
407
+ })
408
+ tx.addOutput({
409
+ lockingScript: new P2PKH().lock(priv.toAddress()),
410
+ change: true
411
+ })
412
+ await tx.fee({ computeFee: async () => 10 })
413
+ expect(tx.outputs[0].satoshis).toEqual(20000 - 10)
414
+ expect(tx.getFee()).toEqual(10)
415
+ })
355
416
  })
356
417
 
357
418
  describe('Broadcast', () => {