Cart checkout fabricates transaction results: executeBatchPurchase never submits to the chain
Labels / Complexity: bug · security · High Complexity — High
Problem
The cart's checkout path does not submit a transaction. BatchTransactionService.executeBatchPurchase (src/lib/batchTransaction.ts) waits 2 seconds, rolls a Math.random() to decide success or failure, and resolves with a fabricated transactionHash:
// For now, we'll simulate a successful transaction.
try {
return new Promise((resolve) => {
setTimeout(() => {
// Simulate a revert for demonstration purposes
if (Math.random() < 0.2) {
// ... decodeRevertReason(sampleErrorBytes) ...
resolve({ success: false, error: reason });
} else {
resolve({
success: true,
transactionHash: `0x${[...Array(64)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join("")}`,
});
}
}, 2000);
});
It is wired into the real checkout UI: src/components/CartSidebar.tsx (line 52) awaits this service when the user checks out. The two TODO comments in the same file confirm the intended-but-missing behavior (// TODO: Fetch 24h price volatility to set a dynamic default slippage. and // TODO: Include slippage intent in EIP-712 typed data.). Closed issues #79 ("Feature: Batch token purchase for multiple properties", whose acceptance criteria required "Batch transaction using multicall") and #431 ("batchTransaction confirmation logic races simulation branches") are both marked COMPLETED, but the simulation is still what ships. Consequences:
- Users see a success hash for a purchase that never happened. The token transfer, payment, and property ownership change do not occur on-chain; the UI's "confirmed" state is fiction.
- The 20% random failure is not a real revert. Users get nondeterministic errors and fabricated revert reasons, which cannot be reproduced or debugged.
- Funds-relevant code paths are untested against a real wallet/chain. The slippage math, EIP-712 intent, and error decoding are only ever exercised against the fake path, so the first real integration will be the first time they run.
Root cause
src/lib/batchTransaction.ts executeBatchPurchase (the setTimeout/Math.random() block, lines ~23-59). The function signature takes walletAddress and slippageTolerance but never calls a contract, never reads the wallet, and never signs.
Why this is architecturally hard
- The replacement must integrate the wallet layer the app already has. The app has
wagmi (wagmi.config.ts), useWalletConnector, useWalletStore, and a walletStore.ts. A real batch purchase needs the connected account, a multicall contract (the app's contract ABI — check src/types/src/config for the deployed batch/multicall address), gas estimation, and a waitForTransactionReceipt flow. The contributor must decide whether the contract is a multicall or a dedicated batch-purchase contract, and where the address/config lives.
- The slippage math becomes real.
minAmount = expectedAmount * (1 - slippageTolerance) is applied per item; with a real swap path this must match the on-chain deadline/slippage semantics and the TODO about 24h volatility, otherwise the UI's estimate and the chain's actual min-out disagree.
- The service API is consumed and tested.
src/components/CartSidebar.tsx:52 and src/lib/__tests__/batchTransaction.test.ts both call executeBatchPurchase; the return contract (success/transactionHash/error) is reasonable, but the tests currently assert the simulation's behavior and must be rewritten against a mocked wagmi/viem client.
- Fake success must be impossible by construction. The current code's worst property is that the happy path needs no wallet at all. The fix must require a connected account and a real receipt before returning
success: true, so a regression cannot silently re-introduce the simulation.
Downstream impact
This is the frontend's own checkout; no sibling repo changes, but the backend's transaction lifecycle (PropChain-BackEnd transactions module and test/e2e/transaction-lifecycle.e2e.spec.ts) is the server-side half a real purchase would notify — confirm the expected backend webhook/notification contract before wiring the success path.
Acceptance criteria
executeBatchPurchase submits a real transaction for the connected account and resolves success: true only after on-chain confirmation (receipt observed); no code path returns a fabricated hash.
- The 20%
Math.random() failure branch and the setTimeout simulation are gone.
- The per-item slippage math is either validated against the on-chain min-out or documented as an estimate with the real values computed from the quote.
src/lib/__tests__/batchTransaction.test.ts is rewritten to mock wagmi/viem and assert: success returns the real hash; user rejection returns success: false with a decoded reason; disconnected wallet is rejected before any submission.
npm run typecheck, npm test, and npm run lint pass.
Out of scope
Dynamic volatility-based slippage (the TODO about 24h price volatility) and EIP-712 typed-data slippage intent are follow-ups; this issue is about making checkout real.
Getting started
src/lib/batchTransaction.ts — the simulation to replace
src/components/CartSidebar.tsx (line 52) — the caller
src/lib/__tests__/batchTransaction.test.ts — the tests to rewrite
src/hooks/useWalletConnector.ts, src/store/walletStore.ts, wagmi.config.ts — the wallet layer to integrate
Commands: npm run typecheck, npm test, npm run lint (scripts in package.json).
Good first files to read: src/lib/batchTransaction.ts, src/components/CartSidebar.tsx, src/hooks/useWalletConnector.ts.
Cart checkout fabricates transaction results: executeBatchPurchase never submits to the chain
Labels / Complexity: bug · security · High Complexity — High
Problem
The cart's checkout path does not submit a transaction.
BatchTransactionService.executeBatchPurchase(src/lib/batchTransaction.ts) waits 2 seconds, rolls aMath.random()to decide success or failure, and resolves with a fabricatedtransactionHash:It is wired into the real checkout UI:
src/components/CartSidebar.tsx(line 52) awaits this service when the user checks out. The twoTODOcomments in the same file confirm the intended-but-missing behavior (// TODO: Fetch 24h price volatility to set a dynamic default slippage.and// TODO: Include slippage intent in EIP-712 typed data.). Closed issues #79 ("Feature: Batch token purchase for multiple properties", whose acceptance criteria required "Batch transaction using multicall") and #431 ("batchTransaction confirmation logic races simulation branches") are both marked COMPLETED, but the simulation is still what ships. Consequences:Root cause
src/lib/batchTransaction.tsexecuteBatchPurchase(thesetTimeout/Math.random()block, lines ~23-59). The function signature takeswalletAddressandslippageTolerancebut never calls a contract, never reads the wallet, and never signs.Why this is architecturally hard
wagmi(wagmi.config.ts),useWalletConnector,useWalletStore, and awalletStore.ts. A real batch purchase needs the connected account, a multicall contract (the app's contract ABI — checksrc/types/src/configfor the deployed batch/multicall address), gas estimation, and awaitForTransactionReceiptflow. The contributor must decide whether the contract is a multicall or a dedicated batch-purchase contract, and where the address/config lives.minAmount = expectedAmount * (1 - slippageTolerance)is applied per item; with a real swap path this must match the on-chain deadline/slippage semantics and theTODOabout 24h volatility, otherwise the UI's estimate and the chain's actual min-out disagree.src/components/CartSidebar.tsx:52andsrc/lib/__tests__/batchTransaction.test.tsboth callexecuteBatchPurchase; the return contract (success/transactionHash/error) is reasonable, but the tests currently assert the simulation's behavior and must be rewritten against a mocked wagmi/viem client.success: true, so a regression cannot silently re-introduce the simulation.Downstream impact
This is the frontend's own checkout; no sibling repo changes, but the backend's transaction lifecycle (
PropChain-BackEndtransactionsmodule andtest/e2e/transaction-lifecycle.e2e.spec.ts) is the server-side half a real purchase would notify — confirm the expected backend webhook/notification contract before wiring the success path.Acceptance criteria
executeBatchPurchasesubmits a real transaction for the connected account and resolvessuccess: trueonly after on-chain confirmation (receipt observed); no code path returns a fabricated hash.Math.random()failure branch and thesetTimeoutsimulation are gone.src/lib/__tests__/batchTransaction.test.tsis rewritten to mock wagmi/viem and assert: success returns the real hash; user rejection returnssuccess: falsewith a decoded reason; disconnected wallet is rejected before any submission.npm run typecheck,npm test, andnpm run lintpass.Out of scope
Dynamic volatility-based slippage (the
TODOabout 24h price volatility) and EIP-712 typed-data slippage intent are follow-ups; this issue is about making checkout real.Getting started
src/lib/batchTransaction.ts— the simulation to replacesrc/components/CartSidebar.tsx(line 52) — the callersrc/lib/__tests__/batchTransaction.test.ts— the tests to rewritesrc/hooks/useWalletConnector.ts,src/store/walletStore.ts,wagmi.config.ts— the wallet layer to integrateCommands:
npm run typecheck,npm test,npm run lint(scripts inpackage.json).Good first files to read:
src/lib/batchTransaction.ts,src/components/CartSidebar.tsx,src/hooks/useWalletConnector.ts.