nodpay 0.2.4 → 0.2.6
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 +58 -101
- package/package.json +1 -1
- package/scripts/keygen.mjs +16 -14
- package/scripts/propose.mjs +19 -1
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
|
-
|
|
10
|
+
**TL;DR:** You propose payments, your human approves with one tap. 2-of-3 multisig — you cannot spend alone.
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
## ⚠️ Security First
|
|
17
13
|
|
|
18
|
-
|
|
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
|
|
30
|
+
npx nodpay keygen
|
|
43
31
|
```
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
If `NODPAY_AGENT_KEY` already exists in the file, it reuses the existing key and prints the address.
|
|
33
|
+
Outputs your **public address** only. Key is stored in `.nodpay/.env`. No gateway restart needed.
|
|
48
34
|
|
|
49
|
-
|
|
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
|
|
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
|
|
49
|
+
The user will copy wallet info back to you:
|
|
69
50
|
|
|
70
51
|
```
|
|
71
52
|
NodPay Wallet (Safe): 0x...
|
|
@@ -75,47 +56,21 @@ Agent: 0x...
|
|
|
75
56
|
Recovery Signer: 0x...
|
|
76
57
|
```
|
|
77
58
|
|
|
78
|
-
**Store all fields**
|
|
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
|
|
83
|
-
|
|
84
|
-
Store wallet info in `.nodpay/wallets/` in your workspace root:
|
|
85
|
-
|
|
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
|
-
```
|
|
59
|
+
**Store all fields** in `.nodpay/wallets/<safe-address>.json`.
|
|
104
60
|
|
|
105
|
-
|
|
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.
|
|
106
62
|
|
|
107
|
-
|
|
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.
|
|
108
64
|
|
|
109
|
-
|
|
65
|
+
---
|
|
110
66
|
|
|
111
|
-
|
|
67
|
+
## Usage
|
|
112
68
|
|
|
113
69
|
### Propose a transaction
|
|
114
70
|
|
|
115
71
|
```bash
|
|
116
|
-
NODPAY_AGENT_KEY=0x... \
|
|
117
72
|
npx nodpay propose \
|
|
118
|
-
--chain <
|
|
73
|
+
--chain <CHAIN> \
|
|
119
74
|
--safe <WALLET_ADDRESS> \
|
|
120
75
|
--to <RECIPIENT> \
|
|
121
76
|
--value-eth <AMOUNT> \
|
|
@@ -125,38 +80,51 @@ npx nodpay propose \
|
|
|
125
80
|
--signer-type passkey
|
|
126
81
|
```
|
|
127
82
|
|
|
128
|
-
|
|
83
|
+
Outputs JSON with an `approveUrl`. Send it to the user:
|
|
129
84
|
|
|
130
85
|
> 💰 Payment: 0.01 ETH → 0xRecipient...
|
|
131
86
|
> 👉 Approve: https://nodpay.ai/approve?safeOpHash=0x...
|
|
132
87
|
|
|
133
|
-
**First transaction deploys the wallet on-chain.** Pass
|
|
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).
|
|
134
89
|
|
|
135
|
-
### Check
|
|
136
|
-
|
|
137
|
-
Use the RPC URL for the wallet's chain (see `references/networks.json`):
|
|
90
|
+
### Check pending transactions
|
|
138
91
|
|
|
139
92
|
```bash
|
|
140
|
-
curl
|
|
141
|
-
-H "Content-Type: application/json" \
|
|
142
|
-
-d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["<WALLET_ADDRESS>","latest"],"id":1}'
|
|
93
|
+
curl https://nodpay.ai/api/txs?safe=<WALLET_ADDRESS>
|
|
143
94
|
```
|
|
144
95
|
|
|
145
|
-
|
|
96
|
+
Always check before proposing — shows current nonce, pending ops, and wallet status.
|
|
146
97
|
|
|
147
|
-
|
|
98
|
+
---
|
|
148
99
|
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
151
107
|
```
|
|
152
108
|
|
|
153
|
-
|
|
109
|
+
Wallet file format:
|
|
154
110
|
|
|
155
|
-
|
|
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
|
+
}
|
|
121
|
+
```
|
|
156
122
|
|
|
157
|
-
|
|
123
|
+
For EOA wallets, replace passkey fields with `"userSigner": "0x..."`.
|
|
124
|
+
|
|
125
|
+
---
|
|
158
126
|
|
|
159
|
-
|
|
127
|
+
## Flags
|
|
160
128
|
|
|
161
129
|
| Flag | Required | Description |
|
|
162
130
|
|------|----------|-------------|
|
|
@@ -165,38 +133,28 @@ Always check before proposing — this tells you the current nonce, pending ops,
|
|
|
165
133
|
| `--to` | ✅ | Recipient address |
|
|
166
134
|
| `--value-eth` | ✅ | Amount in ETH |
|
|
167
135
|
| `--signer-type` | ✅ | `passkey` or `eoa` |
|
|
168
|
-
| `--passkey-x` | passkey
|
|
169
|
-
| `--passkey-y` | passkey
|
|
170
|
-
| `--user-signer` | eoa
|
|
136
|
+
| `--passkey-x` | passkey | Passkey public key X |
|
|
137
|
+
| `--passkey-y` | passkey | Passkey public key Y |
|
|
138
|
+
| `--user-signer` | eoa | User's EOA address |
|
|
171
139
|
| `--recovery` | first tx | Recovery signer address |
|
|
172
140
|
| `--nonce` | optional | Force nonce (for replacements) |
|
|
173
141
|
| `--purpose` | optional | Human-readable label |
|
|
174
142
|
|
|
175
|
-
### Environment
|
|
176
|
-
|
|
177
|
-
Only one env var is required:
|
|
178
|
-
|
|
179
|
-
| Var | Description |
|
|
180
|
-
|-----|-------------|
|
|
181
|
-
| `NODPAY_AGENT_KEY` | Agent signing key (required) |
|
|
182
|
-
|
|
183
|
-
Chain config (RPC, bundler) is auto-resolved via `--chain` from [`@nodpay/core/networks`](https://www.npmjs.com/package/@nodpay/core). You can override with `RPC_URL`/`CHAIN_ID` env vars if needed, but `--chain` is the recommended way.
|
|
184
|
-
|
|
185
143
|
### Supported Chains
|
|
186
144
|
|
|
187
145
|
`ethereum`, `base`, `arbitrum`, `optimism`, `polygon`, `sepolia`, `base_sepolia`
|
|
188
146
|
|
|
189
|
-
|
|
147
|
+
Wallet address is the same across all chains (counterfactual). **Do not assume a default chain.** Ask the user which chain if not specified.
|
|
190
148
|
|
|
191
149
|
---
|
|
192
150
|
|
|
193
151
|
## Transaction Patterns
|
|
194
152
|
|
|
195
|
-
**Sequential**: Just call propose multiple times. Nonces auto-increment
|
|
153
|
+
**Sequential**: Just call propose multiple times. Nonces auto-increment.
|
|
196
154
|
|
|
197
|
-
**Replace**:
|
|
155
|
+
**Replace**: Propose with `--nonce N` to replace a pending tx at nonce N.
|
|
198
156
|
|
|
199
|
-
**Cascade**: Rejecting tx at nonce N
|
|
157
|
+
**Cascade**: Rejecting tx at nonce N invalidates all tx with nonce > N. Irreversible.
|
|
200
158
|
|
|
201
159
|
⚠️ **Never propose a new nonce then reject an older one** — the cascade will destroy your new tx too.
|
|
202
160
|
|
|
@@ -204,7 +162,7 @@ The wallet address is the same across all chains (counterfactual). Chain is only
|
|
|
204
162
|
|
|
205
163
|
## Reconnect (Wallet Recovery)
|
|
206
164
|
|
|
207
|
-
If the user cleared their browser data
|
|
165
|
+
If the user cleared their browser data:
|
|
208
166
|
|
|
209
167
|
```
|
|
210
168
|
https://nodpay.ai/?agent=YOUR_AGENT_ADDRESS&safe=WALLET_ADDRESS&recovery=RECOVERY_SIGNER&x=PASSKEY_X&y=PASSKEY_Y
|
|
@@ -234,7 +192,6 @@ User opens → verifies passkey → wallet restored. No on-chain action needed.
|
|
|
234
192
|
| User says | Action |
|
|
235
193
|
|-----------|--------|
|
|
236
194
|
| "create a wallet" | Send `https://nodpay.ai/?agent=YOUR_ADDRESS` |
|
|
237
|
-
| "send 0.1 ETH to 0x..." |
|
|
238
|
-
| "balance" | RPC `eth_getBalance` on Safe address |
|
|
195
|
+
| "send 0.1 ETH to 0x..." | `npx nodpay propose --chain ...` |
|
|
239
196
|
| "pending?" | `GET /api/txs?safe=...` |
|
|
240
197
|
| "wallet disappeared" | Send reconnect link |
|
package/package.json
CHANGED
package/scripts/keygen.mjs
CHANGED
|
@@ -2,27 +2,24 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Generate (or reuse) an agent keypair.
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
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
|
|
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,
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
}
|
package/scripts/propose.mjs
CHANGED
|
@@ -65,7 +65,25 @@ if (!RPC_URL || !CHAIN_ID) {
|
|
|
65
65
|
process.exit(1);
|
|
66
66
|
}
|
|
67
67
|
const ENTRYPOINT_ADDRESS = ENTRYPOINT;
|
|
68
|
-
|
|
68
|
+
|
|
69
|
+
// Read agent key: env var first, then .nodpay/.env file
|
|
70
|
+
function loadAgentKey() {
|
|
71
|
+
if (process.env.NODPAY_AGENT_KEY) return process.env.NODPAY_AGENT_KEY;
|
|
72
|
+
try {
|
|
73
|
+
const envPath = join(process.cwd(), '.nodpay', '.env');
|
|
74
|
+
const lines = readFileSync(envPath, 'utf8').split('\n');
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (trimmed.startsWith('#') || !trimmed.includes('=')) continue;
|
|
78
|
+
const [name, ...rest] = trimmed.split('=');
|
|
79
|
+
if (name.trim() === 'NODPAY_AGENT_KEY') {
|
|
80
|
+
return rest.join('=').trim().replace(/^["']|["']$/g, '');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const NODPAY_AGENT_KEY = loadAgentKey();
|
|
69
87
|
const DEFAULT_SAFE = process.env.SAFE_ADDRESS;
|
|
70
88
|
|
|
71
89
|
// Safe4337Pack.init requires a bundlerUrl — it calls eth_chainId during init.
|