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 +25 -213
- package/SKILL.md +17 -27
- package/package.json +16 -4
- package/references/networks.json +7 -14
- package/AGENT-NOTES.md +0 -53
- package/scripts/predict-passkey.mjs +0 -167
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
+
No install needed — `npx` handles everything.
|
|
129
14
|
|
|
130
|
-
|
|
131
|
-
> 👉 Approve: https://nodpay.ai/approve?safeOpHash=0x...
|
|
15
|
+
## What it does
|
|
132
16
|
|
|
133
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## Security Model
|
|
37
|
+
## Docs
|
|
214
38
|
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
46
|
+
## License
|
|
229
47
|
|
|
230
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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)
|
|
20
|
-
│
|
|
21
|
-
|
|
22
|
-
│
|
|
23
|
-
│◄── wallet info
|
|
24
|
-
│
|
|
25
|
-
|
|
26
|
-
│
|
|
27
|
-
│
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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": {
|
package/references/networks.json
CHANGED
|
@@ -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
|
-
}
|