PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-near/src/lib
Просмотр файла: transaction.ts
import {
BaseKey,
BaseTransaction,
Entry,
InvalidTransactionError,
TransactionRecipient,
TransactionType,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransactionExplanation, TxData, Action } from './iface';
import { HEX_REGEX, StakingContractMethodNames } from './constants';
import utils from './utils';
import { KeyPair } from './keyPair';
import * as nearAPI from 'near-api-js';
import * as sha256 from 'js-sha256';
import base58 from 'bs58';
export class Transaction extends BaseTransaction {
private _nearTransaction: nearAPI.transactions.Transaction;
private _nearSignedTransaction: nearAPI.transactions.SignedTransaction;
constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
}
get nearTransaction(): nearAPI.transactions.Transaction {
return this._nearTransaction;
}
set nearTransaction(tx: nearAPI.transactions.Transaction) {
this._nearTransaction = tx;
this._id = utils.base58Encode(this.getTransactionHash());
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
try {
new KeyPair({ prv: key.key });
return true;
} catch {
return false;
}
}
/** @inheritdoc */
toBroadcastFormat(): string {
if (!this._nearTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
const txSeralized = this._nearSignedTransaction
? Buffer.from(this._nearSignedTransaction.encode()).toString('base64')
: Buffer.from(this._nearTransaction.encode()).toString('base64');
return txSeralized;
}
/** @inheritdoc */
toJson(): TxData {
if (!this._nearTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
let parsedAction: Action = {};
if (this._nearTransaction.actions[0].enum === 'transfer') {
parsedAction = { transfer: this._nearTransaction.actions[0].transfer };
} else if (this._nearTransaction.actions[0].enum === 'functionCall') {
const functionCallObject = this._nearTransaction.actions[0].functionCall;
parsedAction = {
functionCall: {
methodName: functionCallObject.methodName,
args: JSON.parse(Buffer.from(functionCallObject.args).toString()),
gas: functionCallObject.gas.toString(),
deposit: functionCallObject.deposit.toString(),
},
};
}
return {
id: this._id,
signerId: this._nearTransaction.signerId,
publicKey: this._nearTransaction.publicKey.toString(),
nonce: this._nearTransaction.nonce,
receiverId: this._nearTransaction.receiverId,
actions: [parsedAction],
signature: typeof this._nearSignedTransaction === 'undefined' ? undefined : this._nearSignedTransaction.signature,
};
}
/**
* Set the transaction type.
*
* @param {TransactionType} transactionType The transaction type to be set.
*/
setTransactionType(transactionType: TransactionType): void {
this._type = transactionType;
}
/**
* Sets this transaction payload
*
* @param rawTx
*/
fromRawTransaction(rawTx: string): void {
const bufferRawTransaction = HEX_REGEX.test(rawTx) ? Buffer.from(rawTx, 'hex') : Buffer.from(rawTx, 'base64');
try {
const signedTx = nearAPI.utils.serialize.deserialize(
nearAPI.transactions.SCHEMA,
nearAPI.transactions.SignedTransaction,
bufferRawTransaction
);
signedTx.transaction.nonce = parseInt(signedTx.transaction.nonce.toString(), 10);
this._nearSignedTransaction = signedTx;
this._nearTransaction = signedTx.transaction;
this._id = utils.base58Encode(this.getTransactionHash());
} catch (e) {
try {
const unsignedTx = nearAPI.utils.serialize.deserialize(
nearAPI.transactions.SCHEMA,
nearAPI.transactions.Transaction,
bufferRawTransaction
);
unsignedTx.nonce = parseInt(unsignedTx.nonce.toString(), 10);
this._nearTransaction = unsignedTx;
this._id = utils.base58Encode(this.getTransactionHash());
} catch (e) {
throw new InvalidTransactionError('unable to build transaction from raw');
}
}
this.loadInputsAndOutputs();
}
/**
* Sign this transaction
*
* @param {KeyPair} signer key
*/
sign(signer: KeyPair): void {
if (!this._nearTransaction) {
throw new InvalidTransactionError('empty transaction to sign');
}
const serializedTxHash = this.getTransactionHash();
const signature = signer.signMessageinUint8Array(serializedTxHash);
this._nearSignedTransaction = new nearAPI.transactions.SignedTransaction({
transaction: this._nearTransaction,
signature: new nearAPI.transactions.Signature({
keyType: this._nearTransaction.publicKey.keyType,
data: signature,
}),
});
this.loadInputsAndOutputs();
}
/**
* set transaction type by staking contract method names.
* @param methodName method name to match and set the transaction type
*/
private setTypeByStakingMethod(methodName: string): void {
switch (methodName) {
case StakingContractMethodNames.DepositAndStake:
this.setTransactionType(TransactionType.StakingActivate);
break;
case StakingContractMethodNames.Unstake:
this.setTransactionType(TransactionType.StakingDeactivate);
break;
case StakingContractMethodNames.Withdraw:
this.setTransactionType(TransactionType.StakingWithdraw);
break;
}
}
/**
* Check if method is allowed on Near account-lib implementation.
* This method should check on all contracts added to Near.
* @param methodName contract call method name to check if its allowed.
*/
private validateMethodAllowed(methodName: string): void {
if (!Object.values(StakingContractMethodNames).some((item) => item === methodName)) {
throw new InvalidTransactionError('unsupported function call in raw transaction');
}
}
/**
* Build input and output field for this transaction
*
*/
loadInputsAndOutputs(): void {
if (this._nearTransaction.actions.length !== 1) {
throw new InvalidTransactionError('too many actions in raw transaction');
}
const action = this._nearTransaction.actions[0];
switch (action.enum) {
case 'transfer':
this.setTransactionType(TransactionType.Send);
break;
case 'functionCall':
const methodName = action.functionCall.methodName;
this.validateMethodAllowed(methodName);
this.setTypeByStakingMethod(methodName);
break;
default:
throw new InvalidTransactionError('unsupported action in raw transaction');
}
const outputs: Entry[] = [];
const inputs: Entry[] = [];
switch (this.type) {
case TransactionType.Send:
const amount = action.transfer.deposit.toString();
inputs.push({
address: this._nearTransaction.signerId,
value: amount,
coin: this._coinConfig.name,
});
outputs.push({
address: this._nearTransaction.receiverId,
value: amount,
coin: this._coinConfig.name,
});
break;
case TransactionType.StakingActivate:
const stakingAmount = action.functionCall.deposit.toString();
inputs.push({
address: this._nearTransaction.signerId,
value: stakingAmount,
coin: this._coinConfig.name,
});
outputs.push({
address: this._nearTransaction.receiverId,
value: stakingAmount,
coin: this._coinConfig.name,
});
break;
case TransactionType.StakingWithdraw:
const stakingWithdrawAmount = JSON.parse(Buffer.from(action.functionCall.args).toString()).amount;
inputs.push({
address: this._nearTransaction.receiverId,
value: stakingWithdrawAmount,
coin: this._coinConfig.name,
});
outputs.push({
address: this._nearTransaction.signerId,
value: stakingWithdrawAmount,
coin: this._coinConfig.name,
});
break;
}
this._outputs = outputs;
this._inputs = inputs;
}
/**
* Returns a complete explanation for a transfer transaction
* @param {TxData} json The transaction data in json format
* @param {TransactionExplanation} explanationResult The transaction explanation to be completed
* @returns {TransactionExplanation}
*/
explainTransferTransaction(json: TxData, explanationResult: TransactionExplanation): TransactionExplanation {
return {
...explanationResult,
outputAmount: json.actions[0].transfer?.deposit.toString() || '',
outputs: [
{
address: json.receiverId,
amount: json.actions[0].transfer?.deposit.toString() || '',
},
],
};
}
/**
* Returns a complete explanation for a staking activate transaction
* @param {TxData} json The transaction data in json format
* @param {TransactionExplanation} explanationResult The transaction explanation to be completed
* @returns {TransactionExplanation}
*/
explainStakingActivateTransaction(json: TxData, explanationResult: TransactionExplanation): TransactionExplanation {
return {
...explanationResult,
outputAmount: json.actions[0].functionCall?.deposit.toString() || '',
outputs: [
{
address: json.receiverId,
amount: json.actions[0].functionCall?.deposit.toString() || '',
},
],
};
}
/**
* Returns a complete explanation for a staking withdraw transaction
* @param {TxData} json The transaction data in json format
* @param {TransactionExplanation} explanationResult The transaction explanation to be completed
* @returns {TransactionExplanation}
*/
explainStakingWithdrawTransaction(json: TxData, explanationResult: TransactionExplanation): TransactionExplanation {
const amount = json.actions[0].functionCall?.args.amount as string;
return {
...explanationResult,
outputAmount: amount,
outputs: [
{
address: json.signerId,
amount: amount,
},
],
};
}
/** @inheritdoc */
explainTransaction(): TransactionExplanation {
const result = this.toJson();
const displayOrder = ['outputAmount', 'changeAmount', 'outputs', 'changeOutputs', 'fee', 'type'];
const outputs: TransactionRecipient[] = [];
const explanationResult: TransactionExplanation = {
// txhash used to identify the transactions
id: result.id || '',
displayOrder,
outputAmount: '0',
changeAmount: '0',
changeOutputs: [],
outputs,
fee: { fee: '' },
type: this.type,
};
switch (this.type) {
case TransactionType.Send:
return this.explainTransferTransaction(result, explanationResult);
case TransactionType.StakingActivate:
return this.explainStakingActivateTransaction(result, explanationResult);
case TransactionType.StakingDeactivate:
return explanationResult;
case TransactionType.StakingWithdraw:
return this.explainStakingWithdrawTransaction(result, explanationResult);
default:
throw new InvalidTransactionError('Transaction type not supported');
}
}
private getTransactionHash(): Uint8Array {
const serializedTx = nearAPI.utils.serialize.serialize(nearAPI.transactions.SCHEMA, this._nearTransaction);
return new Uint8Array(sha256.sha256.array(serializedTx));
}
get signablePayload(): Buffer {
if (!this._nearTransaction) {
throw new InvalidTransactionError('empty transaction');
}
return Buffer.from(this.getTransactionHash());
}
/**
* Constructs a signed payload using construct.signTx
* This method will be called during the build step if a TSS signature
* is added and will set the signTransaction which is the txHex that will be broadcasted
* As well as add the signature used to sign to the signature array in hex format
*
* @param {Buffer} signature The signature to be added to a near transaction
*/
constructSignedPayload(signature: Buffer): void {
this._nearSignedTransaction = new nearAPI.transactions.SignedTransaction({
transaction: this._nearTransaction,
signature: new nearAPI.transactions.Signature({
keyType: this._nearTransaction.publicKey.keyType,
data: signature,
}),
});
this.loadInputsAndOutputs();
}
/** @inheritdoc **/
get signature(): string[] {
const signatures: string[] = [];
if (this._nearSignedTransaction) {
signatures.push(base58.encode(this._nearSignedTransaction.signature.data));
}
return signatures;
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!