@sidhujag/sysweb3-keyring 1.0.545 → 1.0.547
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/coverage/clover.xml +2875 -0
- package/coverage/coverage-final.json +29468 -0
- package/coverage/lcov-report/base.css +354 -0
- package/coverage/lcov-report/block-navigation.js +85 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +320 -0
- package/coverage/lcov-report/prettify.css +101 -0
- package/coverage/lcov-report/prettify.js +1008 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +191 -0
- package/coverage/lcov-report/src/index.html +276 -0
- package/coverage/lcov-report/src/index.ts.html +114 -0
- package/coverage/lcov-report/src/initial-state.ts.html +558 -0
- package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
- package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
- package/coverage/lcov-report/src/ledger/index.html +216 -0
- package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
- package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
- package/coverage/lcov-report/src/signers.ts.html +591 -0
- package/coverage/lcov-report/src/storage.ts.html +198 -0
- package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
- package/coverage/lcov-report/src/transactions/index.html +216 -0
- package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
- package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
- package/coverage/lcov-report/src/trezor/index.html +176 -0
- package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
- package/coverage/lcov-report/src/types.ts.html +1443 -0
- package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
- package/coverage/lcov-report/src/utils/index.html +196 -0
- package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
- package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
- package/coverage/lcov-report/test/helpers/index.html +176 -0
- package/coverage/lcov.info +4832 -0
- package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
- package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/{cjs → dist/cjs}/transactions/ethereum.js +6 -2
- package/dist/cjs/transactions/ethereum.js.map +1 -0
- package/dist/package.json +50 -0
- package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
- package/examples/basic-usage.js +140 -0
- package/jest.config.js +32 -0
- package/package.json +31 -13
- package/readme.md +201 -0
- package/src/declare.d.ts +7 -0
- package/src/errorUtils.ts +83 -0
- package/src/hardware-wallet-manager.ts +655 -0
- package/src/index.ts +12 -0
- package/src/initial-state.ts +108 -0
- package/src/keyring-manager.ts +2698 -0
- package/src/ledger/bitcoin_client/index.ts +19 -0
- package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
- package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
- package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
- package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
- package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
- package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
- package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
- package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
- package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
- package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
- package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
- package/src/ledger/consts.ts +3 -0
- package/src/ledger/index.ts +685 -0
- package/src/ledger/types.ts +74 -0
- package/src/network-utils.ts +99 -0
- package/src/providers.ts +345 -0
- package/src/signers.ts +158 -0
- package/src/storage.ts +63 -0
- package/src/transactions/__tests__/integration.test.ts +303 -0
- package/src/transactions/__tests__/syscoin.test.ts +409 -0
- package/src/transactions/ethereum.ts +2503 -0
- package/src/transactions/index.ts +2 -0
- package/src/transactions/syscoin.ts +542 -0
- package/src/trezor/index.ts +1050 -0
- package/src/types.ts +366 -0
- package/src/utils/derivation-paths.ts +133 -0
- package/src/utils/psbt.ts +24 -0
- package/src/utils.ts +191 -0
- package/test/README.md +158 -0
- package/test/__mocks__/ledger-mock.js +20 -0
- package/test/__mocks__/trezor-mock.js +75 -0
- package/test/cleanup-summary.md +167 -0
- package/test/helpers/README.md +78 -0
- package/test/helpers/constants.ts +79 -0
- package/test/helpers/setup.ts +714 -0
- package/test/integration/import-validation.spec.ts +588 -0
- package/test/unit/hardware/ledger.spec.ts +869 -0
- package/test/unit/hardware/trezor.spec.ts +828 -0
- package/test/unit/keyring-manager/account-management.spec.ts +970 -0
- package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
- package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
- package/test/unit/keyring-manager/initialization.spec.ts +782 -0
- package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
- package/test/unit/keyring-manager/security.spec.ts +505 -0
- package/test/unit/keyring-manager/state-management.spec.ts +375 -0
- package/test/unit/network/network-management.spec.ts +372 -0
- package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
- package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
- package/tsconfig.json +14 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
- package/cjs/transactions/ethereum.js.map +0 -1
- /package/{README.md → dist/README.md} +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/index.js.map +0 -0
- /package/{cjs → dist/cjs}/initial-state.js +0 -0
- /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
- /package/{cjs → dist/cjs}/network-utils.js +0 -0
- /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
- /package/{cjs → dist/cjs}/providers.js +0 -0
- /package/{cjs → dist/cjs}/providers.js.map +0 -0
- /package/{cjs → dist/cjs}/signers.js +0 -0
- /package/{cjs → dist/cjs}/signers.js.map +0 -0
- /package/{cjs → dist/cjs}/storage.js +0 -0
- /package/{cjs → dist/cjs}/storage.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
- /package/{cjs → dist/cjs}/types.js +0 -0
- /package/{cjs → dist/cjs}/types.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{cjs → dist/cjs}/utils.js.map +0 -0
- /package/{types → dist/types}/errorUtils.d.ts +0 -0
- /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/initial-state.d.ts +0 -0
- /package/{types → dist/types}/keyring-manager.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
- /package/{types → dist/types}/ledger/consts.d.ts +0 -0
- /package/{types → dist/types}/ledger/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/types.d.ts +0 -0
- /package/{types → dist/types}/network-utils.d.ts +0 -0
- /package/{types → dist/types}/providers.d.ts +0 -0
- /package/{types → dist/types}/signers.d.ts +0 -0
- /package/{types → dist/types}/storage.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
- /package/{types → dist/types}/transactions/index.d.ts +0 -0
- /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
- /package/{types → dist/types}/trezor/index.d.ts +0 -0
- /package/{types → dist/types}/types.d.ts +0 -0
- /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
- /package/{types → dist/types}/utils/psbt.d.ts +0 -0
- /package/{types → dist/types}/utils.d.ts +0 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { INetwork, getNetworkConfig } from '@sidhujag/sysweb3-network';
|
|
2
|
+
import { ITxid, txUtils } from '@sidhujag/sysweb3-utils';
|
|
3
|
+
import * as syscoinjs from 'syscoinjs-lib';
|
|
4
|
+
// import { BIP_84, ONE_HUNDRED_MILLION, SYSCOIN_BASIC_FEE } from 'utils';
|
|
5
|
+
|
|
6
|
+
import { LedgerKeyring } from '../ledger';
|
|
7
|
+
import { DefaultWalletPolicy } from '../ledger/bitcoin_client';
|
|
8
|
+
import { SyscoinHDSigner } from '../signers';
|
|
9
|
+
import { TrezorKeyring } from '../trezor';
|
|
10
|
+
import {
|
|
11
|
+
ISyscoinTransactions,
|
|
12
|
+
KeyringAccountType,
|
|
13
|
+
accountType,
|
|
14
|
+
} from '../types';
|
|
15
|
+
import {
|
|
16
|
+
getAccountDerivationPath,
|
|
17
|
+
convertExtendedKeyVersion,
|
|
18
|
+
} from '../utils/derivation-paths';
|
|
19
|
+
import { PsbtUtils } from '../utils/psbt';
|
|
20
|
+
|
|
21
|
+
type EstimateFeeParams = {
|
|
22
|
+
changeAddress: string;
|
|
23
|
+
feeRateBN: any;
|
|
24
|
+
outputs: { address: string; value: any; subtractFeeFrom?: boolean }[];
|
|
25
|
+
txOptions: any;
|
|
26
|
+
xpub: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class SyscoinTransactions implements ISyscoinTransactions {
|
|
30
|
+
// New separated transaction flow for better UX:
|
|
31
|
+
// 1. Call getEstimateSysTransactionFee() - creates UNSIGNED PSBT and calculates fee
|
|
32
|
+
// 2. Call signPSBT() - signs the PSBT with appropriate wallet (HD/Trezor/Ledger)
|
|
33
|
+
// 3. Call sendTransaction() - broadcasts the signed PSBT
|
|
34
|
+
//
|
|
35
|
+
// This separation allows:
|
|
36
|
+
// - Independent error handling for each step
|
|
37
|
+
// - Better UX feedback (fee estimation, signing, broadcasting)
|
|
38
|
+
// - Hardware wallet compatibility with proper PSBT enhancement
|
|
39
|
+
|
|
40
|
+
private getSigner: () => {
|
|
41
|
+
hd: SyscoinHDSigner;
|
|
42
|
+
main: any;
|
|
43
|
+
};
|
|
44
|
+
private getReadOnlySigner: () => {
|
|
45
|
+
main: any;
|
|
46
|
+
};
|
|
47
|
+
private trezor: TrezorKeyring;
|
|
48
|
+
private ledger: LedgerKeyring;
|
|
49
|
+
private getState: () => {
|
|
50
|
+
accounts: {
|
|
51
|
+
HDAccount: accountType;
|
|
52
|
+
Imported: accountType;
|
|
53
|
+
Ledger: accountType;
|
|
54
|
+
Trezor: accountType;
|
|
55
|
+
};
|
|
56
|
+
activeAccountId: number;
|
|
57
|
+
activeAccountType: KeyringAccountType;
|
|
58
|
+
activeNetwork: INetwork;
|
|
59
|
+
};
|
|
60
|
+
private getAddress: (
|
|
61
|
+
xpub: string,
|
|
62
|
+
isChangeAddress: boolean
|
|
63
|
+
) => Promise<string>;
|
|
64
|
+
|
|
65
|
+
constructor(
|
|
66
|
+
getSyscoinSigner: () => {
|
|
67
|
+
hd: SyscoinHDSigner;
|
|
68
|
+
main: any;
|
|
69
|
+
},
|
|
70
|
+
getReadOnlySigner: () => {
|
|
71
|
+
main: any;
|
|
72
|
+
},
|
|
73
|
+
getState: () => {
|
|
74
|
+
accounts: {
|
|
75
|
+
HDAccount: accountType;
|
|
76
|
+
Imported: accountType;
|
|
77
|
+
Ledger: accountType;
|
|
78
|
+
Trezor: accountType;
|
|
79
|
+
};
|
|
80
|
+
activeAccountId: number;
|
|
81
|
+
activeAccountType: KeyringAccountType;
|
|
82
|
+
activeNetwork: INetwork;
|
|
83
|
+
},
|
|
84
|
+
getAddress: (xpub: string, isChangeAddress: boolean) => Promise<string>,
|
|
85
|
+
ledgerSigner: LedgerKeyring,
|
|
86
|
+
trezorSigner: TrezorKeyring
|
|
87
|
+
) {
|
|
88
|
+
this.getSigner = getSyscoinSigner;
|
|
89
|
+
this.getReadOnlySigner = getReadOnlySigner;
|
|
90
|
+
this.getState = getState;
|
|
91
|
+
this.getAddress = getAddress;
|
|
92
|
+
this.trezor = trezorSigner;
|
|
93
|
+
this.ledger = ledgerSigner;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private getTransactionPSBT = async (
|
|
97
|
+
{ txOptions, outputs, changeAddress, feeRateBN, xpub }: EstimateFeeParams,
|
|
98
|
+
main: any
|
|
99
|
+
) => {
|
|
100
|
+
try {
|
|
101
|
+
// Use syscoinjs-lib directly for transaction creation
|
|
102
|
+
const result = await main.createTransaction(
|
|
103
|
+
txOptions,
|
|
104
|
+
changeAddress,
|
|
105
|
+
outputs,
|
|
106
|
+
feeRateBN,
|
|
107
|
+
xpub // sysFromXpubOrAddress
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (result && result.psbt) {
|
|
111
|
+
return { psbt: result.psbt, fee: result.fee };
|
|
112
|
+
}
|
|
113
|
+
throw new Error('psbt not found');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// Propagate structured error from syscoinjs-lib
|
|
116
|
+
if (error.error && error.code) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
// Wrap non-structured errors
|
|
120
|
+
throw {
|
|
121
|
+
error: true,
|
|
122
|
+
code: 'TRANSACTION_CREATION_FAILED',
|
|
123
|
+
message: error.message || 'Failed to create transaction',
|
|
124
|
+
details: error,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
public decodeRawTransaction = (psbtOrHex: any, isRawHex = false) => {
|
|
130
|
+
const { main } = this.getReadOnlySigner();
|
|
131
|
+
|
|
132
|
+
if (isRawHex) {
|
|
133
|
+
// Handle raw transaction hex
|
|
134
|
+
const bitcoinTx =
|
|
135
|
+
syscoinjs.utils.bitcoinjs.Transaction.fromHex(psbtOrHex);
|
|
136
|
+
return main.decodeRawTransaction(bitcoinTx);
|
|
137
|
+
} else {
|
|
138
|
+
// Handle PSBT format (existing behavior)
|
|
139
|
+
const psbtObj = PsbtUtils.fromPali(psbtOrHex);
|
|
140
|
+
return main.decodeRawTransaction(psbtObj);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
public getRecommendedFee = async (explorerUrl: string): Promise<number> =>
|
|
145
|
+
(await syscoinjs.utils.fetchEstimateFee(explorerUrl, 1)) / 1024;
|
|
146
|
+
|
|
147
|
+
public txUtilsFunctions = () => {
|
|
148
|
+
const { getRawTransaction } = txUtils();
|
|
149
|
+
return {
|
|
150
|
+
getRawTransaction,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Internal method for signing with the HD signer
|
|
155
|
+
private signPSBTWithSigner = async ({
|
|
156
|
+
psbt,
|
|
157
|
+
signer,
|
|
158
|
+
}: {
|
|
159
|
+
psbt: any;
|
|
160
|
+
signer: any;
|
|
161
|
+
}): Promise<any> => await signer.sign(psbt);
|
|
162
|
+
|
|
163
|
+
// Create unsigned PSBT for any transaction type
|
|
164
|
+
private createUnsignedPSBT = async ({
|
|
165
|
+
txOptions = {},
|
|
166
|
+
isMax = false,
|
|
167
|
+
amount,
|
|
168
|
+
receivingAddress,
|
|
169
|
+
feeRateBN,
|
|
170
|
+
token = null,
|
|
171
|
+
}: {
|
|
172
|
+
amount: number | string; // Accept both for safer precision handling
|
|
173
|
+
feeRateBN: any; // BigNumber in satoshis/byte
|
|
174
|
+
receivingAddress: string;
|
|
175
|
+
token?: { guid: string; symbol?: string } | null;
|
|
176
|
+
txOptions?: any;
|
|
177
|
+
isMax?: boolean | false;
|
|
178
|
+
}): Promise<{ psbt: any; fee: number }> => {
|
|
179
|
+
// Ensure RBF is enabled by default if not explicitly set
|
|
180
|
+
const finalTxOptions = { rbf: true, ...txOptions };
|
|
181
|
+
const { activeAccountId, accounts, activeAccountType } = this.getState();
|
|
182
|
+
// Use read-only signer since we're just creating an unsigned PSBT
|
|
183
|
+
const { main } = this.getReadOnlySigner();
|
|
184
|
+
const account = accounts[activeAccountType]?.[activeAccountId];
|
|
185
|
+
if (!account) {
|
|
186
|
+
throw new Error('Active account not found');
|
|
187
|
+
}
|
|
188
|
+
const xpub = account.xpub;
|
|
189
|
+
const isSingleAddressImported =
|
|
190
|
+
activeAccountType === KeyringAccountType.Imported &&
|
|
191
|
+
xpub === account.address;
|
|
192
|
+
// Convert amount to satoshis (1 SYS = 1e8 satoshis)
|
|
193
|
+
// Using BigNumber to prevent precision loss
|
|
194
|
+
const amountStr = amount.toString();
|
|
195
|
+
|
|
196
|
+
// Safe conversion without parseFloat to avoid precision loss
|
|
197
|
+
// Split the string to handle decimal values properly
|
|
198
|
+
const parts = amountStr.split('.');
|
|
199
|
+
const integerPart = parts[0] || '0';
|
|
200
|
+
const decimalPart = parts[1] || '';
|
|
201
|
+
|
|
202
|
+
// Pad or truncate decimal part to 8 places (satoshi precision)
|
|
203
|
+
const paddedDecimal = decimalPart.padEnd(8, '0').substring(0, 8);
|
|
204
|
+
|
|
205
|
+
// Combine to get satoshis (integer + decimal as one number)
|
|
206
|
+
const satoshiStr = integerPart + paddedDecimal;
|
|
207
|
+
|
|
208
|
+
// Remove leading zeros but keep at least one digit
|
|
209
|
+
const trimmedSatoshis = satoshiStr.replace(/^0+/, '') || '0';
|
|
210
|
+
|
|
211
|
+
const value = new syscoinjs.utils.BN(trimmedSatoshis);
|
|
212
|
+
// If this is a single-address imported account (WIF import), use the account.address for both from and change
|
|
213
|
+
const changeAddress = isSingleAddressImported
|
|
214
|
+
? account.address
|
|
215
|
+
: await this.getAddress(xpub, true);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
if (token && token.guid) {
|
|
219
|
+
// Token transaction: use assetAllocationSend
|
|
220
|
+
// Create a Map for the asset allocation
|
|
221
|
+
const assetMap = new Map();
|
|
222
|
+
assetMap.set(token.guid, {
|
|
223
|
+
changeAddress,
|
|
224
|
+
outputs: [
|
|
225
|
+
{
|
|
226
|
+
value: value as any,
|
|
227
|
+
address: receivingAddress,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Pass xpub to get back just the PSBT without signing and sending
|
|
233
|
+
// syscoinjs-lib will validate asset existence and sufficient balance
|
|
234
|
+
const result = await main.assetAllocationSend(
|
|
235
|
+
finalTxOptions,
|
|
236
|
+
assetMap,
|
|
237
|
+
changeAddress,
|
|
238
|
+
feeRateBN,
|
|
239
|
+
isSingleAddressImported ? account.address : xpub // Pass source
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Return PSBT and fee
|
|
243
|
+
return { psbt: result.psbt, fee: result.fee };
|
|
244
|
+
} else {
|
|
245
|
+
// Native transaction: use getTransactionPSBT to create unsigned PSBT
|
|
246
|
+
const outputs = [
|
|
247
|
+
{
|
|
248
|
+
address: receivingAddress,
|
|
249
|
+
value: value, // Pass BN object directly
|
|
250
|
+
...(isMax && { subtractFeeFrom: true }), // Only add subtractFeeFrom if isMax is true
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const result = await this.getTransactionPSBT(
|
|
255
|
+
{
|
|
256
|
+
txOptions: finalTxOptions,
|
|
257
|
+
outputs,
|
|
258
|
+
changeAddress,
|
|
259
|
+
feeRateBN,
|
|
260
|
+
xpub: isSingleAddressImported ? account.address : xpub,
|
|
261
|
+
},
|
|
262
|
+
main
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
// Re-throw structured errors from syscoinjs-lib
|
|
269
|
+
if (error.error && error.code) {
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
// Wrap other errors in structured format
|
|
273
|
+
throw {
|
|
274
|
+
error: true,
|
|
275
|
+
code: 'TRANSACTION_CREATION_FAILED',
|
|
276
|
+
message: error.message || 'Failed to create unsigned PSBT',
|
|
277
|
+
details: error,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Sign PSBT with appropriate method - separated for better error handling
|
|
283
|
+
private signPSBTWithMethod = async (
|
|
284
|
+
psbt: any,
|
|
285
|
+
isTrezor: boolean,
|
|
286
|
+
isLedger = false
|
|
287
|
+
): Promise<any> => {
|
|
288
|
+
const { activeNetwork, activeAccountId, activeAccountType, accounts } =
|
|
289
|
+
this.getState();
|
|
290
|
+
|
|
291
|
+
if (isLedger) {
|
|
292
|
+
// CRITICAL: Enhance PSBT with required Ledger fields
|
|
293
|
+
const account = accounts[activeAccountType]?.[activeAccountId];
|
|
294
|
+
if (!account) {
|
|
295
|
+
throw new Error('Active account not found');
|
|
296
|
+
}
|
|
297
|
+
const accountXpub = account.xpub;
|
|
298
|
+
const accountId = account.id;
|
|
299
|
+
const enhancedPsbt = await this.ledger.convertToLedgerFormat(
|
|
300
|
+
psbt,
|
|
301
|
+
accountXpub,
|
|
302
|
+
accountId,
|
|
303
|
+
activeNetwork.currency,
|
|
304
|
+
activeNetwork.slip44
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Get wallet policy for Ledger
|
|
308
|
+
const fingerprint =
|
|
309
|
+
await this.ledger.ledgerUtxoClient.getMasterFingerprint();
|
|
310
|
+
|
|
311
|
+
const hdPath = getAccountDerivationPath(
|
|
312
|
+
activeNetwork.currency,
|
|
313
|
+
activeNetwork.slip44,
|
|
314
|
+
accountId
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Convert stored/display zpub/vpub to device-friendly xpub/tpub for policy descriptor using network macros
|
|
318
|
+
const { types: deviceTypes } = getNetworkConfig(
|
|
319
|
+
activeNetwork.slip44,
|
|
320
|
+
activeNetwork.currency
|
|
321
|
+
);
|
|
322
|
+
const devicePubMagicDec =
|
|
323
|
+
activeNetwork.slip44 === 1
|
|
324
|
+
? (deviceTypes.xPubType as any).testnet.vpub
|
|
325
|
+
: deviceTypes.xPubType.mainnet.zpub;
|
|
326
|
+
const devicePubMagicHex = Number(devicePubMagicDec)
|
|
327
|
+
.toString(16)
|
|
328
|
+
.padStart(8, '0');
|
|
329
|
+
const deviceXpub = convertExtendedKeyVersion(
|
|
330
|
+
accountXpub,
|
|
331
|
+
devicePubMagicHex
|
|
332
|
+
);
|
|
333
|
+
const xpubWithDescriptor = `[${hdPath}]${deviceXpub}`.replace(
|
|
334
|
+
'm',
|
|
335
|
+
fingerprint
|
|
336
|
+
);
|
|
337
|
+
const walletPolicy = new DefaultWalletPolicy(
|
|
338
|
+
'wpkh(@0/**)',
|
|
339
|
+
xpubWithDescriptor
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Register lazily and retrieve HMAC for silent operations thereafter
|
|
343
|
+
let hmac: Buffer | null = null;
|
|
344
|
+
if (typeof (this.ledger as any).getOrRegisterHmac === 'function') {
|
|
345
|
+
hmac = await (this.ledger as any).getOrRegisterHmac(
|
|
346
|
+
walletPolicy,
|
|
347
|
+
fingerprint
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Sign the enhanced PSBT with Ledger using the HMAC (silent after first approval)
|
|
352
|
+
const signatureEntries = await this.ledger.ledgerUtxoClient.signPsbt(
|
|
353
|
+
enhancedPsbt.toBase64(),
|
|
354
|
+
walletPolicy,
|
|
355
|
+
hmac
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
signatureEntries.forEach(([inputIndex, partialSig]) => {
|
|
359
|
+
enhancedPsbt.updateInput(inputIndex, {
|
|
360
|
+
partialSig: [partialSig],
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Finalize all inputs
|
|
365
|
+
enhancedPsbt.finalizeAllInputs();
|
|
366
|
+
|
|
367
|
+
return enhancedPsbt;
|
|
368
|
+
} else if (isTrezor) {
|
|
369
|
+
// Handle Trezor signing for UTXO
|
|
370
|
+
// Get network configuration for Trezor
|
|
371
|
+
const networkConfig = getNetworkConfig(
|
|
372
|
+
activeNetwork.slip44,
|
|
373
|
+
activeNetwork.currency
|
|
374
|
+
);
|
|
375
|
+
const isTestnet = activeNetwork.slip44 === 1;
|
|
376
|
+
const bitcoinjsNetwork = isTestnet
|
|
377
|
+
? networkConfig?.networks?.testnet
|
|
378
|
+
: networkConfig?.networks?.mainnet;
|
|
379
|
+
|
|
380
|
+
const trezorTx = this.trezor.convertToTrezorFormat({
|
|
381
|
+
psbt,
|
|
382
|
+
coin: activeNetwork.currency.toLowerCase(),
|
|
383
|
+
network: bitcoinjsNetwork || undefined, // Pass network config for isScriptHash check
|
|
384
|
+
});
|
|
385
|
+
const signedPsbt = await this.trezor.signUtxoTransaction(trezorTx, psbt);
|
|
386
|
+
return signedPsbt;
|
|
387
|
+
} else {
|
|
388
|
+
const { hd } = this.getSigner();
|
|
389
|
+
const signedPsbt = await this.signPSBTWithSigner({
|
|
390
|
+
psbt,
|
|
391
|
+
signer: hd,
|
|
392
|
+
});
|
|
393
|
+
return signedPsbt;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Create unsigned PSBT and estimate fee - NO SIGNING
|
|
398
|
+
public getEstimateSysTransactionFee = async ({
|
|
399
|
+
txOptions = {},
|
|
400
|
+
isMax = false,
|
|
401
|
+
amount,
|
|
402
|
+
receivingAddress,
|
|
403
|
+
feeRate,
|
|
404
|
+
token = null,
|
|
405
|
+
}: {
|
|
406
|
+
amount: number | string; // Accept both for safer precision handling
|
|
407
|
+
feeRate?: number;
|
|
408
|
+
receivingAddress: string;
|
|
409
|
+
// Optional fee rate in SYS/byte
|
|
410
|
+
token?: { guid: string; symbol?: string } | null;
|
|
411
|
+
txOptions?: any;
|
|
412
|
+
isMax?: boolean | false;
|
|
413
|
+
}) => {
|
|
414
|
+
try {
|
|
415
|
+
// Ensure RBF is enabled by default if not explicitly set
|
|
416
|
+
const finalTxOptions = { rbf: true, ...txOptions };
|
|
417
|
+
// Use read-only signer since we're just estimating fees and creating unsigned PSBT
|
|
418
|
+
const { main } = this.getReadOnlySigner();
|
|
419
|
+
// Step 1: Determine fee rate
|
|
420
|
+
let actualFeeRate;
|
|
421
|
+
if (feeRate !== undefined) {
|
|
422
|
+
actualFeeRate = feeRate;
|
|
423
|
+
} else {
|
|
424
|
+
actualFeeRate = await this.getRecommendedFee(main.blockbookURL);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Convert fee rate to satoshis/byte and ensure minimum relay of 1 sat/vB
|
|
428
|
+
// Blockbook returns coins per kB; after division by 1024, some testnets
|
|
429
|
+
// can yield < 1 sat/vB (e.g., 0.9765625). Round up and clamp to 1 to
|
|
430
|
+
// avoid zero-fee transactions due to truncation when converting to BN.
|
|
431
|
+
const satPerByte = Math.max(1, Math.ceil(actualFeeRate * 1e8));
|
|
432
|
+
const feeRateBN = new syscoinjs.utils.BN(satPerByte);
|
|
433
|
+
|
|
434
|
+
// Step 2: Create unsigned PSBT
|
|
435
|
+
const result = await this.createUnsignedPSBT({
|
|
436
|
+
txOptions: finalTxOptions,
|
|
437
|
+
isMax,
|
|
438
|
+
amount,
|
|
439
|
+
receivingAddress,
|
|
440
|
+
feeRateBN,
|
|
441
|
+
token,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
fee: result.fee / 1e8,
|
|
446
|
+
psbt: PsbtUtils.toPali(result.psbt), // Return UNSIGNED PSBT as JSON
|
|
447
|
+
};
|
|
448
|
+
} catch (error) {
|
|
449
|
+
// Pass through structured errors from syscoinjs-lib
|
|
450
|
+
if (error.error && error.code) {
|
|
451
|
+
// Convert fee from satoshis to SYS if available
|
|
452
|
+
if (error.fee !== undefined) {
|
|
453
|
+
// If fee is a BN object, convert to number first
|
|
454
|
+
if (typeof error.fee === 'object' && error.fee.toNumber) {
|
|
455
|
+
error.fee = error.fee.toNumber() / 1e8;
|
|
456
|
+
} else {
|
|
457
|
+
error.fee = error.fee / 1e8;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (error.remainingFee !== undefined) {
|
|
461
|
+
// If remainingFee is a BN object, convert to number first
|
|
462
|
+
if (
|
|
463
|
+
typeof error.remainingFee === 'object' &&
|
|
464
|
+
error.remainingFee.toNumber
|
|
465
|
+
) {
|
|
466
|
+
error.remainingFee = error.remainingFee.toNumber() / 1e8;
|
|
467
|
+
} else {
|
|
468
|
+
error.remainingFee = error.remainingFee / 1e8;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (error.shortfall !== undefined) {
|
|
472
|
+
// If shortfall is a BN object, convert to number first
|
|
473
|
+
if (typeof error.shortfall === 'object' && error.shortfall.toNumber) {
|
|
474
|
+
error.shortfall = error.shortfall.toNumber() / 1e8;
|
|
475
|
+
} else {
|
|
476
|
+
error.shortfall = error.shortfall / 1e8;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
throw error;
|
|
480
|
+
}
|
|
481
|
+
// Wrap other errors
|
|
482
|
+
throw {
|
|
483
|
+
error: true,
|
|
484
|
+
code: 'TRANSACTION_CREATION_FAILED',
|
|
485
|
+
message: error.message || 'Failed to estimate transaction fee',
|
|
486
|
+
details: error,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// Removed createAndSignSysTransaction - functionality merged into getEstimateSysTransactionFee
|
|
492
|
+
|
|
493
|
+
private sendSignedTransaction = async (psbt): Promise<ITxid> => {
|
|
494
|
+
try {
|
|
495
|
+
// Use read-only signer since we're just broadcasting an already-signed transaction
|
|
496
|
+
const { main } = this.getReadOnlySigner();
|
|
497
|
+
// Send the transaction
|
|
498
|
+
const result = await main.send(psbt);
|
|
499
|
+
|
|
500
|
+
// Extract the transaction ID
|
|
501
|
+
const txid = result.extractTransaction().getId();
|
|
502
|
+
return { txid };
|
|
503
|
+
} catch (error) {
|
|
504
|
+
// Pass through structured errors
|
|
505
|
+
if (error.error && error.code) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
// Wrap other errors
|
|
509
|
+
throw {
|
|
510
|
+
error: true,
|
|
511
|
+
code: 'TRANSACTION_SEND_FAILED',
|
|
512
|
+
message: error.message || 'Failed to send transaction',
|
|
513
|
+
details: error,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
public signPSBT = async ({
|
|
519
|
+
psbt,
|
|
520
|
+
isTrezor = false,
|
|
521
|
+
isLedger = false,
|
|
522
|
+
}: {
|
|
523
|
+
psbt: any;
|
|
524
|
+
isTrezor?: boolean;
|
|
525
|
+
isLedger?: boolean;
|
|
526
|
+
}): Promise<any> => {
|
|
527
|
+
const psbtObj = PsbtUtils.fromPali(psbt);
|
|
528
|
+
const signedPsbt = await this.signPSBTWithMethod(
|
|
529
|
+
psbtObj,
|
|
530
|
+
isTrezor,
|
|
531
|
+
isLedger
|
|
532
|
+
);
|
|
533
|
+
return PsbtUtils.toPali(signedPsbt);
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
public sendTransaction = async (psbt: any): Promise<ITxid> => {
|
|
537
|
+
if (!psbt) {
|
|
538
|
+
throw new Error('Signed PSBT is required for broadcasting.');
|
|
539
|
+
}
|
|
540
|
+
return await this.sendSignedTransaction(PsbtUtils.fromPali(psbt));
|
|
541
|
+
};
|
|
542
|
+
}
|