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/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
+ };
@@ -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 };