[Phase 2] Balance Change Preview for Transaction Simulation
- Dominant language
- JavaScript
- Stars
- 2
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Overview
Implement balance change tracking during transaction simulation to show users exactly what assets they will send and receive before signing.
## Parent Issue
#140 - Implement Transaction Simulation for Enhanced Wallet Security
## Dependencies
- Requires #141 (Phase 1: Basic On-Chain Simulation) to be completed first
## Background
Phase 1 provides the core simulation infrastructure. This phase adds state diff tracking to show users:
- Token balance changes (before → after)
- ETH balance changes
- Token approval changes
## Technical Approach
### Balance Diff Tracking
```javascript
// frontend/src/lib/transactionSimulator.js
/**
* Simulate and track balance changes
* @param {Object} userOp - UserOperation to simulate
* @param {string} accountAddress - Smart account address
* @param {Array} tokenAddresses - Tokens to track
* @returns {Promise}
*/
export async function simulateWithBalanceChanges(userOp, accountAddress, tokenAddresses, provider) {
// 1. Get balances BEFORE simulation
const balancesBefore = await getBalances(accountAddress, tokenAddresses, provider)
// 2. Run simulation with state override
const simulationResult = await simulateUserOperation(userOp, provider, entryPointAddress)
// 3. Get balances AFTER simulation (using trace or state diff)
const balancesAfter = await getBalancesAfterSimulation(
userOp,
accountAddress,
tokenAddresses,
provider
)
// 4. Calculate diffs
const balanceChanges = calculateBalanceDiffs(balancesBefore, balancesAfter)
return {
...simulationResult,
balanceChanges,
}
}
/**
* Get token balances for an address
*/
async function getBalances(address, tokenAddresses, provider) {
const balances = {}
// ETH balance
balances.ETH = await provider.getBalance(address)
// ERC20 balances
for (const token of tokenAddresses) {
const contract = new ethers.Contract(token, ERC20_ABI, provider)
balances[token] = await contract.balanceOf(address)
}
return balances
}
/**
* Calculate balance differences
*/
function calculateBalanceDiffs(before, after) {
const changes = []
for (const [token, balanceBefore] of Object.entries(before)) {
const balanceAfter = after[token] || 0n
const change = balanceAfter - balanceBefore
if (change !== 0n) {
changes.push({
token,
symbol: getTokenSymbol(token),
icon: getTokenIcon(token),
before: formatBalance(balanceBefore, token),
after: formatBalance(balanceAfter, token),
change: formatBalance(change, token),
isPositive: change > 0n,
})
}
}
return changes
}
```
### Approval Change Tracking
```javascript
/**
* Track approval changes during simulation
*/
export async function trackApprovalChanges(userOp, accountAddress, tokenAddresses, provider) {
const approvalChanges = []
// Parse calldata for approve() calls
const approveCalls = parseApproveCalls(userOp.callData)
for (const call of approveCalls) {
const currentAllowance = await getAllowance(
call.token,
accountAddress,
call.spender,
provider
)
approvalChanges.push({
token: call.token,
symbol: getTokenSymbol(call.token),
spender: call.spender,
spenderName: await getContractName(call.spender), // e.g., "Uniswap V3 Router"
currentAllowance: formatBalance(currentAllowance, call.token),
newAllowance: formatBalance(call.amount, call.token),
isUnlimited: call.amount === MAX_UINT256,
})
}
return approvalChanges
}
```
### Simulation Result Structure
```javascript
const simulationResult = {
// From Phase 1
success: true,
willSucceed: true,
gasUsed: 150000n,
revertReason: null,
// New in Phase 2
balanceChanges: [
{
token: 'ETH',
symbol: 'ETH',
icon: '/assets/eth.svg',
before: '1.5',
after: '1.4',
change: '-0.1',
isPositive: false,
},
{
token: '0xA0b8...', // USDC address
symbol: 'USDC',
icon: '/assets/usdc.svg',
before: '0',
after: '150.5',
change: '+150.5',
isPositive: true,
},
],
approvalChanges: [
{
token: '0xA0b8...',
symbol: 'USDC',
spender: '0x68b3...',
spenderName: 'Uniswap V3 Router',
currentAllowance: '0',
newAllowance: '1000',
isUnlimited: false,
},
],
}
```
## Tasks
### Core Implementation
- [ ] Implement `getBalances()` to fetch ETH + token balances
- [ ] Implement `getBalancesAfterSimulation()` using trace or state diff
- [ ] Implement `calculateBalanceDiffs()` to compute changes
- [ ] Implement `trackApprovalChanges()` to detect approval modifications
- [ ] Implement `parseApproveCalls()` to extract approve() from calldata
- [ ] Add token metadata fetching (symbol, decimals, icon)
### UI Components
- [ ] Create `BalanceChanges.jsx` component:
```
┌─────────────────────────────────────┐
│ 📊 Balance Changes │
├─────────────────────────────────────┤
│ [ETH icon] ETH │
│ 1.5 → 1.4 -0.1 (red) │
├─────────────────────────────────────┤
│ [USDC icon] USDC │
│ 0 → 150.5 +150.5 (green) │
└─────────────────────────────────────┘
```
- [ ] Create `ApprovalChanges.jsx` component:
```
┌─────────────────────────────────────┐
│ 🔓 Approval Changes │
├─────────────────────────────────────┤
│ USDC → Uniswap V3 Router │
│ Allowance: 0 → 1,000 USDC │
└─────────────────────────────────────┘
```
- [ ] Add color coding: green for gains, red for losses
- [ ] Add token icons next to balance changes
- [ ] Format large numbers with commas (1,000,000)
### Integration
- [ ] Update `SwapConfirmationScreen.jsx` to show balance changes
- [ ] Update `TransactionSender.jsx` to show balance changes
- [ ] Pass token addresses to simulation based on transaction type
## Files to Create
1. **`frontend/src/components/BalanceChanges.jsx`** - Balance diff display
2. **`frontend/src/components/ApprovalChanges.jsx`** - Approval changes display
3. **`frontend/src/styles/BalanceChanges.css`** - Styling
## Files to Modify
1. **`frontend/src/lib/transactionSimulator.js`** - Add balance tracking
2. **`frontend/src/screens/SwapConfirmationScreen.jsx`** - Display balance changes
3. **`frontend/src/components/TransactionSender.jsx`** - Display balance changes
## Acceptance Criteria
- [ ] ETH balance change is displayed (before → after)
- [ ] Token balance changes are displayed for relevant tokens
- [ ] Approval changes are tracked and displayed
- [ ] Gains are shown in green, losses in red
- [ ] Token icons are displayed next to balances
- [ ] Balance changes update when simulation is re-run
- [ ] Works for ETH transfers, token transfers, and swaps
## Testing
- [ ] Test ETH send transaction (shows ETH decrease)
- [ ] Test token swap (shows token A decrease, token B increase)
- [ ] Test token approval (shows approval change)
- [ ] Test with multiple token changes
- [ ] Test formatting for large/small amounts
## UI Mockup
```
┌─────────────────────────────────────────┐
│ Transaction Simulation │
├─────────────────────────────────────────┤
│ │
│ 📊 Balance Changes │
│ ┌─────────────────────────────────┐ │
│ │ [🔷] ETH │ │
│ │ 1.500000 → 1.400000 │ │
│ │ -0.1 ETH 🔴 │ │
│ ├─────────────────────────────────┤ │
│ │ [💵] USDC │ │
│ │ 0.00 → 150.50 │ │
│ │ +150.50 USDC 🟢 │ │
│ └─────────────────────────────────┘ │
│ │
│ 🔓 Approvals │
│ ┌─────────────────────────────────┐ │
│ │ USDC → Uniswap V3 Router │ │
│ │ 0 → 150.50 USDC │ │
│ └─────────────────────────────────┘ │
│ │
│ ✅ Simulation Successful │
│ Estimated Gas: 150,000 │
│ │
└─────────────────────────────────────────┘
```
## Estimated Effort
**Medium** - 2-3 days
## Labels
`security`, `enhancement`, `frontend`, `ux`
Contributor guide
Assessment
This issue has not been assessed yet.