nodpay 0.1.0 → 0.2.1

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 CHANGED
@@ -1,236 +1,48 @@
1
- ---
2
- name: nodpay
3
- description: Propose on-chain payments from a shared wallet. Use when user asks to send crypto, make a payment, or create a shared wallet.
4
- metadata:
5
- { "openclaw": { "requires": { "env": ["NODPAY_AGENT_KEY"] }, "primaryEnv": "NODPAY_AGENT_KEY" } }
6
- ---
1
+ # nodpay
7
2
 
8
- # NodPay — Agent Wallet
3
+ Agent toolkit for [NodPay](https://nodpay.ai)propose on-chain payments from a shared wallet.
9
4
 
10
- > Two minds, one wallet.
5
+ > **This package is for AI agents.** If you're a human, visit [nodpay.ai](https://nodpay.ai).
11
6
 
12
- NodPay lets you (an AI agent) propose on-chain payments from a shared wallet. Your human co-signs with one tap. You cannot spend without their approval.
7
+ ## What NodPay does
13
8
 
14
- **When to use**: User asks you to send crypto, make a payment, or create a shared wallet.
15
-
16
- ## How It Works
17
-
18
- ```
19
- You (agent) NodPay User (human)
20
- │ │ │
21
- ├─ generate key ──►│ │
22
- ├─ send link ──────┼──────────────────►│
23
- │ │◄── create wallet ─┤
24
- │◄── wallet info ──┤ │
25
- │ │ │
26
- ├─ propose tx ────►│ │
27
- │ ├── approve link ───►│
28
- │ │◄── sign + submit ─┤
29
- │ │ │
30
- ```
31
-
32
- The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (passkey + recovery). You cannot move funds alone.
33
-
34
- ---
35
-
36
- ## Setup (one-time)
37
-
38
- ### 1. Install
39
-
40
- ```bash
41
- git clone https://github.com/xhyumiracle/nodpay.git
42
- cd nodpay/skill && npm install
43
- ```
44
-
45
- ### 2. Set `NODPAY_AGENT_KEY` in your environment
46
-
47
- Generate one if you don't have it:
48
-
49
- ```bash
50
- node -e "const w=require('ethers').Wallet.createRandom();console.log('Address:',w.address,'\nKey:',w.privateKey)"
51
- ```
52
-
53
- Store the private key wherever your agent framework manages secrets (e.g. `.env`, system env vars). The address is your public agent identity.
54
-
55
- No bundler key needed — NodPay proxies bundler calls for you.
56
-
57
- ---
9
+ You (an AI agent) and your human share a 2-of-3 multisig wallet. You propose transactions; the human approves or rejects each one. You cannot spend without their approval.
58
10
 
59
11
  ## Usage
60
12
 
61
- ### Create a wallet
62
-
63
- Send the user this link:
64
-
65
- ```
66
- https://nodpay.ai/?agent=YOUR_AGENT_ADDRESS
67
- ```
68
-
69
- User opens → creates passkey → saves recovery phrase → gets a wallet. Takes 30 seconds.
70
-
71
- The user will copy wallet info back to you:
72
-
73
- ```
74
- NodPay Wallet (Safe): 0x...
75
- Passkey X: 0x...
76
- Passkey Y: 0x...
77
- Agent: 0x...
78
- Recovery Signer: 0x...
79
- ```
80
-
81
- **Store all fields** — you need them for proposing transactions.
82
-
83
- #### Wallet file management
84
-
85
- Store wallet info in `.nodpay/wallets/` in your workspace root (separate from skill code):
86
-
87
- ```
88
- .nodpay/wallets/
89
- 0xAbC...123.json # one file per wallet, named by Safe address
90
- ```
91
-
92
- Each wallet file:
93
-
94
- ```json
95
- {
96
- "safe": "0x...",
97
- "signerType": "passkey",
98
- "passkeyX": "0x...",
99
- "passkeyY": "0x...",
100
- "recovery": "0x...",
101
- "chain": "sepolia",
102
- "createdAt": "2025-01-01"
103
- }
104
- ```
105
-
106
- For EOA wallets, replace passkey fields with `"userSigner": "0x..."`.
107
-
108
- One agent key serves all wallets — multi-wallet is handled user-side (different passkeys/recovery keys → different Safe addresses, same agent).
109
-
110
- **⚠️ Verify the Agent address matches yours.** If it doesn't, the wallet is bound to someone else's key — alert the user and send a fresh link.
111
-
112
- **Multiple wallets**: A user may have multiple wallets (different devices, different passkeys). Before proposing, confirm which wallet to use — especially if you manage more than one.
113
-
114
- ### Propose a transaction
115
-
116
- ```bash
117
- NODPAY_AGENT_KEY=0x... \
118
- node scripts/propose.mjs \
119
- --safe <WALLET_ADDRESS> \
120
- --to <RECIPIENT> \
121
- --value-eth <AMOUNT> \
122
- --passkey-x <PASSKEY_X> \
123
- --passkey-y <PASSKEY_Y> \
124
- --recovery <RECOVERY_SIGNER> \
125
- --signer-type passkey
126
- ```
127
-
128
- The script outputs JSON with an `approveUrl`. Send it to the user:
129
-
130
- > 💰 Payment: 0.01 ETH → 0xRecipient...
131
- > 👉 Approve: https://nodpay.ai/approve?safeOpHash=0x...
132
-
133
- **First transaction deploys the wallet on-chain.** Pass `--passkey-x`, `--passkey-y`, and `--recovery` for the first tx. After deployment, `--safe` alone is sufficient (but passing all params is always safe).
134
-
135
- ### Check balance
136
-
137
- ```bash
138
- curl -s -X POST https://ethereum-sepolia-rpc.publicnode.com \
139
- -H "Content-Type: application/json" \
140
- -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["<WALLET_ADDRESS>","latest"],"id":1}'
141
- ```
142
-
143
- If balance is 0, remind the user to deposit before proposing.
144
-
145
- ### Check pending transactions
146
-
147
13
  ```bash
148
- curl https://nodpay.ai/api/txs?safe=<WALLET_ADDRESS>
14
+ # No install needed
15
+ NODPAY_AGENT_KEY=0x... npx nodpay propose \
16
+ --safe 0xWALLET --to 0xRECIPIENT --value-eth 0.01 \
17
+ --signer-type passkey --passkey-x 0x... --passkey-y 0x...
149
18
  ```
150
19
 
151
- Always check before proposing — this tells you the current nonce, pending ops, and wallet status.
20
+ ## Full guide
152
21
 
153
- ---
22
+ **[nodpay.ai/skill.md](https://nodpay.ai/skill.md)** — complete setup + integration guide for agents (key generation, wallet creation, proposing, error handling).
154
23
 
155
- ## Script Reference
24
+ ## How it works
156
25
 
157
- ### Flags
158
-
159
- | Flag | Required | Description |
160
- |------|----------|-------------|
161
- | `--safe` | ✅ | Wallet (Safe) address |
162
- | `--to` | ✅ | Recipient address |
163
- | `--value-eth` | ✅ | Amount in ETH |
164
- | `--signer-type` | ✅ | `passkey` or `eoa` |
165
- | `--passkey-x` | passkey wallets | Passkey public key X |
166
- | `--passkey-y` | passkey wallets | Passkey public key Y |
167
- | `--user-signer` | eoa wallets | User's EOA address |
168
- | `--recovery` | first tx | Recovery signer address |
169
- | `--nonce` | ❌ | Force nonce (for replacements) |
170
- | `--purpose` | ❌ | Human-readable label |
171
-
172
- ### Environment Variables
173
-
174
- | Var | Required | Description |
175
- |-----|----------|-------------|
176
- | `NODPAY_AGENT_KEY` | ✅ | Agent signing key |
177
- | `RPC_URL` | ❌ | RPC endpoint (default: Sepolia) |
178
- | `CHAIN_ID` | ❌ | Chain ID (default: 11155111) |
179
- | `PIMLICO_API_KEY` | ❌ | Own bundler key (optional, NodPay proxy is default) |
180
-
181
- ### Supported Chains
182
-
183
- `sepolia`, `ethereum`, `base`, `base_sepolia`, `arbitrum`, `optimism`, `polygon`
184
-
185
- Set `RPC_URL` and `CHAIN_ID` for non-Sepolia chains. See `references/networks.json` for chain configs.
186
-
187
- ---
188
-
189
- ## Transaction Patterns
190
-
191
- **Sequential**: Just call propose multiple times. Nonces auto-increment.
192
-
193
- **Replace**: Propose with `--nonce N` (same nonce as the pending tx). User picks which to approve.
194
-
195
- **Cascade**: Rejecting tx #N auto-invalidates all tx with nonce > N. This is irreversible.
196
-
197
- ⚠️ **Never propose a new nonce then reject the old one** — the cascade will destroy your new tx too.
198
-
199
- ---
200
-
201
- ## Reconnect (Wallet Recovery)
202
-
203
- If the user cleared their browser data, the wallet still exists on-chain. Build a reconnect link:
204
-
205
- ```
206
- https://nodpay.ai/?agent=YOUR_AGENT_ADDRESS&safe=WALLET_ADDRESS&recovery=RECOVERY_SIGNER&x=PASSKEY_X&y=PASSKEY_Y
207
- ```
26
+ 1. Agent generates a key → sends user a wallet creation link
27
+ 2. User creates a passkey wallet at nodpay.ai (30 seconds)
28
+ 3. Agent proposes transactions with `npx nodpay propose`
29
+ 4. User approves/rejects on their phone
208
30
 
209
- User opens → verifies passkey → wallet restored. No on-chain action needed.
31
+ ## Env
210
32
 
211
- ---
33
+ | Variable | Required | Description |
34
+ |----------|----------|-------------|
35
+ | `NODPAY_AGENT_KEY` | ✅ | Agent's private key (hex) |
212
36
 
213
- ## Security Model
37
+ ## Supported chains
214
38
 
215
- | Owner | Holder | Can do |
216
- |-------|--------|--------|
217
- | Agent EOA | You | Propose only |
218
- | Passkey | User's device | Approve or reject |
219
- | Recovery | User's 12-word phrase | Backup access |
39
+ Ethereum · Base · Arbitrum · Optimism · Polygon · Sepolia · Base Sepolia
220
40
 
221
- - 2-of-3 threshold — you cannot execute alone
222
- - Passkey X/Y are public key material, safe to store
223
- - No private keys stored on NodPay's server
224
- - Funds are safe on-chain even if NodPay goes offline
41
+ ## Related
225
42
 
226
- ---
43
+ - [`@nodpay/core`](https://www.npmjs.com/package/@nodpay/core) — Protocol primitives (hash, decode, verify)
44
+ - [nodpay.ai](https://nodpay.ai) — Web app
227
45
 
228
- ## Common Requests
46
+ ## License
229
47
 
230
- | User says | Action |
231
- |-----------|--------|
232
- | "create a wallet" | Send wallet creation link |
233
- | "send 0.1 ETH to 0x..." | Propose transaction |
234
- | "balance" | RPC `eth_getBalance` on Safe address |
235
- | "pending?" | `GET /api/txs?safe=...` |
236
- | "wallet disappeared" | Send reconnect link |
48
+ MIT
package/SKILL.md CHANGED
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  name: nodpay
3
3
  description: Propose on-chain payments from a shared wallet. Use when user asks to send crypto, make a payment, or create a shared wallet.
4
- metadata:
5
- { "openclaw": { "requires": { "env": ["NODPAY_AGENT_KEY"] }, "primaryEnv": "NODPAY_AGENT_KEY" } }
4
+ openclaw:
5
+ requires:
6
+ env: [NODPAY_AGENT_KEY]
7
+ primaryEnv: NODPAY_AGENT_KEY
6
8
  ---
7
9
 
8
10
  # NodPay — Agent Wallet
@@ -16,16 +18,16 @@ NodPay lets you (an AI agent) propose on-chain payments from a shared wallet. Yo
16
18
  ## How It Works
17
19
 
18
20
  ```
19
- You (agent) User (human)
20
-
21
- ├─ generate key ─────────────────────►│ (send setup link)
22
- ├─ create wallet on NodPay
23
- │◄── wallet info ─────────────────────┤ (user sends back wallet details)
24
-
25
- ├─ propose tx ───► NodPay op-store
26
- ├── approve link ──►│
27
- │◄── sign + submit ─┤
28
-
21
+ You (agent) NodPay server User (human)
22
+
23
+ ├── send setup link ──────┼─────────────────────►│
24
+ │ ├── create wallet
25
+ │◄── wallet info ─────────┼───────────────────────┤
26
+
27
+ ├── propose tx ──────────►│
28
+ ├── approve link ──────►│
29
+ │◄── sign + submit ─────┤
30
+
29
31
  ```
30
32
 
31
33
  The wallet is a **Safe 2-of-3 multisig**. You hold 1 key, the user holds 2 (passkey + recovery). You cannot move funds alone.
@@ -54,7 +56,7 @@ Replace `YOUR_AGENT_ADDRESS` with your EOA address and send:
54
56
 
55
57
  **Use the URL above verbatim.** Do not search for NodPay or guess the URL — `nodpay.ai` is the only correct domain.
56
58
 
57
- User opens creates passkey → saves recovery phrase → gets a wallet. They will copy wallet info back to you:
59
+ The user will create the wallet and copy info back to you:
58
60
 
59
61
  ```
60
62
  NodPay Wallet (Safe): 0x...
@@ -66,9 +68,11 @@ Recovery Signer: 0x...
66
68
 
67
69
  **Store all fields** — you need them for proposing transactions.
68
70
 
71
+ **After wallet creation**, tell the user their wallet is ready and the address works on any supported chain. Briefly offer: *"Want to try a test transaction on a testnet first?"* — only guide to testnets (Sepolia, etc.) if the user says yes.
72
+
69
73
  #### Wallet file management
70
74
 
71
- Store wallet info in `.nodpay/wallets/` in your workspace root (separate from skill code):
75
+ Store wallet info in `.nodpay/wallets/` in your workspace root:
72
76
 
73
77
  ```
74
78
  .nodpay/wallets/
@@ -85,7 +89,6 @@ Each wallet file:
85
89
  "passkeyX": "0x...",
86
90
  "passkeyY": "0x...",
87
91
  "recovery": "0x...",
88
- "chain": "sepolia",
89
92
  "createdAt": "2025-01-01"
90
93
  }
91
94
  ```
@@ -98,23 +101,11 @@ One agent key serves all wallets — multi-wallet is handled user-side (differen
98
101
 
99
102
  **⚠️ Verify the Agent address matches yours.** If it doesn't, the wallet is bound to someone else's key — alert the user and send a fresh link.
100
103
 
101
- **Multiple wallets**: A user may have multiple wallets (different devices, different passkeys). Before proposing, confirm which wallet to use — especially if you manage more than one.
102
-
103
104
  ### Propose a transaction
104
105
 
105
- First time only — install the propose script:
106
-
107
- ```bash
108
- git clone https://github.com/xhyumiracle/nodpay.git
109
- cd nodpay/skill && npm install
110
- ```
111
-
112
- Then propose:
113
-
114
106
  ```bash
115
- cd nodpay/skill && \
116
107
  NODPAY_AGENT_KEY=0x... \
117
- node scripts/propose.mjs \
108
+ npx nodpay propose \
118
109
  --safe <WALLET_ADDRESS> \
119
110
  --to <RECIPIENT> \
120
111
  --value-eth <AMOUNT> \
@@ -182,7 +173,9 @@ Chain config (RPC, bundler, explorer) is auto-resolved from `references/networks
182
173
 
183
174
  ### Supported Chains
184
175
 
185
- `sepolia`, `ethereum`, `base`, `base_sepolia`, `arbitrum`, `optimism`, `polygon`
176
+ `ethereum`, `base`, `arbitrum`, `optimism`, `polygon`, `sepolia`, `base_sepolia`
177
+
178
+ The wallet address is the same across all chains (counterfactual). Chain is only relevant at transaction time. **Do not assume a default chain.** When the user asks to send, ask which chain if not specified.
186
179
 
187
180
  ---
188
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,11 +9,22 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "scripts/",
12
- "references/",
13
- "SKILL.md",
14
- "AGENT-NOTES.md"
12
+ "SKILL.md"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/xhyumiracle/nodpay-cli.git"
17
+ },
18
+ "homepage": "https://nodpay.ai",
19
+ "keywords": [
20
+ "crypto",
21
+ "wallet",
22
+ "safe",
23
+ "multisig",
24
+ "agent",
25
+ "erc-4337",
26
+ "payment"
15
27
  ],
16
- "keywords": ["crypto", "wallet", "safe", "multisig", "agent", "erc-4337", "payment"],
17
28
  "author": "xhyumiracle",
18
29
  "license": "MIT",
19
30
  "dependencies": {
@@ -10,7 +10,7 @@
10
10
  * NODPAY_AGENT_KEY - Agent signer private key
11
11
  * SAFE_ADDRESS - Deployed Safe address (can be overridden with --safe)
12
12
  * RPC_URL - RPC endpoint
13
- * CHAIN_ID - Chain ID (default: 11155111)
13
+ * CHAIN_ID - Chain ID (required, no default)
14
14
  *
15
15
  * Args:
16
16
  * --to <address> - Recipient address
@@ -37,8 +37,12 @@ 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
- const RPC_URL = process.env.RPC_URL || 'https://ethereum-sepolia-rpc.publicnode.com';
41
- const CHAIN_ID = process.env.CHAIN_ID || '11155111';
40
+ const RPC_URL = process.env.RPC_URL;
41
+ const CHAIN_ID = process.env.CHAIN_ID;
42
+ if (!RPC_URL || !CHAIN_ID) {
43
+ console.error('Error: RPC_URL and CHAIN_ID environment variables are required.\nSet them for your target chain. See references/networks.json for supported chains.');
44
+ process.exit(1);
45
+ }
42
46
  const ENTRYPOINT_ADDRESS = ENTRYPOINT;
43
47
  const NODPAY_AGENT_KEY = process.env.NODPAY_AGENT_KEY;
44
48
  const DEFAULT_SAFE = process.env.SAFE_ADDRESS;
package/AGENT-NOTES.md DELETED
@@ -1,53 +0,0 @@
1
- # Agent Operational Notes
2
-
3
- Advanced patterns and edge cases. Read SKILL.md first.
4
-
5
- ## Nonce Management
6
-
7
- - **Auto-increment**: propose without `--nonce` → script fetches on-chain nonce, auto-increments past pending ops
8
- - **Replace**: use `--nonce N` with same nonce as pending tx → both appear in dashboard, user picks one
9
- - **Gas reuse**: when nonce > on-chain nonce, bundler rejects gas estimation (`AA25`). Script auto-reuses gas from the first pending op.
10
-
11
- ## Status Lifecycle
12
-
13
- ```
14
- pending → submitted → executed
15
- ↘ rejected
16
- ↘ replaced (another tx at same nonce was executed)
17
- ↘ invalidated (cascade from lower nonce rejection)
18
- ```
19
-
20
- ## Multi-Wallet
21
-
22
- One agent can serve multiple wallets. Different user devices → different passkeys → different Safe addresses. Always confirm which wallet before proposing.
23
-
24
- ## EOA Wallets
25
-
26
- For users who prefer MetaMask/browser wallet over passkey:
27
-
28
- ```bash
29
- node scripts/propose.mjs \
30
- --safe <WALLET_ADDRESS> \
31
- --to <RECIPIENT> \
32
- --value-eth <AMOUNT> \
33
- --user-signer <USER_EOA_ADDRESS> \
34
- --recovery <RECOVERY_SIGNER> \
35
- --signer-type eoa
36
- ```
37
-
38
- ## Owner Ordering (CREATE2)
39
-
40
- Safe address is deterministically derived. Owner order is fixed:
41
-
42
- - **Passkey**: `[agent, recovery, SharedSigner]`
43
- - **EOA**: `[userSigner, agent, recovery]`
44
-
45
- Wrong order → wrong address. The propose script handles this automatically.
46
-
47
- ## API Reference
48
-
49
- ```
50
- GET /api/txs?safe=<addr> # list all ops for a wallet
51
- GET /api/txs?safe=<addr>&status=pending # filter by status
52
- GET /api/tx/<shortHash> # get single op by hash prefix
53
- ```
@@ -1,91 +0,0 @@
1
- {
2
- "mainnet": {
3
- "ethereum": {
4
- "name": "Ethereum",
5
- "chainId": 1,
6
- "rpcUrl": "https://ethereum-rpc.publicnode.com",
7
- "txServiceUrl": "https://safe-transaction-mainnet.safe.global",
8
- "safePrefix": "eth",
9
- "explorerUrl": "https://etherscan.io",
10
- "explorerTxPath": "/tx/",
11
- "explorerAddrPath": "/address/",
12
- "nativeCurrency": "ETH",
13
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/1/rpc?apikey={PIMLICO_API_KEY}"
14
- },
15
- "base": {
16
- "name": "Base",
17
- "chainId": 8453,
18
- "rpcUrl": "https://base-rpc.publicnode.com",
19
- "txServiceUrl": "https://safe-transaction-base.safe.global",
20
- "safePrefix": "base",
21
- "explorerUrl": "https://basescan.org",
22
- "explorerTxPath": "/tx/",
23
- "explorerAddrPath": "/address/",
24
- "nativeCurrency": "ETH",
25
- "gasSponsored": true,
26
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/8453/rpc?apikey={PIMLICO_API_KEY}"
27
- },
28
- "arbitrum": {
29
- "name": "Arbitrum",
30
- "chainId": 42161,
31
- "rpcUrl": "https://arbitrum-one-rpc.publicnode.com",
32
- "txServiceUrl": "https://safe-transaction-arbitrum.safe.global",
33
- "safePrefix": "arb1",
34
- "explorerUrl": "https://arbiscan.io",
35
- "explorerTxPath": "/tx/",
36
- "explorerAddrPath": "/address/",
37
- "nativeCurrency": "ETH",
38
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/42161/rpc?apikey={PIMLICO_API_KEY}"
39
- },
40
- "polygon": {
41
- "name": "Polygon",
42
- "chainId": 137,
43
- "rpcUrl": "https://polygon-bor-rpc.publicnode.com",
44
- "txServiceUrl": "https://safe-transaction-polygon.safe.global",
45
- "safePrefix": "matic",
46
- "explorerUrl": "https://polygonscan.com",
47
- "explorerTxPath": "/tx/",
48
- "explorerAddrPath": "/address/",
49
- "nativeCurrency": "MATIC",
50
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/137/rpc?apikey={PIMLICO_API_KEY}"
51
- },
52
- "optimism": {
53
- "name": "Optimism",
54
- "chainId": 10,
55
- "rpcUrl": "https://optimism-rpc.publicnode.com",
56
- "txServiceUrl": "https://safe-transaction-optimism.safe.global",
57
- "safePrefix": "oeth",
58
- "explorerUrl": "https://optimistic.etherscan.io",
59
- "explorerTxPath": "/tx/",
60
- "explorerAddrPath": "/address/",
61
- "nativeCurrency": "ETH",
62
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/10/rpc?apikey={PIMLICO_API_KEY}"
63
- }
64
- },
65
- "testnet": {
66
- "sepolia": {
67
- "name": "Sepolia (Ethereum)",
68
- "chainId": 11155111,
69
- "rpcUrl": "https://ethereum-sepolia-rpc.publicnode.com",
70
- "txServiceUrl": "https://safe-transaction-sepolia.safe.global",
71
- "safePrefix": "sep",
72
- "explorerUrl": "https://sepolia.etherscan.io",
73
- "explorerTxPath": "/tx/",
74
- "explorerAddrPath": "/address/",
75
- "nativeCurrency": "ETH",
76
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/11155111/rpc?apikey={PIMLICO_API_KEY}"
77
- },
78
- "base_sepolia": {
79
- "name": "Base Sepolia",
80
- "chainId": 84532,
81
- "rpcUrl": "https://base-sepolia-rpc.publicnode.com",
82
- "txServiceUrl": "https://safe-transaction-base-sepolia.safe.global",
83
- "safePrefix": "basesep",
84
- "explorerUrl": "https://sepolia.basescan.org",
85
- "explorerTxPath": "/tx/",
86
- "explorerAddrPath": "/address/",
87
- "nativeCurrency": "ETH",
88
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/84532/rpc?apikey={PIMLICO_API_KEY}"
89
- }
90
- }
91
- }
@@ -1,167 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Predict a Safe address for a passkey-based wallet (counterfactual).
4
- *
5
- * Uses Safe4337Pack with passkey signer support to ensure the Safe setup
6
- * includes SharedSigner.configure() for passkey coordinates.
7
- *
8
- * Env vars:
9
- * NODPAY_AGENT_KEY - Agent signer private key
10
- * RPC_URL - RPC endpoint
11
- * CHAIN_ID - Chain ID (default: 11155111)
12
- * PIMLICO_API_KEY - Pimlico bundler API key
13
- *
14
- * Args:
15
- * --raw-id <base64> - Passkey rawId (base64 encoded)
16
- * --x <hex> - Passkey public key x coordinate
17
- * --y <hex> - Passkey public key y coordinate
18
- * --salt <nonce> - Salt nonce (optional, default: random)
19
- * --chain-id <number> - Override chain ID
20
- *
21
- * Output: JSON { predictedAddress, owners, threshold, chainId, salt, deployed, passkeyCoordinates }
22
- */
23
-
24
- import { Safe4337Pack } from '@safe-global/relay-kit';
25
- import Safe, { getMultiSendContract, encodeMultiSendData, SafeProvider } from '@safe-global/protocol-kit';
26
- import { OperationType } from '@safe-global/types-kit';
27
- import { ethers } from 'ethers';
28
-
29
- const SHARED_SIGNER_ADDRESS = '0x94a4F6affBd8975951142c3999aEAB7ecee555c2';
30
-
31
- // Default P-256 verifier (FCLP256Verifier) on major chains
32
- // From safe-modules-deployments v0.2.1
33
- const FCLP256_VERIFIERS = {
34
- '11155111': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Sepolia
35
- '1': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Mainnet
36
- '8453': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Base
37
- '84532': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Base Sepolia
38
- '42161': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Arbitrum
39
- '10': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Optimism
40
- '137': '0x445a0683e494ea0c5AF3E83c5159fBE47Cf9e765', // Polygon
41
- };
42
-
43
- const RPC_URL = process.env.RPC_URL || 'https://ethereum-sepolia-rpc.publicnode.com';
44
- const NODPAY_AGENT_KEY = process.env.NODPAY_AGENT_KEY;
45
- const PIMLICO_API_KEY = process.env.PIMLICO_API_KEY;
46
-
47
- if (!NODPAY_AGENT_KEY) {
48
- console.error(JSON.stringify({ error: 'Missing NODPAY_AGENT_KEY env var' }));
49
- process.exit(1);
50
- }
51
- if (!PIMLICO_API_KEY) {
52
- console.error(JSON.stringify({ error: 'Missing PIMLICO_API_KEY env var' }));
53
- process.exit(1);
54
- }
55
-
56
- const agentWallet = new ethers.Wallet(NODPAY_AGENT_KEY);
57
- const DEFAULT_AGENT_ADDRESS = agentWallet.address;
58
-
59
- const args = process.argv.slice(2);
60
- function getArg(name) {
61
- const idx = args.indexOf(name);
62
- return idx !== -1 ? args[idx + 1] : undefined;
63
- }
64
-
65
- const rawId = getArg('--raw-id');
66
- const x = getArg('--x');
67
- const y = getArg('--y');
68
- const CHAIN_ID = parseInt(getArg('--chain-id') || process.env.CHAIN_ID || '11155111', 10);
69
- const salt = getArg('--salt') || Math.floor(Math.random() * 1_000_000_000).toString();
70
- // Agent address can be overridden via --agent flag (for multi-agent support)
71
- const AGENT_ADDRESS = getArg('--agent') || DEFAULT_AGENT_ADDRESS;
72
- const RECOVERY_ADDRESS = getArg('--recovery'); // Optional 3rd owner for 2-of-3
73
-
74
- if (!x || !y) {
75
- console.error(JSON.stringify({ error: 'Missing --x <hex> and --y <hex> passkey coordinates' }));
76
- process.exit(1);
77
- }
78
-
79
- try {
80
- const owners = RECOVERY_ADDRESS
81
- ? [AGENT_ADDRESS, SHARED_SIGNER_ADDRESS, RECOVERY_ADDRESS]
82
- : [AGENT_ADDRESS, SHARED_SIGNER_ADDRESS];
83
- const threshold = 2;
84
- const bundlerUrl = `https://api.pimlico.io/v2/${CHAIN_ID}/rpc?apikey=${PIMLICO_API_KEY}`;
85
- const verifier = FCLP256_VERIFIERS[String(CHAIN_ID)] || FCLP256_VERIFIERS['11155111'];
86
-
87
- // Encode SharedSigner.configure() — stores passkey coordinates in Safe's storage
88
- const sharedSignerIface = new ethers.Interface([
89
- 'function configure((uint256 x, uint256 y, uint176 verifiers) signer)'
90
- ]);
91
- const configureData = sharedSignerIface.encodeFunctionData('configure', [{
92
- x: BigInt(x),
93
- y: BigInt(y),
94
- verifiers: BigInt(verifier)
95
- }]);
96
-
97
- // We need Safe4337Pack to use our custom setup that includes:
98
- // 1. enableModules([Safe4337Module]) — done by Safe4337Pack
99
- // 2. SharedSigner.configure({x, y, verifiers}) — we need to inject this
100
- //
101
- // Strategy: Initialize Safe4337Pack normally, then monkey-patch protocolKit
102
- // to include our configure call in the predicted Safe config.
103
-
104
- // First, init Safe4337Pack to get module addresses and standard config
105
- const safe4337Pack = await Safe4337Pack.init({
106
- provider: RPC_URL,
107
- signer: NODPAY_AGENT_KEY,
108
- bundlerUrl,
109
- options: {
110
- owners,
111
- threshold,
112
- saltNonce: salt,
113
- },
114
- });
115
-
116
- // Get the address WITHOUT passkey config (this is what we had before — wrong)
117
- const addressWithoutPasskey = await safe4337Pack.protocolKit.getAddress();
118
-
119
- // Now create a new Safe4337Pack with passkey signer to get the CORRECT address
120
- // We pass a fake passkey signer object that Safe4337Pack recognizes
121
- const passkeySignerObj = {
122
- rawId: rawId || 'passkey-' + salt,
123
- coordinates: { x, y },
124
- customVerifierAddress: verifier,
125
- };
126
-
127
- const ownersForPasskeyPack = RECOVERY_ADDRESS
128
- ? [AGENT_ADDRESS, RECOVERY_ADDRESS] // SharedSigner will be auto-added by SDK
129
- : [AGENT_ADDRESS]; // SharedSigner will be auto-added by SDK
130
- const safe4337PackWithPasskey = await Safe4337Pack.init({
131
- provider: RPC_URL,
132
- signer: passkeySignerObj,
133
- bundlerUrl,
134
- options: {
135
- owners: ownersForPasskeyPack,
136
- threshold,
137
- saltNonce: salt,
138
- },
139
- });
140
-
141
- const predictedAddress = await safe4337PackWithPasskey.protocolKit.getAddress();
142
-
143
- // Check if already deployed
144
- const provider = new ethers.JsonRpcProvider(RPC_URL);
145
- const code = await provider.getCode(predictedAddress);
146
- const deployed = code !== '0x';
147
-
148
- const result = {
149
- predictedAddress,
150
- owners,
151
- threshold,
152
- chainId: CHAIN_ID,
153
- salt,
154
- deployed,
155
- sharedSigner: SHARED_SIGNER_ADDRESS,
156
- verifier,
157
- passkeyCoordinates: { x, y },
158
- rawId: rawId || null,
159
- // For debugging: show old address without passkey config
160
- _addressWithoutPasskeyConfig: addressWithoutPasskey,
161
- };
162
-
163
- console.log(JSON.stringify(result, null, 2));
164
- } catch (error) {
165
- console.error(JSON.stringify({ error: error.message || String(error) }));
166
- process.exit(1);
167
- }