nodpay 0.2.39 → 0.2.41

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 +83 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -113,6 +113,18 @@ if (_remoteWalletUrl) {
113
113
  }
114
114
  AGENT_ADDRESS = wallets[0].agentSigner;
115
115
 
116
+ // remote_wallet: extract passkey/recovery from wallet data for approvalContext
117
+ // (CLI args take precedence if provided, but agents in remote_wallet mode don't pass them)
118
+ const rw = wallets[0];
119
+ if (!passkeyX && rw.humanSignerPasskeyX) {
120
+ passkeyX = rw.humanSignerPasskeyX;
121
+ passkeyY = rw.humanSignerPasskeyY;
122
+ isPasskey = true;
123
+ }
124
+ if (!recoverySigner && rw.recoverySigner) {
125
+ recoverySigner = rw.recoverySigner;
126
+ }
127
+
116
128
  signHash = async (hash) => {
117
129
  const signRes = await fetch(`${_remoteWalletUrl.replace(/\/$/, '')}/sign`, {
118
130
  method: 'POST',
@@ -159,12 +171,12 @@ const humanSigner = getArg('--human-signer-eoa');
159
171
  const salt = getArg('--salt') || '1001';
160
172
 
161
173
  // Passkey support
162
- const passkeyX = getArg('--human-signer-passkey-x');
163
- const passkeyY = getArg('--human-signer-passkey-y');
174
+ let passkeyX = getArg('--human-signer-passkey-x');
175
+ let passkeyY = getArg('--human-signer-passkey-y');
164
176
  const passkeyRawId = getArg('--passkey-raw-id');
165
177
  const passkeyVerifier = getArg('--passkey-verifier') || '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765';
166
- const recoverySigner = getArg('--recovery-signer');
167
- const isPasskey = !!(passkeyX && passkeyY);
178
+ let recoverySigner = getArg('--recovery-signer');
179
+ let isPasskey = !!(passkeyX && passkeyY);
168
180
 
169
181
  // remote_wallet + --human-signer-eoa is not supported
170
182
  if (_remoteWalletUrl && humanSigner) {
@@ -177,19 +189,53 @@ if (!to) {
177
189
  process.exit(1);
178
190
  }
179
191
 
192
+ // ENS support: if --to is not a raw 0x address, try resolving as ENS name.
193
+ // ENS registry lives on Ethereum mainnet — always resolve against mainnet RPC,
194
+ // regardless of the target --chain.
195
+ let resolvedTo = to;
180
196
  if (!ethers.isAddress(to)) {
181
- console.error(JSON.stringify({ error: `Invalid recipient address: ${to}` }));
182
- process.exit(1);
197
+ if (!to.includes('.')) {
198
+ console.error(JSON.stringify({ error: `Invalid recipient address: ${to} (not a valid address or ENS name)` }));
199
+ process.exit(1);
200
+ }
201
+ try {
202
+ const ensRpcUrl = NETWORKS.mainnet.ethereum?.rpcUrl;
203
+ if (!ensRpcUrl) {
204
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name "${to}": no Ethereum mainnet RPC available` }));
205
+ process.exit(1);
206
+ }
207
+ const ensProvider = new ethers.JsonRpcProvider(ensRpcUrl);
208
+ const resolved = await ensProvider.resolveName(to);
209
+ if (!resolved) {
210
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name: ${to}` }));
211
+ process.exit(1);
212
+ }
213
+ resolvedTo = resolved;
214
+ console.error(`[INFO] Resolved ENS ${to} → ${resolved}`);
215
+ } catch (e) {
216
+ console.error(JSON.stringify({ error: `Cannot resolve ENS name "${to}": ${e.message}` }));
217
+ process.exit(1);
218
+ }
183
219
  }
184
220
 
185
221
  const SAFE_ADDRESS = safeOverride || DEFAULT_SAFE;
186
222
 
187
223
  // Read optional wallet JSON for rpId (SafeClaw cross-origin passkey support)
188
224
  // In remote_wallet mode, wallets were already fetched — use that data.
225
+ // rpId MUST be present in approveUrl when available, otherwise the web app
226
+ // defaults to rpId=nodpay.ai which causes passkey mismatch errors.
189
227
  let walletRpId = null;
190
- if (SAFE_ADDRESS && _remoteWalletUrl && _remoteWalletsCache) {
191
- const match = _remoteWalletsCache.find(w => w.safe?.toLowerCase() === SAFE_ADDRESS.toLowerCase());
192
- if (match?.rpId) walletRpId = match.rpId;
228
+ if (_remoteWalletUrl && _remoteWalletsCache) {
229
+ // Try exact safe match first
230
+ if (SAFE_ADDRESS) {
231
+ const match = _remoteWalletsCache.find(w => w.safe?.toLowerCase() === SAFE_ADDRESS.toLowerCase());
232
+ if (match?.rpId) walletRpId = match.rpId;
233
+ }
234
+ // Fallback: use rpId from any wallet in cache (remote proxy typically serves one wallet set)
235
+ if (!walletRpId) {
236
+ const withRpId = _remoteWalletsCache.find(w => w.rpId);
237
+ if (withRpId) walletRpId = withRpId.rpId;
238
+ }
193
239
  }
194
240
  if (!walletRpId && SAFE_ADDRESS) {
195
241
  const walletJsonPath = join(HOME, '.nodpay', 'wallets', `${SAFE_ADDRESS}.json`);
@@ -478,7 +524,7 @@ try {
478
524
 
479
525
  // Create the transaction as a SafeOperation (UserOp wrapper)
480
526
  const safeOperation = await safe4337Pack.createTransaction({
481
- transactions: [{ to, value, data: '0x' }],
527
+ transactions: [{ to: resolvedTo, value, data: '0x' }],
482
528
  options: txOptions,
483
529
  });
484
530
 
@@ -533,7 +579,7 @@ try {
533
579
  userOpHash: entryPointUserOpHash,
534
580
  safeOpHash,
535
581
  shortId,
536
- to,
582
+ to: resolvedTo,
537
583
  value,
538
584
  valueEth,
539
585
  safeAddress,
@@ -548,12 +594,32 @@ try {
548
594
  writeFileSync(join(PENDING_DIR, `4337-${shortId}.json`), JSON.stringify(result, null, 2));
549
595
 
550
596
  // Store to op-store API for hash-based web app lookup
551
- // NOTE: signerType intentionally NOT sent it's determined by user's browser
552
- // (localStorage), not by agent. See ARCHITECTURE.md Client Verification Chain.
597
+ // Build approvalContext: server-side approval context (replaces localStorage dependency)
598
+ const approvalContext = isPasskey
599
+ ? {
600
+ version: 1,
601
+ signerType: 'passkey',
602
+ rpId: walletRpId || 'nodpay.ai',
603
+ passkeyX,
604
+ passkeyY,
605
+ recoveryAddress: recoverySigner || null,
606
+ safeAddress,
607
+ chainId: parseInt(CHAIN_ID, 10),
608
+ }
609
+ : humanSigner
610
+ ? {
611
+ version: 1,
612
+ signerType: 'eoa',
613
+ humanSignerAddress: humanSigner,
614
+ safeAddress,
615
+ chainId: parseInt(CHAIN_ID, 10),
616
+ }
617
+ : null;
618
+
553
619
  const storePayload = {
554
620
  safeOperationJson,
555
621
  userOpHash: entryPointUserOpHash,
556
- to,
622
+ to: resolvedTo,
557
623
  value,
558
624
  valueEth,
559
625
  safeAddress,
@@ -562,10 +628,12 @@ try {
562
628
  agent: AGENT_ADDRESS,
563
629
  agentSignature: safeOperationJson.signatures,
564
630
  createdAt: new Date().toISOString(),
565
- // SafeClaw integration: passkey coordinates + recovery for approve page fallback
631
+ // Legacy fields (kept for URL-param fallback during transition)
566
632
  passkeyX: passkeyX || null,
567
633
  passkeyY: passkeyY || null,
568
634
  recoveryAddress: recoverySigner || null,
635
+ // Server-side approval context
636
+ approvalContext,
569
637
  };
570
638
 
571
639
  // Extract raw agent signature for server auth