Repository navigation
SorobanOnchainAdapter fabricates success from a fake RPC payload: it cannot drive aid_escrow and is not registered #426
Description
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
- Two real options and one trap. The straightforward fix is deletion, but the existence of a second
*soroban*.adapter.tsinvites re-wiring. The decision is whether to removeSorobanOnchainAdapterentirely (and its export) or fold any useful logic intoSorobanAdapter; leaving both is the failure mode. - The adapter interface masks the gap.
OnchainAdapter(onchain.adapter.ts) returnsstatus: 'success' | 'failed'and stringtransactionHashfrom 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 avalidateConfig/ensureConfiguredgate (asSorobanAdapterhas) is the real guardrail. - Tests assert the wrong thing.
onchain.module.spec.tsasserts an adapter instance is created forONCHAIN_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.tsis deleted or reimplemented to sign and submit real Soroban envelopes, and exactly one canonical Soroban adapter remains for thesorobanconfig value. - No adapter returns
status: 'success'with an emptytransactionHashafter a failed or never-sent transaction.
Tests
- A test fails if an adapter's
simulateTransactionpayload is not a valid Soroban envelope (no{contractId, method, args}JSON blob), and passes forSorobanAdapter.
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:checkGood first files to read: soroban.adapter.ts (the correct submitContractOp flow) and onchain.module.ts (what is actually registered).
Activity
lovesmilesmall-hue commentedon Aug 19, 2026
Can I take this please? I have experience, 6 years experience. Thanks
grantfox-oss commentedon Aug 20, 2026
🦊 GrantFox — @lovesmilesmall-hue has been assigned to this issue as part of the Third Campaign campaign!
Next steps:
Closes #426)Good luck! Track your progress on GrantFox.
grantfox-oss commentedon Aug 20, 2026
🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!
@lovesmilesmall-hue's PR #449 was approved and merged by @kilodesodiq-arch.
🏆 @lovesmilesmall-hue: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (519 total points). Track your full progress on GrantFox.
👏 Great work, @lovesmilesmall-hue! Keep contributing to ChainForgee.