PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-hbar/src/lib
Просмотр файла: transactionBuilder.ts
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import * as Long from 'long';
import { proto } from '@hashgraph/proto';
import {
BaseAddress,
BaseFee,
BaseKey,
BaseTransactionBuilder,
BuildTransactionError,
InvalidParameterValueError,
ParseTransactionError,
SigningError,
} from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import {
buildHederaAccountID,
getCurrentTime,
isValidAddress,
isValidRawTransactionFormat,
isValidTimeString,
} from './utils';
import { KeyPair } from './keyPair';
import { HederaNode, SignatureData } from './iface';
export abstract class TransactionBuilder extends BaseTransactionBuilder {
protected _fee: BaseFee;
private _transaction: Transaction;
protected _source: BaseAddress;
protected _startTime: proto.ITimestamp;
protected _memo: string;
protected _txBody: proto.TransactionBody;
protected _node: HederaNode = { nodeId: '0.0.4' };
protected _duration: proto.Duration = new proto.Duration({ seconds: Long.fromNumber(180) });
protected _multiSignerKeyPairs: KeyPair[];
protected _signatures: SignatureData[];
protected constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._txBody = new proto.TransactionBody();
this._txBody.transactionValidDuration = this._duration;
this._multiSignerKeyPairs = [];
this._signatures = [];
this.transaction = new Transaction(_coinConfig);
}
// region Base Builder
/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
this._txBody.nodeAccountID = buildHederaAccountID(this._node.nodeId);
this._txBody.transactionFee = Long.fromString(this._fee.fee);
this._txBody.transactionID = this.buildTxId();
this._txBody.memo = this._memo;
const hTransaction = this.transaction.hederaTx || new proto.Transaction();
hTransaction.bodyBytes = proto.TransactionBody.encode(this._txBody).finish();
this.transaction.body(hTransaction);
for (const kp of this._multiSignerKeyPairs) {
await this.transaction.sign(kp);
}
for (const { signature, keyPair } of this._signatures) {
this.transaction.addSignature(signature, keyPair);
}
return this.transaction;
}
/** @inheritdoc */
protected fromImplementation(rawTransaction: Uint8Array | string): Transaction {
const tx = new Transaction(this._coinConfig);
this.validateRawTransaction(rawTransaction);
tx.fromRawTransaction(rawTransaction);
this.initBuilder(tx);
return this.transaction;
}
/** @inheritdoc */
protected signImplementation(key: BaseKey): Transaction {
this.checkDuplicatedKeys(key);
const signer = new KeyPair({ prv: key.key });
// Signing the transaction is an operation that relies on all the data being set,
// so we set the source here and leave the actual signing for the build step
this._multiSignerKeyPairs.push(signer);
return this.transaction;
}
/**
* Initialize the transaction builder fields using the decoded transaction data
*
* @param {Transaction} tx - the transaction data
*/
initBuilder(tx: Transaction): void {
this.transaction = tx;
this.transaction.loadPreviousSignatures();
const txData = tx.toJson();
this.fee({ fee: txData.fee.toString() });
this.source({ address: txData.from });
this.startTime(txData.startTime);
this.node({ nodeId: txData.node });
this.validDuration(new BigNumber(txData.validDuration).toNumber());
if (txData.memo) {
this.memo(txData.memo);
}
}
/**
* Creates a Hedera TransactionID
*
* @returns {proto.TransactionID} - Created TransactionID
*/
protected buildTxId(): proto.TransactionID {
return new proto.TransactionID({
transactionValidStart: this.validStart,
accountID: buildHederaAccountID(this._source.address),
});
}
// endregion
// region Common builder methods
/**
* Set the memo
*
* @param {string} memo - A hedera memo, can be a maximum of 100 bytes
* @returns {TransactionBuilder} - This transaction builder
*/
memo(memo: string): this {
if (Buffer.from(memo).length > 100) {
throw new InvalidParameterValueError('Memo must not be longer than 100 bytes');
}
this._memo = memo;
return this;
}
/**
* Set the node, it may take the format `'<shard>.<realm>.<account>'` or `'<account>'`
*
* @param {HederaNode} node - A hedera node address
* @returns {TransactionBuilder} - This transaction builder
*/
node(node: HederaNode): this {
if (!isValidAddress(node.nodeId)) {
throw new InvalidParameterValueError('Invalid Hedera node address');
}
this._node = node;
return this;
}
/**
* Set the transaction valid duration
*
* @param {number} validDuration - The transaction valid duration in seconds
* @returns {TransactionBuilder} - This transaction builder
*/
validDuration(validDuration: number): this {
this.validateValue(new BigNumber(validDuration));
this._duration = new proto.Duration({ seconds: Long.fromNumber(validDuration) });
return this;
}
/**
* Set the transaction fees
*
* @param {BaseFee} fee - The maximum gas to pay
* @returns {TransactionBuilder} - This transaction builder
*/
fee(fee: BaseFee): this {
this.validateValue(new BigNumber(fee.fee));
this._fee = fee;
return this;
}
/**
* Set the transaction source
*
* @param {BaseAddress} address - The source account
* @returns {TransactionBuilder} - This transaction builder
*/
source(address: BaseAddress): this {
this.validateAddress(address);
this._source = address;
return this;
}
/**
* Set an external transaction signature
*
* @param {string} signature - Hex encoded signature string
* @param {KeyPair} keyPair - The public key keypair that was used to create the signature
* @returns {TransactionBuilder} - Transaction builder
*/
signature(signature: string, keyPair: KeyPair): this {
// if we already have a signature for this key pair, just update it
for (const oldSignature of this._signatures) {
if (oldSignature.keyPair.getKeys().pub === keyPair.getKeys().pub) {
oldSignature.signature = signature;
return this;
}
}
// otherwise add the new signature
this._signatures.push({ signature, keyPair });
return this;
}
/**
* Set the start time
*
* @param {string} time - String value of the time to set with format <seconds>.<nanos>
* @returns {TransactionBuilder} - this
*/
startTime(time: string): this {
if (!isValidTimeString(time)) {
throw new InvalidParameterValueError('Invalid value for time parameter');
}
const timeParts = time.split('.').map((v) => new BigNumber(v).toNumber());
this._startTime = { seconds: Long.fromNumber(timeParts[0]), nanos: timeParts[1] };
return this;
}
// endregion
// region Getters and Setters
private get validStart(): proto.ITimestamp {
if (!this._startTime) {
this.startTime(getCurrentTime());
}
return this._startTime;
}
/** @inheritdoc */
protected get transaction(): Transaction {
return this._transaction;
}
/** @inheritdoc */
protected set transaction(transaction: Transaction) {
this._transaction = transaction;
}
// endregion
// region Validators
/** @inheritdoc */
validateAddress(address: BaseAddress, addressFormat?: string): void {
if (!isValidAddress(address.address)) {
throw new BuildTransactionError('Invalid address ' + address.address);
}
}
/** @inheritdoc */
validateKey(key: BaseKey): void {
if (!new KeyPair({ prv: key.key })) {
throw new BuildTransactionError('Invalid key');
}
}
/** @inheritdoc */
validateRawTransaction(rawTransaction: any): void {
if (!isValidRawTransactionFormat(rawTransaction)) {
throw new ParseTransactionError('Invalid raw transaction');
}
}
/** @inheritdoc */
validateTransaction(transaction?: Transaction): void {
this.validateMandatoryFields();
}
/** @inheritdoc */
validateValue(value: BigNumber): void {
if (value.isLessThan(0)) {
throw new BuildTransactionError('Value cannot be less than zero');
}
}
validateMandatoryFields(): void {
if (this._fee === undefined) {
throw new BuildTransactionError('Invalid transaction: missing fee');
}
if (this._source === undefined) {
throw new BuildTransactionError('Invalid transaction: missing source');
}
}
/**
* Validates that the given key is not already in this._multiSignerKeyPairs
*
* @param {BaseKey} key - The key to check
*/
private checkDuplicatedKeys(key: BaseKey): void {
this._multiSignerKeyPairs.forEach((_sourceKeyPair) => {
if (_sourceKeyPair.getKeys().prv === key.key) {
throw new SigningError('Repeated sign: ' + key.key);
}
});
}
// endregion
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!