Skip to content

Commit ef8e5a1

Browse files
authored
Merge pull request #161 from ChainSafe/fix/update-fix-NU6
fix: better error logging and prevent transactions over total balance
2 parents 5d9b423 + f194ce6 commit ef8e5a1

7 files changed

Lines changed: 219 additions & 11 deletions

File tree

PCZT_ERROR_TEST_PLAN.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# PCZT Creation Error - Reproduction Test Plan
2+
3+
## Changes Made
4+
**Error Handling Improvements Applied**
5+
- `error.rs`: Changed `PcztCreate` to include error details: `PcztCreate(String)`
6+
- `wallet.rs:569`: Updated `pczt_shield` to log and capture error details
7+
- `wallet.rs:657-660`: Updated `pczt_create` to log and capture error details
8+
- **Build Status**: ✅ Compiled successfully
9+
10+
## Error Will Now Show Details
11+
Previously: `"Failed to create PCZT"` (no details)
12+
Now: `"Failed to create PCZT: <actual error from librustzcash>"` (includes root cause)
13+
14+
## Testing Scenarios
15+
16+
### Scenario 1: Transaction with Memo (PRIORITY - Never Tested)
17+
18+
**Setup:**
19+
1. Open http://localhost:3000
20+
2. Connect MetaMask snap
21+
3. Ensure wallet is fully synced
22+
4. Attempt to send ZEC with a memo
23+
24+
**Steps:**
25+
```
26+
To: <any_valid_zcash_address>
27+
Amount: 0.001 ZEC
28+
Memo: "Test memo for NU6.1"
29+
```
30+
31+
**Expected Behaviors:**
32+
-**Success**: Transaction creates PCZT successfully
33+
-**Failure**: Console shows detailed error message
34+
35+
**Check Console For:**
36+
- `pczt_create: create_pczt_from_proposal failed: <details>`
37+
- Look for: memo-related errors, encoding issues, or validation failures
38+
39+
### Scenario 2: Shielding Operation
40+
41+
**Setup:**
42+
1. Ensure wallet has transparent balance
43+
2. Attempt to shield funds to Sapling/Orchard
44+
45+
**Steps:**
46+
```
47+
Shield Amount: <available_transparent_balance>
48+
Target Pool: Orchard
49+
```
50+
51+
**Check Console For:**
52+
- `pczt_shield: create_pczt_from_proposal failed: <details>`
53+
- Look for: anchor mismatch, tree state issues
54+
55+
### Scenario 3: Multiple Small Transactions (Note Fragmentation)
56+
57+
**Hypothesis:** Wallet with many small notes might trigger input selection issues
58+
59+
**Setup:**
60+
1. Create multiple small notes by receiving several small transactions
61+
2. Attempt to spend more than any single note
62+
3. Forces wallet to combine notes
63+
64+
**Check For:**
65+
- Input selection failures
66+
- "insufficient balance" despite having enough total
67+
68+
### Scenario 4: Transaction After Partial Sync
69+
70+
**Hypothesis:** Wallet not fully synced might have stale anchor
71+
72+
**Setup:**
73+
1. Stop sync before completion (close browser mid-sync)
74+
2. Reload and attempt transaction without re-syncing
75+
3. See if pre-sync validation catches it or if PCZT creation fails
76+
77+
**Check For:**
78+
- "Wallet not fully synced" warning triggers auto-sync (line 598-605)
79+
- OR anchor mismatch in PCZT creation
80+
81+
### Scenario 5: Pre-NU6.1 Notes (If Available)
82+
83+
**Hypothesis:** Notes received before block 3,146,400 might have compatibility issues
84+
85+
**Check:**
86+
- Look at transaction history for any notes from before Jan 2026
87+
- Try to spend these specific notes
88+
- Check if serialization format causes issues
89+
90+
## What to Look For in Console
91+
92+
With our improved error handling, you should now see:
93+
94+
```javascript
95+
// Console output will show:
96+
ERROR pczt_create: create_pczt_from_proposal failed: <ACTUAL ERROR>
97+
98+
// Examples of what might appear:
99+
// - "anchor not found in commitment tree"
100+
// - "insufficient balance after selecting inputs"
101+
// - "note value exceeds maximum"
102+
// - "invalid memo encoding"
103+
// - "transaction would create negative balance"
104+
```
105+
106+
## Debugging Steps
107+
108+
1. **Open Browser Console** (F12) before any transaction attempt
109+
2. **Enable verbose logging** if available
110+
3. **Copy full error message** including the new detailed part
111+
4. **Note wallet state**:
112+
- Current block height
113+
- Number of notes
114+
- Total balance
115+
- Last sync time
116+
117+
## Expected Results
118+
119+
If the forum user's error is reproducible:
120+
- You'll see the SAME detailed error message
121+
- Can then determine if it's:
122+
- ✅ A memo-specific issue
123+
- ✅ A tree/anchor sync issue
124+
- ✅ A NU6.1 compatibility issue
125+
- ✅ An input selection bug
126+
127+
If NO error occurs:
128+
- The issue might be environment-specific
129+
- Or related to a specific wallet state we haven't replicated
130+
- Request more details from the forum user about their setup
131+
132+
## Next Steps After Testing
133+
134+
1. **If error reproduced**: Analyze the detailed error message to identify fix
135+
2. **If error NOT reproduced**: Deploy this improved error logging to production so users can report detailed errors
136+
3. **Check forum**: Ask user to update snap and provide new detailed error message
137+
138+
## Quick Commands
139+
140+
```bash
141+
# Start dev servers
142+
cd /home/skynet/ztest/WebZjs && yarn dev
143+
144+
# Check build status
145+
cd /home/skynet/ztest/WebZjs && just build
146+
147+
# View console logs in browser
148+
# F12 → Console tab → Look for "pczt_create" or "pczt_shield" errors
149+
```

crates/webzjs-wallet/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ pub enum Error {
8080
SerdeWasmBindgen(#[from] serde_wasm_bindgen::Error),
8181
#[error("Fail to parse TxIds")]
8282
TxIdParse,
83-
#[error("Failed to create Pczt")]
84-
PcztCreate,
83+
#[error("Failed to create Pczt: {0}")]
84+
PcztCreate(String),
8585
#[error("Failed to prove Pczt: {0}")]
8686
PcztProve(String),
8787
#[error("Failed to send Pczt: {0}")]

crates/webzjs-wallet/src/wallet.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ where
566566
)
567567
.map_err(|e| {
568568
tracing::error!("pczt_shield: create_pczt_from_proposal failed: {:?}", e);
569-
Error::PcztCreate
569+
Error::PcztCreate(format!("{:?}", e))
570570
})?;
571571
tracing::info!("pczt_shield: PCZT created from proposal successfully");
572572

@@ -654,7 +654,10 @@ where
654654
OvkPolicy::Sender,
655655
&proposal,
656656
)
657-
.map_err(|_| Error::PcztCreate)?;
657+
.map_err(|e| {
658+
tracing::error!("pczt_create: create_pczt_from_proposal failed: {:?}", e);
659+
Error::PcztCreate(format!("{:?}", e))
660+
})?;
658661
Ok(pczt)
659662
}
660663

packages/snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/ChainSafe/WebZjs.git"
88
},
99
"source": {
10-
"shasum": "WOy/9JeJPJ346afb8ZQnvY0N6fX58ci737rwSSfjtLs=",
10+
"shasum": "qzUODaU2KfdU1JVKRMUDL+wReKvUPUX4sx5Jx58D76k=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/web-wallet/src/components/TransferCards/TransferInput.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
} from '../../pages/TransferBalance/useTransferBalanceForm';
66
import Input from '../Input/Input';
77
import Button from '../Button/Button';
8+
import useBalance from '../../hooks/useBalance';
9+
import { zecToZats, zatsToZec } from '../../utils/balance';
810

911
interface TransferInputProps {
1012
formData: TransferBalanceFormData;
@@ -17,6 +19,7 @@ export function TransferInput({
1719
nextStep,
1820
handleChange,
1921
}: TransferInputProps): React.JSX.Element {
22+
const { spendableBalance } = useBalance();
2023

2124
const [errors, setErrors] = useState({
2225
recipient: '',
@@ -35,8 +38,28 @@ export function TransferInput({
3538
newErrors.recipient = 'Please enter a valid address';
3639
}
3740

38-
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
39-
newErrors.amount = 'Please enter an valid amount to transfer';
41+
// Amount validation
42+
if (!amount) {
43+
newErrors.amount = 'Amount is required';
44+
} else if (isNaN(Number(amount))) {
45+
newErrors.amount = 'Please enter a valid number';
46+
} else if (Number(amount) <= 0) {
47+
newErrors.amount = 'Amount must be greater than 0';
48+
} else {
49+
// Balance validation with fee buffer
50+
try {
51+
const amountInZats = zecToZats(amount);
52+
const FEE_BUFFER = 10_000; // Conservative estimate: 0.0001 ZEC buffer for fees
53+
const totalRequired = Number(amountInZats) + FEE_BUFFER;
54+
55+
if (totalRequired > spendableBalance) {
56+
const availableZec = zatsToZec(Math.max(0, spendableBalance - FEE_BUFFER));
57+
newErrors.amount = `Insufficient balance. Available (after fees): ${availableZec.toFixed(8)} ZEC`;
58+
}
59+
} catch (error) {
60+
// If conversion fails, let it pass - the error will be caught later
61+
// This handles edge cases like invalid decimal formats
62+
}
4063
}
4164

4265
setErrors(newErrors);

packages/web-wallet/src/hooks/usePCZT.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,40 @@ export const usePczt = (): IUsePczt => {
179179
await syncStateWithWallet();
180180
} catch (error) {
181181
const rawMessage = error instanceof Error ? error.message : String(error);
182-
console.error('Transaction error:', rawMessage);
182+
183+
// Log full error details for debugging
184+
console.error('=== Transaction Error Details ===');
185+
console.error('Raw error:', error);
186+
console.error('Error message:', rawMessage);
187+
console.error('Error type:', error instanceof Error ? error.constructor.name : typeof error);
188+
if (error instanceof Error && error.stack) {
189+
console.error('Stack trace:', error.stack);
190+
}
191+
console.error('================================');
183192

184193
let errorMessage = rawMessage;
194+
195+
// Parse InsufficientFunds error to extract amounts
185196
if (rawMessage.includes('InsufficientFunds')) {
186-
errorMessage = 'Insufficient balance. Your wallet may still be syncing — please wait for sync to complete or try a Full Resync from the Account Summary page.';
197+
const availableMatch = rawMessage.match(/available:\s*Zatoshis\((\d+)\)/);
198+
const requiredMatch = rawMessage.match(/required:\s*Zatoshis\((\d+)\)/);
199+
200+
if (availableMatch && requiredMatch) {
201+
const available = parseInt(availableMatch[1]);
202+
const required = parseInt(requiredMatch[1]);
203+
const availableZec = (available / 100_000_000).toFixed(8);
204+
const requiredZec = (required / 100_000_000).toFixed(8);
205+
const shortfallZec = ((required - available) / 100_000_000).toFixed(8);
206+
207+
console.error('Insufficient Funds Breakdown:');
208+
console.error(` Available: ${available} zatoshis (${availableZec} ZEC)`);
209+
console.error(` Required: ${required} zatoshis (${requiredZec} ZEC)`);
210+
console.error(` Shortfall: ${required - available} zatoshis (${shortfallZec} ZEC)`);
211+
212+
errorMessage = `Insufficient balance. Available: ${availableZec} ZEC, Required: ${requiredZec} ZEC (includes fees). You need ${shortfallZec} ZEC more to complete this transaction.`;
213+
} else {
214+
errorMessage = 'Insufficient balance. Your wallet may still be syncing — please wait for sync to complete or try a Full Resync from the Account Summary page.';
215+
}
187216
}
188217

189218
setLastError(errorMessage);

packages/web-wallet/src/pages/ShieldBalance/ShieldBalance.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ export function ShieldBalance(): React.JSX.Element {
4646
}
4747

4848
const isMinimalShieldAmount = useMemo(()=>{
49-
return unshieldedBalance > 100000;
49+
// Need at least 0.001 ZEC + fee buffer (0.0015 ZEC total minimum)
50+
// This accounts for the transaction fee which is deducted from the balance
51+
const MINIMUM_SHIELD_AMOUNT = 100000; // 0.001 ZEC
52+
const FEE_BUFFER = 50000; // 0.0005 ZEC conservative fee estimate
53+
return unshieldedBalance > (MINIMUM_SHIELD_AMOUNT + FEE_BUFFER);
5054
},[unshieldedBalance])
5155

5256
return (
@@ -100,7 +104,7 @@ export function ShieldBalance(): React.JSX.Element {
100104
/>
101105
{!isMinimalShieldAmount && (
102106
<div className="text-red-500 text-sm mt-2">
103-
The minimum shielding balance required is 0.001 ZEC.
107+
Minimum balance required: 0.0015 ZEC (includes transaction fees). Your balance: {zatsToZec(unshieldedBalance)} ZEC
104108
</div>
105109
)}
106110
</div>

0 commit comments

Comments
 (0)