@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
238 lines • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateReferencedTransactions = exports.transformReferencedTransactions = exports.transformReferencedTransaction = exports.transformOrigTransactions = exports.parseTransactionHexes = exports.getOrigTransactions = exports.getReferencedTransactions = exports.requireReferencedTransactions = void 0;
const schema_utils_1 = require("@trezor/schema-utils");
const utils_1 = require("@trezor/utils");
const utxo_lib_1 = require("@trezor/utxo-lib");
const constants_1 = require("../../constants");
const errors_1 = require("../../constants/errors");
const pathUtils_1 = require("../../utils/pathUtils");
const requireReferencedTransactions = (inputs, options = {}, coinInfo) => {
if (coinInfo?.shortcut === 'ZEC' || coinInfo?.shortcut === 'TAZ') {
return !(options.version && options.version >= 5);
}
const inputTypes = ['SPENDTAPROOT', 'EXTERNAL'];
return !!inputs.find(input => !inputTypes.find(t => t === input.script_type));
};
exports.requireReferencedTransactions = requireReferencedTransactions;
const getReferencedTransactions = (inputs) => {
const result = [];
inputs.forEach(input => {
if (input.prev_hash && !result.includes(input.prev_hash)) {
result.push(input.prev_hash);
}
});
return result;
};
exports.getReferencedTransactions = getReferencedTransactions;
const getOrigTransactions = (inputs, outputs) => {
const result = [];
inputs.forEach(input => {
if (input.orig_hash && !result.includes(input.orig_hash)) {
result.push(input.orig_hash);
}
});
outputs.forEach(output => {
if (output.orig_hash && !result.includes(output.orig_hash)) {
result.push(output.orig_hash);
}
});
return result;
};
exports.getOrigTransactions = getOrigTransactions;
const parseTransactionHexes = (network) => (hexes) => hexes.map(hex => utxo_lib_1.Transaction.fromHex(hex, { network }));
exports.parseTransactionHexes = parseTransactionHexes;
const enhanceTransaction = (refTx, srcTx) => {
const extraData = srcTx.getExtraData();
if (extraData) {
refTx.extra_data = extraData.toString('hex');
}
const specific = srcTx.getSpecificData();
if (specific) {
if (specific.type === 'zcash' && specific.versionGroupId && refTx.version >= 3) {
refTx.version_group_id = specific.versionGroupId;
}
if (specific.type === 'dash' && srcTx.type && srcTx.version >= 3) {
refTx.version |= srcTx.type << 16;
}
}
return refTx;
};
const parseOutputScript = (output, network) => {
try {
const address = utxo_lib_1.address.fromOutputScript(output, network);
return { type: 'address', address };
}
catch {
try {
const { data } = utxo_lib_1.payments.embed({ output }, { validate: true });
return { type: 'data', data };
}
catch {
return { type: 'unknown' };
}
}
};
const transformOrigTransaction = (tx, coinInfo, currentInputs, addresses) => {
const inputsMap = (input, i) => {
const prev_hash = utils_1.bufferUtils.reverseBuffer(input.hash).toString('hex');
const currentInput = currentInputs.find(inp => inp.prev_hash === prev_hash && inp.prev_index === input.index);
if (!currentInput?.address_n) {
throw (0, errors_1.TypedError)('Method_InvalidParameter', `transformOrigTransactions: invalid input at ${tx.getId()} [${i}]`);
}
return {
address_n: currentInput.address_n,
prev_hash,
prev_index: input.index,
script_sig: input.script.toString('hex'),
sequence: input.sequence,
script_type: (0, pathUtils_1.getScriptType)(currentInput.address_n),
multisig: undefined,
amount: currentInput.amount,
decred_tree: undefined,
witness: tx.getWitness(i)?.toString('hex'),
ownership_proof: undefined,
commitment_data: undefined,
};
};
const outputsMap = (output, i) => {
const parsed = parseOutputScript(output.script, coinInfo.network);
switch (parsed.type) {
case 'data': {
const op_return_data = parsed.data?.shift()?.toString('hex');
if (typeof op_return_data !== 'string') {
throw (0, errors_1.TypedError)('Method_InvalidParameter', `transformOrigTransactions: invalid op_return_data at ${tx.getId()} [${i}]`);
}
return {
script_type: 'PAYTOOPRETURN',
amount: '0',
op_return_data,
};
}
case 'address': {
const { address } = parsed;
const changeAddress = addresses.change.find(addr => addr.address === address);
const address_n = changeAddress && (0, pathUtils_1.getHDPath)(changeAddress.path);
const amount = output.value.toString();
return address_n
? {
address_n,
amount,
script_type: (0, pathUtils_1.getOutputScriptType)(address_n),
}
: {
address,
amount,
script_type: 'PAYTOADDRESS',
};
}
case 'unknown':
default:
throw (0, errors_1.TypedError)('Method_InvalidParameter', `transformOrigTransactions: invalid output at ${tx.getId()} [${i}]`);
}
};
const refTx = {
version: tx.version,
hash: tx.getId(),
inputs: tx.ins.map(inputsMap),
outputs: tx.outs.map(outputsMap),
lock_time: tx.locktime,
timestamp: tx.timestamp,
expiry: tx.expiry,
};
return enhanceTransaction(refTx, tx);
};
const transformOrigTransactions = (txs, coinInfo, currentInputs, addresses) => txs.map(tx => transformOrigTransaction(tx, coinInfo, currentInputs, addresses));
exports.transformOrigTransactions = transformOrigTransactions;
const transformReferencedTransaction = (tx) => {
const inputsMap = (input) => ({
prev_index: input.index,
sequence: input.sequence,
prev_hash: utils_1.bufferUtils.reverseBuffer(input.hash).toString('hex'),
script_sig: input.script.toString('hex'),
});
const binOutputsMap = (output) => ({
amount: output.value.toString(),
script_pubkey: output.script.toString('hex'),
});
const refTx = {
version: tx.version,
hash: tx.getId(),
inputs: tx.ins.map(inputsMap),
bin_outputs: tx.outs.map(binOutputsMap),
lock_time: tx.locktime,
timestamp: tx.timestamp,
expiry: tx.expiry,
};
return enhanceTransaction(refTx, tx);
};
exports.transformReferencedTransaction = transformReferencedTransaction;
const transformReferencedTransactions = (txs) => txs.map(exports.transformReferencedTransaction);
exports.transformReferencedTransactions = transformReferencedTransactions;
const validateReferencedTransactions = ({ transactions, inputs, outputs, addresses, coinInfo, }) => {
if (!Array.isArray(transactions) || transactions.length === 0)
return;
const refTxs = (0, exports.requireReferencedTransactions)(inputs) ? (0, exports.getReferencedTransactions)(inputs) : [];
const origTxs = (0, exports.getOrigTransactions)(inputs, outputs);
const transformedTxs = transactions.map(tx => {
if ('details' in tx) {
if (!tx.hex)
throw (0, errors_1.TypedError)('Method_InvalidParameter', `refTx: hex for ${tx.txid} not provided`);
const srcTx = utxo_lib_1.Transaction.fromHex(tx.hex, { network: coinInfo.network });
if (origTxs.includes(tx.txid)) {
if (!addresses)
throw (0, errors_1.TypedError)('Method_InvalidParameter', `refTx: addresses for ${tx.txid} not provided`);
return transformOrigTransaction(srcTx, coinInfo, inputs, addresses);
}
return (0, exports.transformReferencedTransaction)(srcTx);
}
(0, schema_utils_1.Assert)(schema_utils_1.Type.Object({
hash: schema_utils_1.Type.String(),
version: schema_utils_1.Type.Number(),
lock_time: schema_utils_1.Type.Number(),
extra_data: schema_utils_1.Type.Optional(schema_utils_1.Type.String()),
timestamp: schema_utils_1.Type.Optional(schema_utils_1.Type.Number()),
version_group_id: schema_utils_1.Type.Optional(schema_utils_1.Type.Number()),
}), tx);
if (origTxs.includes(tx.hash)) {
(0, schema_utils_1.Assert)(schema_utils_1.Type.Object({
inputs: schema_utils_1.Type.Array(constants_1.PROTO.TxInput, { minItems: 1 }),
outputs: schema_utils_1.Type.Array(constants_1.PROTO.TxOutputType, { minItems: 1 }),
}), tx);
return tx;
}
(0, schema_utils_1.Assert)(schema_utils_1.Type.Object({
inputs: schema_utils_1.Type.Array(constants_1.PROTO.PrevInput, { minItems: 1 }),
bin_outputs: schema_utils_1.Type.Array(constants_1.PROTO.TxOutputBinType, { minItems: 1 }),
}), tx);
return {
hash: tx.hash,
version: tx.version,
extra_data: tx.extra_data,
lock_time: tx.lock_time,
timestamp: tx.timestamp,
version_group_id: tx.version_group_id,
expiry: tx.expiry,
inputs: tx.inputs.map(input => ({
prev_hash: input.prev_hash,
prev_index: input.prev_index,
script_sig: input.script_sig,
sequence: input.sequence,
decred_tree: input.decred_tree,
})),
bin_outputs: tx.bin_outputs.map(output => ({
amount: output.amount,
script_pubkey: output.script_pubkey,
decred_script_version: output.decred_script_version,
})),
};
});
refTxs.concat(origTxs).forEach(hash => {
if (!transformedTxs.find(tx => tx.hash === hash)) {
throw (0, errors_1.TypedError)('Method_InvalidParameter', `refTx: ${hash} not provided`);
}
});
return transformedTxs;
};
exports.validateReferencedTransactions = validateReferencedTransactions;
//# sourceMappingURL=refTx.js.map