@bsv/sdk 1.8.9 → 1.8.11
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/package.json +1 -1
- package/dist/cjs/src/primitives/utils.js +19 -1
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +2 -7
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/script/templates/PushDrop.js +3 -8
- package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +3 -7
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +3 -3
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/src/wallet/WERR_INSUFFICIENT_FUNDS.js +26 -0
- package/dist/cjs/src/wallet/WERR_INSUFFICIENT_FUNDS.js.map +1 -0
- package/dist/cjs/src/wallet/WERR_INVALID_PARAMETER.js +20 -0
- package/dist/cjs/src/wallet/WERR_INVALID_PARAMETER.js.map +1 -0
- package/dist/cjs/src/wallet/WalletClient.js +23 -0
- package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
- package/dist/cjs/src/wallet/WalletError.js +55 -0
- package/dist/cjs/src/wallet/WalletError.js.map +1 -1
- package/dist/cjs/src/wallet/index.js +18 -1
- package/dist/cjs/src/wallet/index.js.map +1 -1
- package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +28 -11
- package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
- package/dist/cjs/src/wallet/validationHelpers.js +920 -0
- package/dist/cjs/src/wallet/validationHelpers.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/utils.js +17 -0
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +3 -8
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/script/templates/PushDrop.js +3 -8
- package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +4 -8
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +3 -3
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/src/wallet/WERR_INSUFFICIENT_FUNDS.js +25 -0
- package/dist/esm/src/wallet/WERR_INSUFFICIENT_FUNDS.js.map +1 -0
- package/dist/esm/src/wallet/WERR_INVALID_PARAMETER.js +18 -0
- package/dist/esm/src/wallet/WERR_INVALID_PARAMETER.js.map +1 -0
- package/dist/esm/src/wallet/WalletClient.js +23 -0
- package/dist/esm/src/wallet/WalletClient.js.map +1 -1
- package/dist/esm/src/wallet/WalletError.js +55 -0
- package/dist/esm/src/wallet/WalletError.js.map +1 -1
- package/dist/esm/src/wallet/index.js +3 -0
- package/dist/esm/src/wallet/index.js.map +1 -1
- package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +25 -11
- package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
- package/dist/esm/src/wallet/validationHelpers.js +859 -0
- package/dist/esm/src/wallet/validationHelpers.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/utils.d.ts +13 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +1 -0
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/wallet/WERR_INSUFFICIENT_FUNDS.d.ts +19 -0
- package/dist/types/src/wallet/WERR_INSUFFICIENT_FUNDS.d.ts.map +1 -0
- package/dist/types/src/wallet/WERR_INVALID_PARAMETER.d.ts +13 -0
- package/dist/types/src/wallet/WERR_INVALID_PARAMETER.d.ts.map +1 -0
- package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
- package/dist/types/src/wallet/WalletError.d.ts +14 -1
- package/dist/types/src/wallet/WalletError.d.ts.map +1 -1
- package/dist/types/src/wallet/index.d.ts +3 -0
- package/dist/types/src/wallet/index.d.ts.map +1 -1
- package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
- package/dist/types/src/wallet/validationHelpers.d.ts +512 -0
- package/dist/types/src/wallet/validationHelpers.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +4 -4
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/wallet.md +1808 -400
- package/package.json +1 -1
- package/src/primitives/__tests/utils.test.ts +27 -1
- package/src/primitives/utils.ts +17 -0
- package/src/script/__tests/Spend.test.ts +74 -1
- package/src/script/__tests/SpendComplex.test.ts +5 -9
- package/src/script/templates/P2PKH.ts +3 -8
- package/src/script/templates/PushDrop.ts +3 -7
- package/src/transaction/Beef.ts +5 -8
- package/src/transaction/Transaction.ts +3 -3
- package/src/wallet/WERR_INSUFFICIENT_FUNDS.ts +25 -0
- package/src/wallet/WERR_INVALID_PARAMETER.ts +20 -0
- package/src/wallet/WalletClient.ts +30 -0
- package/src/wallet/WalletError.ts +52 -0
- package/src/wallet/__tests/WalletClient.test.ts +31 -0
- package/src/wallet/index.ts +3 -0
- package/src/wallet/substrates/HTTPWalletJSON.ts +19 -9
- package/src/wallet/validationHelpers.ts +1211 -0
package/package.json
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
fromBase58,
|
|
8
8
|
toBase58,
|
|
9
9
|
fromBase58Check,
|
|
10
|
-
toBase58Check
|
|
10
|
+
toBase58Check,
|
|
11
|
+
verifyNotNull
|
|
11
12
|
} from '../../primitives/utils'
|
|
12
13
|
|
|
13
14
|
describe('utils', () => {
|
|
@@ -207,3 +208,28 @@ describe('utils', () => {
|
|
|
207
208
|
expect(toArray(input)).toEqual(expected)
|
|
208
209
|
})
|
|
209
210
|
})
|
|
211
|
+
|
|
212
|
+
describe('verifyNotNull', () => {
|
|
213
|
+
it('should return the value if it is not null or undefined', () => {
|
|
214
|
+
expect(verifyNotNull(42)).toBe(42)
|
|
215
|
+
expect(verifyNotNull('hello')).toBe('hello')
|
|
216
|
+
expect(verifyNotNull({})).toEqual({})
|
|
217
|
+
expect(verifyNotNull([])).toEqual([])
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should throw an error with default message if value is null', () => {
|
|
221
|
+
expect(() => verifyNotNull(null)).toThrow('Expected a valid value, but got undefined or null.')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should throw an error with default message if value is undefined', () => {
|
|
225
|
+
expect(() => verifyNotNull(undefined)).toThrow('Expected a valid value, but got undefined or null.')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should throw an error with custom message if value is null', () => {
|
|
229
|
+
expect(() => verifyNotNull(null, 'Custom error')).toThrow('Custom error')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should throw an error with custom message if value is undefined', () => {
|
|
233
|
+
expect(() => verifyNotNull(undefined, 'Another custom error')).toThrow('Another custom error')
|
|
234
|
+
})
|
|
235
|
+
})
|
package/src/primitives/utils.ts
CHANGED
|
@@ -819,3 +819,20 @@ export const minimallyEncode = (buf: number[]): number[] => {
|
|
|
819
819
|
|
|
820
820
|
const OverflowInt64 = new BigNumber(2).pow(new BigNumber(63))
|
|
821
821
|
const OverflowUint64 = new BigNumber(2).pow(new BigNumber(64))
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Verifies that a value is not null or undefined, throwing an error if it is.
|
|
825
|
+
*
|
|
826
|
+
* @template T - The type of the value being verified
|
|
827
|
+
* @param {T | undefined | null} value - The value to verify
|
|
828
|
+
* @param {string} errorMessage - The error message to throw if the value is null or undefined
|
|
829
|
+
* @returns {T} - The verified value
|
|
830
|
+
* @throws {Error} - If the value is null or undefined
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* const myValue = verifyNotNull(someValue, 'someValue must be defined')
|
|
834
|
+
*/
|
|
835
|
+
export function verifyNotNull<T> (value: T | undefined | null, errorMessage: string = 'Expected a valid value, but got undefined or null.'): T {
|
|
836
|
+
if (value == null) throw new Error(errorMessage)
|
|
837
|
+
return value
|
|
838
|
+
}
|
|
@@ -7,8 +7,30 @@ import RPuzzle from '../../script/templates/RPuzzle'
|
|
|
7
7
|
import Transaction from '../../transaction/Transaction'
|
|
8
8
|
import LockingScript from '../../script/LockingScript'
|
|
9
9
|
import UnlockingScript from '../../script/UnlockingScript'
|
|
10
|
-
|
|
10
|
+
import MerklePath from '../../transaction/MerklePath'
|
|
11
|
+
import ChainTracker from '../../transaction/ChainTracker'
|
|
11
12
|
import spendValid from './spend.valid.vectors'
|
|
13
|
+
import Script from '../../script/Script'
|
|
14
|
+
|
|
15
|
+
export class MockChain implements ChainTracker {
|
|
16
|
+
mock: { blockheaders: string[] }
|
|
17
|
+
|
|
18
|
+
constructor(mock: { blockheaders: string[] }) {
|
|
19
|
+
this.mock = mock
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
addBlock(merkleRoot: string) {
|
|
23
|
+
this.mock.blockheaders.push(merkleRoot)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async isValidRootForHeight(root: string, height: number): Promise<boolean> {
|
|
27
|
+
return this.mock.blockheaders[height] === root
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async currentHeight(): Promise<number> {
|
|
31
|
+
return this.mock.blockheaders.length
|
|
32
|
+
}
|
|
33
|
+
}
|
|
12
34
|
|
|
13
35
|
describe('Spend', () => {
|
|
14
36
|
it('Successfully validates a P2PKH spend', async () => {
|
|
@@ -368,4 +390,55 @@ describe('Spend', () => {
|
|
|
368
390
|
expect(spend.validate()).toBe(true)
|
|
369
391
|
})
|
|
370
392
|
}
|
|
393
|
+
|
|
394
|
+
it('Successfully validates a spend where sequence is set to undefined', async () => {
|
|
395
|
+
const sourceTransaction = new Transaction(
|
|
396
|
+
1,
|
|
397
|
+
[{
|
|
398
|
+
sourceTXID: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
399
|
+
sourceOutputIndex: 0,
|
|
400
|
+
unlockingScript: Script.fromASM('OP_TRUE'),
|
|
401
|
+
sequence: 0xffffffff
|
|
402
|
+
}],
|
|
403
|
+
[
|
|
404
|
+
{
|
|
405
|
+
lockingScript: Script.fromASM('OP_NOP'),
|
|
406
|
+
satoshis: 2
|
|
407
|
+
}
|
|
408
|
+
],
|
|
409
|
+
0
|
|
410
|
+
)
|
|
411
|
+
const txid = sourceTransaction.id('hex')
|
|
412
|
+
sourceTransaction.merklePath = MerklePath.fromCoinbaseTxidAndHeight(txid, 0)
|
|
413
|
+
const chain = new MockChain({ blockheaders: [] })
|
|
414
|
+
chain.addBlock(txid)
|
|
415
|
+
|
|
416
|
+
const spendTx = new Transaction(
|
|
417
|
+
1,
|
|
418
|
+
[
|
|
419
|
+
{
|
|
420
|
+
unlockingScript: Script.fromASM('OP_TRUE'),
|
|
421
|
+
sourceTransaction,
|
|
422
|
+
sourceOutputIndex: 0
|
|
423
|
+
}
|
|
424
|
+
],
|
|
425
|
+
[{
|
|
426
|
+
lockingScript: Script.fromASM('OP_NOP'),
|
|
427
|
+
satoshis: 1
|
|
428
|
+
}],
|
|
429
|
+
0
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
const valid = await spendTx.verify(chain)
|
|
433
|
+
|
|
434
|
+
expect(valid).toBe(true)
|
|
435
|
+
|
|
436
|
+
const b = spendTx.toBinary()
|
|
437
|
+
const t = Transaction.fromBinary(b)
|
|
438
|
+
expect(t.inputs[0].sequence).toBe(0xffffffff)
|
|
439
|
+
|
|
440
|
+
const b2 = spendTx.toEF()
|
|
441
|
+
const t2 = Transaction.fromEF(b2)
|
|
442
|
+
expect(t2.inputs[0].sequence).toBe(0xffffffff)
|
|
443
|
+
})
|
|
371
444
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Script from '../../script/Script'
|
|
2
2
|
import Spend from '../../script/Spend'
|
|
3
3
|
import Transaction from '../../transaction/Transaction'
|
|
4
|
+
import { verifyNotNull } from '../../primitives/utils'
|
|
4
5
|
|
|
5
6
|
describe('SpendComplex', () => {
|
|
6
7
|
it('complex unlock script validation', () => {
|
|
@@ -19,11 +20,6 @@ describe('SpendComplex', () => {
|
|
|
19
20
|
})
|
|
20
21
|
})
|
|
21
22
|
|
|
22
|
-
function verifyTruthy<T> (v: T | undefined): T {
|
|
23
|
-
if (v == null) throw new Error('must have value')
|
|
24
|
-
return v
|
|
25
|
-
}
|
|
26
|
-
|
|
27
23
|
export function validateUnlockScript (
|
|
28
24
|
spendingRawTx: string,
|
|
29
25
|
vin: number,
|
|
@@ -34,16 +30,16 @@ export function validateUnlockScript (
|
|
|
34
30
|
const ls = Script.fromHex(lockingScript)
|
|
35
31
|
|
|
36
32
|
const spend = new Spend({
|
|
37
|
-
sourceTXID:
|
|
38
|
-
sourceOutputIndex:
|
|
33
|
+
sourceTXID: verifyNotNull(spendingTx.inputs[vin].sourceTXID, 'sourceTXID must have value'),
|
|
34
|
+
sourceOutputIndex: verifyNotNull(spendingTx.inputs[vin].sourceOutputIndex, 'sourceOutputIndex must have value'),
|
|
39
35
|
sourceSatoshis: amount,
|
|
40
36
|
lockingScript: ls,
|
|
41
37
|
transactionVersion: spendingTx.version,
|
|
42
38
|
otherInputs: spendingTx.inputs.filter((v, i) => i !== vin),
|
|
43
39
|
inputIndex: vin,
|
|
44
|
-
unlockingScript:
|
|
40
|
+
unlockingScript: verifyNotNull(spendingTx.inputs[vin].unlockingScript, 'unlockingScript must have value'),
|
|
45
41
|
outputs: spendingTx.outputs,
|
|
46
|
-
inputSequence:
|
|
42
|
+
inputSequence: verifyNotNull(spendingTx.inputs[vin].sequence, 'sequence must have value'),
|
|
47
43
|
lockTime: spendingTx.lockTime
|
|
48
44
|
})
|
|
49
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import OP from '../OP.js'
|
|
2
2
|
import ScriptTemplate from '../ScriptTemplate.js'
|
|
3
|
-
import { fromBase58Check } from '../../primitives/utils.js'
|
|
3
|
+
import { fromBase58Check, verifyNotNull } from '../../primitives/utils.js'
|
|
4
4
|
import LockingScript from '../LockingScript.js'
|
|
5
5
|
import UnlockingScript from '../UnlockingScript.js'
|
|
6
6
|
import Transaction from '../../transaction/Transaction.js'
|
|
@@ -9,11 +9,6 @@ import TransactionSignature from '../../primitives/TransactionSignature.js'
|
|
|
9
9
|
import { sha256 } from '../../primitives/Hash.js'
|
|
10
10
|
import Script from '../Script.js'
|
|
11
11
|
|
|
12
|
-
function verifyTruthy<T>(v: T | undefined): T {
|
|
13
|
-
if (v == null) throw new Error('must have value')
|
|
14
|
-
return v
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
/**
|
|
18
13
|
* P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.
|
|
19
14
|
*
|
|
@@ -125,13 +120,13 @@ export default class P2PKH implements ScriptTemplate {
|
|
|
125
120
|
|
|
126
121
|
const preimage = TransactionSignature.format({
|
|
127
122
|
sourceTXID,
|
|
128
|
-
sourceOutputIndex:
|
|
123
|
+
sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
|
|
129
124
|
sourceSatoshis,
|
|
130
125
|
transactionVersion: tx.version,
|
|
131
126
|
otherInputs,
|
|
132
127
|
inputIndex,
|
|
133
128
|
outputs: tx.outputs,
|
|
134
|
-
inputSequence:
|
|
129
|
+
inputSequence: verifyNotNull(input.sequence, 'input.sequence must have value'),
|
|
135
130
|
subscript: lockingScript,
|
|
136
131
|
lockTime: tx.lockTime,
|
|
137
132
|
scope: signatureScope
|
|
@@ -9,11 +9,7 @@ import {
|
|
|
9
9
|
import { WalletInterface } from '../../wallet/Wallet.interfaces.js'
|
|
10
10
|
import { Transaction } from '../../transaction/index.js'
|
|
11
11
|
import { WalletProtocol } from '../../wallet/Wallet.interfaces.js'
|
|
12
|
-
|
|
13
|
-
function verifyTruthy<T>(v: T | undefined): T {
|
|
14
|
-
if (v == null) throw new Error('must have value')
|
|
15
|
-
return v
|
|
16
|
-
}
|
|
12
|
+
import { verifyNotNull } from '../../primitives/utils.js'
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
15
|
* For a given piece of data to push onto the stack in script, creates the correct minimally-encoded script chunk,
|
|
@@ -71,7 +67,7 @@ export default class PushDrop implements ScriptTemplate {
|
|
|
71
67
|
fields: number[][]
|
|
72
68
|
} {
|
|
73
69
|
const lockingPublicKey = PublicKey.fromString(
|
|
74
|
-
Utils.toHex(
|
|
70
|
+
Utils.toHex(verifyNotNull(script.chunks[0].data, 'script.chunks[0].data must have value'))
|
|
75
71
|
)
|
|
76
72
|
|
|
77
73
|
const fields: number[][] = []
|
|
@@ -249,7 +245,7 @@ export default class PushDrop implements ScriptTemplate {
|
|
|
249
245
|
|
|
250
246
|
const preimage = TransactionSignature.format({
|
|
251
247
|
sourceTXID,
|
|
252
|
-
sourceOutputIndex:
|
|
248
|
+
sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
|
|
253
249
|
sourceSatoshis,
|
|
254
250
|
transactionVersion: tx.version,
|
|
255
251
|
otherInputs,
|
package/src/transaction/Beef.ts
CHANGED
|
@@ -2,14 +2,9 @@ import MerklePath from './MerklePath.js'
|
|
|
2
2
|
import Transaction from './Transaction.js'
|
|
3
3
|
import ChainTracker from './ChainTracker.js'
|
|
4
4
|
import BeefTx from './BeefTx.js'
|
|
5
|
-
import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
|
|
5
|
+
import { Reader, Writer, toHex, toArray, verifyNotNull } from '../primitives/utils.js'
|
|
6
6
|
import { hash256 } from '../primitives/Hash.js'
|
|
7
7
|
|
|
8
|
-
function verifyTruthy<T> (v: T | undefined): T {
|
|
9
|
-
if (v == null) throw new Error('Expected a valid value, but got undefined.')
|
|
10
|
-
return v
|
|
11
|
-
}
|
|
12
|
-
|
|
13
8
|
export const BEEF_V1 = 4022206465 // 0100BEEF in LE order
|
|
14
9
|
export const BEEF_V2 = 4022206466 // 0200BEEF in LE order
|
|
15
10
|
export const ATOMIC_BEEF = 0x01010101 // 01010101
|
|
@@ -156,7 +151,7 @@ export class Beef {
|
|
|
156
151
|
|
|
157
152
|
for (const i of beefTx.tx.inputs) {
|
|
158
153
|
if (i.sourceTransaction == null) {
|
|
159
|
-
const itx = this.findTxid(
|
|
154
|
+
const itx = this.findTxid(verifyNotNull(i.sourceTXID, 'sourceTXID must be valid'))
|
|
160
155
|
if (itx != null) {
|
|
161
156
|
i.sourceTransaction = itx.tx
|
|
162
157
|
}
|
|
@@ -185,7 +180,7 @@ export class Beef {
|
|
|
185
180
|
} else {
|
|
186
181
|
for (const i of tx.inputs) {
|
|
187
182
|
if (i.sourceTransaction == null) {
|
|
188
|
-
const itx = beef.findTxid(
|
|
183
|
+
const itx = beef.findTxid(verifyNotNull(i.sourceTXID, 'sourceTXID must be valid'))
|
|
189
184
|
if (itx != null) {
|
|
190
185
|
i.sourceTransaction = itx.tx
|
|
191
186
|
}
|
|
@@ -869,3 +864,5 @@ export class Beef {
|
|
|
869
864
|
}
|
|
870
865
|
}
|
|
871
866
|
}
|
|
867
|
+
|
|
868
|
+
export default Beef
|
|
@@ -618,7 +618,7 @@ export default class Transaction {
|
|
|
618
618
|
const scriptBin = i.unlockingScript.toBinary()
|
|
619
619
|
writer.writeVarIntNum(scriptBin.length)
|
|
620
620
|
writer.write(scriptBin)
|
|
621
|
-
writer.writeUInt32LE(i.sequence ??
|
|
621
|
+
writer.writeUInt32LE(i.sequence ?? 0xffffffff) // default to max sequence
|
|
622
622
|
}
|
|
623
623
|
writer.writeVarIntNum(this.outputs.length)
|
|
624
624
|
for (const o of this.outputs) {
|
|
@@ -659,7 +659,7 @@ export default class Transaction {
|
|
|
659
659
|
const scriptBin = i.unlockingScript.toBinary()
|
|
660
660
|
writer.writeVarIntNum(scriptBin.length)
|
|
661
661
|
writer.write(scriptBin)
|
|
662
|
-
writer.writeUInt32LE(i.sequence ??
|
|
662
|
+
writer.writeUInt32LE(i.sequence ?? 0xffffffff) // default to max sequence
|
|
663
663
|
writer.writeUInt64LE(
|
|
664
664
|
i.sourceTransaction.outputs[i.sourceOutputIndex].satoshis ?? 0
|
|
665
665
|
)
|
|
@@ -866,7 +866,7 @@ export default class Transaction {
|
|
|
866
866
|
transactionVersion: tx.version,
|
|
867
867
|
otherInputs,
|
|
868
868
|
unlockingScript: input.unlockingScript,
|
|
869
|
-
inputSequence: input.sequence ??
|
|
869
|
+
inputSequence: input.sequence ?? 0xffffffff, // default to max sequence
|
|
870
870
|
inputIndex: i,
|
|
871
871
|
outputs: tx.outputs,
|
|
872
872
|
lockTime: tx.lockTime,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insufficient funds in the available inputs to cover the cost of the required outputs
|
|
3
|
+
* and the transaction fee (${moreSatoshisNeeded} more satoshis are needed,
|
|
4
|
+
* for a total of ${totalSatoshisNeeded}), plus whatever would be required in order
|
|
5
|
+
* to pay the fee to unlock and spend the outputs used to provide the additional satoshis.
|
|
6
|
+
*/
|
|
7
|
+
export class WERR_INSUFFICIENT_FUNDS extends Error {
|
|
8
|
+
code: number
|
|
9
|
+
isError: boolean = true
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param totalSatoshisNeeded Total satoshis required to fund transactions after net of required inputs and outputs.
|
|
13
|
+
* @param moreSatoshisNeeded Shortfall on total satoshis required to fund transactions after net of required inputs and outputs.
|
|
14
|
+
*/
|
|
15
|
+
constructor (
|
|
16
|
+
public totalSatoshisNeeded: number,
|
|
17
|
+
public moreSatoshisNeeded: number
|
|
18
|
+
) {
|
|
19
|
+
super(`Insufficient funds in the available inputs to cover the cost of the required outputs and the transaction fee (${moreSatoshisNeeded} more satoshis are needed, for a total of ${totalSatoshisNeeded}), plus whatever would be required in order to pay the fee to unlock and spend the outputs used to provide the additional satoshis.`)
|
|
20
|
+
this.code = 7
|
|
21
|
+
this.name = this.constructor.name
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default WERR_INSUFFICIENT_FUNDS
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ${parameter} parameter is invalid.
|
|
3
|
+
*
|
|
4
|
+
* This is an example of an error object with a custom property `parameter` and templated `message`.
|
|
5
|
+
*/
|
|
6
|
+
export class WERR_INVALID_PARAMETER extends Error {
|
|
7
|
+
code: number
|
|
8
|
+
isError: boolean = true
|
|
9
|
+
|
|
10
|
+
constructor (
|
|
11
|
+
public parameter: string,
|
|
12
|
+
mustBe?: string
|
|
13
|
+
) {
|
|
14
|
+
super(`The ${parameter} parameter must be ${mustBe ?? 'valid.'}`)
|
|
15
|
+
this.code = 6
|
|
16
|
+
this.name = this.constructor.name
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default WERR_INVALID_PARAMETER
|
|
@@ -41,6 +41,23 @@ import WalletWireTransceiver from './substrates/WalletWireTransceiver.js'
|
|
|
41
41
|
import HTTPWalletWire from './substrates/HTTPWalletWire.js'
|
|
42
42
|
import HTTPWalletJSON from './substrates/HTTPWalletJSON.js'
|
|
43
43
|
import ReactNativeWebView from './substrates/ReactNativeWebView.js'
|
|
44
|
+
import {
|
|
45
|
+
validateAbortActionArgs,
|
|
46
|
+
validateAcquireDirectCertificateArgs,
|
|
47
|
+
validateAcquireIssuanceCertificateArgs,
|
|
48
|
+
validateCreateActionArgs,
|
|
49
|
+
validateDiscoverByAttributesArgs,
|
|
50
|
+
validateDiscoverByIdentityKeyArgs,
|
|
51
|
+
validateInternalizeActionArgs,
|
|
52
|
+
validateListActionsArgs,
|
|
53
|
+
validateListCertificatesArgs,
|
|
54
|
+
validateListOutputsArgs,
|
|
55
|
+
validateProveCertificateArgs,
|
|
56
|
+
validateRelinquishCertificateArgs,
|
|
57
|
+
validateRelinquishOutputArgs,
|
|
58
|
+
validateSignActionArgs
|
|
59
|
+
} from './validationHelpers.js'
|
|
60
|
+
import { WERR_INVALID_PARAMETER } from './WERR_INVALID_PARAMETER.js'
|
|
44
61
|
|
|
45
62
|
const MAX_XDM_RESPONSE_WAIT = 200
|
|
46
63
|
|
|
@@ -133,6 +150,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
async createAction (args: CreateActionArgs): Promise<CreateActionResult> {
|
|
153
|
+
validateCreateActionArgs(args)
|
|
136
154
|
await this.connectToSubstrate()
|
|
137
155
|
return await (this.substrate as WalletInterface).createAction(
|
|
138
156
|
args,
|
|
@@ -141,6 +159,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
async signAction (args: SignActionArgs): Promise<SignActionResult> {
|
|
162
|
+
validateSignActionArgs(args)
|
|
144
163
|
await this.connectToSubstrate()
|
|
145
164
|
return await (this.substrate as WalletInterface).signAction(
|
|
146
165
|
args,
|
|
@@ -151,6 +170,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
151
170
|
async abortAction (args: {
|
|
152
171
|
reference: Base64String
|
|
153
172
|
}): Promise<{ aborted: true }> {
|
|
173
|
+
validateAbortActionArgs(args)
|
|
154
174
|
await this.connectToSubstrate()
|
|
155
175
|
return await (this.substrate as WalletInterface).abortAction(
|
|
156
176
|
args,
|
|
@@ -159,6 +179,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
159
179
|
}
|
|
160
180
|
|
|
161
181
|
async listActions (args: ListActionsArgs): Promise<ListActionsResult> {
|
|
182
|
+
validateListActionsArgs(args)
|
|
162
183
|
await this.connectToSubstrate()
|
|
163
184
|
return await (this.substrate as WalletInterface).listActions(
|
|
164
185
|
args,
|
|
@@ -169,6 +190,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
169
190
|
async internalizeAction (
|
|
170
191
|
args: InternalizeActionArgs
|
|
171
192
|
): Promise<{ accepted: true }> {
|
|
193
|
+
validateInternalizeActionArgs(args)
|
|
172
194
|
await this.connectToSubstrate()
|
|
173
195
|
return await (this.substrate as WalletInterface).internalizeAction(
|
|
174
196
|
args,
|
|
@@ -177,6 +199,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
177
199
|
}
|
|
178
200
|
|
|
179
201
|
async listOutputs (args: ListOutputsArgs): Promise<ListOutputsResult> {
|
|
202
|
+
validateListOutputsArgs(args)
|
|
180
203
|
await this.connectToSubstrate()
|
|
181
204
|
return await (this.substrate as WalletInterface).listOutputs(
|
|
182
205
|
args,
|
|
@@ -188,6 +211,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
188
211
|
basket: BasketStringUnder300Bytes
|
|
189
212
|
output: OutpointString
|
|
190
213
|
}): Promise<{ relinquished: true }> {
|
|
214
|
+
validateRelinquishOutputArgs(args)
|
|
191
215
|
await this.connectToSubstrate()
|
|
192
216
|
return await (this.substrate as WalletInterface).relinquishOutput(
|
|
193
217
|
args,
|
|
@@ -352,6 +376,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
352
376
|
async acquireCertificate (
|
|
353
377
|
args: AcquireCertificateArgs
|
|
354
378
|
): Promise<AcquireCertificateResult> {
|
|
379
|
+
if (args.acquisitionProtocol === 'direct') { validateAcquireDirectCertificateArgs(args) } else if (args.acquisitionProtocol === 'issuance') { validateAcquireIssuanceCertificateArgs(args) } else { throw new WERR_INVALID_PARAMETER('acquisitionProtocol', `valid. ${String(args.acquisitionProtocol)} is unrecognized.`) }
|
|
355
380
|
await this.connectToSubstrate()
|
|
356
381
|
return await (this.substrate as WalletInterface).acquireCertificate(
|
|
357
382
|
args,
|
|
@@ -367,6 +392,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
367
392
|
privileged?: BooleanDefaultFalse
|
|
368
393
|
privilegedReason?: DescriptionString5to50Bytes
|
|
369
394
|
}): Promise<ListCertificatesResult> {
|
|
395
|
+
validateListCertificatesArgs(args)
|
|
370
396
|
await this.connectToSubstrate()
|
|
371
397
|
return await (this.substrate as WalletInterface).listCertificates(
|
|
372
398
|
args,
|
|
@@ -377,6 +403,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
377
403
|
async proveCertificate (
|
|
378
404
|
args: ProveCertificateArgs
|
|
379
405
|
): Promise<ProveCertificateResult> {
|
|
406
|
+
validateProveCertificateArgs(args)
|
|
380
407
|
await this.connectToSubstrate()
|
|
381
408
|
return await (this.substrate as WalletInterface).proveCertificate(
|
|
382
409
|
args,
|
|
@@ -389,6 +416,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
389
416
|
serialNumber: Base64String
|
|
390
417
|
certifier: PubKeyHex
|
|
391
418
|
}): Promise<{ relinquished: true }> {
|
|
419
|
+
validateRelinquishCertificateArgs(args)
|
|
392
420
|
await this.connectToSubstrate()
|
|
393
421
|
return await (this.substrate as WalletInterface).relinquishCertificate(
|
|
394
422
|
args,
|
|
@@ -401,6 +429,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
401
429
|
limit?: PositiveIntegerDefault10Max10000
|
|
402
430
|
offset?: PositiveIntegerOrZero
|
|
403
431
|
}): Promise<DiscoverCertificatesResult> {
|
|
432
|
+
validateDiscoverByIdentityKeyArgs(args)
|
|
404
433
|
await this.connectToSubstrate()
|
|
405
434
|
return await (this.substrate as WalletInterface).discoverByIdentityKey(
|
|
406
435
|
args,
|
|
@@ -413,6 +442,7 @@ export default class WalletClient implements WalletInterface {
|
|
|
413
442
|
limit?: PositiveIntegerDefault10Max10000
|
|
414
443
|
offset?: PositiveIntegerOrZero
|
|
415
444
|
}): Promise<DiscoverCertificatesResult> {
|
|
445
|
+
validateDiscoverByAttributesArgs(args)
|
|
416
446
|
await this.connectToSubstrate()
|
|
417
447
|
return await (this.substrate as WalletInterface).discoverByAttributes(
|
|
418
448
|
args,
|
|
@@ -13,6 +13,56 @@ export class WalletError extends Error {
|
|
|
13
13
|
Error.captureStackTrace(this, this.constructor)
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Safely serializes a WalletError (including special cases), Error or unknown error to JSON.
|
|
19
|
+
*
|
|
20
|
+
* Safely means avoiding deep, large, circular issues.
|
|
21
|
+
*
|
|
22
|
+
* Example deserialization can be found in HTTPWalletJSON.ts of bsv ts-sdk.
|
|
23
|
+
*
|
|
24
|
+
* @param error
|
|
25
|
+
* @returns stringified JSON representation of the error such that it can be deserialized to a WalletError.
|
|
26
|
+
*/
|
|
27
|
+
static unknownToJson (error: any): string {
|
|
28
|
+
let e: any | undefined
|
|
29
|
+
if (typeof error.constructor.name === 'string' && String(error.constructor.name).startsWith('WERR_')) {
|
|
30
|
+
e = {
|
|
31
|
+
name: error.constructor.name,
|
|
32
|
+
message: error.message,
|
|
33
|
+
isError: true
|
|
34
|
+
}
|
|
35
|
+
if (e.name === 'WERR_REVIEW_ACTIONS') {
|
|
36
|
+
e.reviewActionResults = error.reviewActionResults
|
|
37
|
+
e.sendWithResults = error.sendWithResults
|
|
38
|
+
e.txid = error.txid
|
|
39
|
+
e.tx = error.tx
|
|
40
|
+
e.noSendChange = error.noSendChange
|
|
41
|
+
e.code = 5
|
|
42
|
+
} else if (e.name === 'WERR_INVALID_PARAMETER') {
|
|
43
|
+
e.parameter = error.parameter
|
|
44
|
+
e.code = 6
|
|
45
|
+
} else if (e.name === 'WERR_INSUFFICIENT_FUNDS') {
|
|
46
|
+
e.totalSatoshisNeeded = error.totalSatoshisNeeded
|
|
47
|
+
e.moreSatoshisNeeded = error.moreSatoshisNeeded
|
|
48
|
+
e.code = 7
|
|
49
|
+
}
|
|
50
|
+
} else if (error instanceof Error) {
|
|
51
|
+
e = {
|
|
52
|
+
name: error.constructor.name,
|
|
53
|
+
message: error.message,
|
|
54
|
+
isError: true
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
e = {
|
|
58
|
+
name: 'WERR_UNKNOWN',
|
|
59
|
+
message: String(error),
|
|
60
|
+
isError: true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const json = JSON.stringify(e)
|
|
64
|
+
return json
|
|
65
|
+
}
|
|
16
66
|
}
|
|
17
67
|
|
|
18
68
|
// NOTE: Enum values must not exceed the UInt8 range (0–255)
|
|
@@ -22,6 +72,8 @@ export enum walletErrors {
|
|
|
22
72
|
invalidHmac = 3,
|
|
23
73
|
invalidSignature = 4,
|
|
24
74
|
reviewActions = 5,
|
|
75
|
+
invalidParameter = 6,
|
|
76
|
+
insufficientFunds = 7,
|
|
25
77
|
}
|
|
26
78
|
|
|
27
79
|
export type WalletErrorCode = keyof typeof walletErrors
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CreateActionArgs } from '../Wallet.interfaces'
|
|
2
|
+
import WalletClient from '../WalletClient'
|
|
3
|
+
describe('WalletClient', () => {
|
|
4
|
+
it('0 createAction', async () => {
|
|
5
|
+
const wallet = new WalletClient('auto', '0.WalletClient.test')
|
|
6
|
+
|
|
7
|
+
async function testArgs(args: CreateActionArgs, parameter: string) {
|
|
8
|
+
try {
|
|
9
|
+
const r = await wallet.createAction(args)
|
|
10
|
+
expect(true).toBe(false)
|
|
11
|
+
} catch (e: any) {
|
|
12
|
+
expect(e.name).toBe('WERR_INVALID_PARAMETER')
|
|
13
|
+
expect(e.parameter).toBe(parameter)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const args: CreateActionArgs = {
|
|
18
|
+
description: 't' // too short to be valid
|
|
19
|
+
}
|
|
20
|
+
testArgs(args, 'description')
|
|
21
|
+
args.description = '12345'
|
|
22
|
+
args.outputs = [{
|
|
23
|
+
lockingScript: '',
|
|
24
|
+
satoshis: 0,
|
|
25
|
+
outputDescription: ''
|
|
26
|
+
}]
|
|
27
|
+
testArgs(args, 'lockingScript')
|
|
28
|
+
args.outputs[0].lockingScript = '1234'
|
|
29
|
+
testArgs(args, 'outputDescription')
|
|
30
|
+
})
|
|
31
|
+
})
|
package/src/wallet/index.ts
CHANGED
|
@@ -6,5 +6,8 @@ export { default as WalletClient } from './WalletClient.js'
|
|
|
6
6
|
// Is this an error? should it be 'walletErrors', the enum not the class?
|
|
7
7
|
export { default as WalletErrors } from './WalletError.js'
|
|
8
8
|
export { default as WERR_REVIEW_ACTIONS } from './WERR_REVIEW_ACTIONS.js'
|
|
9
|
+
export { default as WERR_INVALID_PARAMETER } from './WERR_INVALID_PARAMETER.js'
|
|
10
|
+
export { default as WERR_INSUFFICIENT_FUNDS } from './WERR_INSUFFICIENT_FUNDS.js'
|
|
9
11
|
export * from './WalletError.js'
|
|
12
|
+
export * as Validation from './validationHelpers.js'
|
|
10
13
|
export * from './substrates/index.js'
|
|
@@ -36,7 +36,9 @@ import {
|
|
|
36
36
|
VersionString7To30Bytes,
|
|
37
37
|
} from '../Wallet.interfaces.js'
|
|
38
38
|
import { WERR_REVIEW_ACTIONS } from '../WERR_REVIEW_ACTIONS.js'
|
|
39
|
+
import { WERR_INVALID_PARAMETER } from '../WERR_INVALID_PARAMETER.js'
|
|
39
40
|
import { toOriginHeader } from './utils/toOriginHeader.js'
|
|
41
|
+
import WERR_INSUFFICIENT_FUNDS from '../WERR_INSUFFICIENT_FUNDS.js'
|
|
40
42
|
|
|
41
43
|
export default class HTTPWalletJSON implements WalletInterface {
|
|
42
44
|
baseUrl: string
|
|
@@ -84,17 +86,25 @@ export default class HTTPWalletJSON implements WalletInterface {
|
|
|
84
86
|
|
|
85
87
|
// Check the HTTP status on the original response
|
|
86
88
|
if (!res.ok) {
|
|
87
|
-
if (res.status === 400 && data.isError
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
if (res.status === 400 && data.isError) {
|
|
90
|
+
let err: any
|
|
91
|
+
switch (data.code) {
|
|
92
|
+
case 5:
|
|
93
|
+
err = new WERR_REVIEW_ACTIONS(data.reviewActionResults, data.sendWithResults, data.txid, data.tx, data.noSendChange); break;
|
|
94
|
+
case 6:
|
|
95
|
+
err = new WERR_INVALID_PARAMETER(data.parameter); err.message = data.message; break;
|
|
96
|
+
case 7:
|
|
97
|
+
err = new WERR_INSUFFICIENT_FUNDS(data.totalSatoshisNeeded, data.moreSatoshisNeeded); break;
|
|
98
|
+
default: break;
|
|
95
99
|
}
|
|
96
|
-
|
|
100
|
+
if (err) throw err
|
|
97
101
|
}
|
|
102
|
+
const err = {
|
|
103
|
+
call,
|
|
104
|
+
args,
|
|
105
|
+
message: data.message ?? `HTTP Client error ${res.status}`
|
|
106
|
+
}
|
|
107
|
+
throw new Error(JSON.stringify(err))
|
|
98
108
|
}
|
|
99
109
|
return data
|
|
100
110
|
}
|