Skip to content

SorobanOnchainAdapter fabricates success from a fake RPC payload: it cannot drive aid_escrow and is not registered #426

Description

@kilodesodiq-arch

Problem

app/backend/src/onchain/soroban-onchain.adapter.ts implements the OnchainAdapter interface but cannot actually reach the deployed aid_escrow contract, and it is not wired into the module. Its invokeContract posts a hand-rolled JSON payload to simulateTransaction/sendTransaction that has no XDR transaction envelope, signature, or fee — the shape does not match Soroban RPC at all:

// app/backend/src/onchain/soroban-onchain.adapter.ts
private async invokeContract(method: string, args: unknown[]) {
  const sim = await rpcCall(this.http, this.rpcUrl, 'simulateTransaction', {
    transaction: JSON.stringify({ contractId: this.contractId, method, args }), // ← not a Soroban envelope
  });
  const result = await rpcCall(this.http, this.rpcUrl, 'sendTransaction', {
    transaction: JSON.stringify({ contractId: this.contractId, method, args,
      networkPassphrase: this.networkPassphrase, secret: this.secretKey }),     // ← not a signed envelope
  });
  return ...;
}

It also calls contract methods that do not exist in app/onchain/contracts/aid_escrow/src/lib.rs: initialize (the contract entrypoint is init), claim_package (claim), disburse_package (disburse), and a 6-argument create_package (the contract's create_package takes operator, id, recipient, amount, token, expires_at, metadata). Every mutating method returns transactionHash: '', status: 'success', and amountClaimed: '0'/amountDisbursed: '0' regardless of what the RPC returned.

Consequence: the class fabricates success for every operation. app/backend/src/onchain/onchain.module.ts only registers MockOnchainAdapter and SorobanAdapter (the real, @stellar/stellar-sdk-based implementation in soroban.adapter.ts), so SorobanOnchainAdapter is dead code — but it is exported, named confusingly close to SorobanAdapter, and reads as a working adapter. Any contributor (or future ONCHAIN_ADAPTER config value) that selects it will observe success: true for create/claim/disburse while nothing is written on-chain, silently corrupting the audit trail this platform is built around.

Root cause

SorobanOnchainAdapter was a first-draft stub built on @nestjs/axios with a fabricated JSON RPC shape, then superseded by SorobanAdapter but never deleted or marked deprecated; its method names were never reconciled with the contract ABI.

Why this is architecturally hard

  1. Two real options and one trap. The straightforward fix is deletion, but the existence of a second *soroban*.adapter.ts invites re-wiring. The decision is whether to remove SorobanOnchainAdapter entirely (and its export) or fold any useful logic into SorobanAdapter; leaving both is the failure mode.
  2. The adapter interface masks the gap. OnchainAdapter (onchain.adapter.ts) returns status: 'success' | 'failed' and string transactionHash from every method, so a stub can satisfy the type system while doing nothing. Fixing the class alone does not stop future stubs; the interface or a validateConfig/ensureConfigured gate (as SorobanAdapter has) is the real guardrail.
  3. Tests assert the wrong thing. onchain.module.spec.ts asserts an adapter instance is created for ONCHAIN_ADAPTER=soroban, not that calls reach a contract. A regression test must simulate the RPC boundary to catch a fake payload.

Proposed design

Remove soroban-onchain.adapter.ts and its export { ONCHAIN_ADAPTER_TOKEN } duplicate, keeping the single canonical SorobanAdapter (or, if retained, make it share SorobanAdapter's ensureConfigured()/validateConfig() and @stellar/stellar-sdk submission path). Add a unit test that instantiates each registered adapter and asserts a submit call produces a real TransactionBuilder-built envelope (not a JSON blob).

Downstream impact

No ABI change — this is backend-internal. If the class is removed, confirm nothing imports it (the only reference is its own file); onchain.module.ts already excludes it from providers.

Acceptance criteria

Service

  • soroban-onchain.adapter.ts is deleted or reimplemented to sign and submit real Soroban envelopes, and exactly one canonical Soroban adapter remains for the soroban config value.
  • No adapter returns status: 'success' with an empty transactionHash after a failed or never-sent transaction.

Tests

  • A test fails if an adapter's simulateTransaction payload is not a valid Soroban envelope (no {contractId, method, args} JSON blob), and passes for SorobanAdapter.

Out of scope

Recipient-key signing for claims (the admin-keypair signing gap) and ledger reconciliation are separate issues.

Getting started

Files: app/backend/src/onchain/soroban-onchain.adapter.ts, app/backend/src/onchain/soroban.adapter.ts, app/backend/src/onchain/onchain.module.ts, app/backend/src/onchain/onchain.module.spec.ts.

cd app/backend
npm test
npm run lint:check

Good first files to read: soroban.adapter.ts (the correct submitContractOp flow) and onchain.module.ts (what is actually registered).

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea:backendBackend (NestJS) areabugSomething isn't workinghighHigh severity issues

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions