Skip to content

feat(automation): add EventTrigger for Soroban event monitoring#327

Open
salazarsebas wants to merge 1 commit into
Galaxy-KJ:mainfrom
salazarsebas:feat/event-trigger-302
Open

feat(automation): add EventTrigger for Soroban event monitoring#327
salazarsebas wants to merge 1 commit into
Galaxy-KJ:mainfrom
salazarsebas:feat/event-trigger-302

Conversation

@salazarsebas

@salazarsebas salazarsebas commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements EventTrigger for monitoring Soroban contract events and firing automation callbacks when matching events occur (e.g. Deposit, Liquidation).

  • Adds EventTrigger with startListening(filter, callback) using ContractEventMonitor from @galaxy-kj/core-stellar-sdk/soroban
  • Filters events by contract ID and topic names
  • Supports simulated events for testing via emitSimulatedEvent()
  • Gracefully handles RPC connection dropouts with exponential backoff reconnection (up to 5 attempts)
  • Includes comprehensive unit tests covering subscription, callbacks, simulation, and reconnection behavior

Changes

  • packages/core/automation/src/triggers/event-trigger.ts — new EventTrigger class
  • packages/core/automation/src/test/event-trigger.test.ts — unit tests (7 cases)
  • packages/core/automation/index.ts — export EventTrigger and types
  • packages/core/automation/jest.config.cjs — stellar-sdk/soroban module mapper for tests
  • packages/core/automation/tsconfig.json — soroban subpath resolution

Test plan

  • npx jest src/test/event-trigger.test.ts — all 7 tests pass
  • Detects simulated Soroban events
  • Successfully invokes callback on matching events
  • Gracefully handles RPC connection dropouts with reconnection

Closes #302

Summary by CodeRabbit

  • New Features

    • Added a new event-based automation trigger for listening to matching contract events and handling them in real time.
    • Expanded the public automation API to expose the new trigger and related configuration types.
  • Bug Fixes

    • Improved event subscription reliability with automatic reconnection and retry limits.
    • Updated test/runtime resolution so Soroban imports resolve consistently in automation workflows.
  • Tests

    • Added unit coverage for filtering, live event forwarding, reconnect behavior, and start/stop handling.

Implement EventTrigger to monitor Soroban contract events via RPC polling,
filter by contract ID and topics, invoke callbacks on matching events,
and gracefully reconnect after RPC connection dropouts.

Closes Galaxy-KJ#302
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new EventTrigger class to the automation package that subscribes to Soroban contract events filtered by contract ID and topics, routes matching events to a callback, and auto-reconnects on RPC errors. Includes Jest tests, public re-exports from the package index, and tsconfig/jest module mapper updates for the soroban SDK path alias.

Changes

EventTrigger: Soroban event-based automation trigger

Layer / File(s) Summary
Interfaces, constants, and build config
packages/core/automation/src/triggers/event-trigger.ts, packages/core/automation/tsconfig.json, packages/core/automation/jest.config.cjs
Defines EventFilter and EventTriggerOptions interfaces with default RPC/reconnect constants; adds @galaxy-kj/core-stellar-sdk/soroban path alias to both tsconfig and jest moduleNameMapper.
EventTrigger class: lifecycle, subscription, matching, and reconnect
packages/core/automation/src/triggers/event-trigger.ts
Implements startListening (single-listener enforcement, subscription setup), stopListening (unsubscribe, timer clear, state reset), emitSimulatedEvent, internal subscribe with onEvent/onError routing, matchesFilter/extractTopicStrings for topic normalization, handleConnectionError with backoff scheduling, attemptReconnect, and clearReconnectTimer.
Public exports and Jest tests
packages/core/automation/index.ts, packages/core/automation/src/test/event-trigger.test.ts
Re-exports EventTrigger, EventFilter, and EventTriggerOptions from the package index; adds 7 Jest test cases covering filter-based subscription, callback invocation, topic mismatch rejection, live event forwarding, reconnection with fake timers, max-attempt stopping, and duplicate startListening rejection.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant EventTrigger
  participant ContractEventMonitor

  Caller->>EventTrigger: startListening(filter, callback)
  EventTrigger->>ContractEventMonitor: subscribeToEvents(subscription)
  ContractEventMonitor-->>EventTrigger: subscriptionId
  ContractEventMonitor->>EventTrigger: onEvent(ContractEventDetail)
  EventTrigger->>EventTrigger: matchesFilter(event, filter)
  EventTrigger-->>Caller: callback(event)
  ContractEventMonitor->>EventTrigger: onError(err)
  EventTrigger->>ContractEventMonitor: unsubscribe(subscriptionId)
  EventTrigger->>EventTrigger: handleConnectionError → scheduleReconnect
  EventTrigger->>EventTrigger: attemptReconnect → subscribe
  Caller->>EventTrigger: stopListening()
  EventTrigger->>ContractEventMonitor: unsubscribe(subscriptionId)
  EventTrigger->>EventTrigger: clearReconnectTimer, reset state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • KevinMB0220

Poem

🐇 Hop, hop, I heard a contract call,
A Soroban event, I caught it all!
With filters set and callbacks queued,
Reconnect on error — nothing skewed.
The bunny triggers, swift and keen,
Automation's never looked so clean! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the new EventTrigger Soroban monitoring feature.
Description check ✅ Passed The PR description is mostly complete, covering summary, changes, issue, and testing; the required documentation section is not filled out.
Linked Issues check ✅ Passed The implementation meets #302 by adding EventTrigger, filtering by contract/topic, supporting simulated events, callbacks, and reconnect handling.
Out of Scope Changes check ✅ Passed All changes support the EventTrigger feature and its test/build integration; no unrelated scope is evident.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/core/automation/index.ts

Oops! Something went wrong! :(

ESLint: 9.39.2

YAMLException: Cannot read config file: /.eslintrc.js.bak
Error: end of the stream or a document separator is expected (2:7)

1 | module.exports = {
2 | root: true,
-----------^
3 | env: {
4 | node: true,
at generateError (/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:40)
at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

packages/core/automation/jest.config.cjs

Oops! Something went wrong! :(

ESLint: 9.39.2

YAMLException: Cannot read config file: /.eslintrc.js.bak
Error: end of the stream or a document separator is expected (2:7)

1 | module.exports = {
2 | root: true,
-----------^
3 | env: {
4 | node: true,
at generateError (/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:40)
at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

packages/core/automation/src/test/event-trigger.test.ts

Oops! Something went wrong! :(

ESLint: 9.39.2

YAMLException: Cannot read config file: /.eslintrc.js.bak
Error: end of the stream or a document separator is expected (2:7)

1 | module.exports = {
2 | root: true,
-----------^
3 | env: {
4 | node: true,
at generateError (/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:40)
at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

  • 1 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/automation/src/triggers/event-trigger.ts`:
- Around line 18-22: `EventTriggerOptions` is exported with reconnect settings,
but `EventTrigger` does not accept or use them, so the public API and
implementation are out of sync. Update the `EventTrigger` constructor (and any
call sites/defaults inside `event-trigger.ts`) to accept the full
`EventTriggerOptions` object and apply `maxReconnectAttempts` and
`reconnectDelayMs` when configuring reconnection behavior, or remove those
unused fields from `EventTriggerOptions` if they are not meant to be supported.
Keep the `EventTriggerOptions` and `EventTrigger` symbols aligned so the exposed
API matches what the class actually uses.
- Around line 61-66: In startListening() on EventTrigger, the trigger state is
being committed before subscribe() succeeds, so a failed first subscribe leaves
isListening/currentFilter/callback set and can still leave reconnect handling
active. Update startListening() to only mark the trigger active after
subscribe(filter, callback) completes, and if subscribe throws, roll back any
partial state and clear reconnectAttempts so the instance stays idle. Use the
EventTrigger methods startListening() and subscribe() to locate the fix.
- Around line 101-120: The subscribe flow in EventTrigger.subscribe can complete
after stopListening() has already been called, leaving a leaked listener and
stale callback. Update the subscription lifecycle so late resolves are ignored:
check the trigger’s stopped/listening state before assigning this.subscriptionId
and before wiring the callback path, and make sure a resolved subscribeToEvents
result is immediately unsubscribed if stopListening() has already run. Use the
existing subscribe(), stopListening(), and this.subscriptionId handling in
EventTrigger to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 289e01bc-80df-4da8-822c-0ad57b90cae5

📥 Commits

Reviewing files that changed from the base of the PR and between 99dea15 and 3d2cc27.

📒 Files selected for processing (5)
  • packages/core/automation/index.ts
  • packages/core/automation/jest.config.cjs
  • packages/core/automation/src/test/event-trigger.test.ts
  • packages/core/automation/src/triggers/event-trigger.ts
  • packages/core/automation/tsconfig.json

Comment on lines +18 to +22
export interface EventTriggerOptions {
rpcUrl?: string;
maxReconnectAttempts?: number;
reconnectDelayMs?: number;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wire EventTriggerOptions into the constructor or drop the export.

EventTriggerOptions advertises configurable maxReconnectAttempts and reconnectDelayMs, but the constructor only accepts rpcUrl and always uses the hardcoded defaults. Consumers cannot actually tune reconnection behavior, so the new public API does not match the shipped implementation.

Suggested fix
-  constructor(
-    rpcUrl?: string,
-    monitor?: ContractEventMonitor
-  ) {
+  constructor(
+    optionsOrRpcUrl?: string | EventTriggerOptions,
+    monitor?: ContractEventMonitor
+  ) {
+    const resolvedOptions =
+      typeof optionsOrRpcUrl === 'string'
+        ? { rpcUrl: optionsOrRpcUrl }
+        : (optionsOrRpcUrl ?? {});
+
     this.options = {
-      rpcUrl: rpcUrl ?? DEFAULT_RPC_URL,
-      maxReconnectAttempts: DEFAULT_MAX_RECONNECT_ATTEMPTS,
-      reconnectDelayMs: DEFAULT_RECONNECT_DELAY_MS,
+      rpcUrl: resolvedOptions.rpcUrl ?? DEFAULT_RPC_URL,
+      maxReconnectAttempts:
+        resolvedOptions.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,
+      reconnectDelayMs:
+        resolvedOptions.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS,
     };
     this.monitor = monitor ?? new ContractEventMonitor(this.options.rpcUrl);
   }

Also applies to: 38-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/automation/src/triggers/event-trigger.ts` around lines 18 - 22,
`EventTriggerOptions` is exported with reconnect settings, but `EventTrigger`
does not accept or use them, so the public API and implementation are out of
sync. Update the `EventTrigger` constructor (and any call sites/defaults inside
`event-trigger.ts`) to accept the full `EventTriggerOptions` object and apply
`maxReconnectAttempts` and `reconnectDelayMs` when configuring reconnection
behavior, or remove those unused fields from `EventTriggerOptions` if they are
not meant to be supported. Keep the `EventTriggerOptions` and `EventTrigger`
symbols aligned so the exposed API matches what the class actually uses.

Comment on lines +61 to +66
this.currentFilter = filter;
this.callback = callback;
this.isListening = true;
this.reconnectAttempts = 0;

await this.subscribe(filter, callback);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Roll back state when the first subscription attempt fails.

startListening() marks the trigger active before awaiting subscribe(). If the initial subscribeToEvents() throws, the promise rejects but isListening, currentFilter, and callback stay set. With the current ContractEventMonitor contract, that failed subscribe can also fire onError before rethrowing, so you end up with a rejected startListening() call and a background reconnect timer still running.

Suggested fix
     this.currentFilter = filter;
     this.callback = callback;
     this.isListening = true;
     this.reconnectAttempts = 0;
 
-    await this.subscribe(filter, callback);
+    try {
+      await this.subscribe(filter, callback);
+    } catch (error) {
+      this.clearReconnectTimer();
+      this.subscriptionId = undefined;
+      this.isListening = false;
+      this.callback = undefined;
+      this.currentFilter = undefined;
+      this.reconnectAttempts = 0;
+      throw error;
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.currentFilter = filter;
this.callback = callback;
this.isListening = true;
this.reconnectAttempts = 0;
await this.subscribe(filter, callback);
this.currentFilter = filter;
this.callback = callback;
this.isListening = true;
this.reconnectAttempts = 0;
try {
await this.subscribe(filter, callback);
} catch (error) {
this.clearReconnectTimer();
this.subscriptionId = undefined;
this.isListening = false;
this.callback = undefined;
this.currentFilter = undefined;
this.reconnectAttempts = 0;
throw error;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/automation/src/triggers/event-trigger.ts` around lines 61 - 66,
In startListening() on EventTrigger, the trigger state is being committed before
subscribe() succeeds, so a failed first subscribe leaves
isListening/currentFilter/callback set and can still leave reconnect handling
active. Update startListening() to only mark the trigger active after
subscribe(filter, callback) completes, and if subscribe throws, roll back any
partial state and clear reconnectAttempts so the instance stays idle. Use the
EventTrigger methods startListening() and subscribe() to locate the fix.

Comment on lines +101 to +120
private async subscribe(
filter: EventFilter,
callback: (event: ContractEventDetail) => void
): Promise<void> {
const subscription: EventSubscription = {
id: '',
contractId: filter.contractId,
eventTypes: filter.topics.length > 0 ? filter.topics : undefined,
onEvent: (event: ContractEventDetail) => {
if (this.matchesFilter(event, filter)) {
callback(event);
}
},
onError: (error: Error) => {
this.handleConnectionError(error);
},
};

this.subscriptionId = await this.monitor.subscribeToEvents(subscription);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard late subscription completions after stopListening().

This method stores subscriptionId only after the awaited RPC call returns. If stopListening() runs while that call is in flight, it has nothing to unsubscribe; when the promise resolves, a live subscription is leaked and the closed-over callback can still fire even though the trigger was stopped.

Suggested fix
 export class EventTrigger {
+  private listenGeneration = 0;
   private readonly monitor: ContractEventMonitor;
   private readonly options: Required<EventTriggerOptions>;
   private subscriptionId?: string;
@@
   async startListening(
     filter: EventFilter,
     callback: (event: ContractEventDetail) => void
   ): Promise<void> {
+    const generation = ++this.listenGeneration;
     if (this.isListening) {
       throw new Error('EventTrigger is already listening');
     }
@@
-    await this.subscribe(filter, callback);
+    await this.subscribe(filter, callback, generation);
   }
@@
   stopListening(): void {
+    this.listenGeneration += 1;
     this.clearReconnectTimer();
@@
   private async subscribe(
     filter: EventFilter,
-    callback: (event: ContractEventDetail) => void
+    callback: (event: ContractEventDetail) => void,
+    generation: number
   ): Promise<void> {
@@
-    this.subscriptionId = await this.monitor.subscribeToEvents(subscription);
+    const subscriptionId = await this.monitor.subscribeToEvents(subscription);
+    if (!this.isListening || generation !== this.listenGeneration) {
+      this.monitor.unsubscribe(subscriptionId);
+      return;
+    }
+    this.subscriptionId = subscriptionId;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async subscribe(
filter: EventFilter,
callback: (event: ContractEventDetail) => void
): Promise<void> {
const subscription: EventSubscription = {
id: '',
contractId: filter.contractId,
eventTypes: filter.topics.length > 0 ? filter.topics : undefined,
onEvent: (event: ContractEventDetail) => {
if (this.matchesFilter(event, filter)) {
callback(event);
}
},
onError: (error: Error) => {
this.handleConnectionError(error);
},
};
this.subscriptionId = await this.monitor.subscribeToEvents(subscription);
}
private async subscribe(
filter: EventFilter,
callback: (event: ContractEventDetail) => void,
generation: number
): Promise<void> {
const subscription: EventSubscription = {
id: '',
contractId: filter.contractId,
eventTypes: filter.topics.length > 0 ? filter.topics : undefined,
onEvent: (event: ContractEventDetail) => {
if (this.matchesFilter(event, filter)) {
callback(event);
}
},
onError: (error: Error) => {
this.handleConnectionError(error);
},
};
const subscriptionId = await this.monitor.subscribeToEvents(subscription);
if (!this.isListening || generation !== this.listenGeneration) {
this.monitor.unsubscribe(subscriptionId);
return;
}
this.subscriptionId = subscriptionId;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/automation/src/triggers/event-trigger.ts` around lines 101 -
120, The subscribe flow in EventTrigger.subscribe can complete after
stopListening() has already been called, leaving a leaked listener and stale
callback. Update the subscription lifecycle so late resolves are ignored: check
the trigger’s stopped/listening state before assigning this.subscriptionId and
before wiring the callback path, and make sure a resolved subscribeToEvents
result is immediately unsubscribed if stopListening() has already run. Use the
existing subscribe(), stopListening(), and this.subscriptionId handling in
EventTrigger to locate the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Automation: Event-based triggers using Soroban blockchain events (#47)

1 participant