PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-xtz/src/lib
Просмотр файла: transaction.ts
import {
BaseKey,
BaseTransaction,
InvalidTransactionError,
ParseTransactionError,
TransactionType,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { CODEC, localForger } from '@taquito/local-forging';
import BigNumber from 'bignumber.js';
import { IndexedSignature, OriginationOp, ParsedTransaction, RevealOp, TransactionOp } from './iface';
import { KeyPair } from './keyPair';
import {
getMultisigTransferDataFromOperation,
getMultisigTransferSignatures,
getOriginationDataFromOperation,
getOwnersPublicKeys,
updateMultisigTransferSignatures,
} from './multisigUtils';
import * as Utils from './utils';
/**
* Tezos transaction model.
*/
export class Transaction extends BaseTransaction {
private _parsedTransaction?: ParsedTransaction; // transaction in JSON format
private _encodedTransaction?: string; // transaction in hex format
private _source: string;
private _delegate?: string;
private _forwarderDestination?: string;
private _publicKeyToReveal?: string;
private _owners: string[];
/**
* Public constructor.
*
* @param {Readonly<CoinConfig>} coinConfig
*/
constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
this._owners = [];
}
/**
* Initialize the transaction fields based on another serialized transaction.
*
* @param serializedTransaction Transaction in broadcast format.
*/
async initFromSerializedTransaction(serializedTransaction: string): Promise<void> {
this._encodedTransaction = serializedTransaction;
try {
const parsedTransaction = await localForger.parse(serializedTransaction);
await this.initFromParsedTransaction(parsedTransaction);
} catch (e) {
// If it throws, it is possible the serialized transaction is signed, which is not supported
// by local-forging. Try extracting the last 64 bytes and parse it again.
const unsignedSerializedTransaction = serializedTransaction.slice(0, -128);
const signature = serializedTransaction.slice(-128);
if (Utils.isValidSignature(signature)) {
throw new ParseTransactionError('Invalid transaction');
}
// TODO: encode the signature and save it in _signature
const parsedTransaction = await localForger.parse(unsignedSerializedTransaction);
const transactionId = await Utils.calculateTransactionId(serializedTransaction);
await this.initFromParsedTransaction(parsedTransaction, transactionId);
}
}
/**
* Initialize the transaction fields based on another parsed transaction.
*
* @param {ParsedTransaction} parsedTransaction A Tezos transaction object
* @param {string} transactionId The transaction id of the parsedTransaction if it is signed
*/
async initFromParsedTransaction(parsedTransaction: ParsedTransaction, transactionId?: string): Promise<void> {
if (!this._encodedTransaction) {
this._encodedTransaction = await localForger.forge(parsedTransaction);
}
if (transactionId) {
// If the transaction id is passed, save it and clean up the entries since they will be
// recalculated
this._id = transactionId;
this._inputs = [];
this._outputs = [];
} else {
this._id = '';
}
this._parsedTransaction = parsedTransaction;
let operationIndex = 0;
for (const operation of parsedTransaction.contents) {
if (this._source && this._source !== operation.source) {
throw new InvalidTransactionError(
'Source must be the same for every operation but it changed from ' + this._source + ' to ' + operation.source
);
} else {
this._source = operation.source;
}
switch (operation.kind) {
case CODEC.OP_ORIGINATION:
await this.recordOriginationOpFields(operation as OriginationOp, operationIndex);
operationIndex++;
break;
case CODEC.OP_REVEAL:
this.recordRevealOpFields(operation as RevealOp);
break;
case CODEC.OP_TRANSACTION:
this.recordTransactionOpFields(operation as TransactionOp);
break;
default:
break;
}
}
}
/**
* Record the most important fields from an origination operation.
*
* @param {Operation} operation An operation object from a Tezos transaction
* @param {number} index The origination operation index in the transaction. Used to calculate the
* originated address
*/
private async recordOriginationOpFields(operation: OriginationOp, index: number): Promise<void> {
const originationData = getOriginationDataFromOperation(operation);
if (originationData.forwarderDestination) {
this._type = TransactionType.AddressInitialization;
this._forwarderDestination = originationData.forwarderDestination;
} else {
this._type = TransactionType.WalletInitialization;
this._owners = getOwnersPublicKeys(operation);
}
this._delegate = operation.delegate;
this._outputs.push({
// Kt addresses can only be calculated for signed transactions with an id
address: this._id ? await Utils.calculateOriginatedAddress(this._id, index) : '',
// Balance
value: operation.balance,
});
this._inputs.push({
address: operation.source,
// Balance + fees + max gas + max storage are paid by the source account
value: new BigNumber(operation.balance).plus(operation.fee).toString(),
});
}
/**
* Record the most important fields from a reveal operation.
*
* @param {RevealOp} operation A reveal operation object from a Tezos transaction
*/
private recordRevealOpFields(operation: RevealOp): void {
this._type = TransactionType.AccountUpdate;
this._publicKeyToReveal = operation.public_key;
this._inputs.push({
address: operation.source,
// Balance + fees + max gas + max storage are paid by the source account
value: operation.fee,
});
}
/**
* Record the most important fields for a Transaction operation.
*
* @param {TransactionOp} operation A transaction object from a Tezos operation
*/
private recordTransactionOpFields(operation: TransactionOp): void {
if (operation.parameters) {
this._type = TransactionType.Send;
} else {
this._type = TransactionType.SingleSigSend;
}
const transferData = getMultisigTransferDataFromOperation(operation);
// Fees are paid by the source account, along with the amount in the transaction
this._inputs.push({
address: operation.source,
value: new BigNumber(transferData.fee.fee).toFixed(0),
});
if (transferData.coin === 'mutez') {
this._outputs.push({
// Kt addresses can only be calculated for signed transactions with an id
address: transferData.to,
// Balance
value: transferData.amount,
});
// The funds being transferred from the wallet
this._inputs.push({
address: transferData.from,
// Balance + fees + max gas + max storage are paid by the source account
value: transferData.amount,
});
}
}
/**
* Sign the transaction with the provided key. It does not check if the signer is allowed to sign
* it or not.
*
* @param {KeyPair} keyPair The key to sign the transaction with
*/
async sign(keyPair: KeyPair): Promise<void> {
// TODO: fail if the transaction is already signed
// Check if there is a transaction to sign
if (!this._parsedTransaction) {
throw new InvalidTransactionError('Empty transaction');
}
// Get the transaction body to sign
const encodedTransaction = await localForger.forge(this._parsedTransaction);
const signedTransaction = await Utils.sign(keyPair, encodedTransaction);
this._encodedTransaction = signedTransaction.sbytes;
// The transaction id can only be calculated for signed transactions
this._id = await Utils.calculateTransactionId(this._encodedTransaction);
await this.initFromParsedTransaction(this._parsedTransaction, this._id);
this._signatures.push(signedTransaction.sig);
}
/**
* Update the list of signatures for a multisig transaction operation.
*
* @param {IndexedSignature[]} signatures List of signatures and the index they should be put on
* in the multisig transfer
* @param {number} index The transfer index to add the signatures to
*/
async addTransferSignature(signatures: IndexedSignature[], index: number): Promise<void> {
if (!this._parsedTransaction) {
throw new InvalidTransactionError('Empty transaction');
}
updateMultisigTransferSignatures(this._parsedTransaction.contents[index] as TransactionOp, signatures);
this._encodedTransaction = await localForger.forge(this._parsedTransaction);
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
// TODO: check the key belongs to the _source account in _parsedTransaction
return true;
}
/** @inheritdoc */
toJson(): ParsedTransaction {
if (!this._parsedTransaction) {
throw new InvalidTransactionError('Empty transaction');
}
return this._parsedTransaction;
}
/** @inheritdoc */
toBroadcastFormat(): string {
if (!this._encodedTransaction) {
throw new InvalidTransactionError('Missing encoded transaction');
}
return this._encodedTransaction;
}
/**
* Get the transaction source if it is available.
*
* @returns {string} Source of the transaction
*/
get source(): string {
if (!this._source) {
throw new InvalidTransactionError('Transaction not initialized');
}
return this._source;
}
/**
* Get the transaction delegation address if it is available.
*
* @returns {string} transaction delegation address
*/
get delegate(): string | undefined {
return this._delegate;
}
/**
* Get the public key revealed by the transaction if it exists
*
* @returns {string} public key
*/
get publicKeyToReveal(): string | undefined {
return this._publicKeyToReveal;
}
/**
* Get the destination of an address initialization transaction if it exists
*
* @returns {string} forwarder destination
*/
get forwarderDestination(): string | undefined {
return this._forwarderDestination;
}
get owners(): string[] {
return this._owners;
}
/**
* Get the signatures for the given multisig transfer,
*
* @param {number} transferIndex The transfer script index in the Tezos transaction
* @returns {IndexedSignature[]} A list of signatures with their index inside the multisig transfer
* script
*/
getTransferSignatures(transferIndex = 0): IndexedSignature[] {
if (!this._parsedTransaction) {
return [];
}
return getMultisigTransferSignatures(this._parsedTransaction.contents[transferIndex] as TransactionOp);
}
/**
* Get the list of index per tezos transaction type. This is useful to locate specific operations
* within the transaction and verify or sign them.
*
* @returns {{[p: string]: number[]}} List of indexes where the key is the transaction kind
*/
getIndexesByTransactionType(): { [kind: string]: number[] } {
if (!this._parsedTransaction) {
return {};
}
const indexes = {};
for (let i = 0; i < this._parsedTransaction.contents.length; i++) {
const kind = this._parsedTransaction.contents[i].kind;
indexes[kind] = indexes[kind] ? indexes[kind].concat([i]) : [i];
}
return indexes;
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!