@arp-protocol/cli 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -2
- package/dist/chunks/backfill-j4hxt1w1.js +262 -0
- package/dist/chunks/ccip-qmytgn8e.js +198 -0
- package/dist/chunks/cli-0hqxver6.js +2042 -0
- package/dist/chunks/cli-0z6f3603.js +7527 -0
- package/dist/chunks/cli-3kbbwfbp.js +359 -0
- package/dist/chunks/cli-74hv4etk.js +14371 -0
- package/dist/chunks/cli-c8568g4c.js +4043 -0
- package/dist/chunks/cli-d3dy7cxe.js +2110 -0
- package/dist/chunks/cli-g5zvr10v.js +19 -0
- package/dist/chunks/cli-tyey74p3.js +303 -0
- package/dist/chunks/cli-v446mw5v.js +1033 -0
- package/dist/chunks/cli-zpft011j.js +43 -0
- package/dist/chunks/commands-dnk10vze.js +805 -0
- package/dist/chunks/env-s4585w6j.js +56 -0
- package/dist/chunks/index-3a3ssef1.js +53 -0
- package/dist/chunks/line-zy1tc2np.js +45 -0
- package/dist/chunks/node-0ydee9hy.js +593 -0
- package/dist/chunks/node-init-4v2m2vj0.js +255 -0
- package/dist/chunks/secp256k1-j2w0pmjr.js +17 -0
- package/dist/chunks/server-jmyjttvv.js +3170 -0
- package/dist/chunks/share-g8v5qe6r.js +101 -0
- package/dist/cli.js +229 -0
- package/dist/hook.js +1027 -0
- package/package.json +47 -4
package/README.md
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Wieedze/ARP/main/docs/design/brand/arp-lockup-dark.svg" />
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Wieedze/ARP/main/docs/design/brand/arp-lockup-light.svg" height="40" alt="ARP" />
|
|
4
|
+
</picture>
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @arp-protocol/cli
|
|
7
|
+
|
|
8
|
+
ARP's agent-side programs in one binary. Node 22 or later.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
arp mcp the MCP server, over stdio (how an MCP client launches it). --http for 127.0.0.1
|
|
12
|
+
arp hook the runtime hook: one payload on stdin, one line in .arp/session.jsonl
|
|
13
|
+
arp node the hire endpoint. Opt-in: 127.0.0.1, reached through a tunnel you rent
|
|
14
|
+
arp init --node bind the node to your ERC-8004 agent: the token's owner signs once
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
All three run on your machine, and none of them signs a transaction.
|
|
18
|
+
|
|
19
|
+
- **What each one reads, writes and connects to** is in
|
|
20
|
+
[`docs/22_RUNNING_THE_NODE.md`](https://github.com/Wieedze/ARP/blob/main/docs/22_RUNNING_THE_NODE.md).
|
|
21
|
+
- **What the hook records**, and what it never records, is
|
|
22
|
+
[ADR `0090`](https://github.com/Wieedze/ARP/blob/main/.claude/choices/0090-what-the-hook-records.md).
|
|
23
|
+
|
|
24
|
+
## The Claude Code plugin
|
|
25
|
+
|
|
26
|
+
The plugin carries this program as one file, `bin/arp.js`, so it needs Node 22 and nothing from npm:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
claude plugin marketplace add <a checkout of this repo, or owner/repo>
|
|
30
|
+
claude plugin install arp@arp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
With no `arp` on `PATH`, its commands stand in for `arp share` and `arp line`. A person types them, and the model cannot invoke them:
|
|
34
|
+
|
|
35
|
+
A session that allows `Bash` can still run the file itself: that rests on your permissions.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
/arp:share what would be public, and what stays here. Sends nothing
|
|
39
|
+
/arp:share-on turn sharing on (off by default). Sends nothing
|
|
40
|
+
/arp:share-off turn it off
|
|
41
|
+
/arp:share-send write the shared failures as public lines on testnet. Needs a sponsoring service
|
|
42
|
+
/arp:line write one public line on testnet: --tool <url> --outcome worked|failed
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The search in `arp_pulse` still runs `npx skills`: `npx` comes with Node, and that package comes from the npm registry. Installed from a local path, the plugin is read from that path, so keep the folder where it is.
|
|
46
|
+
|
|
47
|
+
## Install into your runtime
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
arp init --dry-run see what would change
|
|
51
|
+
arp init do it: asks first, backs up every file it edits
|
|
52
|
+
arp doctor check it: entries present, hook in place, the server answers
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`init` finds Claude Code, Grok Build, Cursor, Gemini CLI, Codex and Copilot (VS Code, CLI) on this machine. It adds the MCP
|
|
56
|
+
server to each one, and the hook to Claude Code (Grok Build reads Claude Code's files). For any
|
|
57
|
+
other MCP client, `arp init --print` prints the entry to paste. `--yes` and `--no-input` are for
|
|
58
|
+
scripts, `--runtime claude,cursor` narrows the run, and `--project` writes the project's files.
|
|
59
|
+
|
|
60
|
+
`init` does not replace an MCP server named `arp` that it did not write. It leaves it, says so, does the rest and exits 6. Rename yours, or pass `--force` to replace it; a file is backed up first. Through the `claude` command there is no backup, so the old entry is printed before it is removed.
|
|
61
|
+
|
|
62
|
+
## Uninstall
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
arp uninstall removes what init added, and nothing else
|
|
66
|
+
npm uninstall -g @arp-protocol/cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The `.arp/` directory in each project is the hook's log. Delete it if you want it gone.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SKILL_DOMAINS,
|
|
4
|
+
classifySkill,
|
|
5
|
+
inferDomain,
|
|
6
|
+
readRequires
|
|
7
|
+
} from "./cli-74hv4etk.js";
|
|
8
|
+
import"./cli-c8568g4c.js";
|
|
9
|
+
import"./cli-v446mw5v.js";
|
|
10
|
+
import"./cli-0hqxver6.js";
|
|
11
|
+
import"./cli-d3dy7cxe.js";
|
|
12
|
+
import"./cli-3kbbwfbp.js";
|
|
13
|
+
import"./cli-g5zvr10v.js";
|
|
14
|
+
|
|
15
|
+
// ../hook/src/backfill.ts
|
|
16
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
17
|
+
import { homedir as homedir2 } from "node:os";
|
|
18
|
+
import { basename, join as join2 } from "node:path";
|
|
19
|
+
|
|
20
|
+
// ../hook/src/classify.ts
|
|
21
|
+
var CANCELLED = /\b(interrupted|cancel(l)?ed|aborted) by (the )?user\b|\buser (rejected|denied|declined)\b|\bpermission denied by user\b|\buser doesn't want to (proceed|take this action)\b/i;
|
|
22
|
+
function isCancelled(text) {
|
|
23
|
+
return CANCELLED.test(text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ../hook/src/append.ts
|
|
27
|
+
import { closeSync, constants, lstatSync, openSync, writeSync } from "node:fs";
|
|
28
|
+
import { dirname } from "node:path";
|
|
29
|
+
function appendOwn(path, text) {
|
|
30
|
+
if (lstatSync(dirname(path)).isSymbolicLink())
|
|
31
|
+
throw new Error(`${dirname(path)} is a symbolic link`);
|
|
32
|
+
const flags = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0);
|
|
33
|
+
const fd = openSync(path, flags, 384);
|
|
34
|
+
try {
|
|
35
|
+
writeSync(fd, text);
|
|
36
|
+
} finally {
|
|
37
|
+
closeSync(fd);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../hook/src/skill-failure.ts
|
|
42
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
43
|
+
import { homedir } from "node:os";
|
|
44
|
+
import { dirname as dirname2, join } from "node:path";
|
|
45
|
+
var MISSING_KEY = /\b(api[ _-]?key|access[ _-]?token|credentials?)\b.{0,40}\b(missing|not set|not found|required|invalid|expired)\b|(?<!\bnot )\b(missing|no|invalid|expired)\b.{0,20}\b(api[ _-]?key|access[ _-]?token|credentials?)\b|\b(HTTP|status)(\/[\d.]+| code)?[ :=]*(401|403)\b|\b(401|403) (unauthori[sz]ed|forbidden)\b|\bunauthori[sz]ed\b|not authenticated|not logged in/i;
|
|
46
|
+
var MISSING_KEY_VARIABLE = /\b[A-Z0-9_]*(API_KEY|TOKEN|SECRET)\b.{0,30}\b(not set|missing|required|unset)\b/;
|
|
47
|
+
var MISSING_PROGRAM = /command not found|executable file not found|\bnot installed\b|No module named|spawn \S+ ENOENT|Cannot find module/i;
|
|
48
|
+
var MISSING_FILE = /No such file or directory|\bENOENT\b|file not found|\b(file|path|directory|folder)\b(?!\.).{0,80}\bdoes not exist\b/i;
|
|
49
|
+
function failureReason(text) {
|
|
50
|
+
if (MISSING_PROGRAM.test(text))
|
|
51
|
+
return "missing-program";
|
|
52
|
+
if (MISSING_KEY.test(text) || MISSING_KEY_VARIABLE.test(text))
|
|
53
|
+
return "missing-key";
|
|
54
|
+
if (MISSING_FILE.test(text))
|
|
55
|
+
return "missing-file";
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
var RUNNERS = new Set(["bash", "bun", "bunx", "node", "npx", "python", "python3", "sh", "uv", "uvx", "zsh"]);
|
|
59
|
+
function programsOf(command) {
|
|
60
|
+
const found = [];
|
|
61
|
+
for (const part of command.split(/&&|\|\||[;|\n]/)) {
|
|
62
|
+
const words = part.trim().split(/\s+/).filter((word) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word));
|
|
63
|
+
const first = words[0];
|
|
64
|
+
if (first === undefined || first === "")
|
|
65
|
+
continue;
|
|
66
|
+
const program = first.split("/").pop() ?? first;
|
|
67
|
+
if (!RUNNERS.has(program)) {
|
|
68
|
+
found.push(program);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") && word !== "run");
|
|
72
|
+
if (target !== undefined)
|
|
73
|
+
found.push(target.split("/").pop() ?? target);
|
|
74
|
+
}
|
|
75
|
+
return found;
|
|
76
|
+
}
|
|
77
|
+
var escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
|
+
function askedBySkill(command, markdown, directory) {
|
|
79
|
+
if (new RegExp(`${escaped(directory)}(?=[/\\s'"]|$)`).test(command))
|
|
80
|
+
return true;
|
|
81
|
+
const { cli, runs } = readRequires(markdown);
|
|
82
|
+
const named = new Set([...cli, ...runs]);
|
|
83
|
+
return programsOf(command).some((program) => named.has(program) || program.includes(".") && new RegExp(`(?:^|[\\s\`'"/(])${escaped(program)}(?=[\\s\`'")]|$)`, "m").test(markdown));
|
|
84
|
+
}
|
|
85
|
+
var DOMAINS = [...new Set(SKILL_DOMAINS.map((rule) => rule.domain))];
|
|
86
|
+
function contextOf(words, skill, description) {
|
|
87
|
+
return inferDomain(words, DOMAINS) ?? classifySkill(skill.split(":").pop() ?? skill, description ?? "");
|
|
88
|
+
}
|
|
89
|
+
function skillDirectory(name, cwd) {
|
|
90
|
+
const roots = [join(cwd, ".claude", "skills"), join(homedir(), ".claude", "skills")];
|
|
91
|
+
const given = process.env["ARP_SKILLS_DIR"];
|
|
92
|
+
if (given !== undefined && given !== "")
|
|
93
|
+
roots.unshift(given);
|
|
94
|
+
return roots.map((root) => join(root, name)).find((dir) => existsSync(join(dir, "SKILL.md"))) ?? null;
|
|
95
|
+
}
|
|
96
|
+
function arpHome() {
|
|
97
|
+
const given = process.env["ARP_HOME"]?.trim();
|
|
98
|
+
return given !== undefined && given !== "" ? given : join(homedir(), ".arp");
|
|
99
|
+
}
|
|
100
|
+
function appendFailure(line) {
|
|
101
|
+
const path = join(arpHome(), "failures.jsonl");
|
|
102
|
+
const seen = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
103
|
+
const key = `"session":${JSON.stringify(line.session)},"skill":${JSON.stringify(line.skill)},`;
|
|
104
|
+
if (seen.split(`
|
|
105
|
+
`).some((row) => row.includes(key) && row.includes(`"reason":"${line.reason}"`)))
|
|
106
|
+
return false;
|
|
107
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
108
|
+
appendOwn(path, `${JSON.stringify(line)}
|
|
109
|
+
`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ../hook/src/backfill.ts
|
|
114
|
+
var BODY = /^Base directory for this skill: ([^\n]+)/;
|
|
115
|
+
function blocksOf(content) {
|
|
116
|
+
return Array.isArray(content) ? content : [];
|
|
117
|
+
}
|
|
118
|
+
function textOf(content) {
|
|
119
|
+
if (typeof content === "string")
|
|
120
|
+
return content;
|
|
121
|
+
return blocksOf(content).map((block) => typeof block?.text === "string" ? block.text : "").join(`
|
|
122
|
+
`);
|
|
123
|
+
}
|
|
124
|
+
var lastPart = (name) => name.split(":").pop() ?? name;
|
|
125
|
+
function fromDisk(name, cwd) {
|
|
126
|
+
try {
|
|
127
|
+
const directory = skillDirectory(name, cwd);
|
|
128
|
+
return directory === null ? null : { name, directory, markdown: readFileSync2(join2(directory, "SKILL.md"), "utf8") };
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function readTranscript(text, fallback) {
|
|
134
|
+
const active = [];
|
|
135
|
+
const commands = new Map;
|
|
136
|
+
const pending = new Map;
|
|
137
|
+
const failures = [];
|
|
138
|
+
let uses = 0;
|
|
139
|
+
let words = "";
|
|
140
|
+
for (const line of text.split(`
|
|
141
|
+
`)) {
|
|
142
|
+
if (!/tool_use|tool_result|Base directory for this skill|"user"/.test(line))
|
|
143
|
+
continue;
|
|
144
|
+
let entry;
|
|
145
|
+
try {
|
|
146
|
+
entry = JSON.parse(line);
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (entry["isSidechain"] === true)
|
|
151
|
+
continue;
|
|
152
|
+
const message = entry["message"];
|
|
153
|
+
const content = message?.content;
|
|
154
|
+
const session = typeof entry["sessionId"] === "string" ? entry["sessionId"] : fallback;
|
|
155
|
+
const cwd = typeof entry["cwd"] === "string" ? entry["cwd"] : "";
|
|
156
|
+
const at = typeof entry["timestamp"] === "string" ? entry["timestamp"] : "";
|
|
157
|
+
if (entry["type"] === "assistant") {
|
|
158
|
+
for (const block of blocksOf(content)) {
|
|
159
|
+
if (block?.type !== "tool_use" || typeof block.id !== "string")
|
|
160
|
+
continue;
|
|
161
|
+
const input = block.input ?? {};
|
|
162
|
+
if (block.name === "Skill" && typeof input["skill"] === "string") {
|
|
163
|
+
uses += 1;
|
|
164
|
+
pending.set(block.id, input["skill"]);
|
|
165
|
+
const known = fromDisk(input["skill"], cwd);
|
|
166
|
+
if (known !== null)
|
|
167
|
+
active.unshift(known);
|
|
168
|
+
} else if (block.name === "Bash" && typeof input["command"] === "string") {
|
|
169
|
+
commands.set(block.id, input["command"]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (entry["type"] !== "user")
|
|
175
|
+
continue;
|
|
176
|
+
const typed = textOf(content);
|
|
177
|
+
if (entry["isMeta"] !== true && typed.trim() !== "" && !typed.trimStart().startsWith("<"))
|
|
178
|
+
words = typed;
|
|
179
|
+
const directory = BODY.exec(textOf(content))?.[1]?.trim();
|
|
180
|
+
if (entry["isMeta"] === true && directory !== undefined) {
|
|
181
|
+
const folder = basename(directory);
|
|
182
|
+
const call = [...pending].find(([, name2]) => lastPart(name2) === folder);
|
|
183
|
+
if (call === undefined)
|
|
184
|
+
uses += 1;
|
|
185
|
+
else
|
|
186
|
+
pending.delete(call[0]);
|
|
187
|
+
const name = call?.[1] ?? folder;
|
|
188
|
+
const known = active.findIndex((skill) => skill.name === name);
|
|
189
|
+
if (known !== -1)
|
|
190
|
+
active.splice(known, 1);
|
|
191
|
+
active.unshift({ name, directory, markdown: textOf(content) });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
for (const block of blocksOf(content)) {
|
|
195
|
+
if (block?.type !== "tool_result" || typeof block.tool_use_id !== "string")
|
|
196
|
+
continue;
|
|
197
|
+
if (block.is_error === true)
|
|
198
|
+
pending.delete(block.tool_use_id);
|
|
199
|
+
if (block.is_error !== true)
|
|
200
|
+
continue;
|
|
201
|
+
const command = commands.get(block.tool_use_id);
|
|
202
|
+
if (command === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
const output = textOf(block.content).slice(0, 4000);
|
|
205
|
+
if (isCancelled(output))
|
|
206
|
+
continue;
|
|
207
|
+
const reason = failureReason(output);
|
|
208
|
+
if (reason === null)
|
|
209
|
+
continue;
|
|
210
|
+
const skill = active.find((candidate) => askedBySkill(command, candidate.markdown, candidate.directory));
|
|
211
|
+
if (skill === undefined)
|
|
212
|
+
continue;
|
|
213
|
+
const domain = contextOf(words, skill.name, readRequires(skill.markdown).description);
|
|
214
|
+
failures.push({ at, session, skill: skill.name, source: null, ...domain === null ? {} : { domain }, reason });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { uses, failures };
|
|
218
|
+
}
|
|
219
|
+
function transcripts(root) {
|
|
220
|
+
const found = [];
|
|
221
|
+
let names;
|
|
222
|
+
try {
|
|
223
|
+
names = readdirSync(root);
|
|
224
|
+
} catch {
|
|
225
|
+
return found;
|
|
226
|
+
}
|
|
227
|
+
for (const name of names) {
|
|
228
|
+
const path = join2(root, name);
|
|
229
|
+
try {
|
|
230
|
+
if (statSync(path).isDirectory())
|
|
231
|
+
found.push(...transcripts(path));
|
|
232
|
+
else if (name.endsWith(".jsonl"))
|
|
233
|
+
found.push(path);
|
|
234
|
+
} catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return found;
|
|
239
|
+
}
|
|
240
|
+
function backfill(root = join2(homedir2(), ".claude", "projects")) {
|
|
241
|
+
const files = transcripts(root);
|
|
242
|
+
let uses = 0;
|
|
243
|
+
let added = 0;
|
|
244
|
+
for (const file of files) {
|
|
245
|
+
let text;
|
|
246
|
+
try {
|
|
247
|
+
text = readFileSync2(file, "utf8");
|
|
248
|
+
} catch {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const read = readTranscript(text, basename(file, ".jsonl"));
|
|
252
|
+
uses += read.uses;
|
|
253
|
+
for (const failure of read.failures)
|
|
254
|
+
if (appendFailure(failure))
|
|
255
|
+
added += 1;
|
|
256
|
+
}
|
|
257
|
+
return { transcripts: files.length, uses, added, file: join2(arpHome(), "failures.jsonl") };
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
readTranscript,
|
|
261
|
+
backfill
|
|
262
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HttpRequestError,
|
|
4
|
+
call,
|
|
5
|
+
decodeErrorResult,
|
|
6
|
+
getAbortError,
|
|
7
|
+
getUrl,
|
|
8
|
+
isAbortError,
|
|
9
|
+
isAddressEqual,
|
|
10
|
+
localBatchGatewayRequest,
|
|
11
|
+
localBatchGatewayUrl
|
|
12
|
+
} from "./cli-c8568g4c.js";
|
|
13
|
+
import {
|
|
14
|
+
BaseError,
|
|
15
|
+
concat,
|
|
16
|
+
encodeAbiParameters,
|
|
17
|
+
isHex,
|
|
18
|
+
stringify
|
|
19
|
+
} from "./cli-0hqxver6.js";
|
|
20
|
+
import"./cli-3kbbwfbp.js";
|
|
21
|
+
import"./cli-g5zvr10v.js";
|
|
22
|
+
|
|
23
|
+
// ../../node_modules/.bun/viem@2.52.0+1cd18485b0404aca/node_modules/viem/_esm/errors/ccip.js
|
|
24
|
+
class OffchainLookupError extends BaseError {
|
|
25
|
+
constructor({ callbackSelector, cause, data, extraData, sender, urls }) {
|
|
26
|
+
super(cause.shortMessage || "An error occurred while fetching for an offchain result.", {
|
|
27
|
+
cause,
|
|
28
|
+
metaMessages: [
|
|
29
|
+
...cause.metaMessages || [],
|
|
30
|
+
cause.metaMessages?.length ? "" : [],
|
|
31
|
+
"Offchain Gateway Call:",
|
|
32
|
+
urls && [
|
|
33
|
+
" Gateway URL(s):",
|
|
34
|
+
...urls.map((url) => ` ${getUrl(url)}`)
|
|
35
|
+
],
|
|
36
|
+
` Sender: ${sender}`,
|
|
37
|
+
` Data: ${data}`,
|
|
38
|
+
` Callback selector: ${callbackSelector}`,
|
|
39
|
+
` Extra data: ${extraData}`
|
|
40
|
+
].flat(),
|
|
41
|
+
name: "OffchainLookupError"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class OffchainLookupResponseMalformedError extends BaseError {
|
|
47
|
+
constructor({ result, url }) {
|
|
48
|
+
super("Offchain gateway response is malformed. Response data must be a hex value.", {
|
|
49
|
+
metaMessages: [
|
|
50
|
+
`Gateway URL: ${getUrl(url)}`,
|
|
51
|
+
`Response: ${stringify(result)}`
|
|
52
|
+
],
|
|
53
|
+
name: "OffchainLookupResponseMalformedError"
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class OffchainLookupSenderMismatchError extends BaseError {
|
|
59
|
+
constructor({ sender, to }) {
|
|
60
|
+
super("Reverted sender address does not match target contract address (`to`).", {
|
|
61
|
+
metaMessages: [
|
|
62
|
+
`Contract address: ${to}`,
|
|
63
|
+
`OffchainLookup sender address: ${sender}`
|
|
64
|
+
],
|
|
65
|
+
name: "OffchainLookupSenderMismatchError"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../../node_modules/.bun/viem@2.52.0+1cd18485b0404aca/node_modules/viem/_esm/utils/ccip.js
|
|
71
|
+
var offchainLookupSignature = "0x556f1830";
|
|
72
|
+
var offchainLookupAbiItem = {
|
|
73
|
+
name: "OffchainLookup",
|
|
74
|
+
type: "error",
|
|
75
|
+
inputs: [
|
|
76
|
+
{
|
|
77
|
+
name: "sender",
|
|
78
|
+
type: "address"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "urls",
|
|
82
|
+
type: "string[]"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "callData",
|
|
86
|
+
type: "bytes"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "callbackFunction",
|
|
90
|
+
type: "bytes4"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "extraData",
|
|
94
|
+
type: "bytes"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
async function offchainLookup(client, { blockNumber, blockTag, data, requestOptions, to }) {
|
|
99
|
+
const { args } = decodeErrorResult({
|
|
100
|
+
data,
|
|
101
|
+
abi: [offchainLookupAbiItem]
|
|
102
|
+
});
|
|
103
|
+
const [sender, urls, callData, callbackSelector, extraData] = args;
|
|
104
|
+
const { ccipRead } = client;
|
|
105
|
+
const ccipRequest_ = ccipRead && typeof ccipRead?.request === "function" ? ccipRead.request : ccipRequest;
|
|
106
|
+
try {
|
|
107
|
+
if (!isAddressEqual(to, sender))
|
|
108
|
+
throw new OffchainLookupSenderMismatchError({ sender, to });
|
|
109
|
+
const result = urls.includes(localBatchGatewayUrl) ? await localBatchGatewayRequest({
|
|
110
|
+
data: callData,
|
|
111
|
+
ccipRequest: (parameters) => ccipRequest_({ ...parameters, requestOptions })
|
|
112
|
+
}) : await ccipRequest_({ data: callData, requestOptions, sender, urls });
|
|
113
|
+
const { data: data_ } = await call(client, {
|
|
114
|
+
blockNumber,
|
|
115
|
+
blockTag,
|
|
116
|
+
data: concat([
|
|
117
|
+
callbackSelector,
|
|
118
|
+
encodeAbiParameters([{ type: "bytes" }, { type: "bytes" }], [result, extraData])
|
|
119
|
+
]),
|
|
120
|
+
requestOptions,
|
|
121
|
+
to
|
|
122
|
+
});
|
|
123
|
+
return data_;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (requestOptions?.signal?.aborted)
|
|
126
|
+
throw getAbortError(requestOptions.signal);
|
|
127
|
+
if (isAbortError(err))
|
|
128
|
+
throw err;
|
|
129
|
+
throw new OffchainLookupError({
|
|
130
|
+
callbackSelector,
|
|
131
|
+
cause: err,
|
|
132
|
+
data,
|
|
133
|
+
extraData,
|
|
134
|
+
sender,
|
|
135
|
+
urls
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function ccipRequest({ data, requestOptions, sender, urls }) {
|
|
140
|
+
let error = new Error("An unknown error occurred.");
|
|
141
|
+
for (let i = 0;i < urls.length; i++) {
|
|
142
|
+
if (requestOptions?.signal?.aborted)
|
|
143
|
+
throw getAbortError(requestOptions.signal);
|
|
144
|
+
const url = urls[i];
|
|
145
|
+
const method = url.includes("{data}") ? "GET" : "POST";
|
|
146
|
+
const body = method === "POST" ? { data, sender } : undefined;
|
|
147
|
+
const headers = method === "POST" ? { "Content-Type": "application/json" } : {};
|
|
148
|
+
try {
|
|
149
|
+
const response = await fetch(url.replace("{sender}", sender.toLowerCase()).replace("{data}", data), {
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
headers,
|
|
152
|
+
method,
|
|
153
|
+
...requestOptions?.signal ? { signal: requestOptions.signal } : {}
|
|
154
|
+
});
|
|
155
|
+
let result;
|
|
156
|
+
if (response.headers.get("Content-Type")?.startsWith("application/json")) {
|
|
157
|
+
result = (await response.json()).data;
|
|
158
|
+
} else {
|
|
159
|
+
result = await response.text();
|
|
160
|
+
}
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
error = new HttpRequestError({
|
|
163
|
+
body,
|
|
164
|
+
details: result?.error ? stringify(result.error) : response.statusText,
|
|
165
|
+
headers: response.headers,
|
|
166
|
+
status: response.status,
|
|
167
|
+
url
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (!isHex(result)) {
|
|
172
|
+
error = new OffchainLookupResponseMalformedError({
|
|
173
|
+
result,
|
|
174
|
+
url
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (requestOptions?.signal?.aborted)
|
|
181
|
+
throw getAbortError(requestOptions.signal);
|
|
182
|
+
if (isAbortError(err))
|
|
183
|
+
throw err;
|
|
184
|
+
error = new HttpRequestError({
|
|
185
|
+
body,
|
|
186
|
+
details: err.message,
|
|
187
|
+
url
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
offchainLookupSignature,
|
|
195
|
+
offchainLookupAbiItem,
|
|
196
|
+
offchainLookup,
|
|
197
|
+
ccipRequest
|
|
198
|
+
};
|