nodpay 0.2.3 → 0.2.5
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 +19 -1
- package/SKILL.md +12 -29
- package/package.json +1 -1
- package/scripts/propose.mjs +24 -3
package/README.md
CHANGED
|
@@ -28,11 +28,29 @@ NODPAY_AGENT_KEY=0x... npx nodpay propose \
|
|
|
28
28
|
3. Agent proposes transactions with `npx nodpay propose`
|
|
29
29
|
4. User approves/rejects on their phone
|
|
30
30
|
|
|
31
|
+
## Key generation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx nodpay keygen --env-file .env
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Outputs the agent's **public address only**. The private key is written directly to `.env` — it never appears in stdout, logs, or the agent's context window.
|
|
38
|
+
|
|
39
|
+
If a key already exists, it reuses it and prints the address.
|
|
40
|
+
|
|
41
|
+
### Security design
|
|
42
|
+
|
|
43
|
+
The agent (LLM) **never sees the private key**. `keygen` writes the secret directly to disk; the `propose` command reads it from the environment at runtime. This means:
|
|
44
|
+
|
|
45
|
+
- No private key in conversation history or context window
|
|
46
|
+
- No risk of leaking the key through prompt injection
|
|
47
|
+
- The agent only needs the public address (for wallet links)
|
|
48
|
+
|
|
31
49
|
## Env
|
|
32
50
|
|
|
33
51
|
| Variable | Required | Description |
|
|
34
52
|
|----------|----------|-------------|
|
|
35
|
-
| `NODPAY_AGENT_KEY` | ✅ | Agent's private key
|
|
53
|
+
| `NODPAY_AGENT_KEY` | ✅ | Agent's private key — use `npx nodpay keygen` to generate securely |
|
|
36
54
|
|
|
37
55
|
## Supported chains
|
|
38
56
|
|
package/SKILL.md
CHANGED
|
@@ -42,9 +42,9 @@ The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (pass
|
|
|
42
42
|
npx nodpay keygen --env-file .env
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Outputs your agent's **public address** only. The private key is written directly to `.env` — it never appears in your output or context.
|
|
46
46
|
|
|
47
|
-
If
|
|
47
|
+
If a key already exists in the file, it reuses it and prints the address.
|
|
48
48
|
|
|
49
49
|
**After keygen**, trigger a gateway restart so the env var takes effect. Send your wallet link message to the user BEFORE restarting — the restart will interrupt your session.
|
|
50
50
|
|
|
@@ -102,8 +102,6 @@ Each wallet file:
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
`agentSigner` is your agent's EOA address (derived from `NODPAY_AGENT_KEY`).
|
|
106
|
-
|
|
107
105
|
For EOA wallets, replace passkey fields with `"userSigner": "0x..."`.
|
|
108
106
|
|
|
109
107
|
One agent key serves all wallets — multi-wallet is handled user-side (different passkeys/recovery keys → different Safe addresses, same agent).
|
|
@@ -113,8 +111,8 @@ One agent key serves all wallets — multi-wallet is handled user-side (differen
|
|
|
113
111
|
### Propose a transaction
|
|
114
112
|
|
|
115
113
|
```bash
|
|
116
|
-
NODPAY_AGENT_KEY=0x... \
|
|
117
114
|
npx nodpay propose \
|
|
115
|
+
--chain <CHAIN_NAME> \
|
|
118
116
|
--safe <WALLET_ADDRESS> \
|
|
119
117
|
--to <RECIPIENT> \
|
|
120
118
|
--value-eth <AMOUNT> \
|
|
@@ -133,12 +131,8 @@ The script outputs JSON with an `approveUrl`. Send it to the user:
|
|
|
133
131
|
|
|
134
132
|
### Check balance
|
|
135
133
|
|
|
136
|
-
Use the RPC URL for the wallet's chain (see `references/networks.json`):
|
|
137
|
-
|
|
138
134
|
```bash
|
|
139
|
-
|
|
140
|
-
-H "Content-Type: application/json" \
|
|
141
|
-
-d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["<WALLET_ADDRESS>","latest"],"id":1}'
|
|
135
|
+
npx nodpay propose --chain <CHAIN_NAME> --safe <WALLET_ADDRESS> --check-balance
|
|
142
136
|
```
|
|
143
137
|
|
|
144
138
|
If balance is 0, remind the user to deposit before proposing.
|
|
@@ -153,12 +147,11 @@ Always check before proposing — this tells you the current nonce, pending ops,
|
|
|
153
147
|
|
|
154
148
|
---
|
|
155
149
|
|
|
156
|
-
##
|
|
157
|
-
|
|
158
|
-
### Flags
|
|
150
|
+
## Flags
|
|
159
151
|
|
|
160
152
|
| Flag | Required | Description |
|
|
161
153
|
|------|----------|-------------|
|
|
154
|
+
| `--chain` | ✅ | Chain name (e.g. `ethereum`, `base`, `sepolia`) |
|
|
162
155
|
| `--safe` | ✅ | Wallet (Safe) address |
|
|
163
156
|
| `--to` | ✅ | Recipient address |
|
|
164
157
|
| `--value-eth` | ✅ | Amount in ETH |
|
|
@@ -170,16 +163,6 @@ Always check before proposing — this tells you the current nonce, pending ops,
|
|
|
170
163
|
| `--nonce` | optional | Force nonce (for replacements) |
|
|
171
164
|
| `--purpose` | optional | Human-readable label |
|
|
172
165
|
|
|
173
|
-
### Environment
|
|
174
|
-
|
|
175
|
-
Only one env var is required:
|
|
176
|
-
|
|
177
|
-
| Var | Description |
|
|
178
|
-
|-----|-------------|
|
|
179
|
-
| `NODPAY_AGENT_KEY` | Agent signing key (required) |
|
|
180
|
-
|
|
181
|
-
Chain config (RPC, bundler, explorer) is auto-resolved from `references/networks.json`. No need to set `RPC_URL`, `CHAIN_ID`, or bundler keys.
|
|
182
|
-
|
|
183
166
|
### Supported Chains
|
|
184
167
|
|
|
185
168
|
`ethereum`, `base`, `arbitrum`, `optimism`, `polygon`, `sepolia`, `base_sepolia`
|
|
@@ -190,11 +173,11 @@ The wallet address is the same across all chains (counterfactual). Chain is only
|
|
|
190
173
|
|
|
191
174
|
## Transaction Patterns
|
|
192
175
|
|
|
193
|
-
**Sequential**: Just call propose multiple times. Nonces auto-increment
|
|
176
|
+
**Sequential**: Just call propose multiple times. Nonces auto-increment.
|
|
194
177
|
|
|
195
|
-
**Replace**:
|
|
178
|
+
**Replace**: Propose with `--nonce N` to replace a pending tx at nonce N. Check pending nonces via `GET /api/txs?safe=<ADDRESS>`.
|
|
196
179
|
|
|
197
|
-
**Cascade**: Rejecting tx at nonce N
|
|
180
|
+
**Cascade**: Rejecting tx at nonce N invalidates all tx with nonce > N. Irreversible.
|
|
198
181
|
|
|
199
182
|
⚠️ **Never propose a new nonce then reject an older one** — the cascade will destroy your new tx too.
|
|
200
183
|
|
|
@@ -202,7 +185,7 @@ The wallet address is the same across all chains (counterfactual). Chain is only
|
|
|
202
185
|
|
|
203
186
|
## Reconnect (Wallet Recovery)
|
|
204
187
|
|
|
205
|
-
If the user cleared their browser data,
|
|
188
|
+
If the user cleared their browser data, build a reconnect link:
|
|
206
189
|
|
|
207
190
|
```
|
|
208
191
|
https://nodpay.ai/?agent=YOUR_AGENT_ADDRESS&safe=WALLET_ADDRESS&recovery=RECOVERY_SIGNER&x=PASSKEY_X&y=PASSKEY_Y
|
|
@@ -232,7 +215,7 @@ User opens → verifies passkey → wallet restored. No on-chain action needed.
|
|
|
232
215
|
| User says | Action |
|
|
233
216
|
|-----------|--------|
|
|
234
217
|
| "create a wallet" | Send `https://nodpay.ai/?agent=YOUR_ADDRESS` |
|
|
235
|
-
| "send 0.1 ETH to 0x..." | Propose transaction |
|
|
236
|
-
| "balance" |
|
|
218
|
+
| "send 0.1 ETH to 0x..." | Propose transaction with `--chain` |
|
|
219
|
+
| "balance" | Check balance on the relevant chain |
|
|
237
220
|
| "pending?" | `GET /api/txs?safe=...` |
|
|
238
221
|
| "wallet disappeared" | Send reconnect link |
|
package/package.json
CHANGED
package/scripts/propose.mjs
CHANGED
|
@@ -37,10 +37,31 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
37
37
|
const PENDING_DIR = join(__dirname, '..', '.pending-txs');
|
|
38
38
|
mkdirSync(PENDING_DIR, { recursive: true });
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Resolve chain config: --chain flag auto-resolves from networks.json, env vars as fallback
|
|
41
|
+
import { createRequire } from 'module';
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const NETWORKS = require('@nodpay/core/networks');
|
|
44
|
+
const allChains = { ...NETWORKS.mainnet, ...NETWORKS.testnet };
|
|
45
|
+
|
|
46
|
+
const chainArg = process.argv.includes('--chain')
|
|
47
|
+
? process.argv[process.argv.indexOf('--chain') + 1]
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
let RPC_URL, CHAIN_ID;
|
|
51
|
+
if (chainArg) {
|
|
52
|
+
const net = allChains[chainArg];
|
|
53
|
+
if (!net) {
|
|
54
|
+
console.error(`Error: Unknown chain "${chainArg}". Supported: ${Object.keys(allChains).join(', ')}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
RPC_URL = process.env.RPC_URL || net.rpcUrl;
|
|
58
|
+
CHAIN_ID = String(net.chainId);
|
|
59
|
+
} else {
|
|
60
|
+
RPC_URL = process.env.RPC_URL;
|
|
61
|
+
CHAIN_ID = process.env.CHAIN_ID;
|
|
62
|
+
}
|
|
42
63
|
if (!RPC_URL || !CHAIN_ID) {
|
|
43
|
-
console.error('Error:
|
|
64
|
+
console.error('Error: Specify --chain <name> or set RPC_URL + CHAIN_ID env vars.\nSupported chains: ' + Object.keys(allChains).join(', '));
|
|
44
65
|
process.exit(1);
|
|
45
66
|
}
|
|
46
67
|
const ENTRYPOINT_ADDRESS = ENTRYPOINT;
|