diff --git a/src/errors.ts b/src/errors.ts index 1f23fd1c..30310edc 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,362 +2,373 @@ * The base class for exceptions (errors). */ export class Err extends Error { - inner: Error | undefined = undefined; + inner: Error | undefined = undefined; - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } + public constructor(message: string, inner?: Error) { + super(message); + this.inner = inner; + } - /** - * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). - */ - summary(): any[] { - let result = []; + /** + * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). + */ + summary(): any[] { + let result = []; - result.push({ name: this.name, message: this.message }); + result.push({ name: this.name, message: this.message }); - let inner: any = this.inner; - while (inner) { - result.push({ name: inner.name, message: inner.message }); - inner = inner.inner; - } + let inner: any = this.inner; + while (inner) { + result.push({ name: inner.name, message: inner.message }); + inner = inner.inner; + } - return result; - } + return result; + } } /** * Signals invalid arguments for a function, for an operation. */ export class ErrInvalidArgument extends Err { - public constructor(message: string, inner?: Error) { - super(`Invalid argument: ${message}`, inner); - } + public constructor(message: string, inner?: Error) { + super(`Invalid argument: ${message}`, inner); + } } /** * Signals an unsupported operation. */ export class ErrUnsupportedOperation extends Err { - public constructor(operation: string, reason: string = "not specified") { - super(`Operation "${operation}" not supported. Reason: ${reason}`); - } + public constructor(operation: string, reason: string = "not specified") { + super(`Operation "${operation}" not supported. Reason: ${reason}`); + } } /** * Signals the provisioning of objects of unexpected (bad) types. */ export class ErrBadType extends Err { - public constructor(name: string, type: any, value?: any) { - super(`Bad type of "${name}": ${value}. Expected type: ${type}`); - } + public constructor(name: string, type: any, value?: any) { + super(`Bad type of "${name}": ${value}. Expected type: ${type}`); + } } /** * Signals that an invariant failed. */ export class ErrInvariantFailed extends Err { - public constructor(message: string) { - super(`Invariant failed: [${message}]`); - } + public constructor(message: string) { + super(`Invariant failed: [${message}]`); + } } /** * Signals an unexpected condition. */ export class ErrUnexpectedCondition extends Err { - public constructor(message: string) { - super(`Unexpected condition: [${message}]`); - } + public constructor(message: string) { + super(`Unexpected condition: [${message}]`); + } } /** * Signals issues with {@link Address} instantiation. */ export class ErrAddressCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create address from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create address from: ${input}`; + super(message, inner); + } } /** * Signals issues with the HRP of an {@link Address}. */ export class ErrAddressBadHrp extends Err { - public constructor(expected: string, got: string) { - super(`Wrong address HRP. Expected: ${expected}, got ${got}`); - } + public constructor(expected: string, got: string) { + super(`Wrong address HRP. Expected: ${expected}, got ${got}`); + } } /** * Signals the presence of an empty / invalid address. */ export class ErrAddressEmpty extends Err { - public constructor() { - super(`Address is empty`); - } + public constructor() { + super(`Address is empty`); + } } /** * Signals an invalid value for {@link GasLimit} objects. */ export class ErrNotEnoughGas extends Err { - public constructor(value: number) { - super(`Not enough gas provided: ${value}`); - } + public constructor(value: number) { + super(`Not enough gas provided: ${value}`); + } } /** * Signals an invalid value for {@link Nonce} objects. */ export class ErrNonceInvalid extends Err { - public constructor(value: number) { - super(`Invalid nonce: ${value}`); - } + public constructor(value: number) { + super(`Invalid nonce: ${value}`); + } } /** * Signals an invalid value for {@link TransactionVersion} objects. */ export class ErrTransactionVersionInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction version: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction version: ${value}`); + } } /** * Signals an invalid value for {@link TransactionOptions} objects. */ export class ErrTransactionOptionsInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction options: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction options: ${value}`); + } } /** * Signals an error related to signing a message (a transaction). */ export class ErrSignatureCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create signature from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create signature from: ${input}`; + super(message, inner); + } } /** * Signals an invalid value for the name of a {@link ContractFunction}. */ export class ErrInvalidFunctionName extends Err { - public constructor() { - super(`Invalid function name`); - } + public constructor() { + super(`Invalid function name`); + } } /** * Signals a failed operation, since the Timer is already running. */ export class ErrAsyncTimerAlreadyRunning extends Err { - public constructor() { - super("Async timer already running"); - } + public constructor() { + super("Async timer already running"); + } } /** * Signals a failed operation, since the Timer has been aborted. */ export class ErrAsyncTimerAborted extends Err { - public constructor() { - super("Async timer aborted"); - } + public constructor() { + super("Async timer aborted"); + } } /** * Signals a timout for a {@link TransactionWatcher}. */ export class ErrTransactionWatcherTimeout extends Err { - public constructor() { - super(`TransactionWatcher has timed out`); - } + public constructor() { + super(`TransactionWatcher has timed out`); + } } /** * Signals an issue related to waiting for a specific transaction status. */ export class ErrExpectedTransactionStatusNotReached extends Err { - public constructor() { - super(`Expected transaction status not reached`); - } + public constructor() { + super(`Expected transaction status not reached`); + } } /** * Signals an issue related to waiting for specific transaction events. */ export class ErrExpectedTransactionEventsNotFound extends Err { - public constructor() { - super(`Expected transaction events not found`); - } + public constructor() { + super(`Expected transaction events not found`); + } } /** * Signals a generic error in the context of Smart Contracts. */ export class ErrContract extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } export class ErrContractHasNoAddress extends ErrContract { - public constructor() { - super(` + public constructor() { + super(` The smart contract has no address set. Make sure you provide the address in the constructor, or call setAddress() appropriately. If you need to recompute the address of the contract, make use of SmartContract.computeAddress() (static method). `); - } + } } /** * Signals an error thrown by the mock-like test objects. */ export class ErrMock extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic type error. */ export class ErrTypingSystem extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a missing field on a struct. */ export class ErrMissingFieldOnStruct extends Err { - public constructor(fieldName: string, structName: string) { - super(`field ${fieldName} does not exist on struct ${structName}`); - } + public constructor(fieldName: string, structName: string) { + super(`field ${fieldName} does not exist on struct ${structName}`); + } } /** * Signals a missing field on an enum. */ export class ErrMissingFieldOnEnum extends Err { - public constructor(fieldName: string, enumName: string) { - super(`field ${fieldName} does not exist on enum ${enumName}`); - } + public constructor(fieldName: string, enumName: string) { + super(`field ${fieldName} does not exist on enum ${enumName}`); + } } /** * Signals an error when parsing the contract results. */ export class ErrCannotParseContractResults extends Err { - public constructor(details: string) { - super(`cannot parse contract results: ${details}`); - } + public constructor(details: string) { + super(`cannot parse contract results: ${details}`); + } } /** * Signals an error when parsing the outcome of a transaction (results and logs). */ export class ErrCannotParseTransactionOutcome extends Err { - public constructor(transactionHash: string, message: string) { - super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); - } + public constructor(transactionHash: string, message: string) { + super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); + } } /** * Signals a generic codec (encode / decode) error. */ export class ErrCodec extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic contract interaction error. */ export class ErrContractInteraction extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals that a method is not yet implemented */ export class ErrNotImplemented extends Err { - public constructor() { - super("Method not yet implemented"); - } + public constructor() { + super("Method not yet implemented"); + } } /** * Signals invalid arguments when using the relayed v1 builder */ export class ErrInvalidRelayedV1BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v1 builder"); - } + public constructor() { + super("invalid arguments for relayed v1 builder"); + } } /** * Signals invalid arguments when using the relayed v2 builder */ export class ErrInvalidRelayedV2BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v2 builder"); - } + public constructor() { + super("invalid arguments for relayed v2 builder"); + } } /** * Signals that Gas Limit isn't 0 for an inner tx when using relayed v2 builder */ export class ErrGasLimitShouldBe0ForInnerTransaction extends Err { - public constructor() { - super("gas limit must be 0 for the inner transaction for relayed v2"); - } + public constructor() { + super("gas limit must be 0 for the inner transaction for relayed v2"); + } } /** * Signals that the `isCompleted` property is missing on the transaction obect and is needed for the Transaction Watcher */ export class ErrIsCompletedFieldIsMissingOnTransaction extends Err { - public constructor() { - super("The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.") - } + public constructor() { + super( + "The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.", + ); + } } /** * Signals that the provided token identifier is not valid */ export class ErrInvalidTokenIdentifier extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic bad usage error */ export class ErrBadUsage extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals an invalid inner transaction for relayed transactions */ -export class ErrInvalidInnerTransaction extends Err{ - public constructor(message: string){ - super(message); - } +export class ErrInvalidInnerTransaction extends Err { + public constructor(message: string) { + super(message); + } +} + +/** + * Signals an error when parsing the logs of a transaction. + */ +export class ErrParseTransactionOutcome extends Err { + public constructor(message: string) { + super(message); + } } diff --git a/src/tokenOperations/tokenOperationsOutcomeParser.ts b/src/tokenOperations/tokenOperationsOutcomeParser.ts index 260ffb3d..8fc489f3 100644 --- a/src/tokenOperations/tokenOperationsOutcomeParser.ts +++ b/src/tokenOperations/tokenOperationsOutcomeParser.ts @@ -3,7 +3,6 @@ import { ErrCannotParseTransactionOutcome } from "../errors"; import { IAddress } from "../interface"; import { bufferToBigInt } from "./codec"; - interface ITransactionOnNetwork { hash: string; contractResults: IContractResults; @@ -42,8 +41,7 @@ export interface IRegisterAndSetAllRolesOutcome { roles: string[]; } -export interface IToggleBurnRoleGloballyOutcome { -} +export interface IToggleBurnRoleGloballyOutcome {} export interface ISetSpecialRoleOutcome { userAddress: string; @@ -71,8 +69,7 @@ export interface IBurnOutcome { burntSupply: string; } -export interface IPausingOutcome { -} +export interface IPausingOutcome {} export interface IFreezingOutcome { userAddress: string; @@ -106,6 +103,9 @@ export interface IBurnQuantityOutcome { burntQuantity: string; } +/** + * @deprecated Use {@link TokenManagementTransactionsOutcomeParser} + */ export class TokenOperationsOutcomeParser { parseIssueFungible(transaction: ITransactionOnNetwork): IESDTIssueOutcome { this.ensureNoError(transaction); @@ -146,7 +146,7 @@ export class TokenOperationsOutcomeParser { const tokenIdentifier = this.extractTokenIdentifier(eventRegister); const eventSetRole = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); - const roles = eventSetRole.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = eventSetRole.topics.slice(3).map((topic) => topic.valueOf().toString()); return { tokenIdentifier, roles }; } @@ -167,7 +167,7 @@ export class TokenOperationsOutcomeParser { const event = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); const userAddress = event.address.toString(); const tokenIdentifier = this.extractTokenIdentifier(event); - const roles = event.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = event.topics.slice(3).map((topic) => topic.valueOf().toString()); return { userAddress, tokenIdentifier, roles }; } @@ -284,13 +284,16 @@ export class TokenOperationsOutcomeParser { const data = Buffer.from(event.data.substring(1), "hex").toString(); const message = event.topics[1]?.valueOf().toString(); - throw new ErrCannotParseTransactionOutcome(transaction.hash, `encountered signalError: ${message} (${data})`); + throw new ErrCannotParseTransactionOutcome( + transaction.hash, + `encountered signalError: ${message} (${data})`, + ); } } } private findSingleEventByIdentifier(transaction: ITransactionOnNetwork, identifier: string): ITransactionEvent { - const events = this.gatherAllEvents(transaction).filter(event => event.identifier == identifier); + const events = this.gatherAllEvents(transaction).filter((event) => event.identifier == identifier); if (events.length == 0) { throw new ErrCannotParseTransactionOutcome(transaction.hash, `cannot find event of type ${identifier}`); @@ -330,4 +333,3 @@ export class TokenOperationsOutcomeParser { return Address.fromBuffer(event.topics[3]?.valueOf()).toString(); } } - diff --git a/src/transaction.ts b/src/transaction.ts index f39c232f..39303730 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -560,51 +560,25 @@ export class TransactionNext { /** * Creates a new Transaction object. */ - public constructor({ - sender, - receiver, - gasLimit, - chainID, - nonce, - value, - senderUsername, - receiverUsername, - gasPrice, - data, - version, - options, - guardian, - }: { - nonce?: bigint; - value?: bigint; - sender: string; - receiver: string; - senderUsername?: string; - receiverUsername?: string; - gasPrice?: bigint; - gasLimit: bigint; - data?: Uint8Array; - chainID: string; - version?: number; - options?: number; - guardian?: string; - }) { - this.nonce = nonce || 0n; - this.value = value || 0n; - this.sender = sender; - this.receiver = receiver; - this.senderUsername = senderUsername || ""; - this.receiverUsername = receiverUsername || ""; - this.gasPrice = gasPrice || BigInt(TRANSACTION_MIN_GAS_PRICE); - this.gasLimit = gasLimit; - this.data = data || new Uint8Array(); - this.chainID = chainID; - this.version = version || TRANSACTION_VERSION_DEFAULT; - this.options = options || TRANSACTION_OPTIONS_DEFAULT; - this.guardian = guardian || ""; + public constructor(init: Partial) { + this.nonce = 0n; + this.value = 0n; + this.sender = ""; + this.receiver = ""; + this.senderUsername = ""; + this.receiverUsername = ""; + this.gasPrice = BigInt(TRANSACTION_MIN_GAS_PRICE); + this.gasLimit = 0n; + this.data = new Uint8Array(); + this.chainID = ""; + this.version = TRANSACTION_VERSION_DEFAULT; + this.options = TRANSACTION_OPTIONS_DEFAULT; + this.guardian = ""; this.signature = new Uint8Array(); this.guardianSignature = new Uint8Array(); + + Object.assign(this, init); } } @@ -616,19 +590,22 @@ export class TransactionComputer { computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): bigint { const moveBalanceGas = BigInt( - networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte); + networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte, + ); if (moveBalanceGas > transaction.gasLimit) { - throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); + throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); } const gasPrice = transaction.gasPrice; const feeForMove = moveBalanceGas * gasPrice; if (moveBalanceGas === transaction.gasLimit) { - return feeForMove; + return feeForMove; } const diff = transaction.gasLimit - moveBalanceGas; - const modifiedGasPrice = BigInt(new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0)); + const modifiedGasPrice = BigInt( + new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0), + ); const processingFee = diff * modifiedGasPrice; return feeForMove + processingFee; @@ -651,7 +628,7 @@ export class TransactionComputer { const serialized = JSON.stringify(plainTransaction); - return Buffer.from(serialized); + return new Uint8Array(Buffer.from(serialized)); } computeTransactionHash(transaction: ITransactionNext): Uint8Array { @@ -685,5 +662,4 @@ export class TransactionComputer { guardianSignature: transaction.guardianSignature.length == 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex") } } - } diff --git a/src/transactionsOutcomeParsers/resources.ts b/src/transactionsOutcomeParsers/resources.ts new file mode 100644 index 00000000..4fd7249a --- /dev/null +++ b/src/transactionsOutcomeParsers/resources.ts @@ -0,0 +1,55 @@ +export class TransactionEvent { + address: string; + identifier: string; + topics: string[]; + data: Uint8Array; + + constructor(init: Partial) { + this.address = ""; + this.identifier = ""; + this.topics = []; + this.data = new Uint8Array(); + + Object.assign(this, init); + } +} + +export class TransactionLogs { + address: string; + events: TransactionEvent[]; + + constructor(init: Partial) { + this.address = ""; + this.events = []; + + Object.assign(this, init); + } +} + +export class SmartContractResult { + sender: string; + receiver: string; + data: Uint8Array; + logs: TransactionLogs; + + constructor(init: Partial) { + this.sender = ""; + this.receiver = ""; + this.data = new Uint8Array(); + this.logs = new TransactionLogs({}); + + Object.assign(this, init); + } +} + +export class TransactionOutcome { + smartContractResults: SmartContractResult[]; + transactionLogs: TransactionLogs; + + constructor(init: Partial) { + this.smartContractResults = []; + this.transactionLogs = new TransactionLogs({}); + + Object.assign(this, init); + } +} diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts new file mode 100644 index 00000000..a95813f8 --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts @@ -0,0 +1,528 @@ +import { assert } from "chai"; +import { ErrParseTransactionOutcome } from "../errors"; +import { TokenManagementTransactionsOutcomeParser } from "./tokenManagementTransactionsOutcomeParser"; +import { SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources"; + +describe("test token management transactions outcome parser", () => { + const parser = new TokenManagementTransactionsOutcomeParser(); + + it("should test ensure error", () => { + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "signalError", + topics: ["Avk0jZ1kR+l9c76wQQoYcu4hvXPz+jxxTdqQeaCrbX8=", "dGlja2VyIG5hbWUgaXMgbm90IHZhbGlk"], + data: Buffer.from("QDc1NzM2NTcyMjA2NTcyNzI2Zjcy", "base64"), + }); + + const logs = new TransactionLogs({ events: [event] }); + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); + + assert.throws( + () => { + parser.parseIssueFungible(txOutcome); + }, + ErrParseTransactionOutcome, + "encountered signalError: ticker name is not valid (user error)", + ); + }); + + it("should test parse issue fungible", () => { + const identifier = "ZZZ-9ee87d"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issue", + topics: [base64Identifier, "U0VDT05E", "Wlpa", "RnVuZ2libGVFU0RU", "Ag=="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); + + const outcome = parser.parseIssueFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse issue non fungible", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const firstEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "upgradeProperties", + topics: ["TkZULWYwMWQxZQ==", "", "Y2FuVXBncmFkZQ==", "dHJ1ZQ==", "Y2FuQWRkU3BlY2lhbFJvbGVz", "dHJ1ZQ=="], + }); + + const secondEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetBurnRoleForAll", + topics: ["TkZULWYwMWQxZQ==", "", "", "RVNEVFJvbGVCdXJuRm9yQWxs"], + }); + + const thirdEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueNonFungible", + topics: [base64Identifier, "TkZURVNU", "TkZU", "Tm9uRnVuZ2libGVFU0RU"], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [firstEvent, secondEvent, thirdEvent], + }); + + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); + + const outcome = parser.parseIssueNonFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse issue semi fungible", () => { + const identifier = "SEMIFNG-2c6d9f"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueSemiFungible", + topics: [base64Identifier, "U0VNSQ==", "U0VNSUZORw==", "U2VtaUZ1bmdpYmxlRVNEVA=="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); + + const outcome = parser.parseIssueSemiFungible(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse register meta esdt", () => { + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerMetaESDT", + topics: [base64Identifier, "TUVURVNU", "TUVUQVRFU1Q=", "TWV0YUVTRFQ="], + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ transactionLogs: logs }); + + const outcome = parser.parseRegisterMetaEsdt(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse register and set all roles", () => { + const identifier = "LMAO-d9f892"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const roles = ["ESDTRoleLocalMint", "ESDTRoleLocalBurn"]; + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerAndSetAllRoles", + topics: [base64Identifier, "TE1BTw==", "TE1BTw==", "RnVuZ2libGVFU0RU", "Ag=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const resultEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: ["TE1BTy1kOWY4OTI=", "", "", "RVNEVFJvbGVMb2NhbE1pbnQ=", "RVNEVFJvbGVMb2NhbEJ1cm4="], + }); + + const resultLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [resultEvent], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + data: Buffer.from( + "RVNEVFNldFJvbGVANGM0ZDQxNGYyZDY0Mzk2NjM4MzkzMkA0NTUzNDQ1NDUyNmY2YzY1NGM2ZjYzNjE2YzRkNjk2ZTc0QDQ1NTM0NDU0NTI2ZjZjNjU0YzZmNjM2MTZjNDI3NTcyNmU=", + "base64", + ), + logs: resultLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseRegisterAndSetAllRoles(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + assert.deepEqual(outcome.roles, roles); + }); + + it("should test parse register set special role", () => { + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const roles = ["ESDTRoleNFTCreate", "ESDTRoleNFTAddQuantity", "ESDTRoleNFTBurn"]; + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: [ + base64Identifier, + "", + "", + "RVNEVFJvbGVORlRDcmVhdGU=", + "RVNEVFJvbGVORlRBZGRRdWFudGl0eQ==", + "RVNEVFJvbGVORlRCdXJu", + ], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseSetSpecialRole(txOutcome); + assert.equal(outcome.userAddress, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(outcome.tokenIdentifier, identifier); + assert.deepEqual(outcome.roles, roles); + }); + + it("should test parse nft create", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const initialQuantity = BigInt(1); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTCreate", + topics: [ + base64Identifier, + "AQ==", + "AQ==", + "CAESAgABIuUBCAESCE5GVEZJUlNUGiA8NdfqyxqZpKDMqlN+8MwK4Qn0H2wrQCID5jO/uwcfXCDEEyouUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4RzJDaHR0cHM6Ly9pcGZzLmlvL2lwZnMvUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4Rzo9dGFnczo7bWV0YWRhdGE6UW1SY1A5NGtYcjV6WmpSR3ZpN21KNnVuN0xweFVoWVZSNFI0UnBpY3h6Z1lrdA==", + ], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseNftCreate(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.initialQuantity, initialQuantity); + }); + + it("should test parse local mint", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const mintedSupply = BigInt(100000); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalMint", + topics: [base64Identifier, "", "AYag"], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseLocalMint(txOutcome); + assert.equal(outcome.userAddress, event.address); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.mintedSupply, mintedSupply); + }); + + it("should test parse local burn", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const burntSupply = BigInt(100000); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalBurn", + topics: [base64Identifier, "", "AYag"], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseLocalBurn(txOutcome); + assert.equal(outcome.userAddress, event.address); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.burntSupply, burntSupply); + }); + + it("should test parse pause", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTPause", + topics: [base64Identifier], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parsePause(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse unpause", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTUnPause", + topics: [base64Identifier], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseUnpause(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + }); + + it("should test parse freeze", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTFreeze", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseFreeze(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse unfreeze", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTUnFreeze", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseUnfreeze(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse wipe", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTWipe", + topics: [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseWipe(txOutcome); + assert.equal(outcome.userAddress, address); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.balance, balance); + }); + + it("should test parse update attributes", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const attributes = "metadata:ipfsCID/test.json;tags:tag1,tag2"; + const base64Attributes = Buffer.from(attributes).toString("base64"); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTUpdateAttributes", + topics: [base64Identifier, "AQ==", "", base64Attributes], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseUpdateAttributes(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(Buffer.from(outcome.attributes).toString(), attributes); + }); + + it("should test parse add quantity", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const addedQuantity = BigInt(10); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTAddQuantity", + topics: [base64Identifier, "AQ==", "Cg=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseAddQuantity(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.addedQuantity, addedQuantity); + }); + + it("should test parse burn quantity", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const burntQuantity = BigInt(16); + + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTBurn", + topics: [base64Identifier, "AQ==", "EA=="], + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + transactionLogs: transactionLogs, + }); + + const outcome = parser.parseBurnQuantity(txOutcome); + assert.equal(outcome.tokenIdentifier, identifier); + assert.equal(outcome.nonce, nonce); + assert.equal(outcome.burntQuantity, burntQuantity); + }); +}); diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts new file mode 100644 index 00000000..f9b389eb --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts @@ -0,0 +1,363 @@ +import { TransactionEvent, TransactionOutcome } from "./resources"; +import { ErrParseTransactionOutcome } from "../errors"; +import { Address } from "../address"; +import { bufferToBigInt } from "../smartcontracts/codec/utils"; + +export class TokenManagementTransactionsOutcomeParser { + constructor() {} + + parseIssueFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issue"); + const identifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifier }; + } + + parseIssueNonFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issueNonFungible"); + const identifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifier }; + } + + parseIssueSemiFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "issueSemiFungible"); + const identifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifier }; + } + + parseRegisterMetaEsdt(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "registerMetaESDT"); + const identifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: identifier }; + } + + parseRegisterAndSetAllRoles(transactionOutcome: TransactionOutcome): { tokenIdentifier: string; roles: string[] } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const registerEvent = this.findSingleEventByIdentifier(transactionOutcome, "registerAndSetAllRoles"); + const tokenIdentifier = this.extractTokenIdentifier(registerEvent); + + const setRoleEvent = this.findSingleEventByIdentifier(transactionOutcome, "ESDTSetRole"); + const encodedRoles = setRoleEvent.topics.slice(3); + + let roles: string[] = []; + for (const role of encodedRoles) { + roles = roles.concat(this.decodeTopicAsString(role)); + } + + return { tokenIdentifier: tokenIdentifier, roles: roles }; + } + + parseSetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.transactionLogs.events); + } + + parseUnsetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.transactionLogs.events); + } + + parseSetSpecialRole(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + roles: string[]; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTSetRole"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + + const encodedRoles = event.topics.slice(3); + + let roles: string[] = []; + for (const role of encodedRoles) { + roles = roles.concat(this.decodeTopicAsString(role)); + } + + return { userAddress: userAddress, tokenIdentifier: tokenIdentifier, roles: roles }; + } + + parseNftCreate(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + initialQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTCreate"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const amount = this.extractAmount(event); + + return { tokenIdentifier: tokenIdentifier, nonce: nonce, initialQuantity: amount }; + } + + parseLocalMint(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + mintedSupply: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTLocalMint"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const mintedSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + mintedSupply: mintedSupply, + }; + } + + parseLocalBurn(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + burntSupply: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTLocalBurn"); + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntSupply: burntSupply, + }; + } + + parsePause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTPause"); + const tokenIdentifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: tokenIdentifier }; + } + + parseUnpause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTUnPause"); + const tokenIdentifier = this.extractTokenIdentifier(event); + + return { tokenIdentifier: tokenIdentifier }; + } + + parseFreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTFreeze"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUnfreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTUnFreeze"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseWipe(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTWipe"); + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUpdateAttributes(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + attributes: Uint8Array; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTUpdateAttributes"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const attributes = event.topics[3] ? Buffer.from(event.topics[3], "base64") : new Uint8Array(); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + attributes: attributes, + }; + } + + parseAddQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + addedQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTAddQuantity"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const addedQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + addedQuantity: addedQuantity, + }; + } + + parseBurnQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + burntQuantity: bigint; + } { + this.ensureNoError(transactionOutcome.transactionLogs.events); + + const event = this.findSingleEventByIdentifier(transactionOutcome, "ESDTNFTBurn"); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntQuantity: burntQuantity, + }; + } + + private ensureNoError(transactionEvents: TransactionEvent[]) { + for (const event of transactionEvents) { + if (event.identifier == "signalError") { + const data = Buffer.from(event.data.toString().slice(1)).toString(); + const message = this.decodeTopicAsString(event.topics[1]); + + throw new ErrParseTransactionOutcome( + `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, + ); + } + } + } + + private findSingleEventByIdentifier(transactionOutcome: TransactionOutcome, identifier: string): TransactionEvent { + const events = this.gatherAllEvents(transactionOutcome).filter((event) => event.identifier == identifier); + + if (events.length == 0) { + throw new ErrParseTransactionOutcome(`cannot find event of type ${identifier}`); + } + if (events.length > 1) { + throw new ErrParseTransactionOutcome(`more than one event of type ${identifier}`); + } + + return events[0]; + } + + private gatherAllEvents(transactionOutcome: TransactionOutcome): TransactionEvent[] { + const allEvents = []; + + allEvents.push(...transactionOutcome.transactionLogs.events); + + for (const item of transactionOutcome.smartContractResults) { + allEvents.push(...item.logs.events); + } + + return allEvents; + } + + private extractTokenIdentifier(event: TransactionEvent): string { + if (!event.topics[0]) { + return ""; + } + return this.decodeTopicAsString(event.topics[0]); + } + + private extractNonce(event: TransactionEvent): bigint { + if (!event.topics[1]) { + return BigInt(0); + } + const nonce = Buffer.from(event.topics[1], "base64"); + return BigInt(bufferToBigInt(nonce).toFixed(0)); + } + + private extractAmount(event: TransactionEvent): bigint { + if (!event.topics[2]) { + return BigInt(0); + } + const amount = Buffer.from(event.topics[2], "base64"); + return BigInt(bufferToBigInt(amount).toFixed(0)); + } + + private extractAddress(event: TransactionEvent): string { + if (!event.topics[3]) { + return ""; + } + const address = Buffer.from(event.topics[3], "base64"); + return Address.fromBuffer(address).bech32(); + } + + private decodeTopicAsString(topic: string): string { + return Buffer.from(topic, "base64").toString(); + } +}