nodpay 0.2.38 → 0.2.40

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/propose.mjs +47 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,7 +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
- * Agent key: from process.env.NODPAY_AGENT_KEY (real env > ~/.nodpay/.env file > --remote-signer).
9
+ * Agent key: from process.env.NODPAY_AGENT_KEY (real env > ~/.nodpay/.env file), or remote_wallet proxy.
10
10
  * Chain config: resolved via --chain from @nodpay/core networks registry.
11
11
  * Bundler: NodPay public proxy (override with OP_STORE_URL for self-hosted).
12
12
  *
@@ -17,7 +17,6 @@
17
17
  * --safe <address> - Wallet (Safe) address
18
18
  * --counterfactual - Safe not yet deployed; include deployment in UserOp
19
19
  * --human-signer-eoa <address> - Human's EOA signer address (for EOA mode)
20
- * --remote-signer <url> - SafeClaw proxy URL for remote signing (mutually exclusive with --human-signer-eoa)
21
20
  * --salt <nonce> - Salt nonce (required for counterfactual)
22
21
  * --reuse-gas-from <shortHash> - Reuse gas values from a previous op (shortHash prefix of safeOpHash)
23
22
  * --nonce <n> - Required. Use `txs` to find current nonce.
@@ -86,12 +85,9 @@ const DEFAULT_SAFE = null; // always use --safe flag
86
85
  const opStoreBase = env('OP_STORE_URL', 'https://nodpay.ai/api');
87
86
  const BUNDLER_URL = `${opStoreBase}/bundler/${CHAIN_ID}`;
88
87
 
89
- // --- Agent key resolution: remote_wallet (config.json) > --remote-signer > local key ---
88
+ // --- Agent key resolution: remote_wallet (config.json) or local key ---
90
89
  const _config = loadConfig();
91
90
  const _remoteWalletUrl = _config.remote_wallet || null;
92
- const _remoteSignerUrl = process.argv.includes('--remote-signer')
93
- ? process.argv[process.argv.indexOf('--remote-signer') + 1]
94
- : undefined;
95
91
 
96
92
  let AGENT_ADDRESS;
97
93
  let signHash;
@@ -129,31 +125,6 @@ if (_remoteWalletUrl) {
129
125
  }
130
126
  return (await signRes.json()).signature;
131
127
  };
132
- } else if (_remoteSignerUrl) {
133
- // Legacy --remote-signer mode (deprecated, use config.json remote_wallet)
134
- const addrRes = await fetch(`${_remoteSignerUrl}/address`);
135
- if (!addrRes.ok) {
136
- const err = await addrRes.json().catch(() => ({}));
137
- console.error(JSON.stringify({
138
- error: err.error || 'Failed to get agent address from remote signer',
139
- code: err.code || 'REMOTE_SIGNER_ERROR'
140
- }));
141
- process.exit(1);
142
- }
143
- AGENT_ADDRESS = (await addrRes.json()).address;
144
-
145
- signHash = async (hash) => {
146
- const signRes = await fetch(`${_remoteSignerUrl}/sign/${AGENT_ADDRESS}`, {
147
- method: 'POST',
148
- headers: { 'Content-Type': 'application/json' },
149
- body: JSON.stringify({ hash })
150
- });
151
- if (!signRes.ok) {
152
- const err = await signRes.json().catch(() => ({}));
153
- throw new Error(err.error || 'Remote signing failed');
154
- }
155
- return (await signRes.json()).signature;
156
- };
157
128
  } else {
158
129
  // Local mode: read key from process.env (loaded from real env or ~/.nodpay/.env)
159
130
  const NODPAY_AGENT_KEY = env('NODPAY_AGENT_KEY');
@@ -195,9 +166,9 @@ const passkeyVerifier = getArg('--passkey-verifier') || '0x445a0683e494ea0c5AF3E
195
166
  const recoverySigner = getArg('--recovery-signer');
196
167
  const isPasskey = !!(passkeyX && passkeyY);
197
168
 
198
- // Phase 1: remote modes + --human-signer-eoa is not supported
199
- if ((_remoteSignerUrl || _remoteWalletUrl) && humanSigner) {
200
- console.error(JSON.stringify({ error: 'Remote signer/wallet and --human-signer-eoa cannot be combined. Remote mode only supports passkey wallets.' }));
169
+ // remote_wallet + --human-signer-eoa is not supported
170
+ if (_remoteWalletUrl && humanSigner) {
171
+ console.error(JSON.stringify({ error: 'remote_wallet and --human-signer-eoa cannot be combined. Remote mode only supports passkey wallets.' }));
201
172
  process.exit(1);
202
173
  }
203
174
 
@@ -206,19 +177,53 @@ if (!to) {
206
177
  process.exit(1);
207
178
  }
208
179
 
180
+ // ENS support: if --to is not a raw 0x address, try resolving as ENS name.
181
+ // ENS registry lives on Ethereum mainnet — always resolve against mainnet RPC,
182
+ // regardless of the target --chain.
183
+ let resolvedTo = to;
209
184
  if (!ethers.isAddress(to)) {
210
- console.error(JSON.stringify({ error: `Invalid recipient address: ${to}` }));
211
- process.exit(1);
185
+ if (!to.includes('.')) {
186
+ console.error(JSON.stringify({ error: `Invalid recipient address: ${to} (not a valid address or ENS name)` }));
187
+ process.exit(1);
188
+ }
189
+ try {
190
+ const ensRpcUrl = NETWORKS.mainnet.ethereum?.rpcUrl;
191
+ if (!ensRpcUrl) {
192
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name "${to}": no Ethereum mainnet RPC available` }));
193
+ process.exit(1);
194
+ }
195
+ const ensProvider = new ethers.JsonRpcProvider(ensRpcUrl);
196
+ const resolved = await ensProvider.resolveName(to);
197
+ if (!resolved) {
198
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name: ${to}` }));
199
+ process.exit(1);
200
+ }
201
+ resolvedTo = resolved;
202
+ console.error(`[INFO] Resolved ENS ${to} → ${resolved}`);
203
+ } catch (e) {
204
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name "${to}": ${e.message}` }));
205
+ process.exit(1);
206
+ }
212
207
  }
213
208
 
214
209
  const SAFE_ADDRESS = safeOverride || DEFAULT_SAFE;
215
210
 
216
211
  // Read optional wallet JSON for rpId (SafeClaw cross-origin passkey support)
217
212
  // In remote_wallet mode, wallets were already fetched — use that data.
213
+ // rpId MUST be present in approveUrl when available, otherwise the web app
214
+ // defaults to rpId=nodpay.ai which causes passkey mismatch errors.
218
215
  let walletRpId = null;
219
- if (SAFE_ADDRESS && _remoteWalletUrl && _remoteWalletsCache) {
220
- const match = _remoteWalletsCache.find(w => w.safe?.toLowerCase() === SAFE_ADDRESS.toLowerCase());
221
- if (match?.rpId) walletRpId = match.rpId;
216
+ if (_remoteWalletUrl && _remoteWalletsCache) {
217
+ // Try exact safe match first
218
+ if (SAFE_ADDRESS) {
219
+ const match = _remoteWalletsCache.find(w => w.safe?.toLowerCase() === SAFE_ADDRESS.toLowerCase());
220
+ if (match?.rpId) walletRpId = match.rpId;
221
+ }
222
+ // Fallback: use rpId from any wallet in cache (remote proxy typically serves one wallet set)
223
+ if (!walletRpId) {
224
+ const withRpId = _remoteWalletsCache.find(w => w.rpId);
225
+ if (withRpId) walletRpId = withRpId.rpId;
226
+ }
222
227
  }
223
228
  if (!walletRpId && SAFE_ADDRESS) {
224
229
  const walletJsonPath = join(HOME, '.nodpay', 'wallets', `${SAFE_ADDRESS}.json`);
@@ -507,7 +512,7 @@ try {
507
512
 
508
513
  // Create the transaction as a SafeOperation (UserOp wrapper)
509
514
  const safeOperation = await safe4337Pack.createTransaction({
510
- transactions: [{ to, value, data: '0x' }],
515
+ transactions: [{ to: resolvedTo, value, data: '0x' }],
511
516
  options: txOptions,
512
517
  });
513
518
 
@@ -562,7 +567,7 @@ try {
562
567
  userOpHash: entryPointUserOpHash,
563
568
  safeOpHash,
564
569
  shortId,
565
- to,
570
+ to: resolvedTo,
566
571
  value,
567
572
  valueEth,
568
573
  safeAddress,
@@ -582,7 +587,7 @@ try {
582
587
  const storePayload = {
583
588
  safeOperationJson,
584
589
  userOpHash: entryPointUserOpHash,
585
- to,
590
+ to: resolvedTo,
586
591
  value,
587
592
  valueEth,
588
593
  safeAddress,