@multiversx/sdk-transaction-decoder
Version:
Transaction metadata decoder
269 lines (268 loc) • 9.58 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenTransferProperties = exports.TransactionMetadataTransfer = exports.TransactionMetadata = exports.TransactionToDecode = exports.TransactionDecoder = void 0;
const bech32_1 = require("bech32");
const SMART_CONTRACT_HEX_PUBKEY_PREFIX = "0".repeat(16);
class TransactionDecoder {
getTransactionMetadata(transaction) {
const metadata = this.getNormalTransactionMetadata(transaction);
const esdtMetadata = this.getEsdtTransactionMetadata(metadata);
if (esdtMetadata) {
return esdtMetadata;
}
const nftMetadata = this.getNftTransferMetadata(metadata);
if (nftMetadata) {
return nftMetadata;
}
const multiMetadata = this.getMultiTransferMetadata(metadata);
if (multiMetadata) {
return multiMetadata;
}
return metadata;
}
getNormalTransactionMetadata(transaction) {
const metadata = new TransactionMetadata();
metadata.sender = transaction.sender;
metadata.receiver = transaction.receiver;
metadata.value = BigInt(transaction.value);
if (!transaction.data) {
metadata.functionName = 'transfer';
metadata.functionArgs = undefined;
return metadata;
}
const decodedData = this.base64Decode(transaction.data);
const dataComponents = decodedData.split('@');
const args = dataComponents.slice(1);
if (args.every((x) => this.isSmartContractArgument(x))) {
metadata.functionName = dataComponents[0];
metadata.functionArgs = args;
}
if (args.length === 0 && !this.isSmartContract(transaction.receiver)) {
metadata.functionName = 'transfer';
metadata.functionArgs = undefined;
}
if (metadata.functionName === 'relayedTx' && metadata.functionArgs && metadata.functionArgs.length === 1) {
try {
const relayedTransaction = JSON.parse(this.hexToString(metadata.functionArgs[0]));
relayedTransaction.value = relayedTransaction.value.toString();
relayedTransaction.sender = this.bech32Encode(this.base64ToHex(relayedTransaction.sender));
relayedTransaction.receiver = this.bech32Encode(this.base64ToHex(relayedTransaction.receiver));
return this.getNormalTransactionMetadata(relayedTransaction);
}
catch (error) {
// nothing special
}
}
if (metadata.functionName === 'relayedTxV2' && metadata.functionArgs && metadata.functionArgs.length === 4) {
try {
const relayedTransaction = new TransactionToDecode();
relayedTransaction.sender = transaction.receiver;
relayedTransaction.receiver = this.bech32Encode(metadata.functionArgs[0]);
relayedTransaction.data = this.base64Encode(this.hexToString(metadata.functionArgs[2]));
relayedTransaction.value = '0';
return this.getNormalTransactionMetadata(relayedTransaction);
}
catch (error) {
// nothing special
}
}
return metadata;
}
getMultiTransferMetadata(metadata) {
if (metadata.sender !== metadata.receiver) {
return undefined;
}
if (metadata.functionName !== 'MultiESDTNFTTransfer') {
return undefined;
}
const args = metadata.functionArgs;
if (!args) {
return undefined;
}
if (args.length < 3) {
return undefined;
}
if (!this.isAddressValid(args[0])) {
return undefined;
}
const receiver = this.bech32Encode(args[0]);
const transferCount = this.hexToNumber(args[1]);
const result = new TransactionMetadata();
if (!result.transfers) {
result.transfers = [];
}
let index = 2;
for (let i = 0; i < transferCount; i++) {
const identifier = this.hexToString(args[index++]);
const nonce = args[index++];
const value = this.hexToBigInt(args[index++]);
if (nonce && this.hexToNumber(nonce) > 0) {
result.transfers.push({
value,
properties: {
collection: identifier,
identifier: `${identifier}-${nonce}`,
},
});
}
else {
result.transfers.push({
value,
properties: {
token: identifier,
},
});
}
}
result.sender = metadata.sender;
result.receiver = receiver;
if (args.length > index) {
result.functionName = this.hexToString(args[index++]);
result.functionArgs = args.slice(index++);
}
return result;
}
getNftTransferMetadata(metadata) {
if (metadata.sender !== metadata.receiver) {
return undefined;
}
if (metadata.functionName !== 'ESDTNFTTransfer') {
return undefined;
}
const args = metadata.functionArgs;
if (!args) {
return undefined;
}
if (args.length < 4) {
return undefined;
}
if (!this.isAddressValid(args[3])) {
return undefined;
}
const collectionIdentifier = this.hexToString(args[0]);
const nonce = args[1];
const value = this.hexToBigInt(args[2]);
const receiver = this.bech32Encode(args[3]);
const result = new TransactionMetadata();
result.sender = metadata.sender;
result.receiver = receiver;
result.value = value;
if (args.length > 4) {
result.functionName = this.hexToString(args[4]);
result.functionArgs = args.slice(5);
}
result.transfers = [{
value,
properties: {
collection: collectionIdentifier,
identifier: `${collectionIdentifier}-${nonce}`,
},
}];
return result;
}
base64Encode(str) {
return Buffer.from(str).toString('base64');
}
base64Decode(str) {
return Buffer.from(str, 'base64').toString('binary');
}
hexToNumber(hex) {
return parseInt(hex, 16);
}
getEsdtTransactionMetadata(metadata) {
if (metadata.functionName !== 'ESDTTransfer') {
return undefined;
}
const args = metadata.functionArgs;
if (!args) {
return undefined;
}
if (args.length < 2) {
return undefined;
}
const tokenIdentifier = this.hexToString(args[0]);
const value = this.hexToBigInt(args[1]);
const result = new TransactionMetadata();
result.sender = metadata.sender;
result.receiver = metadata.receiver;
if (args.length > 2) {
result.functionName = this.hexToString(args[2]);
result.functionArgs = args.slice(3);
}
result.transfers = [{
value,
properties: {
identifier: tokenIdentifier,
},
}];
result.value = value;
return result;
}
bech32Encode(address) {
const pubKey = Buffer.from(address, "hex");
const words = bech32_1.bech32.toWords(pubKey);
return bech32_1.bech32.encode('erd', words);
}
bech32Decode(address) {
const decoded = bech32_1.bech32.decodeUnsafe(address);
return decoded ? Buffer.from(bech32_1.bech32.fromWords(decoded.words)).toString('hex') : undefined;
}
isAddressValid(address) {
return Buffer.from(address, "hex").length == 32;
}
isSmartContract(address) {
var _a, _b;
return (_b = (_a = this.bech32Decode(address)) === null || _a === void 0 ? void 0 : _a.startsWith(SMART_CONTRACT_HEX_PUBKEY_PREFIX)) !== null && _b !== void 0 ? _b : false;
}
isSmartContractArgument(arg) {
if (!this.isHex(arg)) {
return false;
}
if (arg.length % 2 !== 0) {
return false;
}
return true;
}
isHex(value) {
return new RegExp(/[^a-f0-9]/gi).test(value) === false;
}
base64ToHex(str) {
return Buffer.from(str, 'base64').toString('hex');
}
hexToString(hex) {
return Buffer.from(hex, 'hex').toString('ascii');
}
hexToBigInt(hex) {
if (!hex) {
return BigInt(0);
}
return BigInt('0x' + hex);
}
}
exports.TransactionDecoder = TransactionDecoder;
class TransactionToDecode {
constructor() {
this.sender = '';
this.receiver = '';
this.data = '';
this.value = '0';
}
}
exports.TransactionToDecode = TransactionToDecode;
class TransactionMetadata {
constructor() {
this.sender = '';
this.receiver = '';
this.value = BigInt(0);
}
}
exports.TransactionMetadata = TransactionMetadata;
class TransactionMetadataTransfer {
constructor() {
this.value = BigInt(0);
}
}
exports.TransactionMetadataTransfer = TransactionMetadataTransfer;
class TokenTransferProperties {
}
exports.TokenTransferProperties = TokenTransferProperties;