ethnotary 1.0.0
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/README.md +514 -0
- package/cli/commands/account/index.js +855 -0
- package/cli/commands/config/index.js +369 -0
- package/cli/commands/contact/index.js +139 -0
- package/cli/commands/contract/index.js +197 -0
- package/cli/commands/data/index.js +536 -0
- package/cli/commands/tx/index.js +841 -0
- package/cli/commands/wallet/index.js +181 -0
- package/cli/index.js +624 -0
- package/cli/utils/auth.js +146 -0
- package/cli/utils/constants.js +68 -0
- package/cli/utils/contacts.js +131 -0
- package/cli/utils/contracts.js +269 -0
- package/cli/utils/crosschain.js +278 -0
- package/cli/utils/networks.js +335 -0
- package/cli/utils/notifications.js +135 -0
- package/cli/utils/output.js +123 -0
- package/cli/utils/pin.js +89 -0
- package/data/balance.js +680 -0
- package/data/events.js +334 -0
- package/data/pending.js +261 -0
- package/data/scanWorker.js +169 -0
- package/data/token_cache.json +54 -0
- package/data/token_database.json +92 -0
- package/data/tokens.js +380 -0
- package/package.json +57 -0
package/data/events.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
const { ethers } = require('ethers');
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Script to get all event logs from a MultiSig contract across all EVM networks
|
|
6
|
+
* Usage: node events.js <multisig_address>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// MultiSig ABI - only the events we need
|
|
10
|
+
const MULTISIG_ABI = [
|
|
11
|
+
// Transaction Lifecycle Events
|
|
12
|
+
"event Confirmation(address indexed sender, uint indexed transactionId)",
|
|
13
|
+
"event Revocation(address indexed sender, uint indexed transactionId)",
|
|
14
|
+
"event Submission(uint indexed transactionId, address dest, uint256 value, bytes func)",
|
|
15
|
+
"event Execution(uint transactionId, address indexed to, uint indexed amount)",
|
|
16
|
+
"event ExecutionFailure(uint indexed transactionId)",
|
|
17
|
+
"event Delete(uint indexed transactionId, address indexed sender)",
|
|
18
|
+
|
|
19
|
+
// Execution Type Events (emitted during execute())
|
|
20
|
+
"event Swap(uint indexed transactionId, address indexed swapModule, address indexed executor, uint256 ethValue)",
|
|
21
|
+
"event TokenTransfer(uint indexed transactionId, address indexed assetContract, address indexed to, uint256 amountOrTokenId, address executor, bool isNFT)",
|
|
22
|
+
"event NativeTransfer(uint indexed transactionId, address indexed to, uint256 amount, address executor)",
|
|
23
|
+
"event ContractInteraction(uint indexed transactionId, address indexed target, address indexed executor, uint256 value, bytes data)",
|
|
24
|
+
"event CashOut(uint256 indexed approvalTxId, uint256 indexed transferTxId, address indexed depositAddress, address tokenAddress, uint256 amount, address executor, bool isNative)",
|
|
25
|
+
|
|
26
|
+
// Asset Receipt Events
|
|
27
|
+
"event Deposit(address sender, uint value)",
|
|
28
|
+
"event NftReceived(address operator, address from, uint256 tokenId, bytes data)",
|
|
29
|
+
|
|
30
|
+
// Owner Management Events
|
|
31
|
+
"event OwnerAddition(address indexed owner)",
|
|
32
|
+
"event OwnerRemoval(address indexed owner)",
|
|
33
|
+
"event OwnerReplace(address indexed oldOwner, address indexed newOwner)",
|
|
34
|
+
"event RequirementChange(uint required)"
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Function selector mappings for decoding submission types
|
|
38
|
+
const FUNCTION_SELECTORS = {
|
|
39
|
+
// ERC20 Token Functions
|
|
40
|
+
'0xa9059cbb': 'ERC20 Transfer',
|
|
41
|
+
'0x23b872dd': 'ERC20 TransferFrom',
|
|
42
|
+
'0x095ea7b3': 'ERC20 Approve',
|
|
43
|
+
'0x40c10f19': 'ERC20 Mint',
|
|
44
|
+
'0x42966c68': 'ERC20 Burn',
|
|
45
|
+
|
|
46
|
+
// ERC721 NFT Functions
|
|
47
|
+
'0x42842e0e': 'NFT SafeTransferFrom',
|
|
48
|
+
'0xb88d4fde': 'NFT SafeTransferFrom (with data)',
|
|
49
|
+
'0xa22cb465': 'NFT SetApprovalForAll',
|
|
50
|
+
|
|
51
|
+
// MultiSig Account Management (now PIN-protected, not via submit)
|
|
52
|
+
// Note: These functions no longer use submitTransaction flow
|
|
53
|
+
|
|
54
|
+
// MultiSig Transaction Management
|
|
55
|
+
'0x0c53c51c': 'Confirm Transaction',
|
|
56
|
+
'0x20ea8d86': 'Revoke Confirmation',
|
|
57
|
+
'0xee22610b': 'Execute Transaction',
|
|
58
|
+
|
|
59
|
+
// Swap/Bridge Functions
|
|
60
|
+
'0x38ed1739': 'Swap Exact Tokens For Tokens',
|
|
61
|
+
'0x7ff36ab5': 'Swap Exact ETH For Tokens',
|
|
62
|
+
'0x18cbafe5': 'Swap Exact Tokens For ETH',
|
|
63
|
+
|
|
64
|
+
// Cash-Out Functions
|
|
65
|
+
'0xa0712d68': 'Cash Out (Off-ramp)'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Decode function selector from bytes data
|
|
70
|
+
* @param {string} funcData - Hex string of function data
|
|
71
|
+
* @returns {string} Human-readable function description
|
|
72
|
+
*/
|
|
73
|
+
function decodeFunctionSelector(funcData) {
|
|
74
|
+
if (!funcData || funcData === '0x' || funcData === '0x0' || funcData.length < 10) {
|
|
75
|
+
return 'Native Transfer (ETH)';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Extract the first 4 bytes (function selector)
|
|
80
|
+
const selector = funcData.slice(0, 10).toLowerCase(); // '0x' + 8 hex chars
|
|
81
|
+
|
|
82
|
+
// Look up the selector
|
|
83
|
+
if (FUNCTION_SELECTORS[selector]) {
|
|
84
|
+
return FUNCTION_SELECTORS[selector];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return `Unknown Function (${selector})`;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return 'Unknown Function';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Available networks
|
|
94
|
+
const NETWORKS = {
|
|
95
|
+
sepolia: {
|
|
96
|
+
name: 'Sepolia',
|
|
97
|
+
rpc: process.env.SEPOLIA_RPC_URL
|
|
98
|
+
},
|
|
99
|
+
'base-sepolia': {
|
|
100
|
+
name: 'Base Sepolia',
|
|
101
|
+
rpc: process.env.BASE_SEPOLIA_RPC_URL
|
|
102
|
+
},
|
|
103
|
+
'arbitrum-sepolia': {
|
|
104
|
+
name: 'Arbitrum Sepolia',
|
|
105
|
+
rpc: process.env.ARBITRUM_SEPOLIA_RPC_URL
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
async function getMultiSigEventsFromNetwork(multisigAddress, networkKey, networkConfig) {
|
|
110
|
+
if (!networkConfig.rpc) {
|
|
111
|
+
console.log(`ā ļø Skipping ${networkConfig.name}: No RPC URL configured`);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
console.log(`š Connecting to ${networkConfig.name}...`);
|
|
117
|
+
const provider = new ethers.JsonRpcProvider(networkConfig.rpc);
|
|
118
|
+
|
|
119
|
+
// Create contract instance
|
|
120
|
+
const multisig = new ethers.Contract(multisigAddress, MULTISIG_ABI, provider);
|
|
121
|
+
|
|
122
|
+
// Check if contract exists
|
|
123
|
+
const code = await provider.getCode(multisigAddress);
|
|
124
|
+
if (code === '0x') {
|
|
125
|
+
console.log(`ā ļø No contract found at ${multisigAddress} on ${networkConfig.name}`);
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get all events
|
|
130
|
+
const filter = {
|
|
131
|
+
address: multisigAddress,
|
|
132
|
+
fromBlock: 0,
|
|
133
|
+
toBlock: 'latest'
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const logs = await provider.getLogs(filter);
|
|
137
|
+
console.log(`š ${networkConfig.name}: Found ${logs.length} total logs`);
|
|
138
|
+
|
|
139
|
+
// Parse events
|
|
140
|
+
const events = [];
|
|
141
|
+
for (const log of logs) {
|
|
142
|
+
try {
|
|
143
|
+
const parsedLog = multisig.interface.parseLog(log);
|
|
144
|
+
if (parsedLog) {
|
|
145
|
+
const block = await provider.getBlock(log.blockNumber);
|
|
146
|
+
|
|
147
|
+
const eventData = {
|
|
148
|
+
event: parsedLog.name,
|
|
149
|
+
args: parsedLog.args,
|
|
150
|
+
blockNumber: log.blockNumber,
|
|
151
|
+
transactionHash: log.transactionHash,
|
|
152
|
+
timestamp: new Date(block.timestamp * 1000).toISOString(),
|
|
153
|
+
timestampUnix: block.timestamp,
|
|
154
|
+
logIndex: log.logIndex,
|
|
155
|
+
network: networkConfig.name,
|
|
156
|
+
networkKey: networkKey
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// For Submission events, decode the function type
|
|
160
|
+
if (parsedLog.name === 'Submission' && parsedLog.args.func) {
|
|
161
|
+
eventData.submissionType = decodeFunctionSelector(parsedLog.args.func);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
events.push(eventData);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Skip logs that don't match our ABI
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(`ā
${networkConfig.name}: Parsed ${events.length} MultiSig events`);
|
|
173
|
+
return events;
|
|
174
|
+
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`ā Error getting events from ${networkConfig.name}:`, error.message);
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getAllMultiSigEvents(multisigAddress) {
|
|
182
|
+
try {
|
|
183
|
+
// Validate address
|
|
184
|
+
if (!ethers.isAddress(multisigAddress)) {
|
|
185
|
+
throw new Error(`Invalid address: ${multisigAddress}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`š Getting events for MultiSig: ${multisigAddress} across all networks\n`);
|
|
189
|
+
|
|
190
|
+
// Get events from all networks
|
|
191
|
+
const allEvents = [];
|
|
192
|
+
const networkSummaries = {};
|
|
193
|
+
|
|
194
|
+
for (const [networkKey, networkConfig] of Object.entries(NETWORKS)) {
|
|
195
|
+
const events = await getMultiSigEventsFromNetwork(multisigAddress, networkKey, networkConfig);
|
|
196
|
+
allEvents.push(...events);
|
|
197
|
+
networkSummaries[networkConfig.name] = events.length;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Sort all events by timestamp (newest to oldest)
|
|
201
|
+
allEvents.sort((a, b) => b.timestampUnix - a.timestampUnix);
|
|
202
|
+
|
|
203
|
+
console.log('\nš Network Summary:');
|
|
204
|
+
console.log('==================');
|
|
205
|
+
Object.entries(networkSummaries).forEach(([network, count]) => {
|
|
206
|
+
console.log(`${network}: ${count} events`);
|
|
207
|
+
});
|
|
208
|
+
console.log(`Total: ${allEvents.length} events across all networks`);
|
|
209
|
+
|
|
210
|
+
// Group events by type across all networks
|
|
211
|
+
const eventsByType = {};
|
|
212
|
+
allEvents.forEach(event => {
|
|
213
|
+
if (!eventsByType[event.event]) {
|
|
214
|
+
eventsByType[event.event] = [];
|
|
215
|
+
}
|
|
216
|
+
eventsByType[event.event].push(event);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
console.log('\nš Event Type Summary:');
|
|
220
|
+
console.log('=====================');
|
|
221
|
+
Object.keys(eventsByType).forEach(eventType => {
|
|
222
|
+
console.log(`${eventType}: ${eventsByType[eventType].length} events`);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// If there are Submission events, show breakdown by type
|
|
226
|
+
if (eventsByType['Submission'] && eventsByType['Submission'].length > 0) {
|
|
227
|
+
console.log('\nš Submission Type Breakdown:');
|
|
228
|
+
console.log('============================');
|
|
229
|
+
const submissionsByType = {};
|
|
230
|
+
eventsByType['Submission'].forEach(event => {
|
|
231
|
+
const type = event.submissionType || 'Unknown';
|
|
232
|
+
if (!submissionsByType[type]) {
|
|
233
|
+
submissionsByType[type] = 0;
|
|
234
|
+
}
|
|
235
|
+
submissionsByType[type]++;
|
|
236
|
+
});
|
|
237
|
+
Object.entries(submissionsByType)
|
|
238
|
+
.sort((a, b) => b[1] - a[1]) // Sort by count descending
|
|
239
|
+
.forEach(([type, count]) => {
|
|
240
|
+
console.log(` ${type}: ${count}`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Display all events in reverse chronological order (newest first)
|
|
245
|
+
console.log('\nš All Events (Newest First):');
|
|
246
|
+
console.log('=============================');
|
|
247
|
+
allEvents.forEach(event => {
|
|
248
|
+
// For Submission events, show the decoded type
|
|
249
|
+
const eventTitle = event.event === 'Submission' && event.submissionType
|
|
250
|
+
? `${event.event} - ${event.submissionType}`
|
|
251
|
+
: event.event;
|
|
252
|
+
|
|
253
|
+
console.log(`${event.timestamp} [${event.network}] - ${eventTitle}`);
|
|
254
|
+
console.log(` Block: ${event.blockNumber}, Tx: ${event.transactionHash}`);
|
|
255
|
+
|
|
256
|
+
// Show event-specific details
|
|
257
|
+
if (event.event === 'Submission') {
|
|
258
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
259
|
+
console.log(` Destination: ${event.args.dest}`);
|
|
260
|
+
console.log(` Value: ${ethers.formatEther(event.args.value)} ETH`);
|
|
261
|
+
if (event.submissionType) {
|
|
262
|
+
console.log(` Type: ${event.submissionType}`);
|
|
263
|
+
}
|
|
264
|
+
} else if (event.event === 'TokenTransfer') {
|
|
265
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
266
|
+
console.log(` Asset Contract: ${event.args.assetContract}`);
|
|
267
|
+
console.log(` To: ${event.args.to}`);
|
|
268
|
+
console.log(` ${event.args.isNFT ? 'Token ID' : 'Amount'}: ${event.args.amountOrTokenId.toString()}`);
|
|
269
|
+
console.log(` Type: ${event.args.isNFT ? 'NFT' : 'ERC20'}`);
|
|
270
|
+
console.log(` Executor: ${event.args.executor}`);
|
|
271
|
+
} else if (event.event === 'NativeTransfer') {
|
|
272
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
273
|
+
console.log(` To: ${event.args.to}`);
|
|
274
|
+
console.log(` Amount: ${ethers.formatEther(event.args.amount)} ETH`);
|
|
275
|
+
console.log(` Executor: ${event.args.executor}`);
|
|
276
|
+
} else if (event.event === 'Swap') {
|
|
277
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
278
|
+
console.log(` Swap Module: ${event.args.swapModule}`);
|
|
279
|
+
console.log(` ETH Value: ${ethers.formatEther(event.args.ethValue)} ETH`);
|
|
280
|
+
console.log(` Executor: ${event.args.executor}`);
|
|
281
|
+
} else if (event.event === 'CashOut') {
|
|
282
|
+
console.log(` Approval Tx ID: ${event.args.approvalTxId}`);
|
|
283
|
+
console.log(` Transfer Tx ID: ${event.args.transferTxId}`);
|
|
284
|
+
console.log(` Deposit Address: ${event.args.depositAddress}`);
|
|
285
|
+
console.log(` Token: ${event.args.isNative ? 'Native (ETH)' : event.args.tokenAddress}`);
|
|
286
|
+
console.log(` Amount: ${ethers.formatEther(event.args.amount)}`);
|
|
287
|
+
console.log(` Executor: ${event.args.executor}`);
|
|
288
|
+
} else if (event.event === 'ContractInteraction') {
|
|
289
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
290
|
+
console.log(` Target: ${event.args.target}`);
|
|
291
|
+
console.log(` Value: ${ethers.formatEther(event.args.value)} ETH`);
|
|
292
|
+
console.log(` Executor: ${event.args.executor}`);
|
|
293
|
+
console.log(` Data: ${event.args.data.slice(0, 66)}...`); // Show first 32 bytes
|
|
294
|
+
} else if (event.event === 'NftReceived') {
|
|
295
|
+
console.log(` From: ${event.args.from}`);
|
|
296
|
+
console.log(` Token ID: ${event.args.tokenId}`);
|
|
297
|
+
console.log(` Operator: ${event.args.operator}`);
|
|
298
|
+
} else if (event.event === 'Delete') {
|
|
299
|
+
console.log(` Transaction ID: ${event.args.transactionId}`);
|
|
300
|
+
console.log(` Deleted by: ${event.args.sender}`);
|
|
301
|
+
} else {
|
|
302
|
+
console.log(` Args:`, event.args);
|
|
303
|
+
}
|
|
304
|
+
console.log('');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return allEvents;
|
|
308
|
+
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error('ā Error getting MultiSig events:', error.message);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// CLI usage
|
|
316
|
+
if (require.main === module) {
|
|
317
|
+
const args = process.argv.slice(2);
|
|
318
|
+
|
|
319
|
+
if (args.length < 1) {
|
|
320
|
+
console.log('Usage: node events.js <multisig_address>');
|
|
321
|
+
console.log('This will search across all configured EVM networks');
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const multisigAddress = args[0];
|
|
326
|
+
getAllMultiSigEvents(multisigAddress);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
getAllMultiSigEvents,
|
|
331
|
+
getMultiSigEventsFromNetwork,
|
|
332
|
+
decodeFunctionSelector,
|
|
333
|
+
FUNCTION_SELECTORS
|
|
334
|
+
};
|
package/data/pending.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const { ethers } = require('ethers');
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Script to get all pending transactions from a MultiSig contract across all EVM networks
|
|
6
|
+
* Usage: node pending.js <multisig_address>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// MultiSig ABI - functions we need to read transaction data
|
|
10
|
+
const MULTISIG_ABI = [
|
|
11
|
+
"function transactionCount() view returns (uint)",
|
|
12
|
+
"function transactions(uint) view returns (address dest, uint value, bytes func, bool executed, uint id)",
|
|
13
|
+
"function isConfirmed(uint transactionId) view returns (bool)",
|
|
14
|
+
"function getConfirmationCount(uint transactionId) view returns (uint)",
|
|
15
|
+
"function getConfirmations(uint transactionId) view returns (address[])",
|
|
16
|
+
"function required() view returns (uint)",
|
|
17
|
+
"function getOwners() view returns (address[])",
|
|
18
|
+
"event Submission(uint indexed transactionId, address dest, uint256 value, bytes func)"
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Available networks
|
|
22
|
+
const NETWORKS = {
|
|
23
|
+
sepolia: {
|
|
24
|
+
name: 'Sepolia',
|
|
25
|
+
rpc: process.env.SEPOLIA_RPC_URL
|
|
26
|
+
},
|
|
27
|
+
'base-sepolia': {
|
|
28
|
+
name: 'Base Sepolia',
|
|
29
|
+
rpc: process.env.BASE_SEPOLIA_RPC_URL
|
|
30
|
+
},
|
|
31
|
+
'arbitrum-sepolia': {
|
|
32
|
+
name: 'Arbitrum Sepolia',
|
|
33
|
+
rpc: process.env.ARBITRUM_SEPOLIA_RPC_URL
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function getPendingTransactionsFromNetwork(multisigAddress, networkKey, networkConfig) {
|
|
38
|
+
if (!networkConfig.rpc) {
|
|
39
|
+
console.log(`ā ļø Skipping ${networkConfig.name}: No RPC URL configured`);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
console.log(`š Connecting to ${networkConfig.name}...`);
|
|
44
|
+
const provider = new ethers.JsonRpcProvider(networkConfig.rpc);
|
|
45
|
+
|
|
46
|
+
// Create contract instance
|
|
47
|
+
const multisig = new ethers.Contract(multisigAddress, MULTISIG_ABI, provider);
|
|
48
|
+
|
|
49
|
+
// Check if contract exists
|
|
50
|
+
const code = await provider.getCode(multisigAddress);
|
|
51
|
+
if (code === '0x') {
|
|
52
|
+
console.log(`ā ļø No contract found at ${multisigAddress} on ${networkConfig.name}`);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get basic info
|
|
57
|
+
const [transactionCount, required, owners] = await Promise.all([
|
|
58
|
+
multisig.transactionCount(),
|
|
59
|
+
multisig.required(),
|
|
60
|
+
multisig.getOwners()
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
console.log(`š ${networkConfig.name} MultiSig Info:`);
|
|
64
|
+
console.log(` Total Transactions: ${transactionCount}`);
|
|
65
|
+
console.log(` Required Confirmations: ${required}`);
|
|
66
|
+
console.log(` Total Owners: ${owners.length}`);
|
|
67
|
+
|
|
68
|
+
if (transactionCount === 0n) {
|
|
69
|
+
console.log(`ā
${networkConfig.name}: No transactions found`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`š ${networkConfig.name}: Checking ${transactionCount} transactions for pending status...`);
|
|
74
|
+
|
|
75
|
+
const pendingTransactions = [];
|
|
76
|
+
|
|
77
|
+
// Check each transaction
|
|
78
|
+
for (let i = 0; i < transactionCount; i++) {
|
|
79
|
+
try {
|
|
80
|
+
const [transaction, isConfirmed, confirmationCount, confirmations] = await Promise.all([
|
|
81
|
+
multisig.transactions(i),
|
|
82
|
+
multisig.isConfirmed(i),
|
|
83
|
+
multisig.getConfirmationCount(i),
|
|
84
|
+
multisig.getConfirmations(i)
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
// Transaction is pending if it's not executed AND not deleted
|
|
88
|
+
// Deleted transactions have dest = address(0)
|
|
89
|
+
const isDeleted = transaction.dest === '0x0000000000000000000000000000000000000000';
|
|
90
|
+
const isPending = !transaction.executed && !isDeleted;
|
|
91
|
+
|
|
92
|
+
if (isPending) {
|
|
93
|
+
// Get transaction creation timestamp by finding the SubmitTransaction event
|
|
94
|
+
let timestamp = null;
|
|
95
|
+
let blockNumber = null;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Search for Submission event for this transaction ID
|
|
99
|
+
const filter = multisig.filters.Submission(i);
|
|
100
|
+
const events = await multisig.queryFilter(filter);
|
|
101
|
+
|
|
102
|
+
if (events.length > 0) {
|
|
103
|
+
const event = events[0];
|
|
104
|
+
blockNumber = event.blockNumber;
|
|
105
|
+
const block = await provider.getBlock(blockNumber);
|
|
106
|
+
timestamp = block.timestamp;
|
|
107
|
+
}
|
|
108
|
+
} catch (eventError) {
|
|
109
|
+
// If we can't get the event, skip timestamp info rather than using current time
|
|
110
|
+
console.warn(`ā ļø Could not get timestamp for transaction ${i}:`, eventError.message);
|
|
111
|
+
timestamp = null;
|
|
112
|
+
blockNumber = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pendingTransactions.push({
|
|
116
|
+
id: i,
|
|
117
|
+
dest: transaction.dest,
|
|
118
|
+
value: ethers.formatEther(transaction.value),
|
|
119
|
+
valueWei: transaction.value.toString(),
|
|
120
|
+
func: transaction.func,
|
|
121
|
+
executed: transaction.executed,
|
|
122
|
+
isConfirmed: isConfirmed,
|
|
123
|
+
confirmationCount: Number(confirmationCount),
|
|
124
|
+
requiredConfirmations: Number(required),
|
|
125
|
+
confirmationsNeeded: Number(required) - Number(confirmationCount),
|
|
126
|
+
confirmedBy: confirmations,
|
|
127
|
+
decodedFunction: decodeFunctionCall(transaction.func),
|
|
128
|
+
network: networkConfig.name,
|
|
129
|
+
networkKey: networkKey,
|
|
130
|
+
timestamp: timestamp,
|
|
131
|
+
blockNumber: blockNumber,
|
|
132
|
+
createdAt: timestamp ? new Date(timestamp * 1000).toISOString() : null
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn(`ā ļø Error checking transaction ${i} on ${networkConfig.name}:`, error.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(`ā
${networkConfig.name}: Found ${pendingTransactions.length} pending transactions`);
|
|
141
|
+
return pendingTransactions;
|
|
142
|
+
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`ā Error getting pending transactions from ${networkConfig.name}:`, error.message);
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getAllPendingTransactions(multisigAddress) {
|
|
150
|
+
try {
|
|
151
|
+
// Validate address
|
|
152
|
+
if (!ethers.isAddress(multisigAddress)) {
|
|
153
|
+
throw new Error(`Invalid address: ${multisigAddress}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`š Getting pending transactions for MultiSig: ${multisigAddress} across all networks\n`);
|
|
157
|
+
|
|
158
|
+
// Get pending transactions from all networks
|
|
159
|
+
const allPendingTransactions = [];
|
|
160
|
+
const networkSummaries = {};
|
|
161
|
+
|
|
162
|
+
for (const [networkKey, networkConfig] of Object.entries(NETWORKS)) {
|
|
163
|
+
const transactions = await getPendingTransactionsFromNetwork(multisigAddress, networkKey, networkConfig);
|
|
164
|
+
allPendingTransactions.push(...transactions);
|
|
165
|
+
networkSummaries[networkConfig.name] = transactions.length;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Sort transactions by timestamp in descending order (newest first)
|
|
169
|
+
// If timestamp is not available, fall back to ID sorting within the same network
|
|
170
|
+
allPendingTransactions.sort((a, b) => {
|
|
171
|
+
// Primary sort: by timestamp (newest first)
|
|
172
|
+
if (a.timestamp && b.timestamp) {
|
|
173
|
+
return b.timestamp - a.timestamp;
|
|
174
|
+
}
|
|
175
|
+
// Secondary sort: if timestamps are equal or missing, sort by network then ID
|
|
176
|
+
if (a.networkKey === b.networkKey) {
|
|
177
|
+
return b.id - a.id;
|
|
178
|
+
}
|
|
179
|
+
// Tertiary sort: by network name for consistency
|
|
180
|
+
return a.networkKey.localeCompare(b.networkKey);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
console.log('\nš Network Summary:');
|
|
184
|
+
console.log('==================');
|
|
185
|
+
Object.entries(networkSummaries).forEach(([network, count]) => {
|
|
186
|
+
console.log(`${network}: ${count} pending transactions`);
|
|
187
|
+
});
|
|
188
|
+
console.log(`Total: ${allPendingTransactions.length} pending transactions across all networks`);
|
|
189
|
+
|
|
190
|
+
if (allPendingTransactions.length > 0) {
|
|
191
|
+
console.log('\nš All Pending Transactions:');
|
|
192
|
+
console.log('============================');
|
|
193
|
+
|
|
194
|
+
allPendingTransactions.forEach(tx => {
|
|
195
|
+
console.log(`\n[${tx.network}] Transaction ID: ${tx.id}`);
|
|
196
|
+
if (tx.createdAt) {
|
|
197
|
+
console.log(` Created: ${tx.createdAt} (Block: ${tx.blockNumber || 'Unknown'})`);
|
|
198
|
+
}
|
|
199
|
+
console.log(` Destination: ${tx.dest}`);
|
|
200
|
+
console.log(` Value: ${tx.value} ETH (${tx.valueWei} wei)`);
|
|
201
|
+
console.log(` Function Data: ${tx.func}`);
|
|
202
|
+
if (tx.decodedFunction) {
|
|
203
|
+
console.log(` Decoded: ${tx.decodedFunction}`);
|
|
204
|
+
}
|
|
205
|
+
console.log(` Confirmations: ${tx.confirmationCount}/${tx.requiredConfirmations} (need ${tx.confirmationsNeeded} more)`);
|
|
206
|
+
console.log(` Confirmed by: ${tx.confirmedBy.length > 0 ? tx.confirmedBy.join(', ') : 'None'}`);
|
|
207
|
+
console.log(` Status: ${tx.isConfirmed ? 'Ready to Execute' : 'Awaiting Confirmations'}`);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return allPendingTransactions;
|
|
212
|
+
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('ā Error getting pending transactions:', error.message);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Try to decode common function calls
|
|
221
|
+
*/
|
|
222
|
+
function decodeFunctionCall(funcData) {
|
|
223
|
+
if (!funcData || funcData === '0x') {
|
|
224
|
+
return 'ETH Transfer (no function call)';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
// Common function selectors
|
|
229
|
+
const selectors = {
|
|
230
|
+
'0xa9059cbb': 'transfer(address,uint256)', // ERC20 transfer
|
|
231
|
+
'0x23b872dd': 'transferFrom(address,address,uint256)', // ERC20 transferFrom
|
|
232
|
+
'0x42842e0e': 'safeTransferFrom(address,address,uint256)', // ERC721 safeTransferFrom
|
|
233
|
+
'0xb88d4fde': 'safeTransferFrom(address,address,uint256,bytes)', // ERC721 safeTransferFrom with data
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const selector = funcData.slice(0, 10);
|
|
237
|
+
if (selectors[selector]) {
|
|
238
|
+
return selectors[selector];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return `Unknown function (${selector})`;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return 'Unable to decode';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// CLI usage
|
|
248
|
+
if (require.main === module) {
|
|
249
|
+
const args = process.argv.slice(2);
|
|
250
|
+
|
|
251
|
+
if (args.length < 1) {
|
|
252
|
+
console.log('Usage: node pending.js <multisig_address>');
|
|
253
|
+
console.log('This will search across all configured EVM networks');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const multisigAddress = args[0];
|
|
258
|
+
getAllPendingTransactions(multisigAddress);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = { getAllPendingTransactions, getPendingTransactionsFromNetwork };
|