PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-algo/src/lib
Просмотр файла: transactionBuilder.ts
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import algosdk from 'algosdk';
import { Transaction } from './transaction';
import { AddressValidationError, InsufficientFeeError } from './errors';
import { KeyPair } from './keyPair';
import { BaseTransactionSchema } from './txnSchema';
import Utils from './utils';
import {
BaseAddress,
BaseFee,
BaseKey,
BaseTransactionBuilder,
BuildTransactionError,
InvalidTransactionError,
isValidEd25519SecretKey,
isValidEd25519Seed,
TransactionType,
} from '@bitgo/sdk-core';
import { algoUtils } from '.';
const MIN_FEE = 1000; // in microalgos
export const MAINNET_GENESIS_ID = 'mainnet-v1.0';
export const MAINNET_GENESIS_HASH = 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=';
export const TESTNET_GENESIS_ID = 'testnet-v1.0';
export const TESTNET_GENESIS_HASH = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=';
export const BETANET_GENESIS_ID = 'betanet-v1.0';
export const BETANET_GENESIS_HASH = 'mFgazF+2uRS1tMiL9dsj01hJGySEmPN28B/TjjvpVW0=';
export abstract class TransactionBuilder extends BaseTransactionBuilder {
protected _transaction: Transaction;
protected _keyPairs: KeyPair[];
// the fee is specified as a number here instead of a big number because
// the algosdk also specifies it as a number.
protected _fee: number;
protected _isFlatFee: boolean;
protected _sender: string;
protected _genesisHash: string;
protected _genesisId: string;
protected _firstRound: number;
protected _lastRound: number;
protected _lease?: Uint8Array;
protected _note?: Uint8Array;
protected _reKeyTo?: string;
protected _suggestedParams: algosdk.SuggestedParams;
constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
this._transaction = new Transaction(coinConfig);
this._keyPairs = [];
}
/**
* Sets the fee.
*
* The minimum fee is 1000 microalgos.
*
* @param {BaseFee} feeObj The amount to pay to the fee sink denoted in microalgos
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
fee(feeObj: BaseFee): this {
this._isFlatFee = true;
const fee = new BigNumber(feeObj.fee).toNumber();
if (this._isFlatFee && fee < MIN_FEE) {
throw new InsufficientFeeError(fee, MIN_FEE);
}
this._fee = fee;
return this;
}
/**
* Sets whether the fee is a flat fee.
*
* A flat fee is the fee for the entire transaction whereas a normal fee
* is a fee for every byte in the transaction.
*
* @param {boolean} isFlatFee Whether the fee should be specified as a flat fee.
* @returns {TransactionBuilder} This transaction builder.
*/
isFlatFee(isFlatFee: boolean): this {
this._isFlatFee = isFlatFee;
return this;
}
/**
* Sets the transaction sender.
*
* @param {BaseAddress} sender The sender account
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
sender(sender: BaseAddress): this {
this.validateAddress(sender);
this._sender = sender.address;
this._transaction.sender(sender.address);
return this;
}
/**
* Sets the genesis id.
*
* @param {string} genesisId The genesis id.
* @example "mainnet-v1.0"
*
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
genesisId(genesisId: string): this {
this._genesisId = genesisId;
return this;
}
/**
* Sets the genesis hash.
*
* The genesis hash must be base64 encoded.
*
* @param {string} genesisHash The genesis hash.
* @example "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="
*
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
genesisHash(genesisHash: string): this {
this._genesisHash = genesisHash;
return this;
}
/**
* Sets the genesis id and hash to mainnet.
*
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/algorand-networks/mainnet/#genesis-id
* @see https://developer.algorand.org/docs/reference/algorand-networks/mainnet/#genesis-hash
*/
mainnet(): this {
this.genesisId(MAINNET_GENESIS_ID);
this.genesisHash(MAINNET_GENESIS_HASH);
return this;
}
/**
* Sets the genesis id and hash to testnet.
*
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/algorand-networks/testnet/#genesis-id
* @see https://developer.algorand.org/docs/reference/algorand-networks/testnet/#genesis-hash
*/
testnet(): this {
this.genesisId(TESTNET_GENESIS_ID);
this.genesisHash(TESTNET_GENESIS_HASH);
return this;
}
/**
* Sets the genesis id and hash to betanet.
*
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/algorand-networks/betanet/#genesis-id
* @see https://developer.algorand.org/docs/reference/algorand-networks/betanet/#genesis-hash
*/
betanet(): this {
this.genesisId(BETANET_GENESIS_ID);
this.genesisHash(BETANET_GENESIS_HASH);
return this;
}
/**
* Sets the first round.
*
* @param {number} round The first protocol round on which this txn is valid.
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
firstRound(round: number): this {
this.validateValue(new BigNumber(round));
this._firstRound = round;
return this;
}
/**
* Sets the last round.
*
* @param {number} round The first protocol round on which this txn is valid.
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
lastRound(round: number): this {
this.validateValue(new BigNumber(round));
this._lastRound = round;
return this;
}
/**
* Sets the lease on the transaction.
*
* A lease is a mutex on the transaction that prevents any other transaction
* from being sent with the same lease until the prior transaction's last
* round has passed.
*
* @param {Uint8Array} lease The lease to put the transaction.
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
lease(lease: Uint8Array): this {
this._lease = lease;
return this;
}
/**
* Sets the note for the transaction.
*
* @param {Uint8Array} note Arbitrary data for sender to store.
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
note(note: Uint8Array): this {
this._note = note;
return this;
}
/**
* Sets the authorized address.
*
* The authorized asset will be used to authorize all future transactions.
*
* @param {BaseAddress} authorizer The address to delegate authorization authority to.
* @returns {TransactionBuilder} This transaction builder.
*
* @see https://developer.algorand.org/docs/reference/transactions/
*/
reKeyTo(authorizer: BaseAddress): this {
this.validateAddress(authorizer);
this._reKeyTo = authorizer.address;
return this;
}
/** @inheritdoc */
validateAddress({ address }: BaseAddress): void {
if (!algosdk.isValidAddress(address)) {
throw new AddressValidationError(address);
}
}
/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
this.transaction.setAlgoTransaction(this.buildAlgoTxn());
this.transaction.setTransactionType(this.transactionType);
this.transaction.sign(this._keyPairs);
this._transaction.loadInputsAndOutputs();
return this._transaction;
}
/**
* Builds the algorand transaction.
*/
protected abstract buildAlgoTxn(): algosdk.Transaction;
/**
* The transaction type.
*/
protected abstract get transactionType(): TransactionType;
/** @inheritdoc */
protected fromImplementation(rawTransaction: Uint8Array | string): Transaction {
const decodedTxn = Utils.decodeAlgoTxn(rawTransaction);
const algosdkTxn = decodedTxn.txn;
if (decodedTxn.signed) {
this._transaction.signedTransaction = decodedTxn.rawTransaction;
if (decodedTxn.signers) {
this.setSigners(decodedTxn.signers);
}
if (decodedTxn.signedBy) {
this._transaction.signedBy = decodedTxn.signedBy;
}
}
this.sender({ address: algosdk.encodeAddress(algosdkTxn.from.publicKey) });
this._isFlatFee = true;
this._fee = algosdkTxn.fee;
this._genesisHash = algosdkTxn.genesisHash.toString('base64');
this._genesisId = algosdkTxn.genesisID;
this._firstRound = algosdkTxn.firstRound;
this._lastRound = algosdkTxn.lastRound;
this._lease = algosdkTxn.lease;
this._note = algosdkTxn.note;
this._reKeyTo = algosdkTxn.reKeyTo ? algosdk.encodeAddress(algosdkTxn.reKeyTo.publicKey) : undefined;
this._transaction.setAlgoTransaction(algosdkTxn);
return this._transaction;
}
/** @inheritdoc */
protected signImplementation({ key }: BaseKey): Transaction {
try {
const buffKey = Utils.decodeSeed(key);
const keypair = new KeyPair({ prv: Buffer.from(buffKey.seed).toString('hex') });
this._keyPairs.push(keypair);
} catch (e) {
if (algoUtils.isValidPrivateKey(key)) {
const keypair = new KeyPair({ prv: key });
this._keyPairs.push(keypair);
} else {
throw e;
}
}
return this._transaction;
}
numberOfSigners(num: number): this {
this._transaction.setNumberOfRequiredSigners(num);
return this;
}
setSigners(addrs: string | string[]): this {
const signers = addrs instanceof Array ? addrs : [addrs];
signers.forEach((address) => this.validateAddress({ address: address }));
this._transaction.signers = signers;
return this;
}
/**
* Sets the number of signers required to sign the transaction.
*
* The number of signers cannot be set to a negative value.
*
* @param {number} n The number of signers.
* @returns {TransactionBuilder} This transaction builder.
*/
numberOfRequiredSigners(n: number): this {
if (n < 0) {
throw new BuildTransactionError(`Number of signers: '${n}' cannot be negative`);
}
this._transaction.setNumberOfRequiredSigners(n);
return this;
}
/**
* @inheritdoc
* @see https://developer.algorand.org/docs/features/accounts/#transformation-private-key-to-base64-private-key
*/
validateKey({ key }: BaseKey): void {
let isValidPrivateKeyFromBytes;
const isValidPrivateKeyFromHex = isValidEd25519Seed(key);
const isValidPrivateKeyFromBase64 = isValidEd25519Seed(Buffer.from(key, 'base64').toString('hex'));
const isValidRootPrvKey = isValidEd25519SecretKey(key);
try {
const decodedSeed = Utils.decodeSeed(key);
isValidPrivateKeyFromBytes = isValidEd25519Seed(Buffer.from(decodedSeed.seed).toString('hex'));
} catch (err) {
isValidPrivateKeyFromBytes = false;
}
if (
!isValidPrivateKeyFromBytes &&
!isValidPrivateKeyFromHex &&
!isValidPrivateKeyFromBase64 &&
!isValidRootPrvKey
) {
throw new BuildTransactionError(`Key validation failed`);
}
}
/** @inheritdoc */
validateRawTransaction(rawTransaction: Uint8Array | string): void {
const decodedTxn = Utils.decodeAlgoTxn(rawTransaction);
const algoTxn = decodedTxn.txn;
const validationResult = BaseTransactionSchema.validate({
fee: algoTxn?.fee,
firstRound: algoTxn?.firstRound,
genesisHash: algoTxn?.genesisHash.toString('base64'),
lastRound: algoTxn?.lastRound,
sender: algoTxn ? algosdk.encodeAddress(algoTxn.from.publicKey) : undefined,
genesisId: algoTxn?.genesisID,
lease: algoTxn?.lease,
note: algoTxn?.note,
reKeyTo: algoTxn?.reKeyTo ? algosdk.encodeAddress(algoTxn.reKeyTo.publicKey) : undefined,
});
if (validationResult.error) {
throw new InvalidTransactionError(`Transaction validation failed: ${validationResult.error.message}`);
}
}
/** @inheritdoc */
validateTransaction(_: Transaction): void {
this.validateBaseFields(
this._fee,
this._firstRound,
this._genesisHash,
this._lastRound,
this._sender,
this._genesisId,
this._lease,
this._note,
this._reKeyTo
);
}
private validateBaseFields(
fee: number,
firstRound: number,
genesisHash: string,
lastRound: number,
sender: string,
genesisId: string,
lease: Uint8Array | undefined,
note: Uint8Array | undefined,
reKeyTo: string | undefined
): void {
const validationResult = BaseTransactionSchema.validate({
fee,
firstRound,
genesisHash,
lastRound,
sender,
genesisId,
lease,
note,
reKeyTo,
});
if (validationResult.error) {
throw new InvalidTransactionError(`Transaction validation failed: ${validationResult.error.message}`);
}
}
/** @inheritdoc */
validateValue(value: BigNumber): void {
if (value.isLessThan(0)) {
throw new BuildTransactionError('Value cannot be less than zero');
}
}
/** @inheritdoc */
protected get transaction(): Transaction {
return this._transaction;
}
/** @inheritdoc */
protected set transaction(transaction: Transaction) {
this._transaction = transaction;
}
/**
* Convenience method to retrieve the algosdk suggested parameters.
*
* @returns {algosdk.SuggestedParams} The algosdk suggested parameters.
*/
protected get suggestedParams(): algosdk.SuggestedParams {
return {
flatFee: this._isFlatFee,
fee: this._fee,
firstRound: this._firstRound,
lastRound: this._lastRound,
genesisID: this._genesisId,
genesisHash: this._genesisHash,
};
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!