@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.
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveNodeKey
4
+ } from "./cli-zpft011j.js";
5
+ import"./cli-tyey74p3.js";
6
+ import {
7
+ asEndpointBinding,
8
+ endpointBindingDocument,
9
+ normaliseEndpoint,
10
+ parseAgentRegistry,
11
+ validateHandshakeRequest
12
+ } from "./cli-74hv4etk.js";
13
+ import"./cli-c8568g4c.js";
14
+ import"./cli-v446mw5v.js";
15
+ import {
16
+ isHex
17
+ } from "./cli-0hqxver6.js";
18
+ import"./cli-d3dy7cxe.js";
19
+ import"./cli-3kbbwfbp.js";
20
+ import"./cli-g5zvr10v.js";
21
+
22
+ // src/node-init.ts
23
+ import { mkdirSync, writeFileSync } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
+
26
+ // ../node/src/register.ts
27
+ async function post(fetchLike, url, body) {
28
+ const response = await fetchLike(url, {
29
+ method: "POST",
30
+ headers: { "content-type": "application/json" },
31
+ body: JSON.stringify(body)
32
+ });
33
+ let parsed = null;
34
+ try {
35
+ parsed = await response.json();
36
+ } catch {
37
+ parsed = null;
38
+ }
39
+ return { status: response.status, ok: response.ok, body: parsed };
40
+ }
41
+ function refusal(status, body) {
42
+ if (typeof body === "object" && body !== null) {
43
+ const { error, reason } = body;
44
+ if (typeof error === "string")
45
+ return typeof reason === "string" ? `${error}: ${reason}` : error;
46
+ }
47
+ return `HTTP ${status}`;
48
+ }
49
+ function isChallenge(value) {
50
+ if (typeof value !== "object" || value === null)
51
+ return false;
52
+ const record = value;
53
+ return typeof record["nonce"] === "string" && typeof record["message"] === "string" && typeof record["owner"] === "string" && typeof record["nodeAddress"] === "string";
54
+ }
55
+ async function register(options) {
56
+ const fetchLike = options.fetch ?? fetch;
57
+ const base = options.directoryUrl.replace(/\/$/, "");
58
+ const request = {
59
+ ...options.agent,
60
+ nodePublicKey: options.node.publicKey,
61
+ endpoint: options.endpoint
62
+ };
63
+ const valid = validateHandshakeRequest(request);
64
+ if (!valid.ok) {
65
+ return {
66
+ ok: false,
67
+ step: "request",
68
+ reason: valid.error.code === "malformed" ? valid.error.reason : valid.error.code
69
+ };
70
+ }
71
+ let answer;
72
+ try {
73
+ answer = await post(fetchLike, `${base}/challenge`, request);
74
+ } catch (error) {
75
+ return {
76
+ ok: false,
77
+ step: "challenge",
78
+ reason: error instanceof Error ? error.message : String(error)
79
+ };
80
+ }
81
+ if (!answer.ok)
82
+ return { ok: false, step: "challenge", reason: refusal(answer.status, answer.body) };
83
+ if (!isChallenge(answer.body))
84
+ return { ok: false, step: "challenge", reason: "not a challenge" };
85
+ const challenge = answer.body;
86
+ if (challenge.nodeAddress.toLowerCase() !== options.node.address.toLowerCase()) {
87
+ return { ok: false, step: "challenge", reason: "the challenge names another node key" };
88
+ }
89
+ let ownerSignature;
90
+ try {
91
+ ownerSignature = await options.signAsOwner(challenge);
92
+ } catch (error) {
93
+ return {
94
+ ok: false,
95
+ step: "owner",
96
+ reason: error instanceof Error ? error.message : String(error)
97
+ };
98
+ }
99
+ if (!isHex(ownerSignature))
100
+ return { ok: false, step: "owner", reason: "the signature is not hex" };
101
+ const nodeSignature = await options.node.signMessage({ message: challenge.message });
102
+ let done;
103
+ try {
104
+ done = await post(fetchLike, `${base}/complete`, {
105
+ nonce: challenge.nonce,
106
+ ownerSignature,
107
+ nodeSignature
108
+ });
109
+ } catch (error) {
110
+ return {
111
+ ok: false,
112
+ step: "complete",
113
+ reason: error instanceof Error ? error.message : String(error)
114
+ };
115
+ }
116
+ if (!done.ok)
117
+ return { ok: false, step: "complete", reason: refusal(done.status, done.body) };
118
+ const binding = asEndpointBinding(done.body);
119
+ if (binding === null || binding.message !== challenge.message) {
120
+ return { ok: false, step: "complete", reason: "the directory did not return this binding" };
121
+ }
122
+ return { ok: true, binding, challenge };
123
+ }
124
+
125
+ // src/node-init.ts
126
+ var NODE_INIT_EXIT = { ok: 0, usage: 2, refused: 3, failed: 6 };
127
+ var VALUE_FLAGS = {
128
+ "--agent-registry": "agentRegistry",
129
+ "--agent-id": "agentId",
130
+ "--endpoint": "endpoint",
131
+ "--directory": "directory"
132
+ };
133
+ function parseNodeInitFlags(args) {
134
+ const flags = {
135
+ agentRegistry: null,
136
+ agentId: null,
137
+ endpoint: null,
138
+ directory: null,
139
+ noInput: false
140
+ };
141
+ for (let i = 0;i < args.length; i += 1) {
142
+ const arg = args[i] ?? "";
143
+ if (arg === "--node")
144
+ continue;
145
+ if (arg === "--no-input") {
146
+ flags.noInput = true;
147
+ continue;
148
+ }
149
+ const [name, inline] = arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, null];
150
+ if (name in VALUE_FLAGS) {
151
+ const value = inline ?? args[i + 1];
152
+ if (inline === null)
153
+ i += 1;
154
+ if (value === undefined || value.length === 0 || value.startsWith("--")) {
155
+ return new Error(`${name} needs a value`);
156
+ }
157
+ flags[VALUE_FLAGS[name]] = value;
158
+ continue;
159
+ }
160
+ return new Error(`unknown option '${arg}' for init --node`);
161
+ }
162
+ return flags;
163
+ }
164
+ function writePrivate(path, text) {
165
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
166
+ writeFileSync(path, text, { mode: 384 });
167
+ }
168
+ async function nodeInit(flags, io) {
169
+ const agentRegistry = flags.agentRegistry ?? io.env["AGENT_REGISTRY"] ?? null;
170
+ const agentId = flags.agentId ?? io.env["AGENT_ID"] ?? null;
171
+ const endpointRaw = flags.endpoint ?? io.env["ARP_NODE_PUBLIC_URL"] ?? null;
172
+ const directory = flags.directory ?? io.env["ARP_DIRECTORY_URL"] ?? null;
173
+ const missing = [];
174
+ if (agentRegistry === null)
175
+ missing.push("--agent-registry (or AGENT_REGISTRY)");
176
+ if (agentId === null)
177
+ missing.push("--agent-id (or AGENT_ID)");
178
+ if (endpointRaw === null)
179
+ missing.push("--endpoint (or ARP_NODE_PUBLIC_URL)");
180
+ if (directory === null)
181
+ missing.push("--directory (or ARP_DIRECTORY_URL)");
182
+ if (missing.length > 0 || agentRegistry === null || agentId === null || endpointRaw === null || directory === null) {
183
+ io.err(`arp init --node: missing ${missing.join(", ")}`);
184
+ return NODE_INIT_EXIT.usage;
185
+ }
186
+ if (parseAgentRegistry(agentRegistry) === null || !/^(0|[1-9][0-9]*)$/.test(agentId)) {
187
+ io.err("arp init --node: the agent is eip155:<chainId>:<registry> plus a decimal token id");
188
+ return NODE_INIT_EXIT.usage;
189
+ }
190
+ const endpoint = normaliseEndpoint(endpointRaw);
191
+ if (endpoint === null) {
192
+ io.err(`arp init --node: the endpoint must be the tunnel's https URL, got ${endpointRaw}`);
193
+ return NODE_INIT_EXIT.usage;
194
+ }
195
+ if (flags.noInput || !io.interactive) {
196
+ io.err("arp init --node: the owner signs the handshake in their wallet, which needs a terminal");
197
+ return NODE_INIT_EXIT.refused;
198
+ }
199
+ let key;
200
+ try {
201
+ key = resolveNodeKey(io.env, { home: io.home });
202
+ } catch (error) {
203
+ io.err(`arp init --node: ${error instanceof Error ? error.message : String(error)}`);
204
+ return NODE_INIT_EXIT.failed;
205
+ }
206
+ io.err(`node key ${key.account.address} (${key.source === "env" ? "AGENT_PRIVATE_KEY" : key.path})`);
207
+ const messagePath = join(io.home, ".arp", "handshake-message.txt");
208
+ const result = await register({
209
+ directoryUrl: directory,
210
+ agent: { agentRegistry, agentId },
211
+ endpoint,
212
+ node: key.account,
213
+ ...io.fetch === undefined ? {} : { fetch: io.fetch },
214
+ async signAsOwner(challenge) {
215
+ writePrivate(messagePath, challenge.message);
216
+ io.err("");
217
+ io.err(`Sign this message as ${challenge.owner}, the token's owner, before ${challenge.expiresAt}:`);
218
+ io.err("");
219
+ for (const line of challenge.message.split(`
220
+ `))
221
+ io.err(` ${line}`);
222
+ io.err("");
223
+ io.err(`It is also in ${messagePath}. With Foundry, for example:`);
224
+ io.err(` cast wallet sign --interactive "$(cat ${messagePath})"`);
225
+ io.err("");
226
+ const answer = (await io.readLine("owner signature (0x…): ")).trim();
227
+ if (!isHex(answer) || answer.length < 132)
228
+ throw new Error("that is not a signature");
229
+ return answer;
230
+ }
231
+ });
232
+ if (!result.ok) {
233
+ io.err(`arp init --node: ${result.step} refused: ${result.reason}`);
234
+ return NODE_INIT_EXIT.failed;
235
+ }
236
+ const bindingPath = join(io.home, ".arp", "node-binding.json");
237
+ const documentPath = join(io.home, ".arp", "node-binding.document.json");
238
+ writePrivate(bindingPath, `${JSON.stringify(result.binding, null, 2)}
239
+ `);
240
+ writePrivate(documentPath, endpointBindingDocument(result.binding));
241
+ io.out(`bound node ${result.binding.nodeAddress} to agent ${agentId} of ${agentRegistry}`);
242
+ io.out(`endpoint ${result.binding.endpoint}`);
243
+ io.out(`saved ${bindingPath}`);
244
+ io.out(`document ${documentPath}`);
245
+ io.out("");
246
+ io.out("Next: pin the document to IPFS, then write the binding on Intuition testnet with");
247
+ io.out("`prepareEndpointBinding`. This command signs no transaction.");
248
+ io.out("A transfer of the token voids the binding until you run this again.");
249
+ return NODE_INIT_EXIT.ok;
250
+ }
251
+ export {
252
+ parseNodeInitFlags,
253
+ nodeInit,
254
+ NODE_INIT_EXIT
255
+ };
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ encodeToCurve,
4
+ hashToCurve,
5
+ schnorr,
6
+ secp256k1,
7
+ secp256k1_hasher
8
+ } from "./cli-d3dy7cxe.js";
9
+ import"./cli-3kbbwfbp.js";
10
+ import"./cli-g5zvr10v.js";
11
+ export {
12
+ secp256k1_hasher,
13
+ secp256k1,
14
+ schnorr,
15
+ hashToCurve,
16
+ encodeToCurve
17
+ };