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.
- package/package.json +1 -1
- package/scripts/propose.mjs +47 -42
package/package.json
CHANGED
package/scripts/propose.mjs
CHANGED
|
@@ -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
|
|
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)
|
|
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
|
-
//
|
|
199
|
-
if (
|
|
200
|
-
console.error(JSON.stringify({ error: '
|
|
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
|
-
|
|
211
|
-
|
|
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 (
|
|
220
|
-
|
|
221
|
-
if (
|
|
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,
|