Skip to content

Prepare deposit transactions with Soroban simulation so /api/vault/deposit/prepare returns submittable XDR #1494

Closed

Description

@greatest0fallt1me

Summary

TransactionBuilderService.buildDepositTransaction builds an invokeContractFunction operation for the vault deposit and returns it unsigned. It never runs Soroban simulation or prepareTransaction, so the XDR has no sorobanData (footprint and resources), no auth entries for the user, and only STELLAR_BASE_FEE as its fee. Any wallet that signs and submits it gets rejected. The service also permanently reserves sequence numbers for the user's own account each time it builds, so one abandoned deposit breaks all later ones with tx_bad_seq. This issue makes the prepare step produce XDR that can be signed and submitted against the testnet vault.

Background and evidence

In src/services/transactionBuilder.ts:

  • No simulation step. buildDepositTransaction (from line 172) creates Operation.invokeContractFunction({ function: 'deposit', args: [Address(user), i128(amount)] }) (about line 238), builds with fee = this.resolveFee() (line 195, STELLAR_BASE_FEE, default 100) and setTimeout(timeout).build() (line 274). There is no RPC simulation call.
  • Dead error branch. The SimulationError branch (about lines 247-255) can never run, because invokeContractFunction does not simulate.
  • Burned sequence numbers.
    • sourceKey = params.sourceAccount ?? params.userPublicKey (line 199), so by default the source is the user's own account.
    • PostgresSequenceStore.allocate (src/services/postgresSequenceStore.ts, lines 11-31) advances next_sequence = GREATEST(stored, ledger) + 1 on every build.
    • If a user abandons one prepared transaction, the next build uses account.seq + 2 and is rejected with tx_bad_seq. The counter never decreases.
  • User id as Stellar key. src/controllers/depositController.ts passes userPublicKey: user.id (about line 169) with the comment "Assuming user.id is the Stellar public key".
  • The ABI matches. The vault's deposit(env, caller: Address, amount: i128) calls caller.require_auth(). The testnet vault is CBJZLUKQBWDMZPKNJAVONZ7GCCWADSETRXWOPN5Z3XWAUNI4F2R7XDYQ; a deposit smoke-test transaction is linked in Callora-Contracts docs/DEPLOYMENTS.md.

Scope

In scope

  1. Simulate and assemble.
    • After building, call rpc.Server.prepareTransaction(tx) using the network's Soroban RPC URL from config (SOROBAN_TESTNET_RPC_URL / SOROBAN_MAINNET_RPC_URL).
    • Return the assembled XDR, the resource fee, the minimum resource fee and the latest ledger in the response.
    • Map simulation errors (including vault contract errors BelowMinDeposit (8), Paused (4)) to the existing SimulationError, with redacted diagnostics.
  2. Sequence handling.
    • Do not reserve sequences in transaction_sequences when the source account is the user's own key (the wallet owns its sequence).
    • Use server.loadAccount / rpc.getAccount at build time and let the wallet's submission be authoritative.
    • Keep the sequence store only for backend-owned source accounts, and document which accounts those are.
  3. Stellar address source. Read the user's Stellar address from the user record (users.stellar_address via userRepository), not from user.id. Return 400 STELLAR_ADDRESS_REQUIRED when it is missing.
  4. Docs and contract. Update docs/deposit-transaction-builder.md and the docs/openapi.json response schema for POST /api/vault/deposit/prepare (new fee fields), and keep npm run validate:openapi green.

Out of scope

  • Submitting the signed transaction on the user's behalf.
  • Fee-bump sponsorship (the fee-abstraction route).
  • Vault balance snapshot syncing.

Design constraints

  • Unsigned only. The returned XDR must carry zero signatures; the existing check stays.
  • Simulation budget. Simulation obeys the configured RPC timeout and the existing withRetry policy for transient RPC errors only.
  • No sequence burn. Building a transaction for a user-owned account must not change any persisted state.
  • Testable. The RPC server is injectable.

Test plan

Extend src/services/transactionBuilder.test.ts and src/controllers/depositController.test.ts, plus an opt-in live smoke test next to src/services/transactionBuilder.smoke.test.ts.

# Scenario Expected
1 Happy path with stubbed simulate returned XDR has sorobanData, auth entries and fee ≥ base + resource fee
2 Simulation returns Error(Contract, #8) SimulationError → 422/502 per the existing mapping, with code 8 in diagnostics
3 RPC timeout retried per policy, then a typed timeout error
4 Build twice for the same user without submitting both XDRs use account.seq + 1; transaction_sequences untouched
5 Backend-owned source_account sequence store still used
6 User without a Stellar address 400 STELLAR_ADDRESS_REQUIRED
7 Signatures on output zero
8 Live (opt-in SOROBAN_LIVE_TESTNET=1) prepareTransaction against CBJZLUKQ...XDYQ for a funded testnet key returns assembled XDR

Regression proof. Row 1 has no sorobanData and row 4 yields seq + 2 on current main.

Acceptance criteria

  • Prepared XDR is accepted by stellar tx simulate / submission on testnet (row 8, documented in the PR).
  • No sequence reservation happens for user-owned source accounts.
  • npm run lint, npm run typecheck, npm run build and npm test pass, and CI is green.
  • At least 90% line coverage on buildDepositTransaction and the controller changes.
  • OpenAPI updated and npm run validate:openapi passes.

Getting started

git checkout -b fix/deposit-prepare-simulation
npm ci && npx prisma generate
npm run test:unit -- src/services/transactionBuilder src/controllers/depositController
stellar keys generate dep-test --network testnet --fund

Read first: buildDepositTransaction and withSequenceAllocationTimeout in src/services/transactionBuilder.ts, src/services/postgresSequenceStore.ts, prepareDeposit in src/controllers/depositController.ts, and deposit in Callora-Contracts contracts/vault/src/lib.rs.

PR requirements

  • Link this issue (Closes #<n>).
  • Show a testnet simulation or submission result for a prepared XDR.
  • Paste test output and coverage.
  • Example commit: fix(vault): simulate and assemble deposit transactions before returning XDR.

Priority rationale

High. Deposits are how users fund their vaults, and today every prepared deposit fails on submission, while abandoned attempts permanently break later ones. The fix needs Soroban resource assembly, auth entries and sequence-number semantics.

Activity

  1. graceful-tech12 commented on Oct 18, 2026

    @graceful-tech12

    @graceful-tech12 has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Good day maintainer, I would like to work on this. I have read and understood the issue requirement and i can deliver a graceful solution, please assign

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @graceful-tech12 to this issue.

  2. IFEANYIBRIGHT commented on Oct 18, 2026

    @IFEANYIBRIGHT
    Contributor

    @IFEANYIBRIGHT has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Hi! I’m a full-stack developer with a strong blockchain background. I’d love to handle this issue and can deliver a clean, reliable solution. Kindly assign it to me.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @IFEANYIBRIGHT to this issue.

  3. drips-wave commented on Oct 19, 2026

    @drips-wave

    Congratulations, @IFEANYIBRIGHT! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @IFEANYIBRIGHT: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

  4. grantfox-oss commented on Oct 24, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox!

    @IFEANYIBRIGHT's PR #1542 was approved and merged by @greatest0fallt1me.

    🏆 @IFEANYIBRIGHT: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (701 total points). Track your full progress on GrantFox.

    👏 Great work, @IFEANYIBRIGHT! Keep contributing to CalloraOrg.

  5. drips-wave commented on Oct 24, 2026

    @drips-wave

    This issue has been completed by @IFEANYIBRIGHT as part of the Stellar Wave Program's 10th Wave 🥳

    😎 @IFEANYIBRIGHT: You earned 100 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.

    🧑‍💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.

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

Metadata

Metadata

Assignees

Labels

Stellar WaveStellar Wave Program issuebackendBackend service workpriority:highHigh implementation difficultystellarvaultVault contract

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions