nodpay 0.2.28 → 0.2.29
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 +7 -1
- package/bin/nodpay.mjs +6 -0
- package/package.json +1 -1
- package/scripts/gasprice.mjs +95 -0
package/SKILL.md
CHANGED
|
@@ -111,7 +111,13 @@ First tx deploys the wallet. Pass all params for first tx; after that `--safe` a
|
|
|
111
111
|
npx nodpay txs --safe <SAFE>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
**Always run `txs` before proposing.** Do not assume a previous transaction is still pending — the human may have approved or rejected it without telling you. Check actual on-chain state first.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx nodpay gasprice --chain <CHAIN>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Returns current gas price and estimated gas cost (`estimatedGasCost.deploy` for first tx, `.call` for subsequent). Use when proposing a sweep: `maxSendable = balance - estimatedGasCost.deploy`.
|
|
115
121
|
|
|
116
122
|
---
|
|
117
123
|
|
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 === 'gasprice') {
|
|
19
|
+
const scriptPath = new URL('../scripts/gasprice.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 === 'version' || command === '--version' || command === '-v') {
|
|
19
23
|
const { readFileSync } = await import('fs');
|
|
20
24
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -26,11 +30,13 @@ Commands:
|
|
|
26
30
|
keygen Generate (or reuse) agent keypair
|
|
27
31
|
propose Propose a transaction for human approval
|
|
28
32
|
txs List pending and completed transactions
|
|
33
|
+
gasprice Get current gas price and estimated gas cost per chain
|
|
29
34
|
|
|
30
35
|
Examples:
|
|
31
36
|
nodpay keygen
|
|
32
37
|
nodpay propose --safe 0x... --to 0x... --value-eth 0.01 --chain base
|
|
33
38
|
nodpay txs --safe 0x...
|
|
39
|
+
nodpay gasprice --chain base
|
|
34
40
|
|
|
35
41
|
Docs: https://nodpay.ai/skill.md`);
|
|
36
42
|
process.exit(command ? 1 : 0);
|
package/package.json
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Get current gas price and estimated gas cost for a NodPay transaction.
|
|
4
|
+
*
|
|
5
|
+
* Useful before proposing a sweep ("send all funds") — lets you calculate
|
|
6
|
+
* how much ETH to leave for gas before proposing the full balance.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx nodpay gasprice --chain <CHAIN>
|
|
10
|
+
*
|
|
11
|
+
* Output:
|
|
12
|
+
* {
|
|
13
|
+
* "chain": "base",
|
|
14
|
+
* "chainId": "8453",
|
|
15
|
+
* "gasPriceGwei": "0.0012",
|
|
16
|
+
* "estimatedGasCost": {
|
|
17
|
+
* "deploy": "0.000152", // first tx (Safe not yet deployed)
|
|
18
|
+
* "call": "0.000043" // subsequent txs
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* The agent should use:
|
|
23
|
+
* maxSendable = balance - estimatedGasCost.deploy (if nonce 0 / counterfactual)
|
|
24
|
+
* maxSendable = balance - estimatedGasCost.call (if Safe already deployed)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { ethers } from 'ethers';
|
|
28
|
+
import { createRequire } from 'module';
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
30
|
+
const NETWORKS = require('@nodpay/core/networks');
|
|
31
|
+
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
function getArg(name) {
|
|
34
|
+
const idx = args.indexOf(name);
|
|
35
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const chainArg = getArg('--chain');
|
|
39
|
+
if (!chainArg) {
|
|
40
|
+
const allChains = { ...NETWORKS.mainnet, ...NETWORKS.testnet };
|
|
41
|
+
console.error(JSON.stringify({ error: '--chain is required. Supported: ' + Object.keys(allChains).join(', ') }));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const allChains = { ...NETWORKS.mainnet, ...NETWORKS.testnet };
|
|
46
|
+
const net = allChains[chainArg];
|
|
47
|
+
if (!net) {
|
|
48
|
+
console.error(JSON.stringify({ error: `Unknown chain "${chainArg}". Supported: ${Object.keys(allChains).join(', ')}` }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Gas baselines from bundler simulation (same as propose.mjs)
|
|
53
|
+
const BUNDLER_BASELINE = {
|
|
54
|
+
deploy: { vgl: 2653312n, cgl: 213422n, pvg: 194498n },
|
|
55
|
+
call: { vgl: 500000n, cgl: 218222n, pvg: 176846n },
|
|
56
|
+
};
|
|
57
|
+
const P256_DELTA = 400000n - 27000n; // FCL vs precompile
|
|
58
|
+
const HAS_P256_PRECOMPILE = new Set(['1', '8453', '42161', '10', '137', '11155111', '84532']);
|
|
59
|
+
const SAFETY = 12n; // 1.2x
|
|
60
|
+
|
|
61
|
+
function totalGasUnits(isCounterfactual, chainId) {
|
|
62
|
+
const base = isCounterfactual ? BUNDLER_BASELINE.deploy : BUNDLER_BASELINE.call;
|
|
63
|
+
let vgl = base.vgl;
|
|
64
|
+
if (!HAS_P256_PRECOMPILE.has(String(chainId))) vgl += P256_DELTA;
|
|
65
|
+
const vglS = vgl * SAFETY / 10n;
|
|
66
|
+
const cglS = base.cgl * SAFETY / 10n;
|
|
67
|
+
const pvgS = base.pvg * SAFETY / 10n;
|
|
68
|
+
return vglS + cglS + pvgS;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const provider = new ethers.JsonRpcProvider(net.rpcUrl);
|
|
73
|
+
const feeData = await provider.getFeeData();
|
|
74
|
+
const gasPrice = feeData.gasPrice || ethers.parseUnits('20', 'gwei');
|
|
75
|
+
const FEE_MULTIPLIER = 3n; // same buffer as propose.mjs (propose→approve window)
|
|
76
|
+
|
|
77
|
+
const effectiveGasPrice = gasPrice * FEE_MULTIPLIER;
|
|
78
|
+
|
|
79
|
+
const deployCost = totalGasUnits(true, net.chainId) * effectiveGasPrice;
|
|
80
|
+
const callCost = totalGasUnits(false, net.chainId) * effectiveGasPrice;
|
|
81
|
+
|
|
82
|
+
console.log(JSON.stringify({
|
|
83
|
+
chain: chainArg,
|
|
84
|
+
chainId: String(net.chainId),
|
|
85
|
+
gasPriceGwei: ethers.formatUnits(gasPrice, 'gwei'),
|
|
86
|
+
effectiveGasPriceGwei: ethers.formatUnits(effectiveGasPrice, 'gwei'),
|
|
87
|
+
estimatedGasCost: {
|
|
88
|
+
deploy: ethers.formatEther(deployCost), // nonce 0 / counterfactual Safe
|
|
89
|
+
call: ethers.formatEther(callCost), // Safe already deployed
|
|
90
|
+
},
|
|
91
|
+
}, null, 2));
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|