nodpay 0.2.29 → 0.2.31
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/SKILL.md +9 -2
- package/bin/nodpay.mjs +6 -0
- package/package.json +1 -1
- package/scripts/nonce.mjs +89 -0
- package/scripts/propose.mjs +8 -32
- package/scripts/txs.mjs +2 -1
package/SKILL.md
CHANGED
|
@@ -93,6 +93,7 @@ npx nodpay propose \
|
|
|
93
93
|
--safe <SAFE> \
|
|
94
94
|
--to <RECIPIENT> \
|
|
95
95
|
--value-eth <AMOUNT> \
|
|
96
|
+
--nonce <N> \
|
|
96
97
|
--human-signer-passkey-x <X> \
|
|
97
98
|
--human-signer-passkey-y <Y> \
|
|
98
99
|
--recovery-signer <RECOVERY>
|
|
@@ -111,7 +112,13 @@ First tx deploys the wallet. Pass all params for first tx; after that `--safe` a
|
|
|
111
112
|
npx nodpay txs --safe <SAFE>
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
**Always
|
|
115
|
+
**Always check nonce before proposing.** Do not assume a previous transaction is still pending — the human may have approved or rejected it without telling you.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx nodpay nonce --safe <SAFE> --chain <CHAIN>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Returns `nextNonce` (from on-chain EntryPoint + pending proposals), `onChainNonce`, and `pendingCount`. Pass `nextNonce` as `--nonce` to propose.
|
|
115
122
|
|
|
116
123
|
```bash
|
|
117
124
|
npx nodpay gasprice --chain <CHAIN>
|
|
@@ -156,7 +163,7 @@ EOA wallets: replace passkey fields with `"humanSignerEoa": "0x..."`.
|
|
|
156
163
|
| `--human-signer-passkey-x/y` | passkey | Human signer passkey public key |
|
|
157
164
|
| `--human-signer-eoa` | eoa | Human signer EOA address |
|
|
158
165
|
| `--recovery-signer` | first tx | Recovery signer address |
|
|
159
|
-
| `--nonce` |
|
|
166
|
+
| `--nonce` | **required** | Nonce for this proposal. Run `txs` first to determine. |
|
|
160
167
|
|
|
161
168
|
Wallet address is the same across all chains. **Ask which chain if not specified.**
|
|
162
169
|
|
package/bin/nodpay.mjs
CHANGED
|
@@ -15,6 +15,10 @@ if (command === 'propose') {
|
|
|
15
15
|
const scriptPath = new URL('../scripts/txs.mjs', import.meta.url).pathname;
|
|
16
16
|
process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
|
|
17
17
|
await import(scriptPath);
|
|
18
|
+
} else if (command === 'nonce') {
|
|
19
|
+
const scriptPath = new URL('../scripts/nonce.mjs', import.meta.url).pathname;
|
|
20
|
+
process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
|
|
21
|
+
await import(scriptPath);
|
|
18
22
|
} else if (command === 'gasprice') {
|
|
19
23
|
const scriptPath = new URL('../scripts/gasprice.mjs', import.meta.url).pathname;
|
|
20
24
|
process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
|
|
@@ -30,12 +34,14 @@ Commands:
|
|
|
30
34
|
keygen Generate (or reuse) agent keypair
|
|
31
35
|
propose Propose a transaction for human approval
|
|
32
36
|
txs List pending and completed transactions
|
|
37
|
+
nonce Query next nonce from on-chain EntryPoint
|
|
33
38
|
gasprice Get current gas price and estimated gas cost per chain
|
|
34
39
|
|
|
35
40
|
Examples:
|
|
36
41
|
nodpay keygen
|
|
37
42
|
nodpay propose --safe 0x... --to 0x... --value-eth 0.01 --chain base
|
|
38
43
|
nodpay txs --safe 0x...
|
|
44
|
+
nodpay nonce --safe 0x... --chain base
|
|
39
45
|
nodpay gasprice --chain base
|
|
40
46
|
|
|
41
47
|
Docs: https://nodpay.ai/skill.md`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Query the next available nonce for a Safe wallet from on-chain EntryPoint.
|
|
4
|
+
*
|
|
5
|
+
* This is the ERC-4337 standard: EntryPoint.getNonce(sender, key).
|
|
6
|
+
* Pure on-chain query — no server dependency.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx nodpay nonce --safe <SAFE_ADDRESS> --chain <CHAIN>
|
|
10
|
+
*
|
|
11
|
+
* Output:
|
|
12
|
+
* { "nonce": 0, "safe": "0x...", "chain": "base", "chainId": "8453" }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { ethers } from 'ethers';
|
|
16
|
+
import { ENTRYPOINT } from '@nodpay/core';
|
|
17
|
+
import { createRequire } from 'module';
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const NETWORKS = require('@nodpay/core/networks');
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
function getArg(name) {
|
|
23
|
+
const idx = args.indexOf(name);
|
|
24
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const safe = getArg('--safe');
|
|
28
|
+
const chainArg = getArg('--chain');
|
|
29
|
+
|
|
30
|
+
if (!safe) {
|
|
31
|
+
console.error(JSON.stringify({ error: '--safe <address> is required' }));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (!chainArg) {
|
|
35
|
+
const allChains = { ...NETWORKS.mainnet, ...NETWORKS.testnet };
|
|
36
|
+
console.error(JSON.stringify({ error: '--chain is required. Supported: ' + Object.keys(allChains).join(', ') }));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const allChains = { ...NETWORKS.mainnet, ...NETWORKS.testnet };
|
|
41
|
+
const net = allChains[chainArg];
|
|
42
|
+
if (!net) {
|
|
43
|
+
console.error(JSON.stringify({ error: `Unknown chain "${chainArg}". Supported: ${Object.keys(allChains).join(', ')}` }));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// 1. On-chain nonce from EntryPoint (source of truth for executed txs)
|
|
49
|
+
const provider = new ethers.JsonRpcProvider(net.rpcUrl);
|
|
50
|
+
const ep = new ethers.Contract(
|
|
51
|
+
ENTRYPOINT,
|
|
52
|
+
['function getNonce(address,uint192) view returns (uint256)'],
|
|
53
|
+
provider
|
|
54
|
+
);
|
|
55
|
+
const onChainNonce = await ep.getNonce(safe, 0);
|
|
56
|
+
|
|
57
|
+
// 2. Check pending ops to find the highest queued nonce
|
|
58
|
+
// (same logic as the battle-tested propose.mjs nonce resolution)
|
|
59
|
+
let nextNonce = onChainNonce;
|
|
60
|
+
let pendingCount = 0;
|
|
61
|
+
try {
|
|
62
|
+
const baseUrl = 'https://nodpay.ai/api';
|
|
63
|
+
const listRes = await fetch(`${baseUrl}/txs?safe=${safe}&chain=${net.chainId}`);
|
|
64
|
+
if (listRes.ok) {
|
|
65
|
+
const listData = await listRes.json();
|
|
66
|
+
for (const op of (listData.txs || listData.ops || [])) {
|
|
67
|
+
const opNonce = BigInt(op.nonce ?? -1);
|
|
68
|
+
if (opNonce >= onChainNonce && opNonce >= nextNonce) {
|
|
69
|
+
pendingCount++;
|
|
70
|
+
nextNonce = opNonce + 1n;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// Non-fatal: op-store may be unavailable, fall back to on-chain only
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(JSON.stringify({
|
|
79
|
+
nextNonce: Number(nextNonce),
|
|
80
|
+
onChainNonce: Number(onChainNonce),
|
|
81
|
+
pendingCount,
|
|
82
|
+
safe,
|
|
83
|
+
chain: chainArg,
|
|
84
|
+
chainId: String(net.chainId),
|
|
85
|
+
}, null, 2));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
package/scripts/propose.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* --human-signer-eoa <address> - Human's EOA signer address (for EOA mode)
|
|
20
20
|
* --salt <nonce> - Salt nonce (required for counterfactual)
|
|
21
21
|
* --reuse-gas-from <shortHash> - Reuse gas values from a previous op (shortHash prefix of safeOpHash)
|
|
22
|
-
* --nonce <n> -
|
|
22
|
+
* --nonce <n> - Required. Use `txs` to find current nonce.
|
|
23
23
|
*
|
|
24
24
|
* Output: JSON with userOpHash, safeTxHash, safeOperationJson, etc.
|
|
25
25
|
*/
|
|
@@ -336,38 +336,14 @@ try {
|
|
|
336
336
|
const opStoreUrl = opStoreBase;
|
|
337
337
|
const safeAddr = await safe4337Pack.protocolKit.getAddress();
|
|
338
338
|
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const provider = new ethers.JsonRpcProvider(RPC_URL);
|
|
346
|
-
const ep = new ethers.Contract('0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
|
|
347
|
-
['function getNonce(address,uint192) view returns (uint256)'], provider);
|
|
348
|
-
const onChainNonce = await ep.getNonce(safeAddr, 0);
|
|
349
|
-
|
|
350
|
-
// Check pending ops to find the highest queued nonce
|
|
351
|
-
const listRes = await fetch(`${opStoreUrl}/txs?safe=${safeAddr}&chain=${CHAIN_ID}`);
|
|
352
|
-
let nextNonce = onChainNonce;
|
|
353
|
-
if (listRes.ok) {
|
|
354
|
-
const listData = await listRes.json();
|
|
355
|
-
for (const op of (listData.txs || listData.ops || [])) {
|
|
356
|
-
const opNonce = BigInt(op.nonce ?? -1);
|
|
357
|
-
if (opNonce >= onChainNonce && opNonce >= nextNonce) {
|
|
358
|
-
nextNonce = opNonce + 1n;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
// Only set custom nonce if we need to skip ahead for queuing
|
|
363
|
-
if (nextNonce > onChainNonce) {
|
|
364
|
-
txOptions.customNonce = nextNonce;
|
|
365
|
-
}
|
|
366
|
-
// else: let SDK use on-chain nonce naturally
|
|
367
|
-
} catch (e) {
|
|
368
|
-
// Non-fatal: SDK will use its own nonce detection
|
|
369
|
-
}
|
|
339
|
+
// --nonce is required. Agent must run `txs` first to determine the correct nonce.
|
|
340
|
+
// This prevents accidental double-proposals: if agent proposes twice with the same nonce,
|
|
341
|
+
// the second is just a replacement (same slot), not a new transaction.
|
|
342
|
+
if (customNonceArg === undefined) {
|
|
343
|
+
console.error(JSON.stringify({ error: '--nonce is required. Run `npx nodpay txs --safe <SAFE>` first to check current nonce.' }));
|
|
344
|
+
process.exit(1);
|
|
370
345
|
}
|
|
346
|
+
txOptions.customNonce = BigInt(customNonceArg);
|
|
371
347
|
|
|
372
348
|
// Resolve gas values: use hardcoded defaults (from RPC gas price) always.
|
|
373
349
|
// If --reuse-gas-from is provided and fetch succeeds, use those values instead
|
package/scripts/txs.mjs
CHANGED
|
@@ -124,7 +124,8 @@ try {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
// BigInt values from @nodpay/core (e.g. decoded value) must be serialized as strings
|
|
128
|
+
console.log(JSON.stringify(data, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));
|
|
128
129
|
} catch (err) {
|
|
129
130
|
console.error(JSON.stringify({ error: err.message }));
|
|
130
131
|
process.exit(1);
|