PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-sui/src/lib
Просмотр файла: unstakingTransaction.ts
import {
BaseKey,
Entry,
InvalidTransactionError,
ParseTransactionError,
PublicKey as BasePublicKey,
Signature,
TransactionRecipient,
TransactionType,
} from '@bitgo/sdk-core';
import { UnstakingProgrammableTransaction, SuiTransaction, TransactionExplanation, TxData } from './iface';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import utils, { isImmOrOwnedObj } from './utils';
import { Buffer } from 'buffer';
import { Transaction } from './transaction';
import {
builder,
Inputs,
MoveCallTransaction,
ObjectCallArg,
PureCallArg,
TransactionBlockInput,
} from './mystenlab/builder';
import { bcs, CallArg, normalizeSuiAddress, SuiObjectRef } from './mystenlab/types';
import { BCS } from '@mysten/bcs';
import { AMOUNT_UNKNOWN_TEXT, SUI_ADDRESS_LENGTH } from './constants';
import { UnstakingBuilder } from './unstakingBuilder';
import { assertEqualTransactionBlocks } from './compareTransactionBlocks';
export class UnstakingTransaction extends Transaction<UnstakingProgrammableTransaction> {
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}
get suiTransaction(): SuiTransaction<UnstakingProgrammableTransaction> {
return this._suiTransaction;
}
setSuiTransaction(tx: SuiTransaction<UnstakingProgrammableTransaction>): void {
this._suiTransaction = tx;
}
addSignature(publicKey: BasePublicKey, signature: Buffer): void {
this._signatures.push(signature.toString('hex'));
this._signature = { publicKey, signature };
this.serialize();
}
get suiSignature(): Signature {
return this._signature;
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
return true;
}
/** @inheritdoc */
toBroadcastFormat(): string {
if (!this._suiTransaction) {
throw new InvalidTransactionError('Empty transaction');
}
return this.serialize();
}
/** @inheritdoc */
toJson(): TxData {
if (!this._suiTransaction) {
throw new ParseTransactionError('Empty transaction');
}
const tx = this._suiTransaction;
return {
id: this._id,
sender: tx.sender,
kind: { ProgrammableTransaction: tx.tx },
gasData: tx.gasData,
expiration: { None: null },
};
}
/** @inheritDoc */
explainTransaction(): TransactionExplanation {
const result = this.toJson();
const displayOrder = [
'id',
'outputs',
'outputAmount',
'changeOutputs',
'changeAmount',
'fee',
'type',
'module',
'function',
'validatorAddress',
];
const outputs: TransactionRecipient[] = [];
const explanationResult: TransactionExplanation = {
displayOrder,
id: this.id,
outputs,
outputAmount: '0',
changeOutputs: [],
changeAmount: '0',
fee: { fee: this.suiTransaction.gasData.budget.toString() },
type: this.type,
};
switch (this.type) {
case TransactionType.StakingClaim:
return this.explainWithdrawStakedSuiTransaction(result, explanationResult);
default:
throw new InvalidTransactionError('Transaction type not supported');
}
}
/**
* Set the transaction type.
*
* @param {TransactionType} transactionType The transaction type to be set.
*/
transactionType(transactionType: TransactionType): void {
this._type = transactionType;
}
getEntriesForStakedSuiInput(stakedSuiInput: SuiObjectRef, amount?: bigint): { inputs: Entry[]; outputs: Entry[] } {
return {
inputs: [
{
address: normalizeSuiAddress(stakedSuiInput.objectId),
value: amount === undefined ? AMOUNT_UNKNOWN_TEXT : amount.toString(),
coin: this._coinConfig.name,
},
],
outputs: [
{
address: this.suiTransaction.sender,
value: amount === undefined ? AMOUNT_UNKNOWN_TEXT : amount.toString(),
coin: this._coinConfig.name,
},
],
};
}
/**
* @param inputs
* @param transactions
*/
static parseTransactionPairReserialized(
inputs: [unknown, unknown, unknown],
transactions: [unknown, unknown]
): {
stakedObjectRef: SuiObjectRef;
amount: bigint;
} {
const [inputStakedSui, inputAmount, inputSharedObj] = inputs;
if (!ObjectCallArg.is(inputStakedSui)) {
throw new Error('Invalid input staked sui');
}
if (!PureCallArg.is(inputAmount)) {
throw new Error('Invalid input amount');
}
if (!ObjectCallArg.is(inputSharedObj)) {
throw new Error('Invalid input shared object');
}
const amount = BigInt(bcs.de(BCS.U64, Uint8Array.from(inputAmount.Pure)));
if (!isImmOrOwnedObj(inputStakedSui.Object)) {
throw new Error('Invalid input shared object');
}
// make sure we parsed the transaction correctly by rebuilding it and comparing the transaction blocks
assertEqualTransactionBlocks(
{ inputs, transactions },
UnstakingBuilder.getTransactionBlockDataReserialized(inputStakedSui.Object.ImmOrOwned, amount)
);
return {
stakedObjectRef: inputStakedSui.Object.ImmOrOwned,
amount,
};
}
static parseTransactionPair(
inputs: SuiTransaction['tx']['inputs'],
transactions: unknown[]
): {
stakedObjectRef: SuiObjectRef;
amount: bigint;
} {
if (transactions.length !== 2) {
throw new Error('Invalid transaction pair');
}
if (!MoveCallTransaction.is(transactions[0]) || !MoveCallTransaction.is(transactions[1])) {
throw new Error('Invalid transaction pair');
}
if (!Array.isArray(inputs) || inputs.length !== 3) {
throw new Error('Invalid inputs');
}
const [inputStakedSui, inputAmount, inputSharedObj] = inputs;
if (
!TransactionBlockInput.is(inputStakedSui) ||
!TransactionBlockInput.is(inputAmount) ||
!TransactionBlockInput.is(inputSharedObj)
) {
// for unclear reasons there seem to be two different serialization formats that we are dealing with
// try the other one here
return this.parseTransactionPairReserialized(
// we have length checked these earlier
inputs as [unknown, unknown, unknown],
transactions as [unknown, unknown]
);
}
if (
inputStakedSui.type !== 'object' ||
inputAmount.type !== 'pure' ||
typeof inputAmount.value !== 'string' ||
inputSharedObj.type !== 'object' ||
!ObjectCallArg.is(inputStakedSui.value) ||
!isImmOrOwnedObj(inputStakedSui.value.Object)
) {
throw new Error('Invalid inputs');
}
const amount = BigInt(inputAmount.value);
// make sure we parsed the transaction correctly by rebuilding it and comparing the transaction blocks
assertEqualTransactionBlocks(
{ inputs, transactions },
UnstakingBuilder.getTransactionBlockData(inputStakedSui.value.Object.ImmOrOwned, amount)
);
return {
stakedObjectRef: inputStakedSui.value.Object.ImmOrOwned,
amount,
};
}
static parseTransactionSingle(
inputs: SuiTransaction['tx']['inputs'],
tx: unknown
): {
stakedObjectRef: SuiObjectRef;
} {
if (!MoveCallTransaction.is(tx) || !TransactionBlockInput.is(tx.arguments[1])) {
throw new Error('Invalid transaction');
}
const stakedSuiInputIdx = tx.arguments[1].index;
let stakedSuiInput: unknown | SuiObjectRef = inputs[stakedSuiInputIdx];
if (!TransactionBlockInput.is(stakedSuiInput)) {
// for unclear reasons, in tests the stakedSuiInput is not a TransactionBlockInput sometimes
if (!ObjectCallArg.is(stakedSuiInput)) {
throw new Error('Invalid transaction');
}
}
if ('Object' in stakedSuiInput && isImmOrOwnedObj(stakedSuiInput.Object)) {
stakedSuiInput = stakedSuiInput.Object.ImmOrOwned as SuiObjectRef;
} else if ('value' in stakedSuiInput && isImmOrOwnedObj(stakedSuiInput.value.Object)) {
stakedSuiInput = stakedSuiInput.value.Object.ImmOrOwned as SuiObjectRef;
} else {
throw new Error('Invalid transaction');
}
if (!SuiObjectRef.is(stakedSuiInput)) {
throw new Error('Invalid transaction');
}
return {
stakedObjectRef: stakedSuiInput,
};
}
static parseTransaction(tx: UnstakingProgrammableTransaction): {
stakedObjectRef: SuiObjectRef;
amount?: bigint;
} {
const { inputs, transactions } = tx;
if (transactions.length === 1) {
return UnstakingTransaction.parseTransactionSingle(inputs, transactions[0]);
} else if (transactions.length === 2) {
return UnstakingTransaction.parseTransactionPair(inputs, transactions);
} else {
throw new InvalidTransactionError('Invalid transaction');
}
}
/**
* Load the input and output data on this transaction.
*/
loadInputsAndOutputs(): void {
if (!this.suiTransaction) {
return;
}
const parsed = UnstakingTransaction.parseTransaction(this.suiTransaction.tx);
const { inputs, outputs } = this.getEntriesForStakedSuiInput(parsed.stakedObjectRef, parsed.amount);
this._inputs = inputs;
this._outputs = outputs;
}
/**
* Sets this transaction payload
*
* @param {string} rawTransaction
*/
fromRawTransaction(rawTransaction: string): void {
try {
utils.isValidRawTransaction(rawTransaction);
this._suiTransaction = Transaction.deserializeSuiTransaction(
rawTransaction
) as SuiTransaction<UnstakingProgrammableTransaction>;
this._type = utils.getTransactionType(this._suiTransaction.type);
this._id = this._suiTransaction.id;
this.loadInputsAndOutputs();
} catch (e) {
throw e;
}
}
/**
* Helper function for serialize() to get the correct txData with transaction type
*
* @return {TxData}
*/
getTxData(): TxData {
if (!this._suiTransaction) {
throw new InvalidTransactionError('empty transaction');
}
const inputs: CallArg[] | TransactionBlockInput[] = this._suiTransaction.tx.inputs.map((input, index) => {
if (input.hasOwnProperty('Object')) {
return input;
}
if (input.hasOwnProperty('Pure')) {
if (input.Pure.length === SUI_ADDRESS_LENGTH) {
const address = normalizeSuiAddress(
builder.de(BCS.ADDRESS, Buffer.from(input.Pure).toString('base64'), 'base64')
);
return Inputs.Pure(address, BCS.ADDRESS);
} else {
const amount = builder.de(BCS.U64, Buffer.from(input.Pure).toString('base64'), 'base64');
return Inputs.Pure(amount, BCS.U64);
}
}
if (input.kind === 'Input' && (input.value.hasOwnProperty('Object') || input.value.hasOwnProperty('Pure'))) {
return input.value;
}
// what's left is the pure number or address string
return Inputs.Pure(input.value, input.type === 'pure' ? BCS.U64 : BCS.ADDRESS);
});
const programmableTx = {
inputs: inputs,
transactions: this._suiTransaction.tx.transactions,
} as UnstakingProgrammableTransaction;
return {
sender: this._suiTransaction.sender,
expiration: { None: null },
gasData: this._suiTransaction.gasData,
kind: {
ProgrammableTransaction: programmableTx,
},
};
}
/**
* Returns a complete explanation for a unstaking transaction
*
* @param {TxData} json The transaction data in json format
* @param {TransactionExplanation} explanationResult The transaction explanation to be completed
* @returns {TransactionExplanation}
*/
explainWithdrawStakedSuiTransaction(json: TxData, explanationResult: TransactionExplanation): TransactionExplanation {
const outputs: TransactionRecipient[] = [
{
address: this.suiTransaction.sender,
amount: AMOUNT_UNKNOWN_TEXT,
},
];
const outputAmount = AMOUNT_UNKNOWN_TEXT;
return {
...explanationResult,
outputAmount,
outputs,
};
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!