PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-avaxp/src/lib
Просмотр файла: deprecatedTransaction.ts
import { AvalancheNetwork, BaseCoin as CoinConfig } from '@bitgo/statics';
import {
BaseKey,
BaseTransaction,
Entry,
InvalidTransactionError,
SigningError,
TransactionFee,
TransactionType,
} from '@bitgo/sdk-core';
import { KeyPair } from './keyPair';
import {
DeprecatedBaseTx,
DecodedUtxoObj,
TransactionExplanation,
DeprecatedTx,
TxData,
INPUT_SEPARATOR,
ADDRESS_SEPARATOR,
} from './iface';
import { AddDelegatorTx, AmountInput, BaseTx as PVMBaseTx, ExportTx, ImportTx } from 'avalanche/dist/apis/platformvm';
import { ExportTx as EVMExportTx, ImportTx as EVMImportTx } from 'avalanche/dist/apis/evm';
import { BN, Buffer as BufferAvax } from 'avalanche';
import utils from './utils';
import { Credential } from 'avalanche/dist/common';
import { Buffer } from 'buffer';
// region utils to sign
interface signatureSerialized {
bytes: string;
}
interface CheckSignature {
(sigature: signatureSerialized, addressHex: string): boolean;
}
function isEmptySignature(s: string): boolean {
return !!s && s.startsWith(''.padStart(90, '0'));
}
/**
* Signatures are prestore as empty buffer for hsm and address of signar for first signature.
* When sign is required, this method return the function that identify a signature to be replaced.
* @param signatures any signatures as samples to identify which signature required replace.
*/
function generateSelectorSignature(signatures: signatureSerialized[]): CheckSignature {
if (signatures.length > 1 && signatures.every((sig) => isEmptySignature(sig.bytes))) {
// Look for address.
return function (sig, address): boolean {
try {
if (!isEmptySignature(sig.bytes)) {
return false;
}
const pub = sig.bytes.substring(90);
return pub === address;
} catch (e) {
return false;
}
};
} else {
// Look for empty string
return function (sig, address): boolean {
if (isEmptySignature(sig.bytes)) return true;
return false;
};
}
}
// end region utils for sign
export class DeprecatedTransaction extends BaseTransaction {
protected _avaxTransaction: DeprecatedTx;
public _type: TransactionType;
public _network: AvalancheNetwork;
public _networkID: number;
public _assetId: BufferAvax;
public _blockchainID: BufferAvax;
public _threshold = 2;
public _locktime: BN = new BN(0);
public _fromAddresses: BufferAvax[] = [];
public _rewardAddresses: BufferAvax[];
public _utxos: DecodedUtxoObj[] = [];
public _to: BufferAvax[];
public _fee: Partial<TransactionFee> = {};
constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
this._network = coinConfig.network as AvalancheNetwork;
this._assetId = utils.cb58Decode(this._network.avaxAssetID);
this._blockchainID = utils.cb58Decode(this._network.blockchainID);
this._networkID = this._network.networkID;
}
get avaxPTransaction(): DeprecatedBaseTx {
return this._avaxTransaction.getUnsignedTx().getTransaction();
}
get signature(): string[] {
if (this.credentials.length === 0) {
return [];
}
const obj: any = this.credentials[0].serialize();
return obj.sigArray.map((s) => s.bytes).filter((s) => !isEmptySignature(s));
}
get credentials(): Credential[] {
return (this._avaxTransaction as any)?.credentials;
}
get hasCredentials(): boolean {
return this.credentials !== undefined && this.credentials.length > 0;
}
/** @inheritdoc */
canSign({ key }: BaseKey): boolean {
// TODO(BG-56700): Improve canSign by check in addresses in empty credentials match signer
return true;
}
/**
* Sign a avaxp transaction and update the transaction hex
* validator, delegator, import, exports extend baseTx
* unsignedTx: UnsignedTx = new UnsignedTx(baseTx) (baseTx = addValidatorTx)
* const tx: Tx = unsignedTx.sign(keychain) (tx is type standard signed tx)
* get baseTx then create new unsignedTx then sign
*
* @param {KeyPair} keyPair
*/
sign(keyPair: KeyPair): void {
const prv = keyPair.getPrivateKey();
const addressHex = keyPair.getAddressBuffer().toString('hex');
if (!prv) {
throw new SigningError('Missing private key');
}
if (!this.avaxPTransaction) {
throw new InvalidTransactionError('empty transaction to sign');
}
if (!this.hasCredentials) {
throw new InvalidTransactionError('empty credentials to sign');
}
const signature = this.createSignature(prv);
let checkSign: CheckSignature | undefined = undefined;
this.credentials.forEach((c, index) => {
const cs: any = c.serialize();
if (checkSign === undefined) {
checkSign = generateSelectorSignature(cs.sigArray);
}
let find = false;
cs.sigArray.forEach((sig) => {
if (checkSign && checkSign(sig, addressHex)) {
sig.bytes = signature;
find = true;
}
});
if (!find) throw new SigningError('Private key cannot sign the transaction');
c.deserialize(cs);
});
}
/** @inheritdoc */
/**
* should be of signedTx doing this with baseTx
*/
toBroadcastFormat(): string {
if (!this.avaxPTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
return this._avaxTransaction.toStringHex();
}
// types - stakingTransaction, import, export
toJson(): TxData {
if (!this.avaxPTransaction) {
throw new InvalidTransactionError('Empty transaction data');
}
return {
id: this.id,
inputs: this.inputs,
fromAddresses: this.fromAddresses,
threshold: this._threshold,
locktime: this._locktime.toString(),
type: this.type,
signatures: this.signature,
outputs: this.outputs,
changeOutputs: this.changeOutputs,
sourceChain: this.sourceChain,
destinationChain: this.destinationChain,
};
}
setTransaction(tx: DeprecatedTx): void {
this._avaxTransaction = tx;
}
/**
* Set the transaction type
*
* @param {TransactionType} transactionType The transaction type to be set
*/
setTransactionType(transactionType: TransactionType): void {
this._type = transactionType;
}
/**
* Returns the portion of the transaction that needs to be signed in Buffer format.
* Only needed for coins that support adding signatures directly (e.g. TSS).
*/
get signablePayload(): Buffer {
return utils.sha256(this._avaxTransaction.getUnsignedTx().toBuffer());
}
get id(): string {
return utils.cb58Encode(BufferAvax.from(utils.sha256(this._avaxTransaction.toBuffer())));
}
get fromAddresses(): string[] {
return this._fromAddresses.map((a) => utils.addressToString(this._network.hrp, this._network.alias, a));
}
get rewardAddresses(): string[] {
return this._rewardAddresses.map((a) => utils.addressToString(this._network.hrp, this._network.alias, a));
}
/**
* Get the list of outputs. Amounts are expressed in absolute value.
*/
get outputs(): Entry[] {
switch (this.type) {
case TransactionType.Import:
return (this.avaxPTransaction as ImportTx | EVMImportTx)
.getOuts()
.map(utils.deprecatedMapOutputToEntry(this._network));
case TransactionType.Export:
if (utils.isTransactionOf(this._avaxTransaction, this._network.cChainBlockchainID)) {
return (this.avaxPTransaction as EVMExportTx)
.getExportedOutputs()
.map(utils.deprecatedMapOutputToEntry(this._network));
} else {
return (this.avaxPTransaction as ExportTx)
.getExportOutputs()
.map(utils.deprecatedMapOutputToEntry(this._network));
}
case TransactionType.AddDelegator:
case TransactionType.AddValidator:
// Get staked outputs
const addValidatorTx = this.avaxPTransaction as AddDelegatorTx;
return [
{
address: addValidatorTx.getNodeIDString(),
value: addValidatorTx.getStakeAmount().toString(),
},
];
default:
return [];
}
}
/**
* Get a Transasction Fee.
*/
get fee(): TransactionFee {
return { fee: '0', ...this._fee };
}
get changeOutputs(): Entry[] {
// C-chain tx adn Import Txs don't have change outputs
if (
this.type === TransactionType.Import ||
utils.isTransactionOf(this._avaxTransaction, this._network.cChainBlockchainID)
) {
return [];
}
// general support any transaction type, but it's scoped yet
return (this.avaxPTransaction as PVMBaseTx).getOuts().map(utils.deprecatedMapOutputToEntry(this._network));
}
get inputs(): Entry[] {
let inputs;
switch (this.type) {
case TransactionType.Import:
inputs = (this.avaxPTransaction as ImportTx | EVMImportTx).getImportInputs();
break;
case TransactionType.Export:
if (utils.isTransactionOf(this._avaxTransaction, this._network.cChainBlockchainID)) {
return (this.avaxPTransaction as EVMExportTx).getInputs().map((evmInput) => ({
address: '0x' + evmInput.getAddressString(),
value: new BN((evmInput as any).amount).toString(),
nonce: evmInput.getNonce().toNumber(),
}));
}
inputs = (this.avaxPTransaction as PVMBaseTx).getIns();
break;
default:
inputs = (this.avaxPTransaction as PVMBaseTx).getIns();
}
return inputs.map((input) => {
const amountInput = input.getInput() as any as AmountInput;
return {
id: utils.cb58Encode(input.getTxID()) + INPUT_SEPARATOR + utils.outputidxBufferToNumber(input.getOutputIdx()),
address: this.fromAddresses.sort().join(ADDRESS_SEPARATOR),
value: amountInput.getAmount().toString(),
};
});
}
/**
* Avax wrapper to create signature and return it for credentials
* @param prv
* @return hexstring
*/
createSignature(prv: Buffer): string {
const signval = utils.createSignatureAvaxBuffer(
this._network,
BufferAvax.from(this.signablePayload),
BufferAvax.from(prv)
);
return signval.toString('hex');
}
/** @inheritdoc */
explainTransaction(): TransactionExplanation {
const txJson = this.toJson();
const displayOrder = ['id', 'inputs', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs', 'fee', 'type'];
const outputAmount = txJson.outputs.reduce((p, n) => p.add(new BN(n.value)), new BN(0)).toString();
const changeAmount = txJson.changeOutputs.reduce((p, n) => p.add(new BN(n.value)), new BN(0)).toString();
let rewardAddresses;
if ([TransactionType.AddValidator, TransactionType.AddDelegator].includes(txJson.type)) {
rewardAddresses = this.rewardAddresses;
displayOrder.splice(6, 0, 'rewardAddresses');
}
return {
displayOrder,
id: txJson.id,
inputs: txJson.inputs,
outputs: txJson.outputs.map((o) => ({ address: o.address, amount: o.value })),
outputAmount,
changeOutputs: txJson.changeOutputs.map((o) => ({ address: o.address, amount: o.value })),
changeAmount,
rewardAddresses,
fee: this.fee,
type: txJson.type,
};
}
/**
* Check if this transaction is a P chain
*/
get isTransactionForCChain(): boolean {
return utils.isTransactionOf(this._avaxTransaction, this._network.cChainBlockchainID);
}
/**
* get the source chain id or undefined if it's a cross chain transfer.
*/
get sourceChain(): string | undefined {
let blockchainID;
switch (this.type) {
case TransactionType.Import:
blockchainID = (this.avaxPTransaction as ImportTx | EVMImportTx).getSourceChain();
break;
case TransactionType.Export:
blockchainID = (this.avaxPTransaction as ExportTx | EVMExportTx).getBlockchainID();
break;
default:
return undefined;
}
return this.blockchainIDtoAlias(blockchainID);
}
/**
* get the destinationChain or undefined if it's a cross chain transfer.
*/
get destinationChain(): string | undefined {
let blockchainID;
switch (this.type) {
case TransactionType.Import:
blockchainID = (this.avaxPTransaction as ImportTx | EVMImportTx).getBlockchainID();
break;
case TransactionType.Export:
blockchainID = (this.avaxPTransaction as ExportTx | EVMExportTx).getDestinationChain();
break;
default:
return undefined;
}
return this.blockchainIDtoAlias(blockchainID);
}
/**
* Convert a blockchainId buffer to string and return P or C alias if match of any of that chains.
* @param {BufferAvax} blockchainIDBuffer
* @return {string} blocchainID or alias if exists.
* @private
*/
private blockchainIDtoAlias(blockchainIDBuffer: BufferAvax): string {
const blockchainId = utils.cb58Encode(blockchainIDBuffer);
switch (blockchainId) {
case this._network.cChainBlockchainID:
return 'C';
case this._network.blockchainID:
return 'P';
default:
return blockchainId;
}
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!