@pioneer-platform/eth-network 8.14.2 → 8.14.4
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 +14 -0
- package/lib/index.d.ts +22 -2
- package/lib/index.js +170 -13
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @pioneer-platform/eth-network
|
|
2
2
|
|
|
3
|
+
## 8.14.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- chore: 🔒 CRITICAL FIX: XRP destination tag validation
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @pioneer-platform/blockbook@8.12.2
|
|
10
|
+
|
|
11
|
+
## 8.14.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- fix: Add node racing to all eth-network \*ByNetwork functions
|
|
16
|
+
|
|
3
17
|
## 8.14.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -57,8 +57,14 @@ export declare const getBalances: (addresses: string[]) => Promise<string[]>;
|
|
|
57
57
|
export declare const getBalanceToken: (address: string, tokenAddress: string) => Promise<string>;
|
|
58
58
|
/**
|
|
59
59
|
* Get ERC20 allowance
|
|
60
|
+
* @deprecated Use getAllowanceByNetwork instead
|
|
60
61
|
*/
|
|
61
62
|
export declare const getAllowance: (tokenAddress: string, spender: string, sender: string) => Promise<any>;
|
|
63
|
+
/**
|
|
64
|
+
* Get ERC20 allowance by network with node racing
|
|
65
|
+
* Uses the node racing system for reliability and performance
|
|
66
|
+
*/
|
|
67
|
+
export declare const getAllowanceByNetwork: (networkId: string, tokenAddress: string, ownerAddress: string, spenderAddress: string) => Promise<string>;
|
|
62
68
|
/**
|
|
63
69
|
* Get symbol from contract address
|
|
64
70
|
*/
|
|
@@ -117,8 +123,20 @@ export declare const estimateGasByNetwork: (networkId: string, transaction: any)
|
|
|
117
123
|
*/
|
|
118
124
|
export declare const broadcastByNetwork: (networkId: string, signedTx: string) => Promise<{
|
|
119
125
|
success: boolean;
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
error: string;
|
|
127
|
+
txid?: undefined;
|
|
128
|
+
transactionHash?: undefined;
|
|
129
|
+
from?: undefined;
|
|
130
|
+
to?: undefined;
|
|
131
|
+
nonce?: undefined;
|
|
132
|
+
gasLimit?: undefined;
|
|
133
|
+
gasPrice?: undefined;
|
|
134
|
+
chainId?: undefined;
|
|
135
|
+
details?: undefined;
|
|
136
|
+
} | {
|
|
137
|
+
success: boolean;
|
|
138
|
+
txid: any;
|
|
139
|
+
transactionHash: any;
|
|
122
140
|
from: string;
|
|
123
141
|
to: string;
|
|
124
142
|
nonce: number;
|
|
@@ -126,9 +144,11 @@ export declare const broadcastByNetwork: (networkId: string, signedTx: string) =
|
|
|
126
144
|
gasPrice: string;
|
|
127
145
|
chainId: number;
|
|
128
146
|
error?: undefined;
|
|
147
|
+
details?: undefined;
|
|
129
148
|
} | {
|
|
130
149
|
success: boolean;
|
|
131
150
|
error: any;
|
|
151
|
+
details: string;
|
|
132
152
|
txid?: undefined;
|
|
133
153
|
transactionHash?: undefined;
|
|
134
154
|
from?: undefined;
|
package/lib/index.js
CHANGED
|
@@ -43,7 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
};
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.getFees = exports.getBalanceAddress = exports.getTransactionsByNetwork = exports.getTransactionByNetwork = exports.broadcastByNetwork = exports.estimateGasByNetwork = exports.getTokenMetadataByNetwork = exports.getTokenMetadata = exports.getTokenDecimalsByNetwork = exports.getBalanceTokensByNetwork = exports.getBalanceTokenByNetwork = exports.getBalanceAddressByNetwork = exports.getMemoEncoded = exports.getTransferData = exports.broadcast = exports.getTransaction = exports.getSymbolFromContract = exports.getAllowance = exports.getBalanceToken = exports.getBalances = exports.getBalance = exports.estimateFee = exports.getGasPriceByNetwork = exports.getGasPrice = exports.getNonceByNetwork = exports.getNonce = exports.addNodes = exports.addNode = exports.getNodes = exports.init = void 0;
|
|
46
|
+
exports.getFees = exports.getBalanceAddress = exports.getTransactionsByNetwork = exports.getTransactionByNetwork = exports.broadcastByNetwork = exports.estimateGasByNetwork = exports.getTokenMetadataByNetwork = exports.getTokenMetadata = exports.getTokenDecimalsByNetwork = exports.getBalanceTokensByNetwork = exports.getBalanceTokenByNetwork = exports.getBalanceAddressByNetwork = exports.getMemoEncoded = exports.getTransferData = exports.broadcast = exports.getTransaction = exports.getSymbolFromContract = exports.getAllowanceByNetwork = exports.getAllowance = exports.getBalanceToken = exports.getBalances = exports.getBalance = exports.estimateFee = exports.getGasPriceByNetwork = exports.getGasPrice = exports.getNonceByNetwork = exports.getNonce = exports.addNodes = exports.addNode = exports.getNodes = exports.init = void 0;
|
|
47
47
|
const TAG = " | eth-network | ";
|
|
48
48
|
const ethers = __importStar(require("ethers"));
|
|
49
49
|
const BigNumber = require('bignumber.js');
|
|
@@ -553,6 +553,7 @@ const getBalanceToken = async function (address, tokenAddress) {
|
|
|
553
553
|
exports.getBalanceToken = getBalanceToken;
|
|
554
554
|
/**
|
|
555
555
|
* Get ERC20 allowance
|
|
556
|
+
* @deprecated Use getAllowanceByNetwork instead
|
|
556
557
|
*/
|
|
557
558
|
const getAllowance = async function (tokenAddress, spender, sender) {
|
|
558
559
|
let tag = TAG + ' | getAllowance | ';
|
|
@@ -567,6 +568,70 @@ const getAllowance = async function (tokenAddress, spender, sender) {
|
|
|
567
568
|
}
|
|
568
569
|
};
|
|
569
570
|
exports.getAllowance = getAllowance;
|
|
571
|
+
/**
|
|
572
|
+
* Get ERC20 allowance by network with node racing
|
|
573
|
+
* Uses the node racing system for reliability and performance
|
|
574
|
+
*/
|
|
575
|
+
const getAllowanceByNetwork = async function (networkId, tokenAddress, ownerAddress, spenderAddress) {
|
|
576
|
+
let tag = TAG + ' | getAllowanceByNetwork | ';
|
|
577
|
+
// Get ALL nodes for this network, excluding dead ones
|
|
578
|
+
let allNodes = NODES.filter((n) => n.networkId === networkId);
|
|
579
|
+
if (!allNodes || allNodes.length === 0) {
|
|
580
|
+
throw Error(`No nodes found for networkId: ${networkId}`);
|
|
581
|
+
}
|
|
582
|
+
// Filter out dead nodes and sort by tier priority
|
|
583
|
+
let nodes = allNodes
|
|
584
|
+
.filter((n) => !isNodeDead(n.service))
|
|
585
|
+
.sort((a, b) => {
|
|
586
|
+
const tierA = a.tier || 'public';
|
|
587
|
+
const tierB = b.tier || 'public';
|
|
588
|
+
return TIER_PRIORITY[tierA] - TIER_PRIORITY[tierB];
|
|
589
|
+
});
|
|
590
|
+
if (nodes.length === 0) {
|
|
591
|
+
log.error(tag, `All ${allNodes.length} nodes are marked dead for ${networkId} token ${tokenAddress} - failing fast`);
|
|
592
|
+
throw Error(`No healthy nodes available for ${networkId} token ${tokenAddress} (all ${allNodes.length} nodes are dead)`);
|
|
593
|
+
}
|
|
594
|
+
const topTier = nodes[0].tier || 'public';
|
|
595
|
+
log.info(tag, `Racing top ${Math.min(RACE_BATCH_SIZE, nodes.length)} nodes (${nodes.length} alive of ${allNodes.length} total, starting with ${topTier})`);
|
|
596
|
+
// Extract chain ID from networkId (e.g., "eip155:1" -> 1)
|
|
597
|
+
let chainId;
|
|
598
|
+
if (networkId.includes(':')) {
|
|
599
|
+
chainId = parseInt(networkId.split(':')[1]);
|
|
600
|
+
}
|
|
601
|
+
// Race nodes in batches
|
|
602
|
+
let batchStartIndex = 0;
|
|
603
|
+
while (batchStartIndex < nodes.length) {
|
|
604
|
+
const batch = nodes.slice(batchStartIndex, batchStartIndex + RACE_BATCH_SIZE);
|
|
605
|
+
// Create racing promises for this batch
|
|
606
|
+
const racePromises = batch.map(async (node, index) => {
|
|
607
|
+
try {
|
|
608
|
+
const provider = new ethers.providers.StaticJsonRpcProvider(node.service, chainId ? { chainId, name: node.networkId } : undefined);
|
|
609
|
+
const contract = new ethers.Contract(tokenAddress, ERC20ABI, provider);
|
|
610
|
+
// Wrap contract call with timeout
|
|
611
|
+
const allowance = await withTimeout(contract.allowance(ownerAddress, spenderAddress), RPC_TIMEOUT_MS, `Node ${node.service} allowance timeout`);
|
|
612
|
+
log.info(tag, `Node ${node.service} (tier: ${node.tier || 'public'}) returned allowance: ${allowance.toString()}`);
|
|
613
|
+
return allowance.toString();
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
log.error(tag, `Node ${node.service} failed:`, error.message);
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
try {
|
|
621
|
+
// Race this batch of nodes
|
|
622
|
+
const result = await Promise.race(racePromises);
|
|
623
|
+
log.info(tag, `Successfully got allowance from racing batch starting at index ${batchStartIndex}`);
|
|
624
|
+
return result;
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
log.warn(tag, `Batch starting at ${batchStartIndex} failed, trying next batch if available`);
|
|
628
|
+
}
|
|
629
|
+
batchStartIndex += RACE_BATCH_SIZE;
|
|
630
|
+
}
|
|
631
|
+
// If we get here, all batches failed
|
|
632
|
+
throw Error(`All ${nodes.length} healthy nodes failed for ${networkId} token ${tokenAddress} allowance check`);
|
|
633
|
+
};
|
|
634
|
+
exports.getAllowanceByNetwork = getAllowanceByNetwork;
|
|
570
635
|
/**
|
|
571
636
|
* Get symbol from contract address
|
|
572
637
|
*/
|
|
@@ -1081,41 +1146,133 @@ const broadcastByNetwork = async function (networkId, signedTx) {
|
|
|
1081
1146
|
if (networkId.includes(':')) {
|
|
1082
1147
|
chainId = parseInt(networkId.split(':')[1]);
|
|
1083
1148
|
}
|
|
1149
|
+
// VALIDATE TRANSACTION BEFORE BROADCASTING
|
|
1150
|
+
// This catches issues early before attempting RPC calls
|
|
1151
|
+
try {
|
|
1152
|
+
log.info(tag, 'Validating signed transaction before broadcast...');
|
|
1153
|
+
const parsedTx = ethers.utils.parseTransaction(signedTx);
|
|
1154
|
+
log.info(tag, 'Parsed transaction fields:', {
|
|
1155
|
+
to: parsedTx.to,
|
|
1156
|
+
from: parsedTx.from,
|
|
1157
|
+
nonce: parsedTx.nonce,
|
|
1158
|
+
gasLimit: parsedTx.gasLimit?.toString(),
|
|
1159
|
+
gasPrice: parsedTx.gasPrice?.toString(),
|
|
1160
|
+
chainId: parsedTx.chainId,
|
|
1161
|
+
value: parsedTx.value?.toString(),
|
|
1162
|
+
data: parsedTx.data?.substring(0, 20) + '...'
|
|
1163
|
+
});
|
|
1164
|
+
// CRITICAL VALIDATION: Check for gasLimit = 0
|
|
1165
|
+
if (!parsedTx.gasLimit || parsedTx.gasLimit.isZero()) {
|
|
1166
|
+
const errorMsg = `Transaction validation failed: gasLimit is 0 or missing. This transaction will be rejected by the network. gasLimit=${parsedTx.gasLimit?.toString()}`;
|
|
1167
|
+
log.error(tag, errorMsg);
|
|
1168
|
+
return {
|
|
1169
|
+
success: false,
|
|
1170
|
+
error: errorMsg
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
// CRITICAL VALIDATION: Check for gasPrice = 0 (for non-EIP1559 txs)
|
|
1174
|
+
if (!parsedTx.gasPrice || parsedTx.gasPrice.isZero()) {
|
|
1175
|
+
const errorMsg = `Transaction validation failed: gasPrice is 0 or missing. This transaction will be rejected by the network. gasPrice=${parsedTx.gasPrice?.toString()}`;
|
|
1176
|
+
log.error(tag, errorMsg);
|
|
1177
|
+
return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
error: errorMsg
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
// Validate chainId matches network
|
|
1183
|
+
if (chainId && parsedTx.chainId !== chainId) {
|
|
1184
|
+
const errorMsg = `Transaction validation failed: chainId mismatch. Expected ${chainId}, got ${parsedTx.chainId}`;
|
|
1185
|
+
log.error(tag, errorMsg);
|
|
1186
|
+
return {
|
|
1187
|
+
success: false,
|
|
1188
|
+
error: errorMsg
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
log.info(tag, '✅ Transaction validation passed - proceeding with broadcast');
|
|
1192
|
+
}
|
|
1193
|
+
catch (validationError) {
|
|
1194
|
+
log.error(tag, 'Transaction parsing/validation failed:', validationError);
|
|
1195
|
+
return {
|
|
1196
|
+
success: false,
|
|
1197
|
+
error: `Invalid transaction format: ${validationError.message}`
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1084
1200
|
// Try each node until one succeeds
|
|
1085
1201
|
let lastError = null;
|
|
1202
|
+
let allErrors = [];
|
|
1086
1203
|
for (let i = 0; i < nodes.length; i++) {
|
|
1087
1204
|
const node = nodes[i];
|
|
1088
1205
|
try {
|
|
1089
1206
|
const provider = new ethers.providers.StaticJsonRpcProvider(node.service, chainId ? { chainId, name: node.networkId } : undefined);
|
|
1090
|
-
|
|
1207
|
+
// Use raw JSON-RPC call for immediate validation errors
|
|
1208
|
+
// eth_sendRawTransaction will validate the tx BEFORE returning
|
|
1209
|
+
log.info(tag, `Broadcasting via eth_sendRawTransaction to ${node.service.substring(0, 50)}...`);
|
|
1210
|
+
const txHash = await withTimeout(provider.send('eth_sendRawTransaction', [signedTx]), RPC_TIMEOUT_MS, `Broadcast timeout after ${RPC_TIMEOUT_MS}ms for ${node.service.substring(0, 50)}`);
|
|
1211
|
+
// If we got here, the transaction was accepted by the network
|
|
1212
|
+
log.info(tag, `✅ Transaction accepted by network: ${txHash}`);
|
|
1091
1213
|
// Success! Log which node worked (only if not the first one)
|
|
1092
1214
|
if (i > 0) {
|
|
1093
1215
|
log.info(tag, `Successfully broadcasted from node ${i + 1}/${nodes.length}: ${node.service.substring(0, 50)}`);
|
|
1094
1216
|
}
|
|
1217
|
+
// Parse the original transaction to get details for response
|
|
1218
|
+
const parsedTx = ethers.utils.parseTransaction(signedTx);
|
|
1095
1219
|
// Return in expected format for pioneer-server
|
|
1096
1220
|
return {
|
|
1097
1221
|
success: true,
|
|
1098
|
-
txid:
|
|
1099
|
-
transactionHash:
|
|
1100
|
-
from:
|
|
1101
|
-
to:
|
|
1102
|
-
nonce:
|
|
1103
|
-
gasLimit:
|
|
1104
|
-
gasPrice:
|
|
1105
|
-
chainId:
|
|
1222
|
+
txid: txHash,
|
|
1223
|
+
transactionHash: txHash,
|
|
1224
|
+
from: parsedTx.from,
|
|
1225
|
+
to: parsedTx.to,
|
|
1226
|
+
nonce: parsedTx.nonce,
|
|
1227
|
+
gasLimit: parsedTx.gasLimit?.toString(),
|
|
1228
|
+
gasPrice: parsedTx.gasPrice?.toString(),
|
|
1229
|
+
chainId: parsedTx.chainId
|
|
1106
1230
|
};
|
|
1107
1231
|
}
|
|
1108
1232
|
catch (e) {
|
|
1109
1233
|
lastError = e;
|
|
1110
|
-
|
|
1234
|
+
// Extract detailed error message from RPC response
|
|
1235
|
+
let errorMessage = e.message || 'Unknown error';
|
|
1236
|
+
// Check for JSON-RPC error format
|
|
1237
|
+
if (e.error) {
|
|
1238
|
+
if (typeof e.error === 'string') {
|
|
1239
|
+
errorMessage = e.error;
|
|
1240
|
+
}
|
|
1241
|
+
else if (e.error.message) {
|
|
1242
|
+
errorMessage = e.error.message;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else if (e.body) {
|
|
1246
|
+
try {
|
|
1247
|
+
const body = JSON.parse(e.body);
|
|
1248
|
+
if (body.error) {
|
|
1249
|
+
if (typeof body.error === 'string') {
|
|
1250
|
+
errorMessage = body.error;
|
|
1251
|
+
}
|
|
1252
|
+
else if (body.error.message) {
|
|
1253
|
+
errorMessage = body.error.message;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
catch (parseErr) {
|
|
1258
|
+
// Ignore parsing errors
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// Log the detailed error
|
|
1262
|
+
log.error(tag, `❌ Broadcast rejected by node ${i + 1}: ${errorMessage}`);
|
|
1263
|
+
allErrors.push(`Node ${i + 1}: ${errorMessage}`);
|
|
1264
|
+
log.warn(tag, `Node ${i + 1}/${nodes.length} failed (${node.service.substring(0, 50)}): ${errorMessage}`);
|
|
1111
1265
|
// Continue to next node
|
|
1112
1266
|
}
|
|
1113
1267
|
}
|
|
1114
|
-
// All nodes failed
|
|
1268
|
+
// All nodes failed - provide comprehensive error details
|
|
1269
|
+
const detailedError = allErrors.join(' | ');
|
|
1115
1270
|
log.error(tag, `All ${nodes.length} nodes failed for ${networkId}`);
|
|
1271
|
+
log.error(tag, 'Detailed errors:', detailedError);
|
|
1116
1272
|
return {
|
|
1117
1273
|
success: false,
|
|
1118
|
-
error: lastError?.message || `Failed to broadcast from any node for ${networkId}
|
|
1274
|
+
error: lastError?.message || `Failed to broadcast from any node for ${networkId}`,
|
|
1275
|
+
details: detailedError
|
|
1119
1276
|
};
|
|
1120
1277
|
};
|
|
1121
1278
|
exports.broadcastByNetwork = broadcastByNetwork;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/eth-network",
|
|
3
|
-
"version": "8.14.
|
|
3
|
+
"version": "8.14.4",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@ethersproject/abstract-provider": "^5.8.0",
|
|
19
19
|
"@ethersproject/bignumber": "^5.8.0",
|
|
20
20
|
"@ethersproject/providers": "^5.8.0",
|
|
21
|
-
"@pioneer-platform/blockbook": "^8.12.
|
|
21
|
+
"@pioneer-platform/blockbook": "^8.12.2",
|
|
22
22
|
"@pioneer-platform/loggerdog": "^8.11.0",
|
|
23
23
|
"@pioneer-platform/nodes": "^8.11.10",
|
|
24
24
|
"@pioneer-platform/pioneer-caip": "^9.10.0",
|