Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .tickets/sa-besp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
id: sa-besp
status: open
deps: []
links: []
created: 2026-03-14T00:06:28Z
type: bug
priority: 2
assignee: Jibles
---
# Transaction history: swaps misclassified as contract interactions & duplicate tool calls

## Summary

When asking "what was my last swap?", the transaction history tool has two issues:

1. **Swaps displayed as "Contract interaction"** — the UI cards show generic "Contract interaction / ETH / N/A" instead of properly parsed swap details, even though the LLM text response correctly identifies the swap.
2. **Duplicate tool calls** — `transactionHistoryTool` is called twice with identical params `{"offset": 0, "includeTransactions": true, "renderTransactions": 1}`.

## Root Cause Analysis

### Misclassification
- `evmParser.ts:74-114` — `determineTransactionType()` falls back to `'contract'` when token transfer data is missing or incomplete from the indexer
- When type is `'contract'` instead of `'swap'`, `transactionUtils.ts:getSwapTokens()` returns null (requires `tx.type === 'swap'` AND 2+ token transfers)
- UI then renders the fallback "Contract interaction" card instead of swap pair display

### Missing type filter
- The LLM is not setting `types: ["swap"]` filter when asking about swaps
- System prompt guidance exists at `chat.ts:348` but is advisory only — LLM sometimes ignores it
- Without the filter, any recent transaction type can be returned

### Duplicate calls
- Likely LLM behavior (up to 5 sequential tool calls allowed via `stepCountIs(5)`)
- LLM may call twice when first result doesn't match expectations, or simply non-deterministic duplication

## Key Files
- `apps/agentic-server/src/lib/transactionHistory/evmParser.ts` — tx type classification
- `apps/agentic-server/src/routes/chat.ts:343-348` — LLM type filter guidance
- `apps/agentic-chat/src/components/tools/GetTransactionHistoryUI.tsx` — UI rendering
- `apps/agentic-chat/src/lib/transactionUtils.ts` — swap token detection

## Reproduction
1. Wallet: `0xeD3EFA66B743e2f0B5579d67E14599D01eFA2440` on Arbitrum
2. Ask: "what was my last swap?"
3. Observe: cards show "Contract interaction" instead of swap details

## Acceptance Criteria

- [ ] Swaps are correctly classified as 'swap' type even when indexer token transfer data is sparse
- [ ] UI displays swap pair (e.g., "ETH → PEPE") for swap transactions
- [ ] Type-specific queries use the types filter (e.g., types: ["swap"] for swap queries)
- [ ] Tool is not called with duplicate identical params for simple queries
70 changes: 70 additions & 0 deletions .tickets/sa-qczu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
id: sa-qczu
status: open
deps: []
links: []
created: 2026-03-14T00:06:56Z
type: bug
priority: 1
assignee: Jibles
---
# Parallel swap execution: stale quotes and approval race conditions

## Summary

When executing multiple swaps in parallel (e.g., 3x PEPE→ETH on Arbitrum), the lock mechanism correctly serializes execution, but subsequent swaps fail because:

1. **Quote staleness** — quotes expire while waiting in the lock queue
2. **Approval race condition** — allowance is checked at quote time but executed later, causing insufficient allowance errors

## Observed Behavior

**Test 1 (3 swaps: $0.5, $0.7, $0.9 PEPE→ETH):**
- Swap 1: succeeds
- Swaps 2 & 3: fail at "Sign swap transaction" with "Execution reverted for an unknown reason"

**Test 2 (2 swaps: $0.5, $0.7 PEPE→ETH):**
- Swap 1: succeeds
- Swap 2: skips approval step, fails with "ERC20: insufficient allowance"

## Root Cause Analysis

### Quote Staleness
- Quotes are generated in parallel BEFORE the lock (`initiateSwap.ts:209-272`)
- Bebop quotes include an `expiry` field (`getBebopRate/types.ts:36`) but it is **never returned or validated**
- The lock (`walletMutex.ts`) queues swaps for serial execution
- By the time swap 2+ executes, its quote/unsignedTx is expired on-chain → revert

### Approval Race Condition
- Allowance is checked at quote time (`initiateSwap.ts:224-231`)
- All parallel quotes see the same initial allowance state (e.g., 0)
- Each builds an approval for its exact amount (`approvalHelpers.ts:10-34`)
- Swap 1's approval sets allowance to its amount, then swap 1 consumes it
- Swap 2's pre-built approval may or may not re-set allowance, but even if it does, the swap tx was built against stale state
- If swap 2 skips approval (because it was marked as not needed at quote time), it gets insufficient allowance

### No Nonce Management
- EVM transaction sending (`chains/evm/transaction.ts:12-79`) relies on wagmi's automatic nonce
- No explicit nonce tracking between queued transactions

## Key Files
- `apps/agentic-chat/src/lib/walletMutex.ts` — lock mechanism (works, but masks deeper issues)
- `apps/agentic-server/src/tools/initiateSwap.ts` — quote generation + allowance check
- `apps/agentic-server/src/utils/approvalHelpers.ts` — builds exact-amount approvals
- `apps/agentic-server/src/utils/getAllowance.ts` — allowance check at quote time
- `apps/agentic-server/src/utils/getBebopRate/types.ts` — BebopQuote has unused `expiry`
- `apps/agentic-chat/src/components/tools/useSwapExecution.tsx` — execution orchestration

## Suggested Fix Direction
1. **Re-fetch quote inside the lock** — move quote fetching (or at least re-validation) into the locked section of `useSwapExecution.tsx` so each swap has a fresh quote
2. **Re-check allowance before approval** — don't rely on the quote-time allowance check; re-check just before executing the approval tx
3. **Use max approval or accumulate** — approve for max uint256 or sum of all pending swap amounts
4. **Validate quote expiry** — store and check `expiry` before submitting swap tx

## Acceptance Criteria

- [ ] Multiple parallel swaps (2-3) all complete successfully
- [ ] Each swap gets a fresh/valid quote at execution time
- [ ] Allowance is sufficient for each swap at execution time
- [ ] Quote expiry is validated before submitting swap transaction
- [ ] No "Execution reverted" or "insufficient allowance" errors for queued swaps
Loading
Loading