nodpay 0.2.28 → 0.2.30

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 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
- Check before proposing shows nonce, pending ops, and wallet status.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
+ }
package/scripts/txs.mjs CHANGED
@@ -124,7 +124,8 @@ try {
124
124
  }
125
125
  }
126
126
 
127
- console.log(JSON.stringify(data, null, 2));
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);