@pioneer-platform/blockbook 8.12.0 → 8.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/UNCONFIRMED_BALANCE_FIX.md +142 -0
- package/lib/index.d.ts +8 -2
- package/lib/index.js +11 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @pioneer-platform/blockbook
|
|
2
2
|
|
|
3
|
+
## 8.12.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- chore: 🔒 CRITICAL FIX: XRP destination tag validation
|
|
8
|
+
|
|
9
|
+
## 8.12.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 🔒 CRITICAL FIX: XRP destination tag validation
|
|
14
|
+
|
|
3
15
|
## 8.12.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# ✅ Critical Fix: Unconfirmed Balance Support
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
**CRITICAL UX BUG**: When a user had pending/unconfirmed Bitcoin transactions, the system would show **0 balance** instead of showing the unconfirmed balance. This made users think all their Bitcoin had disappeared!
|
|
6
|
+
|
|
7
|
+
### Root Cause
|
|
8
|
+
|
|
9
|
+
The `get_balance_by_xpub()` function in `utxo-network` only summed confirmed UTXOs from the `/api/v2/utxo/{xpub}` endpoint. When UTXOs were spent in an unconfirmed transaction:
|
|
10
|
+
|
|
11
|
+
- ✅ Confirmed UTXOs: spent (returns empty array)
|
|
12
|
+
- ❌ Unconfirmed balance: **NOT CHECKED** (showed as $0.00)
|
|
13
|
+
|
|
14
|
+
Result: **User sees 0 balance even though they have pending transactions!**
|
|
15
|
+
|
|
16
|
+
## Solution
|
|
17
|
+
|
|
18
|
+
### 1. Backend Fix (`utxo-network/src/index.ts`)
|
|
19
|
+
|
|
20
|
+
Added unconfirmed balance check to `get_balance_by_xpub`:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// CRITICAL FIX: Also get unconfirmed balance from xpub info
|
|
24
|
+
// When UTXOs are spent but not confirmed, balance would show 0 without this
|
|
25
|
+
let unconfirmedBalance = 0;
|
|
26
|
+
try {
|
|
27
|
+
const xpubInfo = await blockbook.getPubkeyInfo(coin, xpub);
|
|
28
|
+
unconfirmedBalance = parseInt(xpubInfo.unconfirmedBalance || '0');
|
|
29
|
+
|
|
30
|
+
if (isBitcoin) log.info(tag, '🔍 [BITCOIN] Xpub info:', {
|
|
31
|
+
confirmedBalance: xpubInfo.balance,
|
|
32
|
+
unconfirmedBalance: xpubInfo.unconfirmedBalance,
|
|
33
|
+
totalReceived: xpubInfo.totalReceived,
|
|
34
|
+
totalSent: xpubInfo.totalSent,
|
|
35
|
+
txCount: xpubInfo.txs
|
|
36
|
+
});
|
|
37
|
+
} catch (e) {
|
|
38
|
+
log.warn(tag, 'Failed to fetch xpub info for unconfirmed balance:', e);
|
|
39
|
+
// Continue with just confirmed balance
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const totalBalance = balance + unconfirmedBalance;
|
|
43
|
+
|
|
44
|
+
// Return balance, unconfirmed balance, and node URL
|
|
45
|
+
return {
|
|
46
|
+
balance: totalBalance,
|
|
47
|
+
unconfirmedBalance,
|
|
48
|
+
confirmedBalance: balance,
|
|
49
|
+
nodeUrl
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. API Controller Fix (`pioneer-server/src/controllers/utxo.controller.ts`)
|
|
54
|
+
|
|
55
|
+
Updated `GetBalanceByXpub` to return balance breakdown:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
public async GetBalanceByXpub(
|
|
59
|
+
@Path() network: string,
|
|
60
|
+
@Path() xpub: string
|
|
61
|
+
): Promise<{
|
|
62
|
+
balance: string;
|
|
63
|
+
unconfirmedBalance?: number;
|
|
64
|
+
confirmedBalance?: number;
|
|
65
|
+
nodeUrl?: string
|
|
66
|
+
} | BasicResponse> {
|
|
67
|
+
const balanceInfo = await utxoNetwork.getBalanceByXpub(network, xpub);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
balance: balanceInfo.balance.toString(),
|
|
71
|
+
unconfirmedBalance: balanceInfo.unconfirmedBalance || 0,
|
|
72
|
+
confirmedBalance: balanceInfo.confirmedBalance || balanceInfo.balance,
|
|
73
|
+
nodeUrl: balanceInfo.nodeUrl
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API Response Format
|
|
79
|
+
|
|
80
|
+
### Before (Broken)
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"balance": "0" ← Shows 0 even with unconfirmed transactions!
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### After (Fixed)
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"balance": "74953", ← Total balance (confirmed + unconfirmed)
|
|
91
|
+
"confirmedBalance": 0, ← Confirmed balance
|
|
92
|
+
"unconfirmedBalance": 74953, ← Unconfirmed balance (pending TX)
|
|
93
|
+
"nodeUrl": "https://btcbook.nownodes.io/..."
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Testing
|
|
98
|
+
|
|
99
|
+
### Test Case: Pending Transaction
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Wallet with pending transaction
|
|
103
|
+
curl "http://localhost:9001/api/v1/utxo/balance/BTC/{zpub}"
|
|
104
|
+
|
|
105
|
+
# Expected: Shows unconfirmed balance
|
|
106
|
+
{
|
|
107
|
+
"balance": "74953",
|
|
108
|
+
"confirmedBalance": 0,
|
|
109
|
+
"unconfirmedBalance": 74953
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Test Case: Fully Confirmed Balance
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Wallet with confirmed balance
|
|
117
|
+
curl "http://localhost:9001/api/v1/utxo/balance/BTC/{zpub}"
|
|
118
|
+
|
|
119
|
+
# Expected: Shows confirmed balance only
|
|
120
|
+
{
|
|
121
|
+
"balance": "74953",
|
|
122
|
+
"confirmedBalance": 74953,
|
|
123
|
+
"unconfirmedBalance": 0
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Files Changed
|
|
128
|
+
|
|
129
|
+
1. `modules/coins/utxo/utxo-network/src/index.ts` - Added unconfirmed balance query
|
|
130
|
+
2. `services/pioneer-server/src/controllers/utxo.controller.ts` - Updated response format
|
|
131
|
+
|
|
132
|
+
## Impact
|
|
133
|
+
|
|
134
|
+
✅ **Users will no longer panic when they see $0.00 balance during pending transactions**
|
|
135
|
+
✅ **UI can now show "Pending: X BTC" separately from confirmed balance**
|
|
136
|
+
✅ **Proper debugging with detailed balance breakdown in logs**
|
|
137
|
+
|
|
138
|
+
## Related
|
|
139
|
+
|
|
140
|
+
- Blockbook API: `/api/v2/xpub/{xpub}` returns `unconfirmedBalance` field
|
|
141
|
+
- Blockbook API: `/api/v2/utxo/{xpub}?confirmed=false` returns unconfirmed UTXOs (but is empty when spent)
|
|
142
|
+
|
package/lib/index.d.ts
CHANGED
|
@@ -50,7 +50,13 @@ declare let broadcast_transaction: (coin: string, hex: string) => Promise<{
|
|
|
50
50
|
txid?: undefined;
|
|
51
51
|
}>;
|
|
52
52
|
declare let get_transaction: (coin: string, txid: string) => Promise<any>;
|
|
53
|
-
declare let get_utxos_by_xpub: (coin: string, xpub: string) => Promise<
|
|
53
|
+
declare let get_utxos_by_xpub: (coin: string, xpub: string) => Promise<{
|
|
54
|
+
data: any;
|
|
55
|
+
nodeUrl: string;
|
|
56
|
+
} | undefined>;
|
|
54
57
|
declare let update_node_performance: (coin: string, url: string, success: boolean, responseTime: number) => void;
|
|
55
|
-
declare let get_balance_by_xpub: (coin: string, xpub: any) => Promise<
|
|
58
|
+
declare let get_balance_by_xpub: (coin: string, xpub: any) => Promise<{
|
|
59
|
+
balance: number;
|
|
60
|
+
nodeUrl: string;
|
|
61
|
+
}>;
|
|
56
62
|
declare let get_node_info: () => Promise<boolean>;
|
package/lib/index.js
CHANGED
|
@@ -757,7 +757,8 @@ var get_utxos_by_xpub = function (coin, xpub) {
|
|
|
757
757
|
if (isBitcoin) {
|
|
758
758
|
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Returning UTXOs:', resp.data);
|
|
759
759
|
}
|
|
760
|
-
|
|
760
|
+
// Return both data and the node URL that was used
|
|
761
|
+
return [2 /*return*/, { data: resp.data, nodeUrl: node.url }];
|
|
761
762
|
case 5:
|
|
762
763
|
error_2 = _b.sent();
|
|
763
764
|
responseTime = Date.now() - startTime;
|
|
@@ -843,7 +844,7 @@ var update_node_performance = function (coin, url, success, responseTime) {
|
|
|
843
844
|
};
|
|
844
845
|
var get_balance_by_xpub = function (coin, xpub) {
|
|
845
846
|
return __awaiter(this, void 0, void 0, function () {
|
|
846
|
-
var tag, output, balance, i, uxto, e_10;
|
|
847
|
+
var tag, result, output, nodeUrl, balance, i, uxto, e_10;
|
|
847
848
|
return __generator(this, function (_a) {
|
|
848
849
|
switch (_a.label) {
|
|
849
850
|
case 0:
|
|
@@ -855,7 +856,12 @@ var get_balance_by_xpub = function (coin, xpub) {
|
|
|
855
856
|
log.debug(tag, "xpub: ", xpub);
|
|
856
857
|
return [4 /*yield*/, get_utxos_by_xpub(coin, xpub)];
|
|
857
858
|
case 2:
|
|
858
|
-
|
|
859
|
+
result = _a.sent();
|
|
860
|
+
if (!result) {
|
|
861
|
+
throw new Error('No result from get_utxos_by_xpub');
|
|
862
|
+
}
|
|
863
|
+
output = result.data;
|
|
864
|
+
nodeUrl = result.nodeUrl;
|
|
859
865
|
log.debug(tag, "output: ", output);
|
|
860
866
|
balance = 0;
|
|
861
867
|
//tally
|
|
@@ -863,7 +869,8 @@ var get_balance_by_xpub = function (coin, xpub) {
|
|
|
863
869
|
uxto = output[i];
|
|
864
870
|
balance = balance + parseInt(uxto.value);
|
|
865
871
|
}
|
|
866
|
-
|
|
872
|
+
// Return both balance and node URL
|
|
873
|
+
return [2 /*return*/, { balance: balance / 100000000, nodeUrl: nodeUrl }];
|
|
867
874
|
case 3:
|
|
868
875
|
e_10 = _a.sent();
|
|
869
876
|
console.error(tag, e_10);
|