nodpay 0.2.5 → 0.2.7

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
@@ -1,36 +1,24 @@
1
1
  ---
2
2
  name: nodpay
3
3
  description: Propose on-chain payments from a shared wallet. Use when user asks to send crypto, make a payment, or create a shared wallet.
4
- openclaw:
5
- requires:
6
- env: [NODPAY_AGENT_KEY]
7
- primaryEnv: NODPAY_AGENT_KEY
8
4
  ---
9
5
 
10
6
  # NodPay — Agent Wallet
11
7
 
12
8
  > Two minds, one wallet.
13
9
 
14
- NodPay lets you (an AI agent) propose on-chain payments from a shared wallet. Your human co-signs with one tap. You cannot spend without their approval.
10
+ **TL;DR:** You propose payments, your human approves with one tap. 2-of-3 multisig — you cannot spend alone.
15
11
 
16
- **When to use**: User asks you to send crypto, make a payment, or create a shared wallet.
12
+ ## ⚠️ Security First
17
13
 
18
- ## How It Works
14
+ | DO | ❌ DON'T |
15
+ |-------|----------|
16
+ | Use `npx nodpay keygen` (key never in stdout) | Generate keys manually or log them |
17
+ | Store wallet info in `.nodpay/wallets/` | Put private keys in chat or code |
18
+ | Verify agent address matches yours | Trust wallet info without checking |
19
+ | Let the human approve every transaction | Try to bypass approval |
19
20
 
20
- ```
21
- You (agent) NodPay server User (human)
22
- │ │ │
23
- ├── send setup link ──────┼─────────────────────►│
24
- │ │ ├── create wallet
25
- │◄── wallet info ─────────┼───────────────────────┤
26
- │ │ │
27
- ├── propose tx ──────────►│ │
28
- │ ├── approve link ──────►│
29
- │ │◄── sign + submit ─────┤
30
- │ │ │
31
- ```
32
-
33
- The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (passkey + recovery). You cannot move funds alone.
21
+ **Private key never leaves disk.** `keygen` writes directly to `.nodpay/.env` (chmod 600). The key never appears in your output, context, or logs.
34
22
 
35
23
  ---
36
24
 
@@ -39,23 +27,16 @@ The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (pass
39
27
  ### 1. Generate an agent key
40
28
 
41
29
  ```bash
42
- npx nodpay keygen --env-file .env
30
+ npx nodpay keygen
43
31
  ```
44
32
 
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
-
47
- If a key already exists in the file, it reuses it and prints the address.
33
+ Outputs your **public address** only. Key is stored in `.nodpay/.env`. No gateway restart needed.
48
34
 
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
-
51
- Recommended sequence:
52
- 1. Run `npx nodpay keygen --env-file .env` → note the address
53
- 2. Send the wallet link to your user (step 2 below)
54
- 3. Trigger gateway restart
35
+ If a key already exists, it reuses it and prints the address.
55
36
 
56
37
  ### 2. Send the user a wallet link
57
38
 
58
- Replace `YOUR_AGENT_ADDRESS` with your EOA address and send:
39
+ Replace `YOUR_AGENT_ADDRESS` with your address and send:
59
40
 
60
41
  > I've set up a shared wallet for us. Open this link to create your wallet — takes 30 seconds:
61
42
  >
@@ -65,7 +46,7 @@ Replace `YOUR_AGENT_ADDRESS` with your EOA address and send:
65
46
 
66
47
  **Use the URL above verbatim.** Do not search for NodPay or guess the URL — `nodpay.ai` is the only correct domain.
67
48
 
68
- The user will create the wallet and copy info back to you:
49
+ The user will copy wallet info back to you:
69
50
 
70
51
  ```
71
52
  NodPay Wallet (Safe): 0x...
@@ -75,44 +56,21 @@ Agent: 0x...
75
56
  Recovery Signer: 0x...
76
57
  ```
77
58
 
78
- **Store all fields** you need them for proposing transactions.
79
-
80
- **After wallet creation**, tell the user their wallet is ready and the address works on any supported chain. Briefly offer: *"Want to try a test transaction on a testnet first?"* — only guide to testnets (Sepolia, etc.) if the user says yes.
81
-
82
- #### Wallet file management
59
+ **Store all fields** in `.nodpay/wallets/<safe-address>.json`.
83
60
 
84
- Store wallet info in `.nodpay/wallets/` in your workspace root:
61
+ **After wallet creation**, tell the user their wallet is ready and works on any supported chain. Briefly offer: *"Want to try a test transaction on a testnet first?"* — only guide to testnets if the user says yes.
85
62
 
86
- ```
87
- .nodpay/wallets/
88
- 0xAbC...123.json # one file per wallet, named by Safe address
89
- ```
90
-
91
- Each wallet file:
92
-
93
- ```json
94
- {
95
- "safe": "0x...",
96
- "agentSigner": "0x...",
97
- "signerType": "passkey",
98
- "passkeyX": "0x...",
99
- "passkeyY": "0x...",
100
- "recovery": "0x...",
101
- "createdAt": "2025-01-01"
102
- }
103
- ```
104
-
105
- For EOA wallets, replace passkey fields with `"userSigner": "0x..."`.
63
+ **⚠️ Verify the Agent address matches yours.** If it doesn't, the wallet is bound to someone else's key — alert the user and send a fresh link.
106
64
 
107
- One agent key serves all wallets — multi-wallet is handled user-side (different passkeys/recovery keys → different Safe addresses, same agent).
65
+ ---
108
66
 
109
- **⚠️ Verify the Agent address matches yours.** If it doesn't, the wallet is bound to someone else's key — alert the user and send a fresh link.
67
+ ## Usage
110
68
 
111
69
  ### Propose a transaction
112
70
 
113
71
  ```bash
114
72
  npx nodpay propose \
115
- --chain <CHAIN_NAME> \
73
+ --chain <CHAIN> \
116
74
  --safe <WALLET_ADDRESS> \
117
75
  --to <RECIPIENT> \
118
76
  --value-eth <AMOUNT> \
@@ -122,28 +80,47 @@ npx nodpay propose \
122
80
  --signer-type passkey
123
81
  ```
124
82
 
125
- The script outputs JSON with an `approveUrl`. Send it to the user:
83
+ Outputs JSON with an `approveUrl`. Send it to the user:
126
84
 
127
85
  > 💰 Payment: 0.01 ETH → 0xRecipient...
128
86
  > 👉 Approve: https://nodpay.ai/approve?safeOpHash=0x...
129
87
 
130
- **First transaction deploys the wallet on-chain.** Pass `--passkey-x`, `--passkey-y`, and `--recovery` for the first tx. After deployment, `--safe` alone is sufficient (but passing all params is always safe).
88
+ **First transaction deploys the wallet on-chain.** Pass all params for the first tx. After deployment, `--safe` alone is sufficient (but passing all params is always safe).
131
89
 
132
- ### Check balance
90
+ ### Check pending transactions
133
91
 
134
92
  ```bash
135
- npx nodpay propose --chain <CHAIN_NAME> --safe <WALLET_ADDRESS> --check-balance
93
+ curl https://nodpay.ai/api/txs?safe=<WALLET_ADDRESS>
136
94
  ```
137
95
 
138
- If balance is 0, remind the user to deposit before proposing.
96
+ Always check before proposing shows current nonce, pending ops, and wallet status.
139
97
 
140
- ### Check pending transactions
98
+ ---
141
99
 
142
- ```bash
143
- curl https://nodpay.ai/api/txs?safe=<WALLET_ADDRESS>
100
+ ## Data Layout
101
+
102
+ ```
103
+ .nodpay/
104
+ .env # agent key (chmod 600, never touch directly)
105
+ wallets/
106
+ 0xAbC...123.json # one file per wallet
107
+ ```
108
+
109
+ Wallet file format:
110
+
111
+ ```json
112
+ {
113
+ "safe": "0x...",
114
+ "agentSigner": "0x...",
115
+ "signerType": "passkey",
116
+ "passkeyX": "0x...",
117
+ "passkeyY": "0x...",
118
+ "recovery": "0x...",
119
+ "createdAt": "2025-01-01"
120
+ }
144
121
  ```
145
122
 
146
- Always check before proposing this tells you the current nonce, pending ops, and wallet status.
123
+ For EOA wallets, replace passkey fields with `"userSigner": "0x..."`.
147
124
 
148
125
  ---
149
126
 
@@ -156,9 +133,9 @@ Always check before proposing — this tells you the current nonce, pending ops,
156
133
  | `--to` | ✅ | Recipient address |
157
134
  | `--value-eth` | ✅ | Amount in ETH |
158
135
  | `--signer-type` | ✅ | `passkey` or `eoa` |
159
- | `--passkey-x` | passkey wallets | Passkey public key X |
160
- | `--passkey-y` | passkey wallets | Passkey public key Y |
161
- | `--user-signer` | eoa wallets | User's EOA address |
136
+ | `--passkey-x` | passkey | Passkey public key X |
137
+ | `--passkey-y` | passkey | Passkey public key Y |
138
+ | `--user-signer` | eoa | User's EOA address |
162
139
  | `--recovery` | first tx | Recovery signer address |
163
140
  | `--nonce` | optional | Force nonce (for replacements) |
164
141
  | `--purpose` | optional | Human-readable label |
@@ -167,7 +144,7 @@ Always check before proposing — this tells you the current nonce, pending ops,
167
144
 
168
145
  `ethereum`, `base`, `arbitrum`, `optimism`, `polygon`, `sepolia`, `base_sepolia`
169
146
 
170
- The wallet address is the same across all chains (counterfactual). Chain is only relevant at transaction time. **Do not assume a default chain.** When the user asks to send, ask which chain if not specified.
147
+ Wallet address is the same across all chains (counterfactual). **Do not assume a default chain.** Ask the user which chain if not specified.
171
148
 
172
149
  ---
173
150
 
@@ -175,7 +152,7 @@ The wallet address is the same across all chains (counterfactual). Chain is only
175
152
 
176
153
  **Sequential**: Just call propose multiple times. Nonces auto-increment.
177
154
 
178
- **Replace**: Propose with `--nonce N` to replace a pending tx at nonce N. Check pending nonces via `GET /api/txs?safe=<ADDRESS>`.
155
+ **Replace**: Propose with `--nonce N` to replace a pending tx at nonce N.
179
156
 
180
157
  **Cascade**: Rejecting tx at nonce N invalidates all tx with nonce > N. Irreversible.
181
158
 
@@ -185,7 +162,7 @@ The wallet address is the same across all chains (counterfactual). Chain is only
185
162
 
186
163
  ## Reconnect (Wallet Recovery)
187
164
 
188
- If the user cleared their browser data, build a reconnect link:
165
+ If the user cleared their browser data:
189
166
 
190
167
  ```
191
168
  https://nodpay.ai/?agent=YOUR_AGENT_ADDRESS&safe=WALLET_ADDRESS&recovery=RECOVERY_SIGNER&x=PASSKEY_X&y=PASSKEY_Y
@@ -215,7 +192,6 @@ User opens → verifies passkey → wallet restored. No on-chain action needed.
215
192
  | User says | Action |
216
193
  |-----------|--------|
217
194
  | "create a wallet" | Send `https://nodpay.ai/?agent=YOUR_ADDRESS` |
218
- | "send 0.1 ETH to 0x..." | Propose transaction with `--chain` |
219
- | "balance" | Check balance on the relevant chain |
195
+ | "send 0.1 ETH to 0x..." | `npx nodpay propose --chain ...` |
220
196
  | "pending?" | `GET /api/txs?safe=...` |
221
197
  | "wallet disappeared" | Send reconnect link |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,27 +2,24 @@
2
2
  /**
3
3
  * Generate (or reuse) an agent keypair.
4
4
  *
5
- * - If NODPAY_AGENT_KEY already exists in --env-file, derives and prints the address.
6
- * - Otherwise generates a new keypair, appends to --env-file, prints the address.
7
- *
8
- * The private key NEVER appears in stdout — only the public address.
5
+ * - Stores key in .nodpay/.env (chmod 600) never appears in stdout or agent context.
6
+ * - If key already exists, reuses it and prints the address.
7
+ * - Outputs only the public address to stdout.
9
8
  *
10
9
  * Usage:
11
- * npx nodpay keygen --env-file .env
10
+ * npx nodpay keygen
11
+ * npx nodpay keygen --env-file <path> # custom location
12
12
  */
13
13
 
14
14
  import { Wallet } from 'ethers';
15
- import { readFileSync, appendFileSync, existsSync } from 'fs';
16
- import { resolve } from 'path';
15
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'fs';
16
+ import { resolve, dirname } from 'path';
17
17
 
18
18
  const args = process.argv.slice(2);
19
19
  const envFileIdx = args.indexOf('--env-file');
20
- const envFile = envFileIdx !== -1 ? resolve(args[envFileIdx + 1]) : null;
21
-
22
- if (!envFile) {
23
- console.error('Usage: npx nodpay keygen --env-file <path>');
24
- process.exit(1);
25
- }
20
+ const envFile = envFileIdx !== -1
21
+ ? resolve(args[envFileIdx + 1])
22
+ : resolve('.nodpay', '.env');
26
23
 
27
24
  const ENV_VAR = 'NODPAY_AGENT_KEY';
28
25
 
@@ -55,7 +52,12 @@ if (existing) {
55
52
  }
56
53
  } else {
57
54
  const wallet = Wallet.createRandom();
58
- appendFileSync(envFile, `\n${ENV_VAR}=${wallet.privateKey}\n`);
55
+ // Ensure .nodpay/ directory exists with restricted permissions
56
+ const dir = dirname(envFile);
57
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
58
+ // Write key file with restricted permissions (owner read/write only)
59
+ const content = existsSync(envFile) ? readFileSync(envFile, 'utf8') : '';
60
+ writeFileSync(envFile, content + `${ENV_VAR}=${wallet.privateKey}\n`, { mode: 0o600 });
59
61
  console.log(wallet.address);
60
62
  console.error(`Generated new agent key → ${envFile}`);
61
63
  }
@@ -6,11 +6,7 @@
6
6
  * The agent signs first (1 of 2). The serialized SafeOperation is
7
7
  * output so the web app can have the user co-sign and submit.
8
8
  *
9
- * Env vars:
10
- * NODPAY_AGENT_KEY - Agent signer private key
11
- * SAFE_ADDRESS - Deployed Safe address (can be overridden with --safe)
12
- * RPC_URL - RPC endpoint
13
- * CHAIN_ID - Chain ID (required, no default)
9
+ * Agent key is read from .nodpay/.env (run `npx nodpay keygen` to generate).
14
10
  *
15
11
  * Args:
16
12
  * --to <address> - Recipient address
@@ -65,7 +61,24 @@ if (!RPC_URL || !CHAIN_ID) {
65
61
  process.exit(1);
66
62
  }
67
63
  const ENTRYPOINT_ADDRESS = ENTRYPOINT;
68
- const NODPAY_AGENT_KEY = process.env.NODPAY_AGENT_KEY;
64
+
65
+ // Read agent key from .nodpay/.env
66
+ function loadAgentKey() {
67
+ try {
68
+ const envPath = join(process.cwd(), '.nodpay', '.env');
69
+ const lines = readFileSync(envPath, 'utf8').split('\n');
70
+ for (const line of lines) {
71
+ const trimmed = line.trim();
72
+ if (trimmed.startsWith('#') || !trimmed.includes('=')) continue;
73
+ const [name, ...rest] = trimmed.split('=');
74
+ if (name.trim() === 'NODPAY_AGENT_KEY') {
75
+ return rest.join('=').trim().replace(/^["']|["']$/g, '');
76
+ }
77
+ }
78
+ } catch {}
79
+ return null;
80
+ }
81
+ const NODPAY_AGENT_KEY = loadAgentKey();
69
82
  const DEFAULT_SAFE = process.env.SAFE_ADDRESS;
70
83
 
71
84
  // Safe4337Pack.init requires a bundlerUrl — it calls eth_chainId during init.
@@ -79,7 +92,7 @@ const BUNDLER_URL = PIMLICO_API_KEY
79
92
  : `${opStoreBase}/bundler/${CHAIN_ID}`;
80
93
 
81
94
  if (!NODPAY_AGENT_KEY) {
82
- console.error(JSON.stringify({ error: 'Missing NODPAY_AGENT_KEY env var' }));
95
+ console.error(JSON.stringify({ error: 'Missing NODPAY_AGENT_KEY in .nodpay/.env — run npx nodpay keygen first' }));
83
96
  process.exit(1);
84
97
  }
85
98