nodpay 0.2.36 → 0.2.37

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/bin/nodpay.mjs CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  const command = process.argv[2];
4
4
 
5
+ // Remote wallet routing: if config.json has remote_wallet and command is sign/wallets,
6
+ // delegate to SafeClaw vault proxy (HTTP passthrough) instead of local execution.
7
+ const REMOTE_COMMANDS = new Set(['sign', 'wallets']);
8
+ if (command && REMOTE_COMMANDS.has(command)) {
9
+ const { loadConfig } = await import('../scripts/config.mjs');
10
+ const config = loadConfig();
11
+ if (config.remote_wallet) {
12
+ const { remoteSign, remoteWallets } = await import('../scripts/remote.mjs');
13
+ if (command === 'sign') await remoteSign(config.remote_wallet);
14
+ else if (command === 'wallets') await remoteWallets(config.remote_wallet);
15
+ process.exit(0);
16
+ }
17
+ }
18
+
5
19
  if (command === 'propose') {
6
20
  // Forward all args after 'propose' to the propose script
7
21
  const scriptPath = new URL('../scripts/propose.mjs', import.meta.url).pathname;
@@ -23,6 +37,14 @@ if (command === 'propose') {
23
37
  const scriptPath = new URL('../scripts/gasprice.mjs', import.meta.url).pathname;
24
38
  process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
25
39
  await import(scriptPath);
40
+ } else if (command === 'sign') {
41
+ const scriptPath = new URL('../scripts/sign.mjs', import.meta.url).pathname;
42
+ process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
43
+ await import(scriptPath);
44
+ } else if (command === 'wallets') {
45
+ const scriptPath = new URL('../scripts/wallets.mjs', import.meta.url).pathname;
46
+ process.argv = [process.argv[0], scriptPath, ...process.argv.slice(3)];
47
+ await import(scriptPath);
26
48
  } else if (command === 'version' || command === '--version' || command === '-v') {
27
49
  const { readFileSync } = await import('fs');
28
50
  const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
@@ -36,6 +58,8 @@ Commands:
36
58
  txs List pending and completed transactions
37
59
  nonce Query next nonce from on-chain EntryPoint
38
60
  gasprice Get current gas price and estimated gas cost per chain
61
+ sign Sign a hash with agent key (stdin JSON)
62
+ wallets List all local wallets (JSON array)
39
63
 
40
64
  Examples:
41
65
  nodpay keygen
@@ -43,6 +67,12 @@ Examples:
43
67
  nodpay txs --safe 0x...
44
68
  nodpay nonce --safe 0x... --chain base
45
69
  nodpay gasprice --chain base
70
+ echo '{"hash":"0x..."}' | nodpay sign
71
+ nodpay wallets
72
+
73
+ Remote wallet (SafeClaw):
74
+ Set ~/.nodpay/config.json: {"remote_wallet": "http://localhost:23295/nodpay"}
75
+ sign/wallets will delegate to SafeClaw vault proxy automatically.
46
76
 
47
77
  Docs: https://nodpay.ai/skill.md`);
48
78
  process.exit(command ? 1 : 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.2.36",
3
+ "version": "0.2.37",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Load ~/.nodpay/config.json (optional).
3
+ *
4
+ * Returns parsed config or {} if file doesn't exist / is invalid.
5
+ */
6
+
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { HOME } from './env.mjs';
10
+
11
+ const CONFIG_PATH = join(HOME, '.nodpay', 'config.json');
12
+
13
+ export function loadConfig(path = CONFIG_PATH) {
14
+ if (!existsSync(path)) return {};
15
+ try {
16
+ return JSON.parse(readFileSync(path, 'utf8'));
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Remote wallet proxy — delegates sign/wallets to SafeClaw vault proxy.
3
+ *
4
+ * Used when ~/.nodpay/config.json has "remote_wallet" set.
5
+ * Transparent: stdin/stdout/stderr/exitCode passthrough.
6
+ */
7
+
8
+ /**
9
+ * POST {base}/sign — pipe stdin as body, stdout the response.
10
+ * @param {string} base - remote_wallet URL (e.g. http://localhost:23295/nodpay)
11
+ */
12
+ export async function remoteSign(base) {
13
+ // Read stdin
14
+ const chunks = [];
15
+ for await (const chunk of process.stdin) chunks.push(chunk);
16
+ const body = Buffer.concat(chunks).toString('utf8').trim();
17
+
18
+ if (!body) {
19
+ console.error(JSON.stringify({ error: 'Empty stdin. Expected JSON: {"hash":"0x..."}' }));
20
+ process.exit(1);
21
+ }
22
+
23
+ try {
24
+ const res = await fetch(`${base.replace(/\/$/, '')}/sign`, {
25
+ method: 'POST',
26
+ headers: { 'Content-Type': 'application/json' },
27
+ body,
28
+ });
29
+ const text = await res.text();
30
+ if (!res.ok) {
31
+ console.error(text);
32
+ process.exit(1);
33
+ }
34
+ console.log(text);
35
+ } catch (e) {
36
+ console.error(JSON.stringify({ error: `Remote sign failed: ${e.message}` }));
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * GET {base}/wallets — stdout the response.
43
+ * @param {string} base - remote_wallet URL
44
+ */
45
+ export async function remoteWallets(base) {
46
+ try {
47
+ const res = await fetch(`${base.replace(/\/$/, '')}/wallets`);
48
+ const text = await res.text();
49
+ if (!res.ok) {
50
+ console.error(text);
51
+ process.exit(1);
52
+ }
53
+ console.log(text);
54
+ } catch (e) {
55
+ console.error(JSON.stringify({ error: `Remote wallets failed: ${e.message}` }));
56
+ process.exit(1);
57
+ }
58
+ }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sign a hash with the agent's private key.
4
+ *
5
+ * Reads JSON from stdin: {"hash": "0x..."}
6
+ * Outputs JSON to stdout: {"signature": "0x..."}
7
+ *
8
+ * Key source: process.env.NODPAY_AGENT_KEY (real env > ~/.nodpay/.env file)
9
+ *
10
+ * Usage:
11
+ * echo '{"hash":"0xabc..."}' | npx nodpay sign
12
+ */
13
+
14
+ import { ethers } from 'ethers';
15
+ import { loadDotEnv, env } from './env.mjs';
16
+
17
+ // Load .env file (real env takes priority)
18
+ loadDotEnv();
19
+
20
+ const NODPAY_AGENT_KEY = env('NODPAY_AGENT_KEY');
21
+ if (!NODPAY_AGENT_KEY) {
22
+ console.error(JSON.stringify({ error: 'Missing NODPAY_AGENT_KEY — set env var or add to ~/.nodpay/.env' }));
23
+ process.exit(1);
24
+ }
25
+
26
+ // Read stdin
27
+ const chunks = [];
28
+ for await (const chunk of process.stdin) chunks.push(chunk);
29
+ const input = Buffer.concat(chunks).toString('utf8').trim();
30
+
31
+ if (!input) {
32
+ console.error(JSON.stringify({ error: 'Empty stdin. Expected JSON: {"hash":"0x..."}' }));
33
+ process.exit(1);
34
+ }
35
+
36
+ try {
37
+ const { hash } = JSON.parse(input);
38
+ if (!hash || !hash.startsWith('0x')) {
39
+ console.error(JSON.stringify({ error: 'Invalid input. Expected {"hash":"0x..."}' }));
40
+ process.exit(1);
41
+ }
42
+
43
+ const wallet = new ethers.Wallet(NODPAY_AGENT_KEY);
44
+ const sig = wallet.signingKey.sign(hash);
45
+ const signature = ethers.Signature.from(sig).serialized;
46
+
47
+ console.log(JSON.stringify({ signature }));
48
+ } catch (e) {
49
+ console.error(JSON.stringify({ error: e.message || String(e) }));
50
+ process.exit(1);
51
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * List all local NodPay wallets.
4
+ *
5
+ * Reads ~/.nodpay/wallets/*.json and outputs a JSON array to stdout.
6
+ *
7
+ * Usage:
8
+ * npx nodpay wallets
9
+ * npx nodpay wallets --json (same, explicit)
10
+ */
11
+
12
+ import { readdirSync, readFileSync, existsSync } from 'fs';
13
+ import { join } from 'path';
14
+ import { HOME } from './env.mjs';
15
+
16
+ const WALLETS_DIR = join(HOME, '.nodpay', 'wallets');
17
+
18
+ if (!existsSync(WALLETS_DIR)) {
19
+ console.log(JSON.stringify([]));
20
+ process.exit(0);
21
+ }
22
+
23
+ try {
24
+ const files = readdirSync(WALLETS_DIR).filter(f => f.endsWith('.json'));
25
+ const wallets = [];
26
+ for (const file of files) {
27
+ try {
28
+ const data = JSON.parse(readFileSync(join(WALLETS_DIR, file), 'utf8'));
29
+ wallets.push(data);
30
+ } catch {
31
+ // Skip invalid JSON files
32
+ }
33
+ }
34
+ console.log(JSON.stringify(wallets, null, 2));
35
+ } catch (e) {
36
+ console.error(JSON.stringify({ error: e.message || String(e) }));
37
+ process.exit(1);
38
+ }