nodpay 0.2.2 → 0.2.4
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 +13 -4
- package/bin/nodpay.mjs +7 -1
- package/package.json +1 -1
- package/scripts/keygen.mjs +61 -0
- 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
|
@@ -39,12 +39,19 @@ The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (pass
|
|
|
39
39
|
### 1. Generate an agent key
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
|
|
42
|
+
npx nodpay keygen --env-file .env
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
This outputs your agent's **public address** (stdout). The private key is written directly to `.env` — it never appears in your output or context.
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
If `NODPAY_AGENT_KEY` already exists in the file, it reuses the existing key and prints the address.
|
|
48
|
+
|
|
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
|
|
48
55
|
|
|
49
56
|
### 2. Send the user a wallet link
|
|
50
57
|
|
|
@@ -108,6 +115,7 @@ One agent key serves all wallets — multi-wallet is handled user-side (differen
|
|
|
108
115
|
```bash
|
|
109
116
|
NODPAY_AGENT_KEY=0x... \
|
|
110
117
|
npx nodpay propose \
|
|
118
|
+
--chain <CHAIN_NAME> \
|
|
111
119
|
--safe <WALLET_ADDRESS> \
|
|
112
120
|
--to <RECIPIENT> \
|
|
113
121
|
--value-eth <AMOUNT> \
|
|
@@ -152,6 +160,7 @@ Always check before proposing — this tells you the current nonce, pending ops,
|
|
|
152
160
|
|
|
153
161
|
| Flag | Required | Description |
|
|
154
162
|
|------|----------|-------------|
|
|
163
|
+
| `--chain` | ✅ | Chain name (e.g. `ethereum`, `base`, `sepolia`) |
|
|
155
164
|
| `--safe` | ✅ | Wallet (Safe) address |
|
|
156
165
|
| `--to` | ✅ | Recipient address |
|
|
157
166
|
| `--value-eth` | ✅ | Amount in ETH |
|
|
@@ -171,7 +180,7 @@ Only one env var is required:
|
|
|
171
180
|
|-----|-------------|
|
|
172
181
|
| `NODPAY_AGENT_KEY` | Agent signing key (required) |
|
|
173
182
|
|
|
174
|
-
Chain config (RPC, bundler
|
|
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.
|
|
175
184
|
|
|
176
185
|
### Supported Chains
|
|
177
186
|
|
package/bin/nodpay.mjs
CHANGED
|
@@ -7,6 +7,10 @@ if (command === 'propose') {
|
|
|
7
7
|
const scriptPath = new URL('../scripts/propose.mjs', import.meta.url).pathname;
|
|
8
8
|
process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
|
|
9
9
|
await import(scriptPath);
|
|
10
|
+
} else if (command === 'keygen') {
|
|
11
|
+
const scriptPath = new URL('../scripts/keygen.mjs', import.meta.url).pathname;
|
|
12
|
+
process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
|
|
13
|
+
await import(scriptPath);
|
|
10
14
|
} else if (command === 'version' || command === '--version' || command === '-v') {
|
|
11
15
|
const { readFileSync } = await import('fs');
|
|
12
16
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -15,9 +19,11 @@ if (command === 'propose') {
|
|
|
15
19
|
console.log(`Usage: nodpay <command>
|
|
16
20
|
|
|
17
21
|
Commands:
|
|
22
|
+
keygen Generate (or reuse) agent keypair
|
|
18
23
|
propose Propose a transaction for human approval
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
Examples:
|
|
26
|
+
nodpay keygen --env-file .env
|
|
21
27
|
nodpay propose --safe 0x... --to 0x... --value-eth 0.01 --signer-type passkey
|
|
22
28
|
|
|
23
29
|
Docs: https://nodpay.ai/skill.md`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate (or reuse) an agent keypair.
|
|
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.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx nodpay keygen --env-file .env
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Wallet } from 'ethers';
|
|
15
|
+
import { readFileSync, appendFileSync, existsSync } from 'fs';
|
|
16
|
+
import { resolve } from 'path';
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
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
|
+
}
|
|
26
|
+
|
|
27
|
+
const ENV_VAR = 'NODPAY_AGENT_KEY';
|
|
28
|
+
|
|
29
|
+
// Check if key already exists in the env file
|
|
30
|
+
function findExistingKey() {
|
|
31
|
+
if (!existsSync(envFile)) return null;
|
|
32
|
+
const lines = readFileSync(envFile, 'utf8').split('\n');
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
if (trimmed.startsWith('#') || !trimmed.includes('=')) continue;
|
|
36
|
+
const [name, ...rest] = trimmed.split('=');
|
|
37
|
+
if (name.trim() === ENV_VAR) {
|
|
38
|
+
const value = rest.join('=').trim().replace(/^["']|["']$/g, '');
|
|
39
|
+
if (value) return value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const existing = findExistingKey();
|
|
46
|
+
|
|
47
|
+
if (existing) {
|
|
48
|
+
try {
|
|
49
|
+
const wallet = new Wallet(existing);
|
|
50
|
+
console.log(wallet.address);
|
|
51
|
+
console.error(`${ENV_VAR} already configured in ${envFile}`);
|
|
52
|
+
} catch {
|
|
53
|
+
console.error(`${ENV_VAR} exists in ${envFile} but is invalid. Remove it and re-run.`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const wallet = Wallet.createRandom();
|
|
58
|
+
appendFileSync(envFile, `\n${ENV_VAR}=${wallet.privateKey}\n`);
|
|
59
|
+
console.log(wallet.address);
|
|
60
|
+
console.error(`Generated new agent key → ${envFile}`);
|
|
61
|
+
}
|
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;
|