diff --git a/common/changes/@cityofzion/neon-dappkit-types/streamline-eventlistener_2025-10-06-15-49.json b/common/changes/@cityofzion/neon-dappkit-types/streamline-eventlistener_2025-10-06-15-49.json new file mode 100644 index 0000000..6843148 --- /dev/null +++ b/common/changes/@cityofzion/neon-dappkit-types/streamline-eventlistener_2025-10-06-15-49.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/neon-dappkit-types", + "comment": "streamline stack item and notification types in Neo3EventListener", + "type": "major" + } + ], + "packageName": "@cityofzion/neon-dappkit-types" +} \ No newline at end of file diff --git a/common/changes/@cityofzion/neon-dappkit/streamline-eventlistener_2025-10-06-15-49.json b/common/changes/@cityofzion/neon-dappkit/streamline-eventlistener_2025-10-06-15-49.json new file mode 100644 index 0000000..e3046b5 --- /dev/null +++ b/common/changes/@cityofzion/neon-dappkit/streamline-eventlistener_2025-10-06-15-49.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/neon-dappkit", + "comment": "update to use latest type changes", + "type": "major" + } + ], + "packageName": "@cityofzion/neon-dappkit" +} \ No newline at end of file diff --git a/packages/neon-dappkit-types/src/Neo3EventListener.ts b/packages/neon-dappkit-types/src/Neo3EventListener.ts index fce5de4..0c7a2c8 100644 --- a/packages/neon-dappkit-types/src/Neo3EventListener.ts +++ b/packages/neon-dappkit-types/src/Neo3EventListener.ts @@ -1,3 +1,5 @@ +import { InvokeBase, Notification } from './Neo3Invoker' + /** * An interface that defines the event contract and event name */ @@ -6,24 +8,16 @@ export interface Neo3Event { eventname: string } -/** - * An interface that defines the event contract, event name and the state of the event - */ -export interface Neo3EventWithState extends Neo3Event { - state: Neo3StackItem -} - /** * The event listener callback */ -export type Neo3EventListenerCallback = (event: Neo3EventWithState) => void +export type Neo3EventListenerCallback = (event: Notification) => void /** - * An interface that defines the stack item format + * An interface that defines an application execution format */ -export interface Neo3StackItem { - type: string - value?: string | boolean | number | Neo3StackItem[] +export interface ApplicationExecution extends InvokeBase { + trigger: string } /** @@ -31,13 +25,7 @@ export interface Neo3StackItem { */ export interface Neo3ApplicationLog { txid: string - executions: { - trigger: string - vmstate: string - gasconsumed: string - stack?: Neo3StackItem[] - notifications: Neo3EventWithState[] - }[] + executions: ApplicationExecution[] } /** diff --git a/packages/neon-dappkit-types/src/Neo3Invoker.ts b/packages/neon-dappkit-types/src/Neo3Invoker.ts index 4a4858c..ee1c18e 100644 --- a/packages/neon-dappkit-types/src/Neo3Invoker.ts +++ b/packages/neon-dappkit-types/src/Neo3Invoker.ts @@ -191,7 +191,7 @@ export type BuiltTransaction = ContractInvocationMulti & { export type ArrayResponseArgType = { type: 'Array'; value: RpcResponseStackItem[] } export type MapResponseArgType = { type: 'Map'; value: { key: RpcResponseStackItem; value: RpcResponseStackItem }[] } export type ByteStringArgType = { type: 'ByteString'; value: string } -export type InteropInterfaceArgType = { type: 'InteropInterface'; interface: string; id: string } +export type InteropInterfaceArgType = { type: 'InteropInterface'; interface: string; id: string; value?: never } export type PointerArgType = { type: 'Pointer'; value: string } export type BufferArgType = { type: 'Buffer'; value: string } export type StructArgType = { type: 'Struct'; value: RpcResponseStackItem[] } @@ -208,12 +208,16 @@ export type RpcResponseStackItem = | BufferArgType | StructArgType -/** - * Result from calling invokescript or invokefunction. - */ -export interface InvokeResult { - /** The script that is sent for execution on the blockchain as a base64 string. */ - script: string +export interface Notification { + /** hash of the smart contract that emitted the notification */ + contract: string + /** name of the notification */ + eventname: string + /** the emitted notification contents */ + state: RpcResponseStackItem +} + +export interface InvokeBase { /** State of VM on exit. HALT means a successful exit while FAULT means exit with error. */ state: 'HALT' | 'FAULT' /** Amount of gas consumed up to the point of stopping in the VM. If state is FAULT, this value is not representative of the amount of gas it will consume if it somehow succeeds on the blockchain. @@ -222,7 +226,18 @@ export interface InvokeResult { return e.contract === eventToCheck.contract && e.eventname === eventToCheck.eventname }) @@ -173,7 +177,8 @@ export class NeonEventListener implements Neo3EventListener { continue } - const log = await this.rpcClient.getApplicationLog(transaction.hash) + const neonLog = await this.rpcClient.getApplicationLog(transaction.hash) + const log = this.neonApplogToNeo3ApplicationLog(neonLog) for (const notification of log.executions[0].notifications) { const listenersOfContract = this.listeners.get(notification.contract) @@ -191,7 +196,7 @@ export class NeonEventListener implements Neo3EventListener { for (const listener of listenersOfEvent) { try { this.options?.debug && console.log('Calling listener') - listener(notification as Neo3EventWithState) + listener(notification) } catch (e) { this.options?.debug && console.error(e) } @@ -211,4 +216,28 @@ export class NeonEventListener implements Neo3EventListener { private wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } + + private neonApplogToNeo3ApplicationLog(log: NeonTypes.rpc.ApplicationLogJson): Neo3ApplicationLog { + const executions = log.executions.map((execution) => { + return { + trigger: execution.trigger, + state: execution.vmstate, + gasconsumed: execution.gasconsumed, + stack: execution.stack.map((si) => { + return { type: si.type, value: si.value } as RpcResponseStackItem + }), + notifications: execution.notifications.map((notification) => { + return { + contract: notification.contract, + eventname: notification.eventname, + state: { type: notification.state.type, value: notification.state.value }, + } as Notification + }), + } as ApplicationExecution + }) + return { + txid: log.txid, + executions, + } as Neo3ApplicationLog + } } diff --git a/packages/neon-dappkit/src/NeonInvoker.ts b/packages/neon-dappkit/src/NeonInvoker.ts index 7dbeabb..7cc0bb6 100644 --- a/packages/neon-dappkit/src/NeonInvoker.ts +++ b/packages/neon-dappkit/src/NeonInvoker.ts @@ -41,7 +41,8 @@ export class NeonInvoker implements Neo3Invoker { ) if (rpcResult.state === 'FAULT') throw Error(`Execution state is FAULT. Exception: ${rpcResult.exception}`) - return { ...rpcResult, stack: rpcResult.stack as RpcResponseStackItem[] } + // TODO: set actual notifications. Currently not supported in neon-js + return { ...rpcResult, stack: rpcResult.stack as RpcResponseStackItem[], notifications: [] } } async invokeFunction(cimOrBt: ContractInvocationMulti | BuiltTransaction): Promise { diff --git a/packages/neon-dappkit/test/NeonEventListener.spec.ts b/packages/neon-dappkit/test/NeonEventListener.spec.ts index e2f6ad5..b66108d 100644 --- a/packages/neon-dappkit/test/NeonEventListener.spec.ts +++ b/packages/neon-dappkit/test/NeonEventListener.spec.ts @@ -1,11 +1,11 @@ import { ChildProcess, spawn } from 'child_process' -import { NeonEventListener, NeonInvoker, NeonParser } from '../src/index' +import { NeonEventListener, NeonInvoker, NeonParser } from '../src' import assert from 'assert' import { ContractInvocationMulti, Neo3ApplicationLog, Neo3EventListenerCallback, - Neo3EventWithState, + Notification, TypeChecker, } from '@cityofzion/neon-dappkit-types' import { wallet } from '@cityofzion/neon-core' @@ -65,8 +65,8 @@ describe('NeonEventListener', function () { it('adds an eventListener', async () => { const eventName = 'Transfer' - const eventPromise = new Promise((resolve) => { - const callBack = (notification: Neo3EventWithState) => { + const eventPromise = new Promise((resolve) => { + const callBack = (notification: Notification) => { resolve(notification) eventListener.removeAllEventListenersOfEvent(gasScriptHash, eventName) @@ -94,8 +94,8 @@ describe('NeonEventListener', function () { it('adds eventListeners on the same event', async () => { const eventName = 'Transfer' - const eventPromise1 = new Promise((resolve) => { - const callBack1 = (notification: Neo3EventWithState) => { + const eventPromise1 = new Promise((resolve) => { + const callBack1 = (notification: Notification) => { resolve(notification) eventListener.removeAllEventListenersOfEvent(gasScriptHash, eventName) @@ -104,8 +104,8 @@ describe('NeonEventListener', function () { eventListener.addEventListener(gasScriptHash, eventName, callBack1) }) - const eventPromise2 = new Promise((resolve) => { - const callBack2 = (notification: Neo3EventWithState) => { + const eventPromise2 = new Promise((resolve) => { + const callBack2 = (notification: Notification) => { resolve(notification) eventListener.removeAllEventListenersOfEvent(gasScriptHash, eventName) @@ -166,7 +166,7 @@ describe('NeonEventListener', function () { it('adds eventListener that callback throws an error', async () => { const eventName = 'Transfer' - const eventPromise = new Promise((resolve, reject) => { + const eventPromise = new Promise((resolve, reject) => { const callBack = () => { reject('Error') eventListener.removeAllEventListenersOfEvent(gasScriptHash, eventName) @@ -191,7 +191,7 @@ describe('NeonEventListener', function () { const eventName = 'Transfer' let called = 0 - const callBack: Neo3EventListenerCallback = (notification: Neo3EventWithState) => { + const callBack: Neo3EventListenerCallback = (notification: Notification) => { assert(notification) called += 1 } @@ -221,7 +221,7 @@ describe('NeonEventListener', function () { const eventName = 'Transfer' let called = 0 - const callBack: Neo3EventListenerCallback = (notification: Neo3EventWithState) => { + const callBack: Neo3EventListenerCallback = (notification: Notification) => { assert(notification) called += 1 } @@ -251,7 +251,7 @@ describe('NeonEventListener', function () { const eventName = 'Transfer' let called = 0 - const callBack: Neo3EventListenerCallback = (notification: Neo3EventWithState) => { + const callBack: Neo3EventListenerCallback = (notification: Notification) => { assert(notification) called += 1 } @@ -289,7 +289,7 @@ describe('NeonEventListener', function () { assert(applicationLog.txid === txId, 'Transaction ID should be the same') assert(applicationLog.executions.length === 1, 'There should be one execution') assert(applicationLog.executions[0].trigger === 'Application', 'Trigger should be Application') - assert(applicationLog.executions[0].vmstate === 'HALT', 'VMState should be HALT') + assert(applicationLog.executions[0].state === 'HALT', 'VMState should be HALT') assert(applicationLog.executions[0].gasconsumed !== undefined, 'Gas consumed should be returned') assert(applicationLog.executions[0].stack.length === 1, 'Stack should be returned') assert(applicationLog.executions[0].stack[0].type === 'Boolean', 'Stack type should be a boolean') @@ -334,9 +334,11 @@ describe('NeonEventListener', function () { executions: [ { trigger: 'Application', - vmstate: 'FAULT', + state: 'FAULT', gasconsumed: '0', notifications: [], + exception: '', + stack: [], }, ], } diff --git a/packages/neon-dappkit/test/NeonInvoker.spec.ts b/packages/neon-dappkit/test/NeonInvoker.spec.ts index c0ae5a6..3231103 100644 --- a/packages/neon-dappkit/test/NeonInvoker.spec.ts +++ b/packages/neon-dappkit/test/NeonInvoker.spec.ts @@ -1,6 +1,6 @@ import { ChildProcess, spawn, execSync } from 'child_process' import { ContractInvocationMulti } from '@cityofzion/neon-dappkit-types' -import { NeonEventListener, NeonInvoker, NeonParser, TypeChecker } from '../src/index' +import { NeonEventListener, NeonInvoker, NeonParser, TypeChecker } from '../src' import assert from 'assert' import * as path from 'path' import { tx, u } from '@cityofzion/neon-js'