@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.
- package/dist/cjs/mod.js +1 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +5 -3
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +14 -1
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +1 -0
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +38 -8
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +1 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +5 -3
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +14 -1
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +1 -0
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +40 -10
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +1 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/primitives/PrivateKey.d.ts +6 -4
- package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
- package/dist/types/src/primitives/PublicKey.d.ts +4 -2
- package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +9 -2
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts +4 -0
- package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/primitives.md +73 -67
- package/docs/script.md +13 -11
- package/docs/transaction.md +508 -39
- package/mod.ts +1 -0
- package/package.json +1 -1
- package/src/primitives/PrivateKey.ts +6 -4
- package/src/primitives/PublicKey.ts +13 -2
- package/src/script/templates/P2PKH.ts +1 -0
- package/src/transaction/Transaction.ts +41 -10
- package/src/transaction/TransactionInput.ts +4 -0
- 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
|
@@ -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 =
|
|
218
|
-
* const
|
|
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
|
|
14
|
-
import {defaultChainTracker} from
|
|
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
|
|
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 (
|
|
304
|
+
async fee (modelOrFee?: FeeModel | number, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
|
|
304
305
|
this.cachedHash = undefined
|
|
305
|
-
if (typeof
|
|
306
|
-
|
|
306
|
+
if (typeof modelOrFee === 'undefined') {
|
|
307
|
+
modelOrFee = new SatoshisPerKilobyte(10)
|
|
307
308
|
}
|
|
308
|
-
|
|
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
|
|
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', () => {
|