nodpay 0.1.0 → 0.2.0

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
- ---
7
-
8
- # NodPay — Agent Wallet
1
+ # NodPay
9
2
 
10
3
  > Two minds, one wallet.
11
4
 
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.
13
-
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
- ---
58
-
59
- ## Usage
60
-
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):
5
+ AI agents propose on-chain payments. Humans approve with one tap. Self-custodial 2-of-3 multisig powered by [Safe](https://safe.global).
86
6
 
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
7
+ ## Install
115
8
 
116
9
  ```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
10
+ npx nodpay propose --safe 0x... --to 0x... --value-eth 0.01 --signer-type passkey
126
11
  ```
127
12
 
128
- The script outputs JSON with an `approveUrl`. Send it to the user:
13
+ No install needed `npx` handles everything.
129
14
 
130
- > 💰 Payment: 0.01 ETH → 0xRecipient...
131
- > 👉 Approve: https://nodpay.ai/approve?safeOpHash=0x...
15
+ ## What it does
132
16
 
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).
17
+ 1. **Agent** generates a key and sends the user a wallet creation link
18
+ 2. **User** opens the link, creates a passkey wallet in 30 seconds
19
+ 3. **Agent** proposes transactions → user approves or rejects each one
134
20
 
135
- ### Check balance
21
+ The wallet is an ERC-4337 Safe multisig. The agent holds 1 key, the user holds 2 (passkey + recovery). The agent cannot spend without human approval.
136
22
 
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
23
+ ## Quick start
146
24
 
147
25
  ```bash
148
- curl https://nodpay.ai/api/txs?safe=<WALLET_ADDRESS>
149
- ```
150
-
151
- Always check before proposing — this tells you the current nonce, pending ops, and wallet status.
152
-
153
- ---
154
-
155
- ## Script Reference
156
-
157
- ### Flags
26
+ # 1. Generate an agent key
27
+ node -e "const w=require('ethers').Wallet.createRandom();console.log(w.address,w.privateKey)"
158
28
 
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 |
29
+ # 2. Send user: https://nodpay.ai/?agent=YOUR_ADDRESS
171
30
 
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
31
+ # 3. After user creates wallet, propose a transaction:
32
+ NODPAY_AGENT_KEY=0x... npx nodpay propose \
33
+ --safe 0xWALLET --to 0xRECIPIENT --value-eth 0.01 \
34
+ --signer-type passkey --passkey-x 0x... --passkey-y 0x... --recovery 0x...
207
35
  ```
208
36
 
209
- User opens → verifies passkey → wallet restored. No on-chain action needed.
210
-
211
- ---
212
-
213
- ## Security Model
37
+ ## Docs
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
+ - **Full agent guide**: [nodpay.ai/skill.md](https://nodpay.ai/skill.md)
40
+ - **Website**: [nodpay.ai](https://nodpay.ai)
220
41
 
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
42
+ ## Supported chains
225
43
 
226
- ---
44
+ Ethereum · Base · Arbitrum · Optimism · Polygon · Sepolia · Base Sepolia
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...
@@ -68,7 +70,7 @@ Recovery Signer: 0x...
68
70
 
69
71
  #### Wallet file management
70
72
 
71
- Store wallet info in `.nodpay/wallets/` in your workspace root (separate from skill code):
73
+ Store wallet info in `.nodpay/wallets/` in your workspace root:
72
74
 
73
75
  ```
74
76
  .nodpay/wallets/
@@ -98,23 +100,11 @@ One agent key serves all wallets — multi-wallet is handled user-side (differen
98
100
 
99
101
  **⚠️ 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
102
 
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
103
  ### Propose a transaction
104
104
 
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
105
  ```bash
115
- cd nodpay/skill && \
116
106
  NODPAY_AGENT_KEY=0x... \
117
- node scripts/propose.mjs \
107
+ npx nodpay propose \
118
108
  --safe <WALLET_ADDRESS> \
119
109
  --to <RECIPIENT> \
120
110
  --value-eth <AMOUNT> \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodpay",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "NodPay CLI — propose on-chain payments from agent-human shared wallets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,10 +10,22 @@
10
10
  "bin/",
11
11
  "scripts/",
12
12
  "references/",
13
- "SKILL.md",
14
- "AGENT-NOTES.md"
13
+ "SKILL.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/xhyumiracle/nodpay-cli.git"
18
+ },
19
+ "homepage": "https://nodpay.ai",
20
+ "keywords": [
21
+ "crypto",
22
+ "wallet",
23
+ "safe",
24
+ "multisig",
25
+ "agent",
26
+ "erc-4337",
27
+ "payment"
15
28
  ],
16
- "keywords": ["crypto", "wallet", "safe", "multisig", "agent", "erc-4337", "payment"],
17
29
  "author": "xhyumiracle",
18
30
  "license": "MIT",
19
31
  "dependencies": {
@@ -9,8 +9,7 @@
9
9
  "explorerUrl": "https://etherscan.io",
10
10
  "explorerTxPath": "/tx/",
11
11
  "explorerAddrPath": "/address/",
12
- "nativeCurrency": "ETH",
13
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/1/rpc?apikey={PIMLICO_API_KEY}"
12
+ "nativeCurrency": "ETH"
14
13
  },
15
14
  "base": {
16
15
  "name": "Base",
@@ -22,8 +21,7 @@
22
21
  "explorerTxPath": "/tx/",
23
22
  "explorerAddrPath": "/address/",
24
23
  "nativeCurrency": "ETH",
25
- "gasSponsored": true,
26
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/8453/rpc?apikey={PIMLICO_API_KEY}"
24
+ "gasSponsored": true
27
25
  },
28
26
  "arbitrum": {
29
27
  "name": "Arbitrum",
@@ -34,8 +32,7 @@
34
32
  "explorerUrl": "https://arbiscan.io",
35
33
  "explorerTxPath": "/tx/",
36
34
  "explorerAddrPath": "/address/",
37
- "nativeCurrency": "ETH",
38
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/42161/rpc?apikey={PIMLICO_API_KEY}"
35
+ "nativeCurrency": "ETH"
39
36
  },
40
37
  "polygon": {
41
38
  "name": "Polygon",
@@ -46,8 +43,7 @@
46
43
  "explorerUrl": "https://polygonscan.com",
47
44
  "explorerTxPath": "/tx/",
48
45
  "explorerAddrPath": "/address/",
49
- "nativeCurrency": "MATIC",
50
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/137/rpc?apikey={PIMLICO_API_KEY}"
46
+ "nativeCurrency": "MATIC"
51
47
  },
52
48
  "optimism": {
53
49
  "name": "Optimism",
@@ -58,8 +54,7 @@
58
54
  "explorerUrl": "https://optimistic.etherscan.io",
59
55
  "explorerTxPath": "/tx/",
60
56
  "explorerAddrPath": "/address/",
61
- "nativeCurrency": "ETH",
62
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/10/rpc?apikey={PIMLICO_API_KEY}"
57
+ "nativeCurrency": "ETH"
63
58
  }
64
59
  },
65
60
  "testnet": {
@@ -72,8 +67,7 @@
72
67
  "explorerUrl": "https://sepolia.etherscan.io",
73
68
  "explorerTxPath": "/tx/",
74
69
  "explorerAddrPath": "/address/",
75
- "nativeCurrency": "ETH",
76
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/11155111/rpc?apikey={PIMLICO_API_KEY}"
70
+ "nativeCurrency": "ETH"
77
71
  },
78
72
  "base_sepolia": {
79
73
  "name": "Base Sepolia",
@@ -84,8 +78,7 @@
84
78
  "explorerUrl": "https://sepolia.basescan.org",
85
79
  "explorerTxPath": "/tx/",
86
80
  "explorerAddrPath": "/address/",
87
- "nativeCurrency": "ETH",
88
- "bundlerUrlTemplate": "https://api.pimlico.io/v2/84532/rpc?apikey={PIMLICO_API_KEY}"
81
+ "nativeCurrency": "ETH"
89
82
  }
90
83
  }
91
84
  }
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,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
- }