@sidhujag/sysweb3-keyring 2.1.3 → 2.1.5
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/CHANGELOG.md +12 -0
- package/README.md +47 -47
- package/cjs/hardware-transaction-authority.js +36 -0
- package/cjs/hardware-transaction-authority.js.map +1 -0
- package/cjs/index.js +5 -5
- package/cjs/index.js.map +1 -1
- package/cjs/keyring-core.js +171 -220
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-host.js +113 -0
- package/cjs/keyring-host.js.map +1 -0
- package/cjs/keyring-manager.js +15 -151
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/ledger/host-types.js.map +1 -1
- package/cjs/ledger/index.js +44 -19
- package/cjs/ledger/index.js.map +1 -1
- package/cjs/providers.js +3 -1
- package/cjs/providers.js.map +1 -1
- package/cjs/software.js +10 -56
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/ethereum.js +306 -229
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-local-signer.js +15 -0
- package/cjs/transactions/evm-local-signer.js.map +1 -1
- package/cjs/transactions/evm-submission.js +61 -65
- package/cjs/transactions/evm-submission.js.map +1 -1
- package/cjs/transactions/hardware-psbt.js +11 -8
- package/cjs/transactions/hardware-psbt.js.map +1 -1
- package/cjs/transactions/index.js +10 -2
- package/cjs/transactions/index.js.map +1 -1
- package/cjs/transactions/prepared-evm.js +10 -7
- package/cjs/transactions/prepared-evm.js.map +1 -1
- package/cjs/transactions/syscoin-core.js +308 -205
- package/cjs/transactions/syscoin-core.js.map +1 -1
- package/cjs/trezor/connect.js +19 -2
- package/cjs/trezor/connect.js.map +1 -1
- package/cjs/trezor/index.js +38 -16
- package/cjs/trezor/index.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/cjs/utils/excluded-utxos.js +6 -2
- package/cjs/utils/excluded-utxos.js.map +1 -1
- package/cjs/utils/owned-blockbook-account.js +45 -0
- package/cjs/utils/owned-blockbook-account.js.map +1 -0
- package/esm/current-vault-refusal.js +3 -0
- package/esm/current-vault-refusal.js.map +1 -0
- package/esm/evm-fees.js +242 -0
- package/esm/evm-fees.js.map +1 -0
- package/esm/hardware-transaction-authority.js +33 -0
- package/esm/hardware-transaction-authority.js.map +1 -0
- package/esm/initial-state.js +102 -0
- package/esm/initial-state.js.map +1 -0
- package/esm/keyring-core.js +2781 -0
- package/esm/keyring-core.js.map +1 -0
- package/esm/keyring-host.js +108 -0
- package/esm/keyring-host.js.map +1 -0
- package/esm/ledger/dmk-session.js +1 -0
- package/esm/ledger/dmk-session.js.map +1 -1
- package/esm/ledger/dmk-utxo.js +9 -3
- package/esm/ledger/dmk-utxo.js.map +1 -1
- package/esm/ledger/host-types.js.map +1 -1
- package/esm/ledger/index.js +44 -19
- package/esm/ledger/index.js.map +1 -1
- package/esm/network-utils.js +70 -0
- package/esm/network-utils.js.map +1 -0
- package/esm/providers.js +3 -1
- package/esm/providers.js.map +1 -1
- package/esm/software-hd-account.js +20 -0
- package/esm/software-hd-account.js.map +1 -0
- package/esm/storage-core.js +258 -0
- package/esm/storage-core.js.map +1 -0
- package/esm/transactions/ethereum.js +2753 -0
- package/esm/transactions/ethereum.js.map +1 -0
- package/esm/transactions/evm-cancellation.js +40 -0
- package/esm/transactions/evm-cancellation.js.map +1 -0
- package/esm/transactions/evm-local-signer.js +202 -0
- package/esm/transactions/evm-local-signer.js.map +1 -0
- package/esm/transactions/evm-submission.js +187 -0
- package/esm/transactions/evm-submission.js.map +1 -0
- package/esm/transactions/prepared-evm.js +194 -0
- package/esm/transactions/prepared-evm.js.map +1 -0
- package/esm/transactions/syscoin-core.js +714 -0
- package/esm/transactions/syscoin-core.js.map +1 -0
- package/esm/trezor/connect.js +19 -2
- package/esm/trezor/connect.js.map +1 -1
- package/esm/trezor/index.js +38 -16
- package/esm/trezor/index.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/excluded-utxos.js +57 -0
- package/esm/utils/excluded-utxos.js.map +1 -0
- package/esm/utils/owned-blockbook-account.js +9 -0
- package/esm/utils/owned-blockbook-account.js.map +1 -0
- package/esm/utils/psbt.js +32 -0
- package/esm/utils/psbt.js.map +1 -0
- package/esm/utils/watch-descriptor.js +138 -0
- package/esm/utils/watch-descriptor.js.map +1 -0
- package/package.json +5 -11
- package/types/hardware-transaction-authority.d.ts +5 -0
- package/types/index.d.ts +3 -3
- package/types/keyring-core.d.ts +22 -51
- package/types/keyring-host.d.ts +46 -0
- package/types/keyring-manager.d.ts +13 -18
- package/types/ledger/host-types.d.ts +4 -1
- package/types/ledger/index.d.ts +1 -1
- package/types/ledger/types.d.ts +3 -2
- package/types/providers.d.ts +1 -1
- package/types/software.d.ts +6 -19
- package/types/transactions/ethereum.d.ts +14 -11
- package/types/transactions/evm-local-signer.d.ts +2 -2
- package/types/transactions/evm-submission.d.ts +11 -4
- package/types/transactions/hardware-psbt.d.ts +1 -1
- package/types/transactions/index.d.ts +3 -2
- package/types/transactions/prepared-evm.d.ts +4 -1
- package/types/transactions/syscoin-core.d.ts +48 -21
- package/types/trezor/connect.d.ts +6 -3
- package/types/trezor/index.d.ts +4 -3
- package/types/types.d.ts +95 -63
- package/types/utils/excluded-utxos.d.ts +1 -1
- package/types/utils/owned-blockbook-account.d.ts +3 -0
- package/cjs/hardware-wallet-manager-singleton.js +0 -34
- package/cjs/hardware-wallet-manager-singleton.js.map +0 -1
- package/cjs/storage.js +0 -20
- package/cjs/storage.js.map +0 -1
- package/cjs/transactions/syscoin.js +0 -16
- package/cjs/transactions/syscoin.js.map +0 -1
- package/cjs/utils/blockbook-cache.js +0 -76
- package/cjs/utils/blockbook-cache.js.map +0 -1
- package/types/hardware-wallet-manager-singleton.d.ts +0 -19
- package/types/storage.d.ts +0 -5
- package/types/transactions/syscoin.d.ts +0 -9
- package/types/utils/blockbook-cache.d.ts +0 -6
|
@@ -34,22 +34,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.SyscoinTransactionsCore = exports.createHardwareSignerUnavailableError = void 0;
|
|
37
|
-
|
|
37
|
+
exports.readPublicSyscoinFee = readPublicSyscoinFee;
|
|
38
|
+
exports.captureOwnedSyscoinRead = captureOwnedSyscoinRead;
|
|
39
|
+
const sysweb3_network_1 = require("@sidhujag/sysweb3-network");
|
|
38
40
|
const syscoinjs = __importStar(require("syscoinjs-lib"));
|
|
39
41
|
// import { BIP_84, ONE_HUNDRED_MILLION, SYSCOIN_BASIC_FEE } from 'utils';
|
|
40
42
|
const ethers_v6_1 = require("../ethers-v6");
|
|
41
43
|
const types_1 = require("../types");
|
|
42
|
-
const blockbook_cache_1 = require("../utils/blockbook-cache");
|
|
43
44
|
const excluded_utxos_1 = require("../utils/excluded-utxos");
|
|
45
|
+
const owned_blockbook_account_1 = require("../utils/owned-blockbook-account");
|
|
44
46
|
const psbt_1 = require("../utils/psbt");
|
|
45
47
|
const psbt_account_scope_1 = require("../utils/psbt-account-scope");
|
|
46
48
|
const createHardwareSignerUnavailableError = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
47
49
|
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
48
50
|
});
|
|
49
51
|
exports.createHardwareSignerUnavailableError = createHardwareSignerUnavailableError;
|
|
50
|
-
// Recommended fee changes slowly; cache per explorer to avoid hammering
|
|
51
|
-
// Blockbook while the user edits amounts on the send screen
|
|
52
|
-
const FEE_CACHE_TTL_MS = 60000;
|
|
53
52
|
const sweepError = (code, message) => ({
|
|
54
53
|
error: true,
|
|
55
54
|
code,
|
|
@@ -69,8 +68,90 @@ const sweepAtomicValue = (value) => {
|
|
|
69
68
|
throw sweepError('INVALID_NATIVE_SWEEP_UTXOS', 'Invalid native sweep atomic value.');
|
|
70
69
|
return new syscoinjs.utils.BN(text);
|
|
71
70
|
};
|
|
71
|
+
function captureBlockbookRead(options, assertContext) {
|
|
72
|
+
if (!options)
|
|
73
|
+
throw new Error('Missing owned UTXO read');
|
|
74
|
+
const { fetch, signal, assertCurrent: assertHost } = options;
|
|
75
|
+
if (typeof fetch !== 'function' ||
|
|
76
|
+
typeof assertHost !== 'function' ||
|
|
77
|
+
typeof signal?.aborted !== 'boolean')
|
|
78
|
+
throw new Error('Invalid owned UTXO read');
|
|
79
|
+
const assertCurrent = () => {
|
|
80
|
+
if (signal.aborted)
|
|
81
|
+
throw new Error('Wallet UTXO preparation context changed');
|
|
82
|
+
const result = assertHost();
|
|
83
|
+
if (result !== undefined) {
|
|
84
|
+
if (result &&
|
|
85
|
+
(typeof result === 'object' || typeof result === 'function'))
|
|
86
|
+
void Promise.resolve(result).catch(() => undefined);
|
|
87
|
+
throw new Error('Owned UTXO read requires a synchronous guard');
|
|
88
|
+
}
|
|
89
|
+
assertContext();
|
|
90
|
+
if (signal.aborted)
|
|
91
|
+
throw new Error('Wallet UTXO preparation context changed');
|
|
92
|
+
};
|
|
93
|
+
assertCurrent();
|
|
94
|
+
const transport = Object.freeze({
|
|
95
|
+
signal,
|
|
96
|
+
async fetch(input, init) {
|
|
97
|
+
assertCurrent();
|
|
98
|
+
const response = await fetch(input, init);
|
|
99
|
+
assertCurrent();
|
|
100
|
+
return response;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
return { assertCurrent, transport };
|
|
104
|
+
}
|
|
105
|
+
async function readOwnedFee(explorerUrl, { assertCurrent, transport }) {
|
|
106
|
+
assertCurrent();
|
|
107
|
+
const fee = await syscoinjs.utils.fetchEstimateFee(explorerUrl, 1, undefined, transport);
|
|
108
|
+
assertCurrent();
|
|
109
|
+
return fee / 1024;
|
|
110
|
+
}
|
|
111
|
+
/** Public network fee estimate. It has no wallet, account or signing access. */
|
|
112
|
+
async function readPublicSyscoinFee(explorerUrl, owner) {
|
|
113
|
+
return readOwnedFee(explorerUrl, captureBlockbookRead(owner, () => undefined));
|
|
114
|
+
}
|
|
115
|
+
/** Bind both transaction preparation and address reads to the active wallet. */
|
|
116
|
+
function captureOwnedSyscoinRead(options, getState, captureSigningContext) {
|
|
117
|
+
if (!options)
|
|
118
|
+
throw new Error('Missing owned UTXO read');
|
|
119
|
+
const assertSession = captureSigningContext(true);
|
|
120
|
+
const { activeNetwork, activeAccountId, activeAccountType, accounts } = getState();
|
|
121
|
+
if (activeNetwork.kind !== sysweb3_network_1.INetworkType.Syscoin)
|
|
122
|
+
throw new Error('Switch to UTXO chain');
|
|
123
|
+
const selected = accounts[activeAccountType]?.[activeAccountId];
|
|
124
|
+
if (!selected)
|
|
125
|
+
throw new Error('Active account not found');
|
|
126
|
+
const account = Object.freeze({
|
|
127
|
+
address: selected.address,
|
|
128
|
+
xpub: selected.xpub,
|
|
129
|
+
});
|
|
130
|
+
const network = Object.freeze({ ...activeNetwork });
|
|
131
|
+
const read = captureBlockbookRead(options, () => {
|
|
132
|
+
assertSession();
|
|
133
|
+
const current = getState();
|
|
134
|
+
const selected = current.accounts[current.activeAccountType]?.[current.activeAccountId];
|
|
135
|
+
if (current.activeAccountId !== activeAccountId ||
|
|
136
|
+
current.activeAccountType !== activeAccountType ||
|
|
137
|
+
selected?.address !== account.address ||
|
|
138
|
+
selected?.xpub !== account.xpub ||
|
|
139
|
+
current.activeNetwork.kind !== network.kind ||
|
|
140
|
+
current.activeNetwork.chainId !== network.chainId ||
|
|
141
|
+
current.activeNetwork.slip44 !== network.slip44 ||
|
|
142
|
+
current.activeNetwork.currency !== network.currency ||
|
|
143
|
+
current.activeNetwork.url !== network.url)
|
|
144
|
+
throw new Error('Wallet UTXO preparation context changed');
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
account,
|
|
148
|
+
accountId: activeAccountId,
|
|
149
|
+
accountType: activeAccountType,
|
|
150
|
+
read: Object.freeze({ ...read, network, xpub: account.xpub }),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
72
153
|
class SyscoinTransactionsCore {
|
|
73
|
-
constructor(getSyscoinSigner, getReadOnlySigner, getState,
|
|
154
|
+
constructor(getSyscoinSigner, getReadOnlySigner, getState, hardwareSigners, captureSigningContext) {
|
|
74
155
|
this.getTransactionPSBT = async ({ txOptions, outputs, changeAddress, feeRateBN, xpub, fundingUtxos, }, main) => {
|
|
75
156
|
try {
|
|
76
157
|
// Use syscoinjs-lib directly for transaction creation
|
|
@@ -108,28 +189,27 @@ class SyscoinTransactionsCore {
|
|
|
108
189
|
return main.decodeRawTransaction(psbtObj);
|
|
109
190
|
}
|
|
110
191
|
};
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
const fee = (await syscoinjs.utils.fetchEstimateFee(explorerUrl, 1)) / 1024;
|
|
118
|
-
this.feeCache.set(explorerUrl, { fee, timestamp: Date.now() });
|
|
119
|
-
return fee;
|
|
120
|
-
};
|
|
121
|
-
this.txUtilsFunctions = () => {
|
|
122
|
-
const { getRawTransaction } = (0, sysweb3_utils_1.txUtils)();
|
|
123
|
-
return {
|
|
124
|
-
getRawTransaction,
|
|
125
|
-
};
|
|
192
|
+
this.getRecommendedFee = async (explorerUrl, ownedRead) => {
|
|
193
|
+
const { read } = this.captureOwnedRead(ownedRead);
|
|
194
|
+
if (explorerUrl !== read.network.url)
|
|
195
|
+
throw new Error('Owned UTXO read endpoint changed');
|
|
196
|
+
return readOwnedFee(explorerUrl, read);
|
|
126
197
|
};
|
|
127
198
|
// Internal method for signing with the HD signer
|
|
128
199
|
this.signPSBTWithSigner = async ({ psbt, signer, }) => await signer.sign(psbt);
|
|
129
|
-
this.createNativeSweepPSBT = async ({ txOptions, changeAddress, receivingAddress, feeRateBN, source, excluded, }, main) => {
|
|
130
|
-
const raw = await syscoinjs.utils.fetchBackendUTXOS(main.blockbookURL, source);
|
|
200
|
+
this.createNativeSweepPSBT = async ({ txOptions, changeAddress, receivingAddress, feeRateBN, source, excluded, }, main, read) => {
|
|
201
|
+
const raw = await syscoinjs.utils.fetchBackendUTXOS(main.blockbookURL, source, undefined, read.transport);
|
|
202
|
+
read.assertCurrent();
|
|
203
|
+
const excludedNativeValues = new Map();
|
|
131
204
|
const rows = excluded.size
|
|
132
|
-
? (0, excluded_utxos_1.filterExcludedUtxos)(raw, excluded)
|
|
205
|
+
? (0, excluded_utxos_1.filterExcludedUtxos)(raw, excluded, (row, outpoint) => {
|
|
206
|
+
const unambiguous = !excludedNativeValues.has(outpoint) &&
|
|
207
|
+
row.assetInfo == null &&
|
|
208
|
+
(row.txId === undefined ||
|
|
209
|
+
row.txid === undefined ||
|
|
210
|
+
row.txId === row.txid);
|
|
211
|
+
excludedNativeValues.set(outpoint, unambiguous ? row.value : undefined);
|
|
212
|
+
}).utxos
|
|
133
213
|
: Array.isArray(raw)
|
|
134
214
|
? raw
|
|
135
215
|
: raw?.utxos;
|
|
@@ -164,8 +244,23 @@ class SyscoinTransactionsCore {
|
|
|
164
244
|
gross = gross.add(value);
|
|
165
245
|
utxos.push({ ...row, value });
|
|
166
246
|
}
|
|
167
|
-
if (gross.isZero())
|
|
168
|
-
|
|
247
|
+
if (gross.isZero()) {
|
|
248
|
+
const error = sweepError('INSUFFICIENT_FUNDS', 'No native UTXOs are available to sweep.');
|
|
249
|
+
const excludedNativeOutpoints = [...excludedNativeValues].flatMap(([outpoint, value]) => {
|
|
250
|
+
if (typeof value === 'number')
|
|
251
|
+
return Number.isSafeInteger(value) && value > 0 ? [outpoint] : [];
|
|
252
|
+
if (typeof value === 'string' &&
|
|
253
|
+
value.length <= 20 &&
|
|
254
|
+
!/[^0-9]/.test(value) &&
|
|
255
|
+
/[1-9]/.test(value) &&
|
|
256
|
+
(value.length < 20 || value <= '18446744073709551615'))
|
|
257
|
+
return [outpoint];
|
|
258
|
+
return [];
|
|
259
|
+
});
|
|
260
|
+
if (excludedNativeOutpoints.length)
|
|
261
|
+
throw { ...error, excludedNativeOutpoints };
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
169
264
|
// The library sanitizes this snapshot once; its optional UTXO argument
|
|
170
265
|
// prevents a second funding lookup between resolving MAX and building it.
|
|
171
266
|
const result = await main.createTransaction(txOptions, changeAddress, [{ address: receivingAddress, value: gross, subtractFeeFrom: true }], feeRateBN, source, { utxos, assets: new Map() });
|
|
@@ -195,16 +290,10 @@ class SyscoinTransactionsCore {
|
|
|
195
290
|
};
|
|
196
291
|
};
|
|
197
292
|
// Create unsigned PSBT for any transaction type
|
|
198
|
-
this.createUnsignedPSBT = async ({ txOptions = {}, isMax = false, nativeSweep = false, amount, receivingAddress, feeRateBN, token = null,
|
|
293
|
+
this.createUnsignedPSBT = async ({ txOptions = {}, isMax = false, nativeSweep = false, amount, receivingAddress, feeRateBN, token = null, }, owned) => {
|
|
199
294
|
// Ensure RBF is enabled by default if not explicitly set
|
|
200
295
|
const finalTxOptions = { rbf: true, ...txOptions };
|
|
201
|
-
const {
|
|
202
|
-
// Use read-only signer since we're just creating an unsigned PSBT
|
|
203
|
-
const { main } = this.getReadOnlySigner();
|
|
204
|
-
const account = accounts[activeAccountType]?.[activeAccountId];
|
|
205
|
-
if (!account) {
|
|
206
|
-
throw new Error('Active account not found');
|
|
207
|
-
}
|
|
296
|
+
const { account, accountType: activeAccountType, excluded, main, read, } = owned;
|
|
208
297
|
const xpub = account.xpub;
|
|
209
298
|
const isSingleAddressImported = activeAccountType === types_1.KeyringAccountType.Imported &&
|
|
210
299
|
xpub === account.address;
|
|
@@ -231,9 +320,9 @@ class SyscoinTransactionsCore {
|
|
|
231
320
|
// If this is a single-address imported account (WIF import), use the account.address for both from and change
|
|
232
321
|
const changeAddress = isSingleAddressImported
|
|
233
322
|
? account.address
|
|
234
|
-
: await
|
|
323
|
+
: await owned.getOwnedAddress(xpub, true);
|
|
324
|
+
read.assertCurrent();
|
|
235
325
|
try {
|
|
236
|
-
const excluded = (0, excluded_utxos_1.excludedOutpointIds)(excludedOutpoints);
|
|
237
326
|
if (nativeSweep) {
|
|
238
327
|
return await this.createNativeSweepPSBT({
|
|
239
328
|
txOptions: finalTxOptions,
|
|
@@ -242,11 +331,12 @@ class SyscoinTransactionsCore {
|
|
|
242
331
|
feeRateBN,
|
|
243
332
|
source: sourceForUtxoLookup,
|
|
244
333
|
excluded,
|
|
245
|
-
}, main);
|
|
334
|
+
}, main, read);
|
|
246
335
|
}
|
|
247
336
|
const fundingUtxos = excluded.size
|
|
248
|
-
? (0, excluded_utxos_1.filterExcludedUtxos)(await syscoinjs.utils.fetchBackendUTXOS(main.blockbookURL, sourceForUtxoLookup), excluded)
|
|
337
|
+
? (0, excluded_utxos_1.filterExcludedUtxos)(await syscoinjs.utils.fetchBackendUTXOS(main.blockbookURL, sourceForUtxoLookup, undefined, read.transport), excluded)
|
|
249
338
|
: undefined;
|
|
339
|
+
read.assertCurrent();
|
|
250
340
|
if (token && token.guid) {
|
|
251
341
|
// Token transaction: use assetAllocationSend
|
|
252
342
|
// Create a Map for the asset allocation
|
|
@@ -306,43 +396,24 @@ class SyscoinTransactionsCore {
|
|
|
306
396
|
}
|
|
307
397
|
};
|
|
308
398
|
// Sign PSBT with appropriate method - separated for better error handling
|
|
309
|
-
this.signPSBTWithMethod = async (psbt,
|
|
310
|
-
const {
|
|
399
|
+
this.signPSBTWithMethod = async (psbt, ownedRead) => {
|
|
400
|
+
const { assertCurrent, network: activeNetwork, transport } = ownedRead;
|
|
401
|
+
assertCurrent();
|
|
402
|
+
const { activeAccountId, activeAccountType, accounts } = this.getState();
|
|
311
403
|
const account = accounts[activeAccountType]?.[activeAccountId];
|
|
312
404
|
if (!account)
|
|
313
405
|
throw new Error('Active account not found');
|
|
314
406
|
// Device choice belongs to the selected account, never to PSBT/caller flags.
|
|
315
|
-
isTrezor =
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
activeAccountType === types_1.KeyringAccountType.Ledger ||
|
|
320
|
-
account.isLedgerWallet === true;
|
|
321
|
-
const assertSession = this.captureSigningContext?.() || (() => undefined);
|
|
322
|
-
// The adapter gets a detached, immutable public context. It must not be
|
|
323
|
-
// able to mutate the wallet state or the baseline used by assertCurrent.
|
|
407
|
+
const isTrezor = activeAccountType === types_1.KeyringAccountType.Trezor ||
|
|
408
|
+
account.isTrezorWallet === true;
|
|
409
|
+
const isLedger = activeAccountType === types_1.KeyringAccountType.Ledger ||
|
|
410
|
+
account.isLedgerWallet === true;
|
|
324
411
|
const scope = Object.freeze({
|
|
325
412
|
account: Object.freeze({ address: account.address, xpub: account.xpub }),
|
|
326
413
|
accountId: activeAccountId,
|
|
327
414
|
accountType: activeAccountType,
|
|
328
|
-
network:
|
|
415
|
+
network: activeNetwork,
|
|
329
416
|
});
|
|
330
|
-
const assertCurrent = () => {
|
|
331
|
-
assertSession();
|
|
332
|
-
const current = this.getState();
|
|
333
|
-
const selected = current.accounts[current.activeAccountType]?.[current.activeAccountId];
|
|
334
|
-
if (current.activeAccountId !== activeAccountId ||
|
|
335
|
-
current.activeAccountType !== activeAccountType ||
|
|
336
|
-
selected?.address !== scope.account.address ||
|
|
337
|
-
selected?.xpub !== scope.account.xpub ||
|
|
338
|
-
current.activeNetwork.kind !== scope.network.kind ||
|
|
339
|
-
current.activeNetwork.chainId !== scope.network.chainId ||
|
|
340
|
-
current.activeNetwork.slip44 !== scope.network.slip44 ||
|
|
341
|
-
current.activeNetwork.url !== scope.network.url) {
|
|
342
|
-
throw new Error('Wallet signing context changed');
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
assertCurrent();
|
|
346
417
|
const sign = isLedger
|
|
347
418
|
? this.hardwareSigners.ledger
|
|
348
419
|
: isTrezor
|
|
@@ -353,17 +424,29 @@ class SyscoinTransactionsCore {
|
|
|
353
424
|
if (isTrezor || isLedger)
|
|
354
425
|
(0, psbt_account_scope_1.assertPsbtAccountScope)(psbt, scope);
|
|
355
426
|
if (isLedger || isTrezor) {
|
|
427
|
+
const assertHardwareCurrent = () => {
|
|
428
|
+
try {
|
|
429
|
+
assertCurrent();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// The vendor retry loop already treats AbortError as terminal.
|
|
433
|
+
throw Object.assign(new Error('Wallet signing context changed'), {
|
|
434
|
+
name: 'AbortError',
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
};
|
|
356
438
|
const signed = await sign({
|
|
357
439
|
psbt,
|
|
358
440
|
account: Object.freeze({ id: account.id, xpub: account.xpub }),
|
|
359
441
|
network: scope.network,
|
|
360
442
|
scope,
|
|
361
|
-
assertCurrent,
|
|
443
|
+
assertCurrent: assertHardwareCurrent,
|
|
444
|
+
signal: transport.signal,
|
|
362
445
|
...(isLedger && {
|
|
363
|
-
getRawTransaction:
|
|
446
|
+
getRawTransaction: (txid) => syscoinjs.utils.fetchBackendRawTx(activeNetwork.url, txid, transport),
|
|
364
447
|
}),
|
|
365
448
|
});
|
|
366
|
-
|
|
449
|
+
assertHardwareCurrent();
|
|
367
450
|
return signed;
|
|
368
451
|
}
|
|
369
452
|
else {
|
|
@@ -373,99 +456,94 @@ class SyscoinTransactionsCore {
|
|
|
373
456
|
//
|
|
374
457
|
// We can recover by enriching PSBT inputs with a proprietary `path` field using
|
|
375
458
|
// Blockbook's xpub token list (address -> path mapping), which reliably includes paths.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
for (const input of inputs) {
|
|
394
|
-
const unknowns = input?.unknownKeyVals || [];
|
|
395
|
-
const pathKv = unknowns.find((kv) => kv?.key?.toString?.() === 'path');
|
|
396
|
-
if (!pathKv?.value)
|
|
397
|
-
continue;
|
|
398
|
-
const fullPath = String(Buffer.from(pathKv.value).toString());
|
|
399
|
-
const parts = fullPath.split('/').filter(Boolean);
|
|
400
|
-
// Expected: m/84'/slip44'/account'/change/index
|
|
401
|
-
if (parts.length >= 2) {
|
|
402
|
-
const change = parts[parts.length - 2];
|
|
403
|
-
const index = parts[parts.length - 1];
|
|
404
|
-
const rel = `${change}/${index}`;
|
|
405
|
-
pathKv.value = Buffer.from(rel);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const needsAnyPath = psbt?.data?.inputs?.some((input) => {
|
|
459
|
+
const account = accounts[activeAccountType]?.[activeAccountId];
|
|
460
|
+
const accountXpub = account?.xpub;
|
|
461
|
+
const accountAddress = account?.address;
|
|
462
|
+
const isSingleAddressImported = activeAccountType === types_1.KeyringAccountType.Imported &&
|
|
463
|
+
Boolean(accountXpub) &&
|
|
464
|
+
Boolean(accountAddress) &&
|
|
465
|
+
accountXpub === accountAddress;
|
|
466
|
+
const isImportedExtended = activeAccountType === types_1.KeyringAccountType.Imported &&
|
|
467
|
+
Boolean(accountXpub) &&
|
|
468
|
+
!isSingleAddressImported;
|
|
469
|
+
// IMPORTANT: Imported extended keys (zprv/vprv) are typically *account-level* keys.
|
|
470
|
+
// syscoinjs-lib signs using the imported key as the BIP32 root, so PSBT `path` must be
|
|
471
|
+
// relative (change/index), not the full `m/84'/.../change/index`. If we keep full paths,
|
|
472
|
+
// bip32.derivePath can throw, leaving no derivations and causing "Need derivation path".
|
|
473
|
+
if (isImportedExtended) {
|
|
474
|
+
const inputs = psbt?.data?.inputs || [];
|
|
475
|
+
for (const input of inputs) {
|
|
410
476
|
const unknowns = input?.unknownKeyVals || [];
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const res = await (0, blockbook_cache_1.fetchBackendAccountCached)(blockbookUrl, accountXpub, 'tokens=used&details=tokens');
|
|
423
|
-
const tokens = Array.isArray(res?.tokens) ? res.tokens : [];
|
|
424
|
-
const addrToPath = new Map();
|
|
425
|
-
for (const t of tokens) {
|
|
426
|
-
if (t?.name && t?.path) {
|
|
427
|
-
addrToPath.set(String(t.name), String(t.path));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
const inputs = psbt?.data?.inputs || [];
|
|
431
|
-
for (let i = 0; i < inputs.length; i++) {
|
|
432
|
-
const input = inputs[i];
|
|
433
|
-
const unknowns = input?.unknownKeyVals || [];
|
|
434
|
-
const hasPath = unknowns.some((kv) => kv?.key?.toString?.() === 'path');
|
|
435
|
-
if (hasPath)
|
|
436
|
-
continue;
|
|
437
|
-
const addrKv = unknowns.find((kv) => kv?.key?.toString?.() === 'address');
|
|
438
|
-
const addr = addrKv?.value
|
|
439
|
-
? String(Buffer.from(addrKv.value).toString())
|
|
440
|
-
: null;
|
|
441
|
-
if (!addr)
|
|
442
|
-
continue;
|
|
443
|
-
const fullPath = addrToPath.get(addr);
|
|
444
|
-
if (!fullPath)
|
|
445
|
-
continue;
|
|
446
|
-
// Imported extended keys are account-level (BIP84 account node), so we must provide a
|
|
447
|
-
// path relative to that node (change/index), not the full m/84'/... path.
|
|
448
|
-
const pathForSigner = isImportedExtended
|
|
449
|
-
? (() => {
|
|
450
|
-
const parts = String(fullPath).split('/').filter(Boolean);
|
|
451
|
-
// Expected: m/84'/slip44'/account'/change/index
|
|
452
|
-
if (parts.length >= 2) {
|
|
453
|
-
const change = parts[parts.length - 2];
|
|
454
|
-
const index = parts[parts.length - 1];
|
|
455
|
-
return `${change}/${index}`;
|
|
456
|
-
}
|
|
457
|
-
return fullPath;
|
|
458
|
-
})()
|
|
459
|
-
: fullPath;
|
|
460
|
-
psbt.addUnknownKeyValToInput(i, {
|
|
461
|
-
key: Buffer.from('path'),
|
|
462
|
-
value: Buffer.from(pathForSigner),
|
|
463
|
-
});
|
|
477
|
+
const pathKv = unknowns.find((kv) => kv?.key?.toString?.() === 'path');
|
|
478
|
+
if (!pathKv?.value)
|
|
479
|
+
continue;
|
|
480
|
+
const fullPath = String(Buffer.from(pathKv.value).toString());
|
|
481
|
+
const parts = fullPath.split('/').filter(Boolean);
|
|
482
|
+
// Expected: m/84'/slip44'/account'/change/index
|
|
483
|
+
if (parts.length >= 2) {
|
|
484
|
+
const change = parts[parts.length - 2];
|
|
485
|
+
const index = parts[parts.length - 1];
|
|
486
|
+
const rel = `${change}/${index}`;
|
|
487
|
+
pathKv.value = Buffer.from(rel);
|
|
464
488
|
}
|
|
465
489
|
}
|
|
466
490
|
}
|
|
467
|
-
|
|
468
|
-
|
|
491
|
+
const needsAnyPath = psbt?.data?.inputs?.some((input) => {
|
|
492
|
+
const unknowns = input?.unknownKeyVals || [];
|
|
493
|
+
return (!input.bip32Derivation?.length &&
|
|
494
|
+
!input.tapBip32Derivation?.length &&
|
|
495
|
+
!unknowns.some((kv) => kv?.key?.toString?.() === 'path'));
|
|
496
|
+
});
|
|
497
|
+
if (needsAnyPath &&
|
|
498
|
+
accountXpub &&
|
|
499
|
+
!isSingleAddressImported &&
|
|
500
|
+
typeof psbt.addUnknownKeyValToInput === 'function') {
|
|
501
|
+
// Fetch address->path mapping from Blockbook xpub endpoint
|
|
502
|
+
const blockbookUrl = activeNetwork.url;
|
|
503
|
+
const res = await (0, owned_blockbook_account_1.fetchOwnedBlockbookAccount)(blockbookUrl, accountXpub, transport);
|
|
504
|
+
assertCurrent();
|
|
505
|
+
const tokens = Array.isArray(res?.tokens) ? res.tokens : [];
|
|
506
|
+
const addrToPath = new Map();
|
|
507
|
+
for (const t of tokens) {
|
|
508
|
+
if (t?.name && t?.path) {
|
|
509
|
+
addrToPath.set(String(t.name), String(t.path));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const inputs = psbt?.data?.inputs || [];
|
|
513
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
514
|
+
const input = inputs[i];
|
|
515
|
+
const unknowns = input?.unknownKeyVals || [];
|
|
516
|
+
const hasPath = unknowns.some((kv) => kv?.key?.toString?.() === 'path');
|
|
517
|
+
if (hasPath)
|
|
518
|
+
continue;
|
|
519
|
+
const addrKv = unknowns.find((kv) => kv?.key?.toString?.() === 'address');
|
|
520
|
+
const addr = addrKv?.value
|
|
521
|
+
? String(Buffer.from(addrKv.value).toString())
|
|
522
|
+
: null;
|
|
523
|
+
if (!addr)
|
|
524
|
+
continue;
|
|
525
|
+
const fullPath = addrToPath.get(addr);
|
|
526
|
+
if (!fullPath)
|
|
527
|
+
continue;
|
|
528
|
+
// Imported extended keys are account-level (BIP84 account node), so we must provide a
|
|
529
|
+
// path relative to that node (change/index), not the full m/84'/... path.
|
|
530
|
+
const pathForSigner = isImportedExtended
|
|
531
|
+
? (() => {
|
|
532
|
+
const parts = String(fullPath).split('/').filter(Boolean);
|
|
533
|
+
// Expected: m/84'/slip44'/account'/change/index
|
|
534
|
+
if (parts.length >= 2) {
|
|
535
|
+
const change = parts[parts.length - 2];
|
|
536
|
+
const index = parts[parts.length - 1];
|
|
537
|
+
return `${change}/${index}`;
|
|
538
|
+
}
|
|
539
|
+
return fullPath;
|
|
540
|
+
})()
|
|
541
|
+
: fullPath;
|
|
542
|
+
psbt.addUnknownKeyValToInput(i, {
|
|
543
|
+
key: Buffer.from('path'),
|
|
544
|
+
value: Buffer.from(pathForSigner),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
469
547
|
}
|
|
470
548
|
assertCurrent();
|
|
471
549
|
(0, psbt_account_scope_1.assertPsbtAccountScope)(psbt, scope);
|
|
@@ -479,23 +557,39 @@ class SyscoinTransactionsCore {
|
|
|
479
557
|
}
|
|
480
558
|
};
|
|
481
559
|
// Create unsigned PSBT and estimate fee - NO SIGNING
|
|
482
|
-
this.getEstimateSysTransactionFee = async ({ txOptions = {}, isMax = false, nativeSweep = false, amount, receivingAddress, feeRate, token = null, excludedOutpoints = [], }) => {
|
|
560
|
+
this.getEstimateSysTransactionFee = async ({ txOptions = {}, isMax = false, nativeSweep = false, amount, receivingAddress, feeRate, token = null, excludedOutpoints = [], }, ownedRead) => {
|
|
483
561
|
try {
|
|
484
562
|
if (nativeSweep !== false &&
|
|
485
563
|
(nativeSweep !== true || isMax !== true || token !== null))
|
|
486
564
|
throw sweepError('INVALID_NATIVE_SWEEP', 'Native sweep requires explicit MAX and a native transaction.');
|
|
487
565
|
// Ensure RBF is enabled by default if not explicitly set
|
|
488
566
|
const finalTxOptions = { rbf: true, ...txOptions };
|
|
489
|
-
|
|
490
|
-
const { main } = this.getReadOnlySigner();
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
567
|
+
const preparation = this.captureOwnedRead(ownedRead);
|
|
568
|
+
const { main, getOwnedAddress } = this.getReadOnlySigner(preparation.read);
|
|
569
|
+
if (typeof getOwnedAddress !== 'function' ||
|
|
570
|
+
main.blockbookURL !== preparation.read.network.url)
|
|
571
|
+
throw new Error('Owned UTXO read adapter is unavailable');
|
|
572
|
+
const owned = {
|
|
573
|
+
...preparation,
|
|
574
|
+
main,
|
|
575
|
+
getOwnedAddress,
|
|
576
|
+
excluded: (0, excluded_utxos_1.excludedOutpointIds)(excludedOutpoints),
|
|
577
|
+
};
|
|
578
|
+
const selectedToken = token ? { ...token } : token;
|
|
579
|
+
const { assetWhiteList } = finalTxOptions;
|
|
580
|
+
if (assetWhiteList instanceof Map)
|
|
581
|
+
finalTxOptions.assetWhiteList = new Map(assetWhiteList);
|
|
582
|
+
else if (assetWhiteList instanceof Set)
|
|
583
|
+
finalTxOptions.assetWhiteList = new Set(assetWhiteList);
|
|
584
|
+
for (const key of ['memo', 'memoHeader', 'blobHash', 'blobData']) {
|
|
585
|
+
const bytes = finalTxOptions[key];
|
|
586
|
+
if (bytes instanceof Uint8Array)
|
|
587
|
+
finalTxOptions[key] = Buffer.from(bytes);
|
|
498
588
|
}
|
|
589
|
+
const actualFeeRate = feeRate === undefined
|
|
590
|
+
? await readOwnedFee(main.blockbookURL, owned.read)
|
|
591
|
+
: feeRate;
|
|
592
|
+
owned.read.assertCurrent();
|
|
499
593
|
// Convert fee rate to satoshis/byte and ensure minimum relay of 1 sat/vB
|
|
500
594
|
// Blockbook returns coins per kB; after division by 1024, some testnets
|
|
501
595
|
// can yield < 1 sat/vB (e.g., 0.9765625). Round up and clamp to 1 to
|
|
@@ -510,10 +604,10 @@ class SyscoinTransactionsCore {
|
|
|
510
604
|
amount,
|
|
511
605
|
receivingAddress,
|
|
512
606
|
feeRateBN,
|
|
513
|
-
token,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
607
|
+
token: selectedToken,
|
|
608
|
+
}, owned);
|
|
609
|
+
owned.read.assertCurrent();
|
|
610
|
+
const quote = {
|
|
517
611
|
fee: result.fee / 1e8,
|
|
518
612
|
psbt: psbt_1.PsbtUtils.toPali(result.psbt), // Return UNSIGNED PSBT as JSON
|
|
519
613
|
...(nativeSweep
|
|
@@ -523,6 +617,8 @@ class SyscoinTransactionsCore {
|
|
|
523
617
|
}
|
|
524
618
|
: {}),
|
|
525
619
|
};
|
|
620
|
+
owned.read.assertCurrent();
|
|
621
|
+
return quote;
|
|
526
622
|
}
|
|
527
623
|
catch (error) {
|
|
528
624
|
// Pass through structured errors from syscoinjs-lib
|
|
@@ -568,32 +664,10 @@ class SyscoinTransactionsCore {
|
|
|
568
664
|
}
|
|
569
665
|
};
|
|
570
666
|
// Removed createAndSignSysTransaction - functionality merged into getEstimateSysTransactionFee
|
|
571
|
-
this.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
// Send the transaction
|
|
576
|
-
const result = await main.send(psbt);
|
|
577
|
-
// Extract the transaction ID
|
|
578
|
-
const txid = result.extractTransaction().getId();
|
|
579
|
-
return { txid };
|
|
580
|
-
}
|
|
581
|
-
catch (error) {
|
|
582
|
-
// Pass through structured errors
|
|
583
|
-
if (error.error && error.code) {
|
|
584
|
-
throw error;
|
|
585
|
-
}
|
|
586
|
-
// Wrap other errors
|
|
587
|
-
throw {
|
|
588
|
-
error: true,
|
|
589
|
-
code: 'TRANSACTION_SEND_FAILED',
|
|
590
|
-
message: error.message || 'Failed to send transaction',
|
|
591
|
-
details: error,
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
this.signPSBT = async ({ psbt, isTrezor = false, isLedger = false, }) => {
|
|
596
|
-
const psbtObj = psbt_1.PsbtUtils.fromPali(psbt, this.getState().activeNetwork);
|
|
667
|
+
this.signPSBT = async ({ psbt }, ownedRead) => {
|
|
668
|
+
const { read } = this.captureOwnedRead(ownedRead);
|
|
669
|
+
const psbtObj = psbt_1.PsbtUtils.fromPali(psbt, read.network);
|
|
670
|
+
const unsigned = Buffer.from(psbtObj.data.globalMap.unsignedTx.toBuffer());
|
|
597
671
|
// Scope validation temporarily removes other signers' hints. Preserve the
|
|
598
672
|
// public metadata for the next cosigner, without exposing it to our signer.
|
|
599
673
|
const cosignerMetadata = psbtObj.data.inputs.map((input, index) => ({
|
|
@@ -610,7 +684,10 @@ class SyscoinTransactionsCore {
|
|
|
610
684
|
value: Buffer.from(value.value),
|
|
611
685
|
}))[0],
|
|
612
686
|
}));
|
|
613
|
-
const signedPsbt = await this.signPSBTWithMethod(psbtObj,
|
|
687
|
+
const signedPsbt = await this.signPSBTWithMethod(psbtObj, read);
|
|
688
|
+
read.assertCurrent();
|
|
689
|
+
if (!unsigned.equals(Buffer.from(signedPsbt.data.globalMap.unsignedTx.toBuffer())))
|
|
690
|
+
throw new Error('Signed PSBT differs from the prepared transaction');
|
|
614
691
|
signedPsbt.data.inputs.forEach((input, index) => {
|
|
615
692
|
if (input.finalScriptSig || input.finalScriptWitness)
|
|
616
693
|
return;
|
|
@@ -631,21 +708,47 @@ class SyscoinTransactionsCore {
|
|
|
631
708
|
input.unknownKeyVals.push(metadata.path);
|
|
632
709
|
}
|
|
633
710
|
});
|
|
634
|
-
|
|
711
|
+
const result = psbt_1.PsbtUtils.toPali(signedPsbt);
|
|
712
|
+
read.assertCurrent();
|
|
713
|
+
return result;
|
|
635
714
|
};
|
|
636
|
-
this.sendTransaction = async (
|
|
637
|
-
|
|
638
|
-
|
|
715
|
+
this.sendTransaction = async (rawTransaction, authority) => {
|
|
716
|
+
let read;
|
|
717
|
+
let fetch;
|
|
718
|
+
let signal;
|
|
719
|
+
let prepare;
|
|
720
|
+
try {
|
|
721
|
+
if (!authority)
|
|
722
|
+
throw new Error('Missing owned UTXO submission context');
|
|
723
|
+
const { assertCurrent } = authority;
|
|
724
|
+
({ fetch, signal, prepare } = authority);
|
|
725
|
+
if (typeof prepare !== 'function')
|
|
726
|
+
throw new Error('Missing UTXO submission admission');
|
|
727
|
+
({ read } = this.captureOwnedRead({ assertCurrent, fetch, signal }));
|
|
639
728
|
}
|
|
640
|
-
|
|
729
|
+
catch {
|
|
730
|
+
throw Object.assign(new Error('UTXO submission was not dispatched'), {
|
|
731
|
+
code: 'UTXO_SUBMISSION_NOT_DISPATCHED',
|
|
732
|
+
transactionNotBroadcast: true,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
// SyscoinJS owns the admission, final synchronous guard and exact ACK.
|
|
736
|
+
// The POST uses the captured transport directly so a matching ACK can
|
|
737
|
+
// still be observed after a host context change without a signal abort.
|
|
738
|
+
const { result: txid } = await syscoinjs.utils.sendRawTransaction(read.network.url, rawTransaction, undefined, { assertCurrent: read.assertCurrent, fetch, prepare, signal });
|
|
739
|
+
return { txid };
|
|
641
740
|
};
|
|
741
|
+
if (typeof captureSigningContext !== 'function')
|
|
742
|
+
throw new Error('Missing UTXO signing context');
|
|
642
743
|
this.getSigner = getSyscoinSigner;
|
|
643
744
|
this.getReadOnlySigner = getReadOnlySigner;
|
|
644
745
|
this.getState = getState;
|
|
645
|
-
this.getAddress = getAddress;
|
|
646
746
|
this.hardwareSigners = hardwareSigners;
|
|
647
747
|
this.captureSigningContext = captureSigningContext;
|
|
648
748
|
}
|
|
749
|
+
captureOwnedRead(options) {
|
|
750
|
+
return captureOwnedSyscoinRead(options, this.getState, this.captureSigningContext);
|
|
751
|
+
}
|
|
649
752
|
}
|
|
650
753
|
exports.SyscoinTransactionsCore = SyscoinTransactionsCore;
|
|
651
754
|
//# sourceMappingURL=syscoin-core.js.map
|