PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-icp/src/lib
Просмотр файла: transaction.ts
import {
BaseKey,
BaseTransaction,
TransactionRecipient,
TransactionType,
InvalidTransactionError,
TransactionType as BitGoTransactionType,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import {
IcpTransaction,
IcpTransactionData,
PayloadsData,
OperationType,
Signatures,
TxData,
IcpTransactionExplanation,
CborUnsignedTransaction,
HttpCanisterUpdate,
ParsedTransaction,
IcpOperation,
UpdateEnvelope,
IcpAccount,
MAX_INGRESS_TTL,
PERMITTED_DRIFT,
RawTransaction,
} from './iface';
import { Utils } from './utils';
export class Transaction extends BaseTransaction {
protected _icpTransactionData: IcpTransactionData;
protected _icpTransaction: IcpTransaction;
protected _payloadsData: PayloadsData;
protected _signedTransaction: string;
protected _signaturePayload: Signatures[];
protected _createdTimestamp: number | bigint | undefined;
protected _utils: Utils;
constructor(_coinConfig: Readonly<CoinConfig>, utils: Utils) {
super(_coinConfig);
this._utils = utils;
}
get icpTransactionData(): IcpTransactionData {
return this._icpTransactionData;
}
set icpTransactionData(icpTransactionData: IcpTransactionData) {
this._icpTransactionData = icpTransactionData;
}
get icpTransaction(): IcpTransaction {
return this._icpTransaction;
}
set icpTransaction(icpTransaction: IcpTransaction) {
this._icpTransaction = icpTransaction;
}
get unsignedTransaction(): string {
return this._payloadsData.unsigned_transaction;
}
get signaturePayload(): Signatures[] {
return this._signaturePayload;
}
set signedTransaction(signature: string) {
this._signedTransaction = signature;
}
get signedTransaction(): string {
return this._signedTransaction;
}
set payloadsData(payloadsData: PayloadsData) {
this._payloadsData = payloadsData;
}
get payloadsData(): PayloadsData {
return this._payloadsData;
}
set createdTimestamp(createdTimestamp: number) {
this._createdTimestamp = createdTimestamp;
}
get createdTimestamp(): number | bigint | undefined {
return this._createdTimestamp;
}
async fromRawTransaction(rawTransaction: string): Promise<void> {
try {
const serializedTxFormatBuffer = Buffer.from(rawTransaction, 'hex');
const serializedTxFormatJsonString = serializedTxFormatBuffer.toString('utf-8');
const jsonRawTransaction: RawTransaction = JSON.parse(serializedTxFormatJsonString);
const payloadsData = jsonRawTransaction.serializedTxHex;
this._payloadsData = payloadsData;
const parsedTx = await this.parseUnsignedTransaction(payloadsData.unsigned_transaction);
const senderPublicKeyHex = jsonRawTransaction.publicKey;
const transactionType = parsedTx.operations[0].type;
switch (transactionType) {
case OperationType.TRANSACTION:
this._icpTransactionData = {
senderAddress: parsedTx.operations[0].account.address,
receiverAddress: parsedTx.operations[1].account.address,
amount: parsedTx.operations[1].amount.value,
fee: parsedTx.operations[2].amount.value,
senderPublicKeyHex: senderPublicKeyHex,
transactionType: transactionType,
expiryTime: Number(parsedTx.metadata.ingress_end ?? parsedTx.metadata.created_at_time + MAX_INGRESS_TTL),
memo: parsedTx.metadata.memo,
};
this._utils.validateRawTransaction(this._icpTransactionData);
this._id = this.generateTransactionId();
break;
default:
throw new Error('Invalid transaction type');
}
} catch (error) {
throw new InvalidTransactionError(`Invalid transaction: ${error.message}`);
}
}
addSignature(signaturePayloads: Signatures[]): void {
if (!signaturePayloads) {
throw new Error('signatures not provided');
}
if (signaturePayloads.length !== this._payloadsData.payloads.length) {
throw new Error('signatures length is not matching');
}
this._signaturePayload = signaturePayloads;
if (this._id === undefined || this._id === null) {
this._id = this.generateTransactionId();
}
}
/** @inheritdoc */
toJson(): TxData {
if (!this._icpTransactionData) {
throw new InvalidTransactionError('Empty transaction');
}
switch (this._icpTransactionData.transactionType) {
case OperationType.TRANSACTION:
const txData: TxData = {
id: this._id,
sender: this._icpTransactionData.senderAddress,
senderPublicKey: this._icpTransactionData.senderPublicKeyHex,
recipient: this._icpTransactionData.receiverAddress,
memo: this._icpTransactionData.memo,
feeAmount: this._icpTransactionData.fee,
expirationTime: this._icpTransactionData.expiryTime,
type: BitGoTransactionType.Send,
};
if (this._icpTransactionData.memo !== undefined) {
txData.memo = this._icpTransactionData.memo;
}
return txData;
default:
throw new Error(`Unsupported transaction type: ${this._icpTransactionData.transactionType}`);
}
}
/** @inheritDoc */
explainTransaction(): IcpTransactionExplanation {
const result = this.toJson();
const displayOrder = ['id', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs', 'fee'];
const outputs: TransactionRecipient[] = [];
const explanationResult = {
displayOrder,
id: this.id,
outputs,
outputAmount: '0',
fee: { fee: '0' },
type: result.type,
changeOutputs: [], // account based does not use change outputs
changeAmount: '0', // account based does not make change
};
switch (explanationResult.type) {
case TransactionType.Send:
return this.explainTransferTransaction(explanationResult);
default:
throw new InvalidTransactionError('Transaction type not supported');
}
}
/**
* Explains a transfer transaction by providing details about the recipients and the total output amount.
*
* @param {IcpTransactionExplanation} explanationResult - The initial explanation result to be extended.
* @returns {IcpTransactionExplanation} The extended explanation result including the output amount and recipients.
*/
explainTransferTransaction(explanationResult: IcpTransactionExplanation): IcpTransactionExplanation {
explanationResult.fee = { fee: this.icpTransactionData.fee };
const recipients = this._utils.getRecipients(this.icpTransactionData);
const outputs: TransactionRecipient[] = [recipients];
const outputAmountBN = recipients.amount;
const outputAmount = outputAmountBN.toString();
return {
...explanationResult,
outputAmount,
outputs,
};
}
/** @inheritdoc */
toBroadcastFormat(): string {
if (!this._signedTransaction) {
throw new InvalidTransactionError('Empty transaction');
}
return this.serialize();
}
serialize(): string {
return this._signedTransaction;
}
async parseUnsignedTransaction(rawTransaction: string): Promise<ParsedTransaction> {
const unsignedTransaction = this._utils.cborDecode(
this._utils.blobFromHex(rawTransaction)
) as CborUnsignedTransaction;
const update = unsignedTransaction.updates[0];
const httpCanisterUpdate = (update as unknown as [string, HttpCanisterUpdate])[1];
httpCanisterUpdate.ingress_expiry = BigInt(unsignedTransaction.ingress_expiries[0]);
return await this.getParsedTransactionFromUpdate(httpCanisterUpdate, false);
}
private async getParsedTransactionFromUpdate(
httpCanisterUpdate: HttpCanisterUpdate,
isSigned: boolean
): Promise<ParsedTransaction> {
const senderPrincipal = this._utils.convertSenderBlobToPrincipal(httpCanisterUpdate.sender);
const ACCOUNT_ID_PREFIX = this._utils.getAccountIdPrefix();
const subAccount = new Uint8Array(32);
const senderAccount = this._utils.getAccountIdFromPrincipalBytes(
ACCOUNT_ID_PREFIX,
Buffer.from(senderPrincipal.buffer),
subAccount
);
const args = await this._utils.fromArgs(httpCanisterUpdate.arg);
const senderOperation: IcpOperation = {
type: OperationType.TRANSACTION,
account: { address: senderAccount },
amount: {
value: `-${args.payment.receiverGets.e8s.toString()}`,
currency: {
symbol: this._coinConfig.family,
decimals: this._coinConfig.decimalPlaces,
},
},
};
const receiverOperation: IcpOperation = {
type: OperationType.TRANSACTION,
account: { address: args.to.hash.toString('hex') },
amount: {
value: args.payment.receiverGets.e8s.toString(),
currency: {
symbol: this._coinConfig.family,
decimals: this._coinConfig.decimalPlaces,
},
},
};
const feeOperation: IcpOperation = {
type: OperationType.FEE,
account: { address: senderAccount },
amount: {
value: `-${args.maxFee.e8s.toString()}`,
currency: {
symbol: this._coinConfig.family,
decimals: this._coinConfig.decimalPlaces,
},
},
};
const accountIdentifierSigners: IcpAccount[] = [];
if (isSigned) {
accountIdentifierSigners.push({ address: senderAccount });
}
const parsedTxn: ParsedTransaction = {
operations: [senderOperation, receiverOperation, feeOperation],
metadata: {
created_at_time: args.createdAtTime.timestampNanos,
memo: Number(args.memo.memo),
ingress_end: Number(httpCanisterUpdate.ingress_expiry) + PERMITTED_DRIFT,
},
account_identifier_signers: accountIdentifierSigners,
};
this.createdTimestamp = args.createdAtTime.timestampNanos;
return parsedTxn;
}
async parseSignedTransaction(rawTransaction: string): Promise<ParsedTransaction> {
const signedTransaction = this._utils.cborDecode(this._utils.blobFromHex(rawTransaction));
const httpCanisterUpdate = (signedTransaction as UpdateEnvelope).content as HttpCanisterUpdate;
httpCanisterUpdate.ingress_expiry = BigInt((signedTransaction as UpdateEnvelope).content.ingress_expiry);
return await this.getParsedTransactionFromUpdate(httpCanisterUpdate, true);
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
return true;
}
/**
* Generates a unique transaction ID for the current transaction.
* The transaction ID is derived using the unsigned transaction data,
* the sender's address, and the receiver's address.
*
* @returns {string} The generated transaction ID.
*/
private generateTransactionId(): string {
const id = this._utils.getTransactionId(
this.unsignedTransaction,
this.icpTransactionData.senderAddress,
this.icpTransactionData.receiverAddress
);
return id;
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!