@coinfello/agent-cli 0.0.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/LICENSE.md +1 -0
- package/README.md +102 -0
- package/dist/index.js +1082 -0
- package/package.json +41 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
© 2026 HyperPlay Labs, Inc. - All Rights Reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# CoinFello Agent CLI
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm build
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Manual Testing
|
|
11
|
+
|
|
12
|
+
You can run the CLI via `node dist/index.js` after building.
|
|
13
|
+
|
|
14
|
+
### 1. create_account
|
|
15
|
+
|
|
16
|
+
Generates a new private key, creates a MetaMask smart account on the specified chain, and saves both the key and address to `~/.clawdbot/skills/coinfello/config.json`.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
node dist/index.js create_account sepolia
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Expected output:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Creating smart account on sepolia...
|
|
26
|
+
Smart account created successfully.
|
|
27
|
+
Address: 0x...
|
|
28
|
+
Config saved to: /home/<user>/.clawdbot/skills/coinfello/config.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Verify the config was written:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cat ~/.clawdbot/skills/coinfello/config.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. set_delegation
|
|
38
|
+
|
|
39
|
+
Stores a parent delegation JSON object in config. Only needed if you plan to use `--use-redelegation` with `send_prompt`.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node dist/index.js set_delegation '{"delegate":"0x0000000000000000000000000000000000000001","delegator":"0x...","authority":"0x0","caveats":[],"salt":"0x0","signature":"0x..."}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Expected output:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Delegation saved successfully.
|
|
49
|
+
Config saved to: /home/<user>/.clawdbot/skills/coinfello/config.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. send_prompt
|
|
53
|
+
|
|
54
|
+
Sends a prompt to CoinFello with a locally-created ERC-20 subdelegation. Requires `create_account` to have been run first. The private key is read from the config file.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node dist/index.js send_prompt "swap 5 USDC for ETH" \
|
|
58
|
+
--token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
59
|
+
--max-amount 5 \
|
|
60
|
+
--decimals 6
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With redelegation (requires `set_delegation` first):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
node dist/index.js send_prompt "swap 5 USDC for ETH" \
|
|
67
|
+
--token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
68
|
+
--max-amount 5 \
|
|
69
|
+
--decimals 6 \
|
|
70
|
+
--use-redelegation
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Expected output:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Fetching CoinFello delegate address...
|
|
77
|
+
Loading smart account...
|
|
78
|
+
Creating subdelegation...
|
|
79
|
+
Signing subdelegation...
|
|
80
|
+
Sending to conversation endpoint...
|
|
81
|
+
Transaction submitted successfully.
|
|
82
|
+
Transaction ID: <txn_id>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. get_transaction_status
|
|
86
|
+
|
|
87
|
+
Checks the status of a previously submitted transaction.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node dist/index.js get_transaction_status <txn_id>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expected output is a JSON object with the transaction status.
|
|
94
|
+
|
|
95
|
+
### Help
|
|
96
|
+
|
|
97
|
+
View all commands and options:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
node dist/index.js --help
|
|
101
|
+
node dist/index.js send_prompt --help
|
|
102
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { toMetaMaskSmartAccount, Implementation, createDelegation } from "@metamask/smart-accounts-kit";
|
|
4
|
+
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
|
|
5
|
+
import { createPublicClient, http, parseUnits } from "viem";
|
|
6
|
+
import * as chains from "viem/chains";
|
|
7
|
+
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
function resolveChain(chainName) {
|
|
11
|
+
const chain = chains[chainName];
|
|
12
|
+
if (!chain) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Unknown chain "${chainName}". Use a viem chain name (e.g. sepolia, mainnet, polygon, arbitrum).`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return chain;
|
|
18
|
+
}
|
|
19
|
+
async function createSmartAccount(privateKey, chainName) {
|
|
20
|
+
const chain = resolveChain(chainName);
|
|
21
|
+
const publicClient = createPublicClient({
|
|
22
|
+
chain,
|
|
23
|
+
transport: http()
|
|
24
|
+
});
|
|
25
|
+
const owner = privateKeyToAccount(privateKey);
|
|
26
|
+
const smartAccount = await toMetaMaskSmartAccount({
|
|
27
|
+
client: publicClient,
|
|
28
|
+
implementation: Implementation.Hybrid,
|
|
29
|
+
deployParams: [owner.address, [], [], []],
|
|
30
|
+
deploySalt: "0x",
|
|
31
|
+
signer: { account: owner }
|
|
32
|
+
});
|
|
33
|
+
const address = await smartAccount.getAddress();
|
|
34
|
+
return { smartAccount, address, owner };
|
|
35
|
+
}
|
|
36
|
+
async function getSmartAccount(privateKey, chainName) {
|
|
37
|
+
const { smartAccount } = await createSmartAccount(privateKey, chainName);
|
|
38
|
+
return smartAccount;
|
|
39
|
+
}
|
|
40
|
+
function createSubdelegation({
|
|
41
|
+
smartAccount,
|
|
42
|
+
delegateAddress,
|
|
43
|
+
parentDelegation,
|
|
44
|
+
tokenAddress,
|
|
45
|
+
maxAmount
|
|
46
|
+
}) {
|
|
47
|
+
return createDelegation({
|
|
48
|
+
scope: {
|
|
49
|
+
type: "erc20TransferAmount",
|
|
50
|
+
tokenAddress,
|
|
51
|
+
maxAmount
|
|
52
|
+
},
|
|
53
|
+
to: delegateAddress,
|
|
54
|
+
from: smartAccount.address,
|
|
55
|
+
parentDelegation,
|
|
56
|
+
environment: smartAccount.environment
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const CONFIG_DIR = join(homedir(), ".clawdbot", "skills", "coinfello");
|
|
60
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
61
|
+
async function loadConfig() {
|
|
62
|
+
try {
|
|
63
|
+
const raw = await readFile(CONFIG_PATH, "utf-8");
|
|
64
|
+
return JSON.parse(raw);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.code === "ENOENT") {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function saveConfig(config) {
|
|
73
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
74
|
+
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
75
|
+
}
|
|
76
|
+
const BASE_URL = "https://app.coinfello.com/api/v1";
|
|
77
|
+
async function getCoinFelloAddress() {
|
|
78
|
+
const response = await fetch(`${BASE_URL}/coinfello-address`);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
throw new Error(`Failed to get CoinFello address (${response.status}): ${text}`);
|
|
82
|
+
}
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
return data.address;
|
|
85
|
+
}
|
|
86
|
+
async function sendConversation({
|
|
87
|
+
prompt,
|
|
88
|
+
signedSubdelegation,
|
|
89
|
+
smartAccountAddress
|
|
90
|
+
}) {
|
|
91
|
+
const response = await fetch(`${BASE_URL}/conversation`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
prompt,
|
|
96
|
+
signed_subdelegation: signedSubdelegation,
|
|
97
|
+
smart_account_address: smartAccountAddress
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
throw new Error(`Conversation request failed (${response.status}): ${text}`);
|
|
103
|
+
}
|
|
104
|
+
return response.json();
|
|
105
|
+
}
|
|
106
|
+
async function getTransactionStatus(txnId) {
|
|
107
|
+
const response = await fetch(`${BASE_URL}/transaction_status?txn_id=${encodeURIComponent(txnId)}`);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
throw new Error(`Transaction status request failed (${response.status}): ${text}`);
|
|
111
|
+
}
|
|
112
|
+
return response.json();
|
|
113
|
+
}
|
|
114
|
+
const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
115
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
116
|
+
function fromBig(n, le = false) {
|
|
117
|
+
if (le)
|
|
118
|
+
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
|
119
|
+
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
120
|
+
}
|
|
121
|
+
function split(lst, le = false) {
|
|
122
|
+
const len = lst.length;
|
|
123
|
+
let Ah = new Uint32Array(len);
|
|
124
|
+
let Al = new Uint32Array(len);
|
|
125
|
+
for (let i = 0; i < len; i++) {
|
|
126
|
+
const { h, l } = fromBig(lst[i], le);
|
|
127
|
+
[Ah[i], Al[i]] = [h, l];
|
|
128
|
+
}
|
|
129
|
+
return [Ah, Al];
|
|
130
|
+
}
|
|
131
|
+
const rotlSH = (h, l, s) => h << s | l >>> 32 - s;
|
|
132
|
+
const rotlSL = (h, l, s) => l << s | h >>> 32 - s;
|
|
133
|
+
const rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s;
|
|
134
|
+
const rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s;
|
|
135
|
+
function isBytes(a) {
|
|
136
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
137
|
+
}
|
|
138
|
+
function anumber(n) {
|
|
139
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
140
|
+
throw new Error("positive integer expected, got " + n);
|
|
141
|
+
}
|
|
142
|
+
function abytes(b, ...lengths) {
|
|
143
|
+
if (!isBytes(b))
|
|
144
|
+
throw new Error("Uint8Array expected");
|
|
145
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
146
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
147
|
+
}
|
|
148
|
+
function aexists(instance, checkFinished = true) {
|
|
149
|
+
if (instance.destroyed)
|
|
150
|
+
throw new Error("Hash instance has been destroyed");
|
|
151
|
+
if (checkFinished && instance.finished)
|
|
152
|
+
throw new Error("Hash#digest() has already been called");
|
|
153
|
+
}
|
|
154
|
+
function aoutput(out, instance) {
|
|
155
|
+
abytes(out);
|
|
156
|
+
const min = instance.outputLen;
|
|
157
|
+
if (out.length < min) {
|
|
158
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function u32(arr) {
|
|
162
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
163
|
+
}
|
|
164
|
+
function clean(...arrays) {
|
|
165
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
166
|
+
arrays[i].fill(0);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
170
|
+
function byteSwap(word) {
|
|
171
|
+
return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
|
|
172
|
+
}
|
|
173
|
+
function byteSwap32(arr) {
|
|
174
|
+
for (let i = 0; i < arr.length; i++) {
|
|
175
|
+
arr[i] = byteSwap(arr[i]);
|
|
176
|
+
}
|
|
177
|
+
return arr;
|
|
178
|
+
}
|
|
179
|
+
const swap32IfBE = isLE ? (u) => u : byteSwap32;
|
|
180
|
+
function utf8ToBytes(str) {
|
|
181
|
+
if (typeof str !== "string")
|
|
182
|
+
throw new Error("string expected");
|
|
183
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
184
|
+
}
|
|
185
|
+
function toBytes$1(data) {
|
|
186
|
+
if (typeof data === "string")
|
|
187
|
+
data = utf8ToBytes(data);
|
|
188
|
+
abytes(data);
|
|
189
|
+
return data;
|
|
190
|
+
}
|
|
191
|
+
class Hash {
|
|
192
|
+
}
|
|
193
|
+
function createHasher(hashCons) {
|
|
194
|
+
const hashC = (msg) => hashCons().update(toBytes$1(msg)).digest();
|
|
195
|
+
const tmp = hashCons();
|
|
196
|
+
hashC.outputLen = tmp.outputLen;
|
|
197
|
+
hashC.blockLen = tmp.blockLen;
|
|
198
|
+
hashC.create = () => hashCons();
|
|
199
|
+
return hashC;
|
|
200
|
+
}
|
|
201
|
+
const _0n = BigInt(0);
|
|
202
|
+
const _1n = BigInt(1);
|
|
203
|
+
const _2n = BigInt(2);
|
|
204
|
+
const _7n = BigInt(7);
|
|
205
|
+
const _256n = BigInt(256);
|
|
206
|
+
const _0x71n = BigInt(113);
|
|
207
|
+
const SHA3_PI = [];
|
|
208
|
+
const SHA3_ROTL = [];
|
|
209
|
+
const _SHA3_IOTA = [];
|
|
210
|
+
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
|
|
211
|
+
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
212
|
+
SHA3_PI.push(2 * (5 * y + x));
|
|
213
|
+
SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
|
|
214
|
+
let t = _0n;
|
|
215
|
+
for (let j = 0; j < 7; j++) {
|
|
216
|
+
R = (R << _1n ^ (R >> _7n) * _0x71n) % _256n;
|
|
217
|
+
if (R & _2n)
|
|
218
|
+
t ^= _1n << (_1n << /* @__PURE__ */ BigInt(j)) - _1n;
|
|
219
|
+
}
|
|
220
|
+
_SHA3_IOTA.push(t);
|
|
221
|
+
}
|
|
222
|
+
const IOTAS = split(_SHA3_IOTA, true);
|
|
223
|
+
const SHA3_IOTA_H = IOTAS[0];
|
|
224
|
+
const SHA3_IOTA_L = IOTAS[1];
|
|
225
|
+
const rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
|
|
226
|
+
const rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
|
|
227
|
+
function keccakP(s, rounds = 24) {
|
|
228
|
+
const B = new Uint32Array(5 * 2);
|
|
229
|
+
for (let round = 24 - rounds; round < 24; round++) {
|
|
230
|
+
for (let x = 0; x < 10; x++)
|
|
231
|
+
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
|
|
232
|
+
for (let x = 0; x < 10; x += 2) {
|
|
233
|
+
const idx1 = (x + 8) % 10;
|
|
234
|
+
const idx0 = (x + 2) % 10;
|
|
235
|
+
const B0 = B[idx0];
|
|
236
|
+
const B1 = B[idx0 + 1];
|
|
237
|
+
const Th = rotlH(B0, B1, 1) ^ B[idx1];
|
|
238
|
+
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
|
|
239
|
+
for (let y = 0; y < 50; y += 10) {
|
|
240
|
+
s[x + y] ^= Th;
|
|
241
|
+
s[x + y + 1] ^= Tl;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let curH = s[2];
|
|
245
|
+
let curL = s[3];
|
|
246
|
+
for (let t = 0; t < 24; t++) {
|
|
247
|
+
const shift = SHA3_ROTL[t];
|
|
248
|
+
const Th = rotlH(curH, curL, shift);
|
|
249
|
+
const Tl = rotlL(curH, curL, shift);
|
|
250
|
+
const PI = SHA3_PI[t];
|
|
251
|
+
curH = s[PI];
|
|
252
|
+
curL = s[PI + 1];
|
|
253
|
+
s[PI] = Th;
|
|
254
|
+
s[PI + 1] = Tl;
|
|
255
|
+
}
|
|
256
|
+
for (let y = 0; y < 50; y += 10) {
|
|
257
|
+
for (let x = 0; x < 10; x++)
|
|
258
|
+
B[x] = s[y + x];
|
|
259
|
+
for (let x = 0; x < 10; x++)
|
|
260
|
+
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
|
|
261
|
+
}
|
|
262
|
+
s[0] ^= SHA3_IOTA_H[round];
|
|
263
|
+
s[1] ^= SHA3_IOTA_L[round];
|
|
264
|
+
}
|
|
265
|
+
clean(B);
|
|
266
|
+
}
|
|
267
|
+
class Keccak extends Hash {
|
|
268
|
+
// NOTE: we accept arguments in bytes instead of bits here.
|
|
269
|
+
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
|
|
270
|
+
super();
|
|
271
|
+
this.pos = 0;
|
|
272
|
+
this.posOut = 0;
|
|
273
|
+
this.finished = false;
|
|
274
|
+
this.destroyed = false;
|
|
275
|
+
this.enableXOF = false;
|
|
276
|
+
this.blockLen = blockLen;
|
|
277
|
+
this.suffix = suffix;
|
|
278
|
+
this.outputLen = outputLen;
|
|
279
|
+
this.enableXOF = enableXOF;
|
|
280
|
+
this.rounds = rounds;
|
|
281
|
+
anumber(outputLen);
|
|
282
|
+
if (!(0 < blockLen && blockLen < 200))
|
|
283
|
+
throw new Error("only keccak-f1600 function is supported");
|
|
284
|
+
this.state = new Uint8Array(200);
|
|
285
|
+
this.state32 = u32(this.state);
|
|
286
|
+
}
|
|
287
|
+
clone() {
|
|
288
|
+
return this._cloneInto();
|
|
289
|
+
}
|
|
290
|
+
keccak() {
|
|
291
|
+
swap32IfBE(this.state32);
|
|
292
|
+
keccakP(this.state32, this.rounds);
|
|
293
|
+
swap32IfBE(this.state32);
|
|
294
|
+
this.posOut = 0;
|
|
295
|
+
this.pos = 0;
|
|
296
|
+
}
|
|
297
|
+
update(data) {
|
|
298
|
+
aexists(this);
|
|
299
|
+
data = toBytes$1(data);
|
|
300
|
+
abytes(data);
|
|
301
|
+
const { blockLen, state } = this;
|
|
302
|
+
const len = data.length;
|
|
303
|
+
for (let pos = 0; pos < len; ) {
|
|
304
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
305
|
+
for (let i = 0; i < take; i++)
|
|
306
|
+
state[this.pos++] ^= data[pos++];
|
|
307
|
+
if (this.pos === blockLen)
|
|
308
|
+
this.keccak();
|
|
309
|
+
}
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
finish() {
|
|
313
|
+
if (this.finished)
|
|
314
|
+
return;
|
|
315
|
+
this.finished = true;
|
|
316
|
+
const { state, suffix, pos, blockLen } = this;
|
|
317
|
+
state[pos] ^= suffix;
|
|
318
|
+
if ((suffix & 128) !== 0 && pos === blockLen - 1)
|
|
319
|
+
this.keccak();
|
|
320
|
+
state[blockLen - 1] ^= 128;
|
|
321
|
+
this.keccak();
|
|
322
|
+
}
|
|
323
|
+
writeInto(out) {
|
|
324
|
+
aexists(this, false);
|
|
325
|
+
abytes(out);
|
|
326
|
+
this.finish();
|
|
327
|
+
const bufferOut = this.state;
|
|
328
|
+
const { blockLen } = this;
|
|
329
|
+
for (let pos = 0, len = out.length; pos < len; ) {
|
|
330
|
+
if (this.posOut >= blockLen)
|
|
331
|
+
this.keccak();
|
|
332
|
+
const take = Math.min(blockLen - this.posOut, len - pos);
|
|
333
|
+
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
|
|
334
|
+
this.posOut += take;
|
|
335
|
+
pos += take;
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
xofInto(out) {
|
|
340
|
+
if (!this.enableXOF)
|
|
341
|
+
throw new Error("XOF is not possible for this instance");
|
|
342
|
+
return this.writeInto(out);
|
|
343
|
+
}
|
|
344
|
+
xof(bytes) {
|
|
345
|
+
anumber(bytes);
|
|
346
|
+
return this.xofInto(new Uint8Array(bytes));
|
|
347
|
+
}
|
|
348
|
+
digestInto(out) {
|
|
349
|
+
aoutput(out, this);
|
|
350
|
+
if (this.finished)
|
|
351
|
+
throw new Error("digest() was already called");
|
|
352
|
+
this.writeInto(out);
|
|
353
|
+
this.destroy();
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
digest() {
|
|
357
|
+
return this.digestInto(new Uint8Array(this.outputLen));
|
|
358
|
+
}
|
|
359
|
+
destroy() {
|
|
360
|
+
this.destroyed = true;
|
|
361
|
+
clean(this.state);
|
|
362
|
+
}
|
|
363
|
+
_cloneInto(to) {
|
|
364
|
+
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
|
|
365
|
+
to || (to = new Keccak(blockLen, suffix, outputLen, enableXOF, rounds));
|
|
366
|
+
to.state32.set(this.state32);
|
|
367
|
+
to.pos = this.pos;
|
|
368
|
+
to.posOut = this.posOut;
|
|
369
|
+
to.finished = this.finished;
|
|
370
|
+
to.rounds = rounds;
|
|
371
|
+
to.suffix = suffix;
|
|
372
|
+
to.outputLen = outputLen;
|
|
373
|
+
to.enableXOF = enableXOF;
|
|
374
|
+
to.destroyed = this.destroyed;
|
|
375
|
+
return to;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const gen = (suffix, blockLen, outputLen) => createHasher(() => new Keccak(blockLen, suffix, outputLen));
|
|
379
|
+
const keccak_256 = /* @__PURE__ */ (() => gen(1, 136, 256 / 8))();
|
|
380
|
+
function isHex(value, { strict = true } = {}) {
|
|
381
|
+
if (!value)
|
|
382
|
+
return false;
|
|
383
|
+
if (typeof value !== "string")
|
|
384
|
+
return false;
|
|
385
|
+
return strict ? /^0x[0-9a-fA-F]*$/.test(value) : value.startsWith("0x");
|
|
386
|
+
}
|
|
387
|
+
const version = "2.45.1";
|
|
388
|
+
let errorConfig = {
|
|
389
|
+
getDocsUrl: ({ docsBaseUrl, docsPath = "", docsSlug }) => docsPath ? `${docsBaseUrl ?? "https://viem.sh"}${docsPath}${docsSlug ? `#${docsSlug}` : ""}` : void 0,
|
|
390
|
+
version: `viem@${version}`
|
|
391
|
+
};
|
|
392
|
+
class BaseError extends Error {
|
|
393
|
+
constructor(shortMessage, args = {}) {
|
|
394
|
+
const details = (() => {
|
|
395
|
+
if (args.cause instanceof BaseError)
|
|
396
|
+
return args.cause.details;
|
|
397
|
+
if (args.cause?.message)
|
|
398
|
+
return args.cause.message;
|
|
399
|
+
return args.details;
|
|
400
|
+
})();
|
|
401
|
+
const docsPath = (() => {
|
|
402
|
+
if (args.cause instanceof BaseError)
|
|
403
|
+
return args.cause.docsPath || args.docsPath;
|
|
404
|
+
return args.docsPath;
|
|
405
|
+
})();
|
|
406
|
+
const docsUrl = errorConfig.getDocsUrl?.({ ...args, docsPath });
|
|
407
|
+
const message = [
|
|
408
|
+
shortMessage || "An error occurred.",
|
|
409
|
+
"",
|
|
410
|
+
...args.metaMessages ? [...args.metaMessages, ""] : [],
|
|
411
|
+
...docsUrl ? [`Docs: ${docsUrl}`] : [],
|
|
412
|
+
...details ? [`Details: ${details}`] : [],
|
|
413
|
+
...errorConfig.version ? [`Version: ${errorConfig.version}`] : []
|
|
414
|
+
].join("\n");
|
|
415
|
+
super(message, args.cause ? { cause: args.cause } : void 0);
|
|
416
|
+
Object.defineProperty(this, "details", {
|
|
417
|
+
enumerable: true,
|
|
418
|
+
configurable: true,
|
|
419
|
+
writable: true,
|
|
420
|
+
value: void 0
|
|
421
|
+
});
|
|
422
|
+
Object.defineProperty(this, "docsPath", {
|
|
423
|
+
enumerable: true,
|
|
424
|
+
configurable: true,
|
|
425
|
+
writable: true,
|
|
426
|
+
value: void 0
|
|
427
|
+
});
|
|
428
|
+
Object.defineProperty(this, "metaMessages", {
|
|
429
|
+
enumerable: true,
|
|
430
|
+
configurable: true,
|
|
431
|
+
writable: true,
|
|
432
|
+
value: void 0
|
|
433
|
+
});
|
|
434
|
+
Object.defineProperty(this, "shortMessage", {
|
|
435
|
+
enumerable: true,
|
|
436
|
+
configurable: true,
|
|
437
|
+
writable: true,
|
|
438
|
+
value: void 0
|
|
439
|
+
});
|
|
440
|
+
Object.defineProperty(this, "version", {
|
|
441
|
+
enumerable: true,
|
|
442
|
+
configurable: true,
|
|
443
|
+
writable: true,
|
|
444
|
+
value: void 0
|
|
445
|
+
});
|
|
446
|
+
Object.defineProperty(this, "name", {
|
|
447
|
+
enumerable: true,
|
|
448
|
+
configurable: true,
|
|
449
|
+
writable: true,
|
|
450
|
+
value: "BaseError"
|
|
451
|
+
});
|
|
452
|
+
this.details = details;
|
|
453
|
+
this.docsPath = docsPath;
|
|
454
|
+
this.metaMessages = args.metaMessages;
|
|
455
|
+
this.name = args.name ?? this.name;
|
|
456
|
+
this.shortMessage = shortMessage;
|
|
457
|
+
this.version = version;
|
|
458
|
+
}
|
|
459
|
+
walk(fn) {
|
|
460
|
+
return walk(this, fn);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function walk(err, fn) {
|
|
464
|
+
if (fn?.(err))
|
|
465
|
+
return err;
|
|
466
|
+
if (err && typeof err === "object" && "cause" in err && err.cause !== void 0)
|
|
467
|
+
return walk(err.cause, fn);
|
|
468
|
+
return fn ? null : err;
|
|
469
|
+
}
|
|
470
|
+
class SizeExceedsPaddingSizeError extends BaseError {
|
|
471
|
+
constructor({ size: size2, targetSize, type }) {
|
|
472
|
+
super(`${type.charAt(0).toUpperCase()}${type.slice(1).toLowerCase()} size (${size2}) exceeds padding size (${targetSize}).`, { name: "SizeExceedsPaddingSizeError" });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function pad(hexOrBytes, { dir, size: size2 = 32 } = {}) {
|
|
476
|
+
if (typeof hexOrBytes === "string")
|
|
477
|
+
return padHex(hexOrBytes, { dir, size: size2 });
|
|
478
|
+
return padBytes(hexOrBytes, { dir, size: size2 });
|
|
479
|
+
}
|
|
480
|
+
function padHex(hex_, { dir, size: size2 = 32 } = {}) {
|
|
481
|
+
if (size2 === null)
|
|
482
|
+
return hex_;
|
|
483
|
+
const hex = hex_.replace("0x", "");
|
|
484
|
+
if (hex.length > size2 * 2)
|
|
485
|
+
throw new SizeExceedsPaddingSizeError({
|
|
486
|
+
size: Math.ceil(hex.length / 2),
|
|
487
|
+
targetSize: size2,
|
|
488
|
+
type: "hex"
|
|
489
|
+
});
|
|
490
|
+
return `0x${hex[dir === "right" ? "padEnd" : "padStart"](size2 * 2, "0")}`;
|
|
491
|
+
}
|
|
492
|
+
function padBytes(bytes, { dir, size: size2 = 32 } = {}) {
|
|
493
|
+
if (size2 === null)
|
|
494
|
+
return bytes;
|
|
495
|
+
if (bytes.length > size2)
|
|
496
|
+
throw new SizeExceedsPaddingSizeError({
|
|
497
|
+
size: bytes.length,
|
|
498
|
+
targetSize: size2,
|
|
499
|
+
type: "bytes"
|
|
500
|
+
});
|
|
501
|
+
const paddedBytes = new Uint8Array(size2);
|
|
502
|
+
for (let i = 0; i < size2; i++) {
|
|
503
|
+
const padEnd = dir === "right";
|
|
504
|
+
paddedBytes[padEnd ? i : size2 - i - 1] = bytes[padEnd ? i : bytes.length - i - 1];
|
|
505
|
+
}
|
|
506
|
+
return paddedBytes;
|
|
507
|
+
}
|
|
508
|
+
class IntegerOutOfRangeError extends BaseError {
|
|
509
|
+
constructor({ max, min, signed, size: size2, value }) {
|
|
510
|
+
super(`Number "${value}" is not in safe ${size2 ? `${size2 * 8}-bit ${signed ? "signed" : "unsigned"} ` : ""}integer range ${max ? `(${min} to ${max})` : `(above ${min})`}`, { name: "IntegerOutOfRangeError" });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
class SizeOverflowError extends BaseError {
|
|
514
|
+
constructor({ givenSize, maxSize }) {
|
|
515
|
+
super(`Size cannot exceed ${maxSize} bytes. Given size: ${givenSize} bytes.`, { name: "SizeOverflowError" });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function size(value) {
|
|
519
|
+
if (isHex(value, { strict: false }))
|
|
520
|
+
return Math.ceil((value.length - 2) / 2);
|
|
521
|
+
return value.length;
|
|
522
|
+
}
|
|
523
|
+
function assertSize(hexOrBytes, { size: size$1 }) {
|
|
524
|
+
if (size(hexOrBytes) > size$1)
|
|
525
|
+
throw new SizeOverflowError({
|
|
526
|
+
givenSize: size(hexOrBytes),
|
|
527
|
+
maxSize: size$1
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function numberToHex(value_, opts = {}) {
|
|
531
|
+
const { signed, size: size2 } = opts;
|
|
532
|
+
const value = BigInt(value_);
|
|
533
|
+
let maxValue;
|
|
534
|
+
if (size2) {
|
|
535
|
+
if (signed)
|
|
536
|
+
maxValue = (1n << BigInt(size2) * 8n - 1n) - 1n;
|
|
537
|
+
else
|
|
538
|
+
maxValue = 2n ** (BigInt(size2) * 8n) - 1n;
|
|
539
|
+
} else if (typeof value_ === "number") {
|
|
540
|
+
maxValue = BigInt(Number.MAX_SAFE_INTEGER);
|
|
541
|
+
}
|
|
542
|
+
const minValue = typeof maxValue === "bigint" && signed ? -maxValue - 1n : 0;
|
|
543
|
+
if (maxValue && value > maxValue || value < minValue) {
|
|
544
|
+
const suffix = typeof value_ === "bigint" ? "n" : "";
|
|
545
|
+
throw new IntegerOutOfRangeError({
|
|
546
|
+
max: maxValue ? `${maxValue}${suffix}` : void 0,
|
|
547
|
+
min: `${minValue}${suffix}`,
|
|
548
|
+
signed,
|
|
549
|
+
size: size2,
|
|
550
|
+
value: `${value_}${suffix}`
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const hex = `0x${(signed && value < 0 ? (1n << BigInt(size2 * 8)) + BigInt(value) : value).toString(16)}`;
|
|
554
|
+
if (size2)
|
|
555
|
+
return pad(hex, { size: size2 });
|
|
556
|
+
return hex;
|
|
557
|
+
}
|
|
558
|
+
const encoder = /* @__PURE__ */ new TextEncoder();
|
|
559
|
+
function toBytes(value, opts = {}) {
|
|
560
|
+
if (typeof value === "number" || typeof value === "bigint")
|
|
561
|
+
return numberToBytes(value, opts);
|
|
562
|
+
if (typeof value === "boolean")
|
|
563
|
+
return boolToBytes(value, opts);
|
|
564
|
+
if (isHex(value))
|
|
565
|
+
return hexToBytes(value, opts);
|
|
566
|
+
return stringToBytes(value, opts);
|
|
567
|
+
}
|
|
568
|
+
function boolToBytes(value, opts = {}) {
|
|
569
|
+
const bytes = new Uint8Array(1);
|
|
570
|
+
bytes[0] = Number(value);
|
|
571
|
+
if (typeof opts.size === "number") {
|
|
572
|
+
assertSize(bytes, { size: opts.size });
|
|
573
|
+
return pad(bytes, { size: opts.size });
|
|
574
|
+
}
|
|
575
|
+
return bytes;
|
|
576
|
+
}
|
|
577
|
+
const charCodeMap = {
|
|
578
|
+
zero: 48,
|
|
579
|
+
nine: 57,
|
|
580
|
+
A: 65,
|
|
581
|
+
F: 70,
|
|
582
|
+
a: 97,
|
|
583
|
+
f: 102
|
|
584
|
+
};
|
|
585
|
+
function charCodeToBase16(char) {
|
|
586
|
+
if (char >= charCodeMap.zero && char <= charCodeMap.nine)
|
|
587
|
+
return char - charCodeMap.zero;
|
|
588
|
+
if (char >= charCodeMap.A && char <= charCodeMap.F)
|
|
589
|
+
return char - (charCodeMap.A - 10);
|
|
590
|
+
if (char >= charCodeMap.a && char <= charCodeMap.f)
|
|
591
|
+
return char - (charCodeMap.a - 10);
|
|
592
|
+
return void 0;
|
|
593
|
+
}
|
|
594
|
+
function hexToBytes(hex_, opts = {}) {
|
|
595
|
+
let hex = hex_;
|
|
596
|
+
if (opts.size) {
|
|
597
|
+
assertSize(hex, { size: opts.size });
|
|
598
|
+
hex = pad(hex, { dir: "right", size: opts.size });
|
|
599
|
+
}
|
|
600
|
+
let hexString = hex.slice(2);
|
|
601
|
+
if (hexString.length % 2)
|
|
602
|
+
hexString = `0${hexString}`;
|
|
603
|
+
const length = hexString.length / 2;
|
|
604
|
+
const bytes = new Uint8Array(length);
|
|
605
|
+
for (let index = 0, j = 0; index < length; index++) {
|
|
606
|
+
const nibbleLeft = charCodeToBase16(hexString.charCodeAt(j++));
|
|
607
|
+
const nibbleRight = charCodeToBase16(hexString.charCodeAt(j++));
|
|
608
|
+
if (nibbleLeft === void 0 || nibbleRight === void 0) {
|
|
609
|
+
throw new BaseError(`Invalid byte sequence ("${hexString[j - 2]}${hexString[j - 1]}" in "${hexString}").`);
|
|
610
|
+
}
|
|
611
|
+
bytes[index] = nibbleLeft * 16 + nibbleRight;
|
|
612
|
+
}
|
|
613
|
+
return bytes;
|
|
614
|
+
}
|
|
615
|
+
function numberToBytes(value, opts) {
|
|
616
|
+
const hex = numberToHex(value, opts);
|
|
617
|
+
return hexToBytes(hex);
|
|
618
|
+
}
|
|
619
|
+
function stringToBytes(value, opts = {}) {
|
|
620
|
+
const bytes = encoder.encode(value);
|
|
621
|
+
if (typeof opts.size === "number") {
|
|
622
|
+
assertSize(bytes, { size: opts.size });
|
|
623
|
+
return pad(bytes, { dir: "right", size: opts.size });
|
|
624
|
+
}
|
|
625
|
+
return bytes;
|
|
626
|
+
}
|
|
627
|
+
function keccak256(value, to_) {
|
|
628
|
+
const bytes = keccak_256(isHex(value, { strict: false }) ? toBytes(value) : value);
|
|
629
|
+
return bytes;
|
|
630
|
+
}
|
|
631
|
+
class LruMap extends Map {
|
|
632
|
+
constructor(size2) {
|
|
633
|
+
super();
|
|
634
|
+
Object.defineProperty(this, "maxSize", {
|
|
635
|
+
enumerable: true,
|
|
636
|
+
configurable: true,
|
|
637
|
+
writable: true,
|
|
638
|
+
value: void 0
|
|
639
|
+
});
|
|
640
|
+
this.maxSize = size2;
|
|
641
|
+
}
|
|
642
|
+
get(key) {
|
|
643
|
+
const value = super.get(key);
|
|
644
|
+
if (super.has(key) && value !== void 0) {
|
|
645
|
+
this.delete(key);
|
|
646
|
+
super.set(key, value);
|
|
647
|
+
}
|
|
648
|
+
return value;
|
|
649
|
+
}
|
|
650
|
+
set(key, value) {
|
|
651
|
+
super.set(key, value);
|
|
652
|
+
if (this.maxSize && this.size > this.maxSize) {
|
|
653
|
+
const firstKey = this.keys().next().value;
|
|
654
|
+
if (firstKey)
|
|
655
|
+
this.delete(firstKey);
|
|
656
|
+
}
|
|
657
|
+
return this;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
class InvalidAddressError extends BaseError {
|
|
661
|
+
constructor({ address }) {
|
|
662
|
+
super(`Address "${address}" is invalid.`, {
|
|
663
|
+
metaMessages: [
|
|
664
|
+
"- Address must be a hex value of 20 bytes (40 hex characters).",
|
|
665
|
+
"- Address must match its checksum counterpart."
|
|
666
|
+
],
|
|
667
|
+
name: "InvalidAddressError"
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const checksumAddressCache = /* @__PURE__ */ new LruMap(8192);
|
|
672
|
+
function checksumAddress(address_, chainId) {
|
|
673
|
+
if (checksumAddressCache.has(`${address_}.${chainId}`))
|
|
674
|
+
return checksumAddressCache.get(`${address_}.${chainId}`);
|
|
675
|
+
const hexAddress = address_.substring(2).toLowerCase();
|
|
676
|
+
const hash = keccak256(stringToBytes(hexAddress));
|
|
677
|
+
const address = hexAddress.split("");
|
|
678
|
+
for (let i = 0; i < 40; i += 2) {
|
|
679
|
+
if (hash[i >> 1] >> 4 >= 8 && address[i]) {
|
|
680
|
+
address[i] = address[i].toUpperCase();
|
|
681
|
+
}
|
|
682
|
+
if ((hash[i >> 1] & 15) >= 8 && address[i + 1]) {
|
|
683
|
+
address[i + 1] = address[i + 1].toUpperCase();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const result = `0x${address.join("")}`;
|
|
687
|
+
checksumAddressCache.set(`${address_}.${chainId}`, result);
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
function getAddress(address, chainId) {
|
|
691
|
+
if (!isAddress(address, { strict: false }))
|
|
692
|
+
throw new InvalidAddressError({ address });
|
|
693
|
+
return checksumAddress(address, chainId);
|
|
694
|
+
}
|
|
695
|
+
const addressRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
696
|
+
const isAddressCache = /* @__PURE__ */ new LruMap(8192);
|
|
697
|
+
function isAddress(address, options) {
|
|
698
|
+
const { strict = true } = options ?? {};
|
|
699
|
+
const cacheKey = `${address}.${strict}`;
|
|
700
|
+
if (isAddressCache.has(cacheKey))
|
|
701
|
+
return isAddressCache.get(cacheKey);
|
|
702
|
+
const result = (() => {
|
|
703
|
+
if (!addressRegex.test(address))
|
|
704
|
+
return false;
|
|
705
|
+
if (address.toLowerCase() === address)
|
|
706
|
+
return true;
|
|
707
|
+
if (strict)
|
|
708
|
+
return checksumAddress(address) === address;
|
|
709
|
+
return true;
|
|
710
|
+
})();
|
|
711
|
+
isAddressCache.set(cacheKey, result);
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
class SiweInvalidMessageFieldError extends BaseError {
|
|
715
|
+
constructor(parameters) {
|
|
716
|
+
const { docsPath, field, metaMessages } = parameters;
|
|
717
|
+
super(`Invalid Sign-In with Ethereum message field "${field}".`, {
|
|
718
|
+
docsPath,
|
|
719
|
+
metaMessages,
|
|
720
|
+
name: "SiweInvalidMessageFieldError"
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function isUri(value) {
|
|
725
|
+
if (/[^a-z0-9:/?#[\]@!$&'()*+,;=.\-_~%]/i.test(value))
|
|
726
|
+
return false;
|
|
727
|
+
if (/%[^0-9a-f]/i.test(value))
|
|
728
|
+
return false;
|
|
729
|
+
if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value))
|
|
730
|
+
return false;
|
|
731
|
+
const splitted = splitUri(value);
|
|
732
|
+
const scheme = splitted[1];
|
|
733
|
+
const authority = splitted[2];
|
|
734
|
+
const path = splitted[3];
|
|
735
|
+
const query = splitted[4];
|
|
736
|
+
const fragment = splitted[5];
|
|
737
|
+
if (!(scheme?.length && path.length >= 0))
|
|
738
|
+
return false;
|
|
739
|
+
if (authority?.length) {
|
|
740
|
+
if (!(path.length === 0 || /^\//.test(path)))
|
|
741
|
+
return false;
|
|
742
|
+
} else {
|
|
743
|
+
if (/^\/\//.test(path))
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
if (!/^[a-z][a-z0-9+\-.]*$/.test(scheme.toLowerCase()))
|
|
747
|
+
return false;
|
|
748
|
+
let out = "";
|
|
749
|
+
out += `${scheme}:`;
|
|
750
|
+
if (authority?.length)
|
|
751
|
+
out += `//${authority}`;
|
|
752
|
+
out += path;
|
|
753
|
+
if (query?.length)
|
|
754
|
+
out += `?${query}`;
|
|
755
|
+
if (fragment?.length)
|
|
756
|
+
out += `#${fragment}`;
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
function splitUri(value) {
|
|
760
|
+
return value.match(/(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/);
|
|
761
|
+
}
|
|
762
|
+
function createSiweMessage(parameters) {
|
|
763
|
+
const { chainId, domain, expirationTime, issuedAt = /* @__PURE__ */ new Date(), nonce, notBefore, requestId, resources, scheme, uri, version: version2 } = parameters;
|
|
764
|
+
{
|
|
765
|
+
if (chainId !== Math.floor(chainId))
|
|
766
|
+
throw new SiweInvalidMessageFieldError({
|
|
767
|
+
field: "chainId",
|
|
768
|
+
metaMessages: [
|
|
769
|
+
"- Chain ID must be a EIP-155 chain ID.",
|
|
770
|
+
"- See https://eips.ethereum.org/EIPS/eip-155",
|
|
771
|
+
"",
|
|
772
|
+
`Provided value: ${chainId}`
|
|
773
|
+
]
|
|
774
|
+
});
|
|
775
|
+
if (!(domainRegex.test(domain) || ipRegex.test(domain) || localhostRegex.test(domain)))
|
|
776
|
+
throw new SiweInvalidMessageFieldError({
|
|
777
|
+
field: "domain",
|
|
778
|
+
metaMessages: [
|
|
779
|
+
"- Domain must be an RFC 3986 authority.",
|
|
780
|
+
"- See https://www.rfc-editor.org/rfc/rfc3986",
|
|
781
|
+
"",
|
|
782
|
+
`Provided value: ${domain}`
|
|
783
|
+
]
|
|
784
|
+
});
|
|
785
|
+
if (!nonceRegex.test(nonce))
|
|
786
|
+
throw new SiweInvalidMessageFieldError({
|
|
787
|
+
field: "nonce",
|
|
788
|
+
metaMessages: [
|
|
789
|
+
"- Nonce must be at least 8 characters.",
|
|
790
|
+
"- Nonce must be alphanumeric.",
|
|
791
|
+
"",
|
|
792
|
+
`Provided value: ${nonce}`
|
|
793
|
+
]
|
|
794
|
+
});
|
|
795
|
+
if (!isUri(uri))
|
|
796
|
+
throw new SiweInvalidMessageFieldError({
|
|
797
|
+
field: "uri",
|
|
798
|
+
metaMessages: [
|
|
799
|
+
"- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.",
|
|
800
|
+
"- See https://www.rfc-editor.org/rfc/rfc3986",
|
|
801
|
+
"",
|
|
802
|
+
`Provided value: ${uri}`
|
|
803
|
+
]
|
|
804
|
+
});
|
|
805
|
+
if (version2 !== "1")
|
|
806
|
+
throw new SiweInvalidMessageFieldError({
|
|
807
|
+
field: "version",
|
|
808
|
+
metaMessages: [
|
|
809
|
+
"- Version must be '1'.",
|
|
810
|
+
"",
|
|
811
|
+
`Provided value: ${version2}`
|
|
812
|
+
]
|
|
813
|
+
});
|
|
814
|
+
if (scheme && !schemeRegex.test(scheme))
|
|
815
|
+
throw new SiweInvalidMessageFieldError({
|
|
816
|
+
field: "scheme",
|
|
817
|
+
metaMessages: [
|
|
818
|
+
"- Scheme must be an RFC 3986 URI scheme.",
|
|
819
|
+
"- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1",
|
|
820
|
+
"",
|
|
821
|
+
`Provided value: ${scheme}`
|
|
822
|
+
]
|
|
823
|
+
});
|
|
824
|
+
const statement2 = parameters.statement;
|
|
825
|
+
if (statement2?.includes("\n"))
|
|
826
|
+
throw new SiweInvalidMessageFieldError({
|
|
827
|
+
field: "statement",
|
|
828
|
+
metaMessages: [
|
|
829
|
+
"- Statement must not include '\\n'.",
|
|
830
|
+
"",
|
|
831
|
+
`Provided value: ${statement2}`
|
|
832
|
+
]
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const address = getAddress(parameters.address);
|
|
836
|
+
const origin = (() => {
|
|
837
|
+
if (scheme)
|
|
838
|
+
return `${scheme}://${domain}`;
|
|
839
|
+
return domain;
|
|
840
|
+
})();
|
|
841
|
+
const statement = (() => {
|
|
842
|
+
if (!parameters.statement)
|
|
843
|
+
return "";
|
|
844
|
+
return `${parameters.statement}
|
|
845
|
+
`;
|
|
846
|
+
})();
|
|
847
|
+
const prefix = `${origin} wants you to sign in with your Ethereum account:
|
|
848
|
+
${address}
|
|
849
|
+
|
|
850
|
+
${statement}`;
|
|
851
|
+
let suffix = `URI: ${uri}
|
|
852
|
+
Version: ${version2}
|
|
853
|
+
Chain ID: ${chainId}
|
|
854
|
+
Nonce: ${nonce}
|
|
855
|
+
Issued At: ${issuedAt.toISOString()}`;
|
|
856
|
+
if (expirationTime)
|
|
857
|
+
suffix += `
|
|
858
|
+
Expiration Time: ${expirationTime.toISOString()}`;
|
|
859
|
+
if (notBefore)
|
|
860
|
+
suffix += `
|
|
861
|
+
Not Before: ${notBefore.toISOString()}`;
|
|
862
|
+
if (requestId)
|
|
863
|
+
suffix += `
|
|
864
|
+
Request ID: ${requestId}`;
|
|
865
|
+
if (resources) {
|
|
866
|
+
let content = "\nResources:";
|
|
867
|
+
for (const resource of resources) {
|
|
868
|
+
if (!isUri(resource))
|
|
869
|
+
throw new SiweInvalidMessageFieldError({
|
|
870
|
+
field: "resources",
|
|
871
|
+
metaMessages: [
|
|
872
|
+
"- Every resource must be a RFC 3986 URI.",
|
|
873
|
+
"- See https://www.rfc-editor.org/rfc/rfc3986",
|
|
874
|
+
"",
|
|
875
|
+
`Provided value: ${resource}`
|
|
876
|
+
]
|
|
877
|
+
});
|
|
878
|
+
content += `
|
|
879
|
+
- ${resource}`;
|
|
880
|
+
}
|
|
881
|
+
suffix += content;
|
|
882
|
+
}
|
|
883
|
+
return `${prefix}
|
|
884
|
+
${suffix}`;
|
|
885
|
+
}
|
|
886
|
+
const domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/;
|
|
887
|
+
const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:[0-9]{1,5})?$/;
|
|
888
|
+
const localhostRegex = /^localhost(:[0-9]{1,5})?$/;
|
|
889
|
+
const nonceRegex = /^[a-zA-Z0-9]{8,}$/;
|
|
890
|
+
const schemeRegex = /^([a-zA-Z][a-zA-Z0-9+-.]*)$/;
|
|
891
|
+
async function signInWithAgent(baseUrl, config) {
|
|
892
|
+
if (!config.private_key) {
|
|
893
|
+
throw new Error("No private key found in config. Run 'create_account' first.");
|
|
894
|
+
}
|
|
895
|
+
if (!config.smart_account_address) {
|
|
896
|
+
throw new Error("No smart account address found in config. Run 'create_account' first.");
|
|
897
|
+
}
|
|
898
|
+
if (!config.chain) {
|
|
899
|
+
throw new Error("No chain found in config. Run 'create_account' first.");
|
|
900
|
+
}
|
|
901
|
+
const chain = resolveChain(config.chain);
|
|
902
|
+
const chainId = chain.id;
|
|
903
|
+
const walletAddress = config.smart_account_address;
|
|
904
|
+
const { smartAccount } = await createSmartAccount(config.private_key, config.chain);
|
|
905
|
+
const url = new URL(baseUrl);
|
|
906
|
+
const domain = url.host;
|
|
907
|
+
console.log("fetching nonce...");
|
|
908
|
+
const nonceResponse = await fetch(`${baseUrl}/siwe/nonce`, {
|
|
909
|
+
method: "POST",
|
|
910
|
+
headers: { "Content-Type": "application/json" },
|
|
911
|
+
body: JSON.stringify({ walletAddress, chainId })
|
|
912
|
+
});
|
|
913
|
+
if (!nonceResponse.ok) {
|
|
914
|
+
const text = await nonceResponse.text();
|
|
915
|
+
throw new Error(`Failed to fetch SIWE nonce (${nonceResponse.status}): ${text}`);
|
|
916
|
+
}
|
|
917
|
+
const { nonce } = await nonceResponse.json();
|
|
918
|
+
console.log("creating siwe message...");
|
|
919
|
+
const message = createSiweMessage({
|
|
920
|
+
address: walletAddress,
|
|
921
|
+
chainId: 1,
|
|
922
|
+
domain,
|
|
923
|
+
nonce,
|
|
924
|
+
uri: url.origin,
|
|
925
|
+
version: "1",
|
|
926
|
+
scheme: url.protocol.replace(":", ""),
|
|
927
|
+
issuedAt: /* @__PURE__ */ new Date()
|
|
928
|
+
});
|
|
929
|
+
if (!smartAccount.signMessage) {
|
|
930
|
+
throw new Error("Smart account does not support signMessage()");
|
|
931
|
+
}
|
|
932
|
+
const signature = await smartAccount.signMessage({ message });
|
|
933
|
+
console.log("signing in with siwe message...");
|
|
934
|
+
const verifyResponse = await fetch(`${baseUrl}/siwe/verify`, {
|
|
935
|
+
method: "POST",
|
|
936
|
+
headers: { "Content-Type": "application/json" },
|
|
937
|
+
body: JSON.stringify({ message, signature, walletAddress, chainId })
|
|
938
|
+
});
|
|
939
|
+
if (!verifyResponse.ok) {
|
|
940
|
+
const text = await verifyResponse.text();
|
|
941
|
+
throw new Error(`SIWE verification failed (${verifyResponse.status}): ${text}`);
|
|
942
|
+
}
|
|
943
|
+
const result = await verifyResponse.json();
|
|
944
|
+
if (!result.success) {
|
|
945
|
+
throw new Error("SIWE verification returned success: false");
|
|
946
|
+
}
|
|
947
|
+
console.log("saving token...");
|
|
948
|
+
config.session_token = result.token;
|
|
949
|
+
await saveConfig(config);
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
const program = new Command();
|
|
953
|
+
program.name("openclaw").description("CoinFello CLI - MetaMask Smart Account interactions").version("1.0.0");
|
|
954
|
+
program.command("create_account").description("Create a MetaMask smart account and save its address to local config").argument("<chain>", "Chain name (e.g. sepolia, mainnet, polygon, arbitrum)").action(async (chain) => {
|
|
955
|
+
try {
|
|
956
|
+
console.log(`Creating smart account on ${chain}...`);
|
|
957
|
+
const privateKey = generatePrivateKey();
|
|
958
|
+
const { address } = await createSmartAccount(privateKey, chain);
|
|
959
|
+
const config = await loadConfig();
|
|
960
|
+
config.private_key = privateKey;
|
|
961
|
+
config.smart_account_address = address;
|
|
962
|
+
config.chain = chain;
|
|
963
|
+
await saveConfig(config);
|
|
964
|
+
console.log("Smart account created successfully.");
|
|
965
|
+
console.log(`Address: ${address}`);
|
|
966
|
+
console.log(`Config saved to: ${CONFIG_PATH}`);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
console.error(`Failed to create account: ${err.message}`);
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
program.command("get_account").description("Display the current smart account address from local config").action(async () => {
|
|
973
|
+
try {
|
|
974
|
+
const config = await loadConfig();
|
|
975
|
+
if (!config.smart_account_address) {
|
|
976
|
+
console.error("Error: No smart account found. Run 'create_account' first.");
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
console.log(config.smart_account_address);
|
|
980
|
+
} catch (err) {
|
|
981
|
+
console.error(`Failed to get account: ${err.message}`);
|
|
982
|
+
process.exit(1);
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
program.command("sign_in").description("Sign in to a server using SIWE with your smart account").option(
|
|
986
|
+
"--base-url <baseUrl>",
|
|
987
|
+
"The server base URL override (e.g. https://api.example.com)",
|
|
988
|
+
"https://app.coinfello.com/api/auth"
|
|
989
|
+
).action(async (opts) => {
|
|
990
|
+
try {
|
|
991
|
+
console.log("Signing in with smart account...");
|
|
992
|
+
const config = await loadConfig();
|
|
993
|
+
const result = await signInWithAgent(opts.baseUrl, config);
|
|
994
|
+
console.log("Sign-in successful.");
|
|
995
|
+
console.log(`User ID: ${result.user.id}`);
|
|
996
|
+
console.log(`Session token saved to config.`);
|
|
997
|
+
} catch (err) {
|
|
998
|
+
console.error(`Failed to sign in: ${err.message}`);
|
|
999
|
+
process.exit(1);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
program.command("set_delegation").description("Store a signed delegation (JSON) in local config").argument("<delegation>", "The signed delegation as a JSON string").action(async (delegationJson) => {
|
|
1003
|
+
try {
|
|
1004
|
+
const delegation = JSON.parse(delegationJson);
|
|
1005
|
+
const config = await loadConfig();
|
|
1006
|
+
config.delegation = delegation;
|
|
1007
|
+
await saveConfig(config);
|
|
1008
|
+
console.log("Delegation saved successfully.");
|
|
1009
|
+
console.log(`Config saved to: ${CONFIG_PATH}`);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
console.error(`Failed to set delegation: ${err.message}`);
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
program.command("send_prompt").description("Send a prompt to CoinFello, creating and signing a subdelegation locally").argument("<prompt>", "The prompt to send").requiredOption(
|
|
1016
|
+
"--token-address <address>",
|
|
1017
|
+
"ERC-20 token contract address for the subdelegation scope"
|
|
1018
|
+
).requiredOption("--amount <amount>", "Maximum token amount (human-readable, e.g. '5')").option("--decimals <decimals>", "Token decimals for parsing max-amount", "18").option("--use-redelegation", "Create a redelegation from a stored parent delegation").action(
|
|
1019
|
+
async (prompt, opts) => {
|
|
1020
|
+
try {
|
|
1021
|
+
const config = await loadConfig();
|
|
1022
|
+
if (!config.private_key) {
|
|
1023
|
+
console.error("Error: No private key found in config. Run 'create_account' first.");
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
if (!config.smart_account_address) {
|
|
1027
|
+
console.error("Error: No smart account found. Run 'create_account' first.");
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
if (!config.chain) {
|
|
1031
|
+
console.error("Error: No chain found in config. Run 'create_account' first.");
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
if (opts.useRedelegation && !config.delegation) {
|
|
1035
|
+
console.error(
|
|
1036
|
+
"Error: --use-redelegation requires a parent delegation. Run 'set_delegation' first."
|
|
1037
|
+
);
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1040
|
+
console.log("Fetching CoinFello delegate address...");
|
|
1041
|
+
const delegateAddress = await getCoinFelloAddress();
|
|
1042
|
+
console.log("Loading smart account...");
|
|
1043
|
+
const smartAccount = await getSmartAccount(config.private_key, config.chain);
|
|
1044
|
+
console.log("Parsing amount...");
|
|
1045
|
+
const maxAmount = parseUnits(opts.amount, Number(opts.decimals));
|
|
1046
|
+
console.log("Creating subdelegation...");
|
|
1047
|
+
const subdelegation = createSubdelegation({
|
|
1048
|
+
smartAccount,
|
|
1049
|
+
delegateAddress,
|
|
1050
|
+
parentDelegation: opts.useRedelegation ? config.delegation : void 0,
|
|
1051
|
+
tokenAddress: opts.tokenAddress,
|
|
1052
|
+
maxAmount
|
|
1053
|
+
});
|
|
1054
|
+
console.log("Signing subdelegation...");
|
|
1055
|
+
const signature = await smartAccount.signDelegation({
|
|
1056
|
+
delegation: subdelegation
|
|
1057
|
+
});
|
|
1058
|
+
const signedSubdelegation = { ...subdelegation, signature };
|
|
1059
|
+
console.log("Sending to conversation endpoint...");
|
|
1060
|
+
const result = await sendConversation({
|
|
1061
|
+
prompt,
|
|
1062
|
+
signedSubdelegation,
|
|
1063
|
+
smartAccountAddress: config.smart_account_address
|
|
1064
|
+
});
|
|
1065
|
+
console.log("Transaction submitted successfully.");
|
|
1066
|
+
console.log(`Transaction ID: ${result.txn_id}`);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
console.error(`Failed to send prompt: ${err.message}`);
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
program.command("get_transaction_status").description("Check the status of a submitted transaction").argument("<txn_id>", "The transaction ID to check").action(async (txnId) => {
|
|
1074
|
+
try {
|
|
1075
|
+
const result = await getTransactionStatus(txnId);
|
|
1076
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
console.error(`Failed to get transaction status: ${err.message}`);
|
|
1079
|
+
process.exit(1);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coinfello/agent-cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"openclaw": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "UNLICENSED",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@metamask/smart-accounts-kit": "0.4.0-beta.1",
|
|
21
|
+
"commander": "^14.0.3",
|
|
22
|
+
"viem": "^2.45.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^25.2.1",
|
|
26
|
+
"eslint": "^10.0.0",
|
|
27
|
+
"eslint-config-prettier": "^10.1.8",
|
|
28
|
+
"prettier": "^3.8.1",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
|
+
"typescript-eslint": "^8.56.0",
|
|
31
|
+
"vite": "^7.3.1"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "vite build",
|
|
35
|
+
"codecheck": "tsc --noEmit",
|
|
36
|
+
"lint": "eslint src",
|
|
37
|
+
"prettier-fix": "prettier --write src coinfello",
|
|
38
|
+
"prettier": "prettier --check src coinfello",
|
|
39
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
40
|
+
}
|
|
41
|
+
}
|