PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-dot/src/lib
Просмотр файла: batchTransactionBuilder.ts
import {
BuildTransactionError,
InvalidTransactionError,
NotImplementedError,
TransactionType,
toUint8Array,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { DecodedSignedTx, DecodedSigningPayload, UnsignedTransaction } from '@substrate/txwrapper-core';
import { methods } from '@substrate/txwrapper-polkadot';
import { ValidationResult } from 'joi';
import {
AddAnonymousProxyBatchCallArgs,
AddProxyBatchCallArgs,
BatchCallObject,
BatchArgs,
MethodNames,
SectionNames,
StakeArgsPayee,
StakeBatchCallArgs,
StakeBatchCallPayee,
UnbondCallArgs,
StakeMoreCallArgs,
} from './iface';
import {
getDelegateAddress,
isStakeBatchCallPayeeStaked,
isStakeBatchCallPayeeStash,
isStakeBatchCallPayeeController,
isStakeBatchCallPayeeAccount,
} from './iface_utils';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import { BatchTransactionSchema } from './txnSchema';
import utils from './utils';
export class BatchTransactionBuilder extends TransactionBuilder {
protected _calls: string[];
protected _type: TransactionType;
private _atomic = false;
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}
/** @inheritDoc */
protected buildTransaction(): UnsignedTransaction {
return this.buildBatchTransaction();
}
/**
* Build a transaction which batches together multiple transactions.
* The transactions which are batched together are passed in as an array of hex strings
* which are composed of the method to call and the arguments to pass into the method.
*
* @returns {UnsignedTransaction}
*
* @see https://polkadot.js.org/docs/substrate/extrinsics/#batchcalls-veccall
*/
protected buildBatchTransaction(): UnsignedTransaction {
const baseTxInfo = this.createBaseTxInfo();
if (this._atomic) {
return methods.utility.batchAll(
{
calls: this._calls,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
} else {
return methods.utility.batch(
{
calls: this._calls,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
}
}
protected get transactionType(): TransactionType {
return TransactionType.Batch;
}
/**
* Set multiple unsigned transactions to be batched and broadcast as a single transaction
*
* @param {BatchCall[]} calls unsigned transactions
* @returns {BatchTransactionBuilder} This batch transaction builder.
*/
calls(calls: string[]): this {
this.validateCalls(calls);
this._calls = calls;
return this;
}
/**
* If true when a batched call fails the entire transactions is rolled back, if false no roll back
* is performed and the effects of any successful call prior to the error remain.
*
* @param atomic true if calls must succeed atomically, false otherwise.
*/
atomic(atomic: boolean): this {
this._atomic = atomic;
return this;
}
/** @inheritdoc */
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx): void {
const txMethod = decodedTxn.method.args as unknown as BatchArgs;
const validationResult = this.validateBatchTransactionFields(txMethod.calls);
if (validationResult.error) {
throw new InvalidTransactionError(`Transaction validation failed: ${validationResult.error.message}`);
}
}
/** @inheritdoc */
protected fromImplementation(rawTransaction: string): Transaction {
const tx = super.fromImplementation(rawTransaction);
if (this._method?.name === MethodNames.Batch || this._method?.name === MethodNames.BatchAll) {
if (this._method?.name === MethodNames.BatchAll) {
this.atomic(true);
}
const txMethod = this._method.args as BatchArgs;
if (!txMethod.calls) {
throw new InvalidTransactionError('failed to decode calls from batch transaction');
}
const callsToBatch: string[] = [];
txMethod.calls.forEach((call) => {
const method = (call as BatchCallObject).callIndex;
const decodedMethod = toUint8Array(utils.stripHexPrefix(method));
const decodedCall = this._registry.findMetaCall(decodedMethod);
if (
decodedCall.section === SectionNames.Proxy &&
(decodedCall.method === MethodNames.Anonymous || decodedCall.method === MethodNames.PureProxy)
) {
callsToBatch.push(this.getPureProxyCall(call.args as AddAnonymousProxyBatchCallArgs));
} else if (decodedCall.section === SectionNames.Proxy && decodedCall.method === MethodNames.AddProxy) {
callsToBatch.push(this.getAddProxyCall(call.args as AddProxyBatchCallArgs));
} else if (decodedCall.section === SectionNames.Staking && decodedCall.method === MethodNames.BondExtra) {
callsToBatch.push(this.getBondExtraCall(call.args as StakeMoreCallArgs));
} else if (decodedCall.section === SectionNames.Staking && decodedCall.method === MethodNames.Bond) {
callsToBatch.push(this.getBondCall(call.args as StakeBatchCallArgs));
} else if (decodedCall.section === SectionNames.Staking && decodedCall.method === MethodNames.Unbond) {
callsToBatch.push(this.getUnbondCall(call.args as UnbondCallArgs));
} else if (decodedCall.section === SectionNames.Staking && decodedCall.method === MethodNames.Chill) {
callsToBatch.push(this.getChillCall());
} else if (decodedCall.section === SectionNames.Proxy && decodedCall.method === MethodNames.RemoveProxy) {
callsToBatch.push(this.getRemoveProxyCall(call.args as AddProxyBatchCallArgs));
} else {
throw new NotImplementedError(`batching of transaction with index ${method} unsupported`);
}
});
this.calls(callsToBatch);
} else {
throw new InvalidTransactionError(
`Invalid Transaction Type: ${this._method?.name}. Expected ${MethodNames.Batch}`
);
}
return tx;
}
/** @inheritdoc */
validateTransaction(_: Transaction): void {
super.validateTransaction(_);
this.validateFields();
}
/**
* Validate list of unsigned transactions added to batch
*
* @param {string[]} calls
*
*/
validateCalls(calls: string[]): void {
calls.forEach((call) => {
if (call.slice(0, 2) !== '0x') {
// example: '0x160400000000000000'
throw new BuildTransactionError('call in string format must be hex format of a method and its arguments');
}
});
}
private validateFields(): void {
const validationResult = this.validateBatchTransactionFields(this._calls);
if (validationResult.error) {
throw new InvalidTransactionError(
`AddressInitialization Transaction validation failed: ${validationResult.error.message}`
);
}
}
private validateBatchTransactionFields(calls: (string | BatchCallObject)[]): ValidationResult {
return BatchTransactionSchema.validate({
calls,
});
}
private getPureProxyCall(args: AddAnonymousProxyBatchCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = utils.pureProxy(
{
proxyType: args.proxy_type,
index: args.index,
delay: args.delay,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getBondCall(args: StakeBatchCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.staking.bond(
{
value: args.value,
payee: this.getPayee(args.payee),
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getUnbondCall(args: UnbondCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.staking.unbond(
{
value: args.value,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getPayee(payee: StakeBatchCallPayee): StakeArgsPayee {
if (isStakeBatchCallPayeeStash(payee)) {
return 'Stash';
} else if (isStakeBatchCallPayeeController(payee)) {
return 'Controller';
} else if (isStakeBatchCallPayeeAccount(payee)) {
return { Account: payee.account };
} else if (isStakeBatchCallPayeeStaked(payee)) {
return 'Staked';
} else {
throw new Error(`Invalid payee: ${payee}`);
}
}
private getAddProxyCall(args: AddProxyBatchCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.proxy.addProxy(
{
delegate: getDelegateAddress(args),
proxyType: args.proxy_type,
delay: args.delay,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getBondExtraCall(args: StakeMoreCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.staking.bondExtra(
{
maxAdditional: args.max_additional,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getRemoveProxyCall(args: AddProxyBatchCallArgs): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.proxy.removeProxy(
{
delegate: getDelegateAddress(args),
proxyType: args.proxy_type,
delay: args.delay,
},
baseTxInfo.baseTxInfo,
baseTxInfo.options
);
return unsigned.method;
}
private getChillCall(): string {
const baseTxInfo = this.createBaseTxInfo();
const unsigned = methods.staking.chill({}, baseTxInfo.baseTxInfo, baseTxInfo.options);
return unsigned.method;
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!