feat: Soroban signer, sequence management, submit retry, and on-chain intent creation - #148
Open
chideraisiguzor wants to merge 4 commits into
Conversation
Backend hot-wallet key is env-injected via SOROBAN_SIGNER_SECRET_KEY, never logged (custom toString/toJSON/inspect redact it), and required by env.validation.ts when NODE_ENV=production so boot fails fast instead of at first submit. Optional elsewhere so dev/test don't need a real key. Closes stellar-vortex-protocol#21
Replaces the naive per-request getAccount() pattern with an in-process, lock-serialized sequence counter on SignerService. withNextSequence() fetches the account's sequence from the network once, caches it, and hands out a fresh incremented value to each caller in strict call order — so concurrent submissions from the backend's signing account can't race on the same sequence number. The cache is dropped on failure so the next call re-syncs from the network instead of drifting. Closes stellar-vortex-protocol#30
Adds StellarTxService for building, signing, and submitting Soroban contract-invocation transactions on the signer's account. Submission retries with exponential backoff, capped at maxAttempts, but never resends blindly: before every retry it first checks the transaction's status by hash (the signed envelope is fixed for the call) so a lost response or a poll timeout can't cause a double submission. A confirmed on-chain FAILED status is treated as terminal and is not retried. Exhausted retries and terminal failures both surface as SorobanSubmissionError with a clear message. Also extends SorobanService with prepareTransaction/sendTransaction/ getTransactionStatus, the write-path RPC calls it was missing. Closes stellar-vortex-protocol#29
IntentsService.create() now calls the settlement contract through StellarTxService.invokeContract() when ONCHAIN_INTENTS_ENABLED is on, gated behind a rollout flag (default off) that falls back to the existing pure in-memory behavior. IntentsController.create()'s response shape is unchanged. When the flag is on, a missing SETTLEMENT_CONTRACT_ID or a failed on-chain submission surfaces as a 503 and the intent is not added to the local store -- no phantom off-chain-only intents. Argument encoding for the contract call is provisional pending the on-chain settlement ADR and typed contract bindings, both still open. Closes stellar-vortex-protocol#22
|
@chideraisiguzor Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four related pieces of the on-chain settlement path, landed as one stack since each builds directly on the last:
src/soroban/signer.service.ts,src/config/env.validation.ts) — the backend hot-wallet key is env-injected viaSOROBAN_SIGNER_SECRET_KEY, never logged (customtoString/toJSON/inspect redaction), and required byenv.validation.tswhenNODE_ENV=productionso boot fails fast instead of failing at first submit. Optional elsewhere so dev/test don't need a real key.src/soroban/signer.service.ts) — replaces the naive per-requestgetAccount()pattern with an in-process, lock-serialized sequence counter (SignerService.withNextSequence), so concurrent submissions from the signing account can't race on the same sequence number. Cache resets on failure so it re-syncs from the network instead of drifting.src/soroban/stellar-tx.service.ts) — newStellarTxServicebuilds, signs, and submits Soroban contract-invocation transactions with capped exponential backoff. Never resends blindly: before every retry it checks the transaction's status by hash first (idempotency guard against double submission), and a confirmed on-chainFAILEDis treated as terminal rather than retried.src/intents/intents.service.ts,src/intents/intents.controller.ts) —IntentsService.create()now calls the settlement contract viaStellarTxService.invokeContract()behind a rollout flag (ONCHAIN_INTENTS_ENABLED, default off) that falls back to the existing pure in-memory behavior.IntentsController.create()'s response shape is unchanged. When the flag is on, a missingSETTLEMENT_CONTRACT_IDor a failed submission surfaces as a 503 and the intent is not added to the local store.Recommended merge/review order (each is its own logical commit on this branch): #21 → #30 → #29 → #22.
Design notes / open dependencies
create_intentmethod name and argument encoding inIntentsService.buildCreateIntentArgs()are a best-effort, clearly-commented placeholder — there's no contract ABI or typed bindings in this repo yet (issue Add typed contract client bindings for the settlement contract #32, also open/unassigned). This is expected to change once real bindings land; it's isolated to one private method.Test plan
Ran on the final branch (includes all four features):
Also manually smoke-tested against a live-booted server:
POST /api/v1/intentsbehaves exactly as before →201.ONCHAIN_INTENTS_ENABLED=truewith a settlement contract ID but no reachable RPC/contract: fails cleanly with503 {"error":"Failed to register intent with the settlement contract"}— no crash, no phantom in-memory intent left behind.New unit coverage:
signer.service.spec.ts(11 tests, including concurrency/race tests for sequence management),stellar-tx.service.spec.ts(8 tests, including the idempotency-before-resend and terminal-vs-retryable-failure behavior), and additions tointents.service.spec.ts(4 new tests for the on-chain path).Closes #21
Closes #30
Closes #29
Closes #22