@everyprotocol/every-cli 0.1.8 → 0.1.10

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/.every.toml CHANGED
@@ -1,17 +1,53 @@
1
- [universes.anvil]
1
+ [universes.local]
2
2
  id = 31337
3
3
  rpc = "http://localhost:8545"
4
- observer = "dev"
4
+ observer = "local"
5
5
  explorer = "http://localhost"
6
6
 
7
- [universes.anvil.contracts]
8
- SetRegistry = "0x854C35Fd2b65fE9fcE71dddE91De8c3e1A7Dc8Ae"
9
- OmniRegistry = "0x4b7fc108F2c4fCDD990240448D2eED8034713c7D"
10
- KindRegistry = "0x7A8B3E5A9c227858C5917b7de8ba1684Cd868630"
11
- ElementRegistry = "0x8De1EE1dbAE2Ffd1CAe1e6bA83E6cAede1461507"
12
- ObjectMinter = "0x12CaBC370b316F247126F3Fab529Ee25e03aE226"
7
+ [universes.local.contracts]
8
+ SetRegistry = "0x5b6C313987497dD4625Cc3953070CD3bD7Eaf867"
9
+ OmniRegistry = "0x66bc6E783A49078AdC8B159657e59623CF39b162"
10
+ KindRegistry = "0xCeA503d28e8Bb1466cc9a1a37a542b718AC26c4a"
11
+ ElementRegistry = "0xBD9f9306807a75EB72F9a5Bbe9b52B3CeB63A4Fb"
12
+ ObjectMinter = "0x0Ca78907Ff1a06C100939B71091914D5973b0Ade"
13
13
 
14
- [observers.dev]
14
+ [universes.ethereum-alpha]
15
+ id = 11155111
16
+ rpc = "https://eth-sepolia.g.alchemy.com/v2/BI83rsifAeoz4ZEQkWu6yC0IaOHDBTIE"
17
+ observer = "devnet"
18
+ explorer = "https://sepolia.etherscan.io"
19
+
20
+ [universes.ethereum-alpha.contracts]
21
+ SetRegistry = "0xdd87c04d79efDF668F3ad5Cd06fF45d75E5b1734"
22
+ OmniRegistry = "0x8906Fa8b9749207e819c76Fc7D1B26d185b58eF8"
23
+ KindRegistry = "0x8a26F2DdD9382fc193f6A2eFc6C3BB14A47f522c"
24
+ ElementRegistry = "0x74D8673d9d2E5B475b8C4558F1E5756Dc190638E"
25
+ ObjectMinter = "0xC2dd118038127b5f9eA048B65d17500Fe022CcAD"
26
+
27
+ [universes.ethereum-beta]
28
+ id = 11155111
29
+ rpc = "https://eth-sepolia.g.alchemy.com/v2/BI83rsifAeoz4ZEQkWu6yC0IaOHDBTIE"
30
+ observer = "testnet"
31
+ explorer = "https://sepolia.etherscan.io"
32
+
33
+ [universes.ethereum-beta.contracts]
34
+ SetRegistry = "0x42aaDB79D6015a6a5c1419276a8f7286298Db174"
35
+ OmniRegistry = "0x8D696bD0ff8272e4BcC901cC7b360cC8e55Ade69"
36
+ KindRegistry = "0x63ed128a5584c959Bd8709a440A6035CE90DfeA7"
37
+ ElementRegistry = "0x24b07562939663B06aAEF3FD9eC387e646077e6b"
38
+ ObjectMinter = "0x0E44f5440C972dB6ca809F93aCa7A7DeA6C432b7"
39
+
40
+ [observers.local]
15
41
  rpc = "ws://localhost:9944"
16
42
  gateway = "http://every.lo"
17
43
  explorer = "https://portal.every.fun/?rpc=ws://localhost:9944#/explorer"
44
+
45
+ [observers.devnet]
46
+ rpc = "wss://devnet.every.network"
47
+ gateway = "http://every.bz"
48
+ explorer = "https://portal.every.fun/?rpc=wss://devnet.every.network#/explorer"
49
+
50
+ [observers.testnet]
51
+ rpc = "wss://testnet.every.network"
52
+ gateway = "http://every.im"
53
+ explorer = "https://portal.every.fun/?rpc=wss://testnet.every.network#/explorer"
package/dist/cmdgen.js CHANGED
@@ -25,6 +25,7 @@ const getWriteAction = (config, funcName, abiFunc, abi) => async function writeA
25
25
  const address = config.getContract(conf, this.args, abiFunc);
26
26
  const account = walletClient.account;
27
27
  const simulation = { address, abi, functionName: funcName, args, account };
28
+ console.log({ address, functionName: funcName, args, account });
28
29
  await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
29
30
  };
30
31
  export const getCommandGen = (config) => function genCmd(cmdName) {
@@ -1,16 +1,13 @@
1
1
  import { Command } from "commander";
2
2
  import "@polkadot/api-augment/substrate";
3
3
  import * as fs from "fs";
4
- import * as JSON11 from "json11";
4
+ import path from "path";
5
5
  import columify from "columnify";
6
- import { loadBinary, loadJson } from "../utils.js";
6
+ import { j11String, loadBinary, loadJson } from "../utils.js";
7
7
  import { submitTransaction } from "../substrate.js";
8
8
  import { network } from "../commander-patch.js";
9
9
  import { Logger } from "../logger.js";
10
- import path from "path";
11
- import csv from "csv-parser";
12
- import { fromHex, pad, sha256 } from "viem";
13
- import { memoize } from "lodash-es";
10
+ import { compileEnumCsv, compilePermCsv, computeHash, parseFileSpec, specToInput, } from "../matter.js";
14
11
  const matterRegisterCmd = new Command("register")
15
12
  .description("Register matters")
16
13
  .argument("<files...>", "Paths of matter blob files")
@@ -24,7 +21,7 @@ const matterRegisterCmd = new Command("register")
24
21
  const inputs = [];
25
22
  for (const file of files) {
26
23
  const spec = parseFileSpec(file);
27
- const mi = await toRegisterInput(spec);
24
+ const mi = await specToInput(spec);
28
25
  mi.forEach((input) => {
29
26
  if (!deduper.has(input.path)) {
30
27
  inputs.push(input);
@@ -52,7 +49,7 @@ const matterRegisterCmd = new Command("register")
52
49
  console.log(`Transaction confirmed: ${txn.txHash} ${filePath}`);
53
50
  console.log(`Confirmed in: block ${header.number}, hash ${header.hash}`);
54
51
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
- const events = r.events.map((e) => [e.event.method, JSON11.stringify(e.event.data.toJSON())]);
52
+ const events = r.events.map((e) => [e.event.method, j11String(e.event.data.toJSON())]);
56
53
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
54
  const receipt = r.events.map((e) => ({ event: e.event.method, data: e.event.data.toJSON() }));
58
55
  console.log(columify(events, { showHeaders: false }));
@@ -97,7 +94,7 @@ const matterHashCmd = new Command("hash")
97
94
  const outputs = [];
98
95
  for (const file of files) {
99
96
  const spec = parseFileSpec(file);
100
- const inputs = await toRegisterInput(spec);
97
+ const inputs = await specToInput(spec);
101
98
  inputs.forEach((input) => {
102
99
  if (!seen.has(input.path)) {
103
100
  seen.add(input.path);
@@ -135,311 +132,21 @@ export const matterCmd = new Command("matter")
135
132
  .addCommand(matterCompileCmd)
136
133
  .addCommand(matterHashCmd)
137
134
  .addCommand(matterPickCmd);
138
- function parseFileSpec(file) {
139
- if (file.startsWith("@")) {
140
- const p = file.slice(1).trim();
141
- if (!p)
142
- throw new Error("Invalid manifest file spec (empty path)");
143
- return { path: p, type: "manifest" };
144
- }
145
- const parts = file.split(":");
146
- if (parts.length < 1 || parts.length > 3) {
147
- throw new Error(`Invalid matter spec: ${file}`);
148
- }
149
- const pathPart = parts[0];
150
- let form;
151
- let mime;
152
- parts.slice(1).forEach((part) => {
153
- if (part.startsWith("form=")) {
154
- form = Number(part.slice(5));
155
- }
156
- else if (part.startsWith("mime=")) {
157
- mime = part.slice(5);
158
- }
159
- else {
160
- throw new Error(`Invalid extra info: ${part}`);
161
- }
162
- });
163
- const type = pathPart.endsWith(".enum.csv") || pathPart.endsWith(".perm.csv") ? "raw" : "blob";
164
- return { path: pathPart, type, form, mime };
165
- }
166
- async function toRegisterInput(spec) {
167
- if (spec.type == "blob") {
168
- if (spec.form && spec.mime)
169
- return [{ path: spec.path, form: spec.form, mime: spec.mime }];
170
- const g = inferMatterMeta(spec.path);
171
- return [{ path: spec.path, form: spec.form ?? g.form, mime: spec.mime ?? g.mime }];
172
- }
173
- else if (spec.type == "manifest") {
174
- return loadJson(spec.path).map((item) => {
175
- const { path, form, mime } = item;
176
- return { path, form, mime };
177
- });
178
- }
179
- else if (spec.type == "raw") {
180
- if (spec.path.endsWith(".enum.csv")) {
181
- const { blob, deps } = await compileEnumMatter(spec.path);
182
- const path = spec.path.slice(0, -9) + ".enum.bin";
183
- const { form, mime } = inferMatterMeta(path);
184
- return [...deps, { blob, form, mime, path }];
185
- }
186
- else if (spec.path.endsWith(".perm.csv")) {
187
- const { blob, deps } = await compileEnumMatter(spec.path);
188
- const path = spec.path.slice(0, -9) + ".perm.bin";
189
- const { form, mime } = inferMatterMeta(path);
190
- return [...deps, { blob, form, mime, path }];
191
- }
192
- else {
193
- throw new Error("unknown raw matter file extension");
194
- }
195
- }
196
- else {
197
- throw new Error("unknown matter file type");
198
- }
199
- }
200
135
  function getOutFiles(file, outDir) {
201
136
  const dir = outDir ? path.resolve(outDir) : path.dirname(path.resolve(file));
202
137
  if (file.endsWith(".enum.csv")) {
203
138
  const baseName = path.basename(file).slice(0, -9);
204
139
  const binFile = path.join(dir, `${baseName}.enum.bin`);
205
140
  const depsFile = path.join(dir, `${baseName}.enum.deps`);
206
- return { binFile, depsFile, outDir: dir, compile: compileEnumMatter };
141
+ return { binFile, depsFile, outDir: dir, compile: compileEnumCsv };
207
142
  }
208
143
  else if (file.endsWith(".perm.csv")) {
209
144
  const baseName = path.basename(file).slice(0, -9);
210
145
  const binFile = path.join(dir, `${baseName}.perm.bin`);
211
146
  const depsFile = path.join(dir, `${baseName}.perm.deps`);
212
- return { binFile, depsFile, outDir: dir, compile: compilePermMatter };
147
+ return { binFile, depsFile, outDir: dir, compile: compilePermCsv };
213
148
  }
214
149
  else {
215
150
  throw new Error("Unsupported raw matter file extension");
216
151
  }
217
152
  }
218
- async function compileEnumMatter(file) {
219
- const computeHashMemo = memoize(computeHashFile);
220
- const deps = new Map();
221
- let rows = 0;
222
- let colTypes;
223
- const mapHeaders = ({ header }) => {
224
- const h = header.trim().toUpperCase();
225
- if (h in CELL_FORMS) {
226
- return h;
227
- }
228
- else {
229
- throw new Error(`Invalid column element type: ${h}`);
230
- }
231
- };
232
- function resolvePath(v) {
233
- if (path.isAbsolute(v))
234
- return v;
235
- const dir = path.dirname(file);
236
- if (path.isAbsolute(file)) {
237
- return path.resolve(dir, v);
238
- }
239
- else {
240
- return path.normalize(path.join(dir, v));
241
- }
242
- }
243
- const mapValues = ({ header, value }) => {
244
- const v = value.trim();
245
- if (v.startsWith("0x")) {
246
- return fromHex(pad(v, { size: 32, dir: "left" }), "bytes");
247
- }
248
- else if (header == "IMAGE" || header == "JSON") {
249
- const { hash, mime, form, path } = computeHashMemo(resolvePath(v));
250
- if (!deps.has(path)) {
251
- deps.set(path, { hash, mime, form, path });
252
- }
253
- return fromHex(hash, "bytes");
254
- }
255
- else {
256
- throw new Error("expect hex strings for cols other than IMAGE, JSON");
257
- }
258
- };
259
- const aux = parseAuxData(file, { mapValues, mapHeaders });
260
- const parser = csv({ skipComments: true, strict: true, mapHeaders, mapValues });
261
- const blob = await new Promise((resolve, reject) => {
262
- const content = [];
263
- fs.createReadStream(file)
264
- .pipe(parser)
265
- .on("headers", (headers) => {
266
- colTypes = headers;
267
- })
268
- .on("data", (data) => {
269
- Object.values(data).forEach((value) => content.push(value));
270
- rows += 1;
271
- })
272
- .on("end", () => {
273
- const auxTypes = Object.keys(aux ?? {});
274
- const auxValues = Object.values(aux ?? {});
275
- const enumHeader = buildEnumHeader(auxTypes ?? [], colTypes ?? [], rows);
276
- resolve(Buffer.concat([enumHeader, ...auxValues, ...content]));
277
- })
278
- .on("error", (e /* eslint-disable-line */) => {
279
- reject(e);
280
- });
281
- });
282
- return { blob, deps: Array.from(deps.values()) };
283
- }
284
- async function compilePermMatter(file) {
285
- void file;
286
- throw new Error("unimplemented");
287
- }
288
- function computeHash(form, mime, blob) {
289
- if (form < 0 || form > 255) {
290
- throw new Error("form must be uint8 (0..255)");
291
- }
292
- const mimeUTF8 = new TextEncoder().encode(mime);
293
- if (mimeUTF8.length <= 0 || mimeUTF8.length > 31) {
294
- throw new Error("form must be uint8 (0..255)");
295
- }
296
- const msg = new Uint8Array(32 + blob.length);
297
- msg[0] = form & 0xff;
298
- msg.set(pad(mimeUTF8, { size: 32, dir: "right" }), 1);
299
- msg.set(blob, 32);
300
- // SHA256(form:1 || mime:31 || blob:var)
301
- return sha256(msg);
302
- }
303
- function computeHashFile(file) {
304
- const spec = parseFileSpec(file);
305
- if (spec.type != "blob") {
306
- throw new Error("not blob type");
307
- }
308
- let form, mime;
309
- if (spec.form && spec.mime) {
310
- form = spec.form;
311
- mime = spec.mime;
312
- }
313
- else {
314
- const g = inferMatterMeta(spec.path);
315
- form = spec.form ?? g.form;
316
- mime = spec.mime ?? g.mime;
317
- }
318
- const hash = computeHash(form, mime, loadBinary(spec.path));
319
- return { path: spec.path, form, mime, hash };
320
- }
321
- /**
322
- * Build a 32-byte EnumMatter header.
323
- * Layout:
324
- * [0..3] = "ENUM"
325
- * [4] = ver, aux (u8.hi4, u8.lo4)
326
- * [5] = cols (u8)
327
- * [6..7] = rows (u16 LE)
328
- * [8..15] = aux_types [u8; 8]
329
- * [16..31] = col_types [u8; 16]
330
- */
331
- function buildEnumHeader(auxTypes, colTypes, rows = 0) {
332
- const colTypesLen = colTypes.length;
333
- const auxTypesLen = auxTypes.length;
334
- if (colTypesLen + auxTypesLen == 0) {
335
- throw new Error("Empty header list");
336
- }
337
- if (auxTypesLen > 8) {
338
- throw new Error(`Too many aux types (max 8): ${auxTypesLen}`);
339
- }
340
- if (colTypesLen > 16) {
341
- throw new Error(`Too many column types (max 16): ${colTypesLen}`);
342
- }
343
- if (rows < 0 || rows > 0xffff) {
344
- throw new Error(`Rows out of range: ${rows}`);
345
- }
346
- const buf = new Uint8Array(32);
347
- buf.set([0x45, 0x4e, 0x55, 0x4d], 0); // magic "ENUM"
348
- buf[4] = (0x01 << 4) | (auxTypesLen & 0x0f); // version(hi4), aux(lo4)
349
- buf[5] = colTypesLen; // cols
350
- buf[6] = rows & 0xff; // rows.lo
351
- buf[7] = (rows >>> 8) & 0xff; // rows.hi
352
- auxTypes.forEach((t, i) => (buf[8 + i] = formByName(t))); // aux types
353
- colTypes.forEach((t, i) => (buf[16 + i] = formByName(t))); // col types
354
- return buf;
355
- }
356
- const MATTER_FORMS = {
357
- // Simple
358
- JSON: 0x01,
359
- IMAGE: 0x02,
360
- // Code
361
- WASM: 0xc0,
362
- // Data
363
- ENUM: 0xd0,
364
- PERM: 0xd1,
365
- // Info
366
- INFO: 0xff,
367
- };
368
- const CELL_FORMS = {
369
- JSON: 0x01,
370
- IMAGE: 0x02,
371
- INFO: 0xff,
372
- };
373
- function formByName(name) {
374
- const value = MATTER_FORMS[name];
375
- if (value === undefined) {
376
- throw new Error(`Invalid form name: "${name}"`);
377
- }
378
- return value;
379
- }
380
- function inferMatterMeta(filePath) {
381
- const ext = path.extname(filePath);
382
- switch (ext) {
383
- case ".wasm":
384
- return { mime: "application/wasm", form: MATTER_FORMS.WASM };
385
- case ".json":
386
- return { mime: "application/json", form: MATTER_FORMS.JSON };
387
- case ".jpg":
388
- return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
389
- case ".jpeg":
390
- return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
391
- case ".png":
392
- return { mime: "image/png", form: MATTER_FORMS.IMAGE };
393
- case ".bin":
394
- if (filePath.endsWith(".enum.bin")) {
395
- return { mime: "application/vnd.every.enum", form: MATTER_FORMS.ENUM };
396
- }
397
- else if (filePath.endsWith(".perm.bin")) {
398
- return { mime: "application/vnd.every.perm", form: MATTER_FORMS.PERM };
399
- }
400
- else {
401
- throw new Error("unknown matter file extension");
402
- }
403
- default:
404
- throw new Error("unknown matter file extension");
405
- }
406
- }
407
- function parseAuxData(filePath, options) {
408
- const lines = readPrologue(filePath)
409
- .filter((l) => /^\s*#\s*@aux\s+/i.test(l))
410
- .map((l) => l.replace(/^\s*#\s*@aux\s+/i, "").trim());
411
- const mapHeaders = options.mapHeaders ?? (({ header }) => header);
412
- const mapValues = options.mapValues ?? (({ value }) => value);
413
- if (lines.length == 0) {
414
- return undefined;
415
- }
416
- else if (lines.length == 2) {
417
- const headers = lines[0].split(",").map((header, index) => mapHeaders({ header, index }));
418
- const parts = lines[1].split(",");
419
- if (parts.length != headers.length) {
420
- throw new Error("aux header and values mismatch");
421
- }
422
- const values = parts.map((value, index) => mapValues({ header: headers[index], index, value }));
423
- return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
424
- }
425
- else {
426
- throw new Error("invalid aux data");
427
- }
428
- }
429
- function readPrologue(filePath, maxBytes = 2048) {
430
- const fd = fs.openSync(filePath, "r");
431
- const buf = Buffer.alloc(maxBytes);
432
- const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
433
- fs.closeSync(fd);
434
- const lines = buf.subarray(0, bytesRead).toString("utf8").split(/\r?\n/);
435
- const prologue = [];
436
- for (const line of lines) {
437
- if (line.trim().startsWith("#")) {
438
- prologue.push(line);
439
- }
440
- else if (line.trim().length > 0) {
441
- break; // stop at the first non-comment, non-empty line
442
- }
443
- }
444
- return prologue;
445
- }
package/dist/cmds/pick.js CHANGED
@@ -5,7 +5,7 @@ import * as _ from "lodash-es";
5
5
  import { Command } from "commander";
6
6
  import { loadMergedConfig } from "../config.js";
7
7
  const DEFAULT_DIR = "./register";
8
- const DEFAULT_UNIVERSE = "anvil";
8
+ const DEFAULT_UNIVERSE = "local";
9
9
  const STATIC_RULES = {
10
10
  // config
11
11
  "universe.id": { file: "config://universe.json", root: "$", field: "id" },
@@ -1,7 +1,15 @@
1
- import { Command } from "commander";
1
+ import { Argument, Command, Option } from "commander";
2
+ import { pad, padHex } from "viem";
3
+ import fs from "node:fs";
2
4
  import { abi } from "../abi.js";
3
5
  import { getCommandGen, makeFuncName } from "../cmdgen.js";
4
- const cmdGenConfig = {
6
+ import { coerceValue, j11Parse, stringify } from "../utils.js";
7
+ import { outputOptions, writeOptions } from "../commander-patch.js";
8
+ import { submitSimulation } from "../ethereum.js";
9
+ import { Logger } from "../logger.js";
10
+ import { FromOpts } from "../from-opts.js";
11
+ import { parseEnum, RelationOwnerShift, RelationTerminator } from "../enums.js";
12
+ const genRelationCmd = getCommandGen({
5
13
  getFuncName: (cmdName) => makeFuncName(cmdName, `relation`),
6
14
  getAbiFuncs: (funcName) => abi.funcs.omniRegistry.filter((i) => i.name == funcName),
7
15
  // eslint-disable-next-line
@@ -10,7 +18,137 @@ const cmdGenConfig = {
10
18
  getContract: (conf, args, abiFunc) => conf.contracts.OmniRegistry,
11
19
  // eslint-disable-next-line
12
20
  getFuncArgs: (args, abiFunc) => args,
13
- };
14
- const cmdGen = getCommandGen(cmdGenConfig);
15
- const subCmds = "register,upgrade,touch,transfer,owner,descriptor,snapshot,rule,admit".split(",");
16
- export const relationCmd = new Command("relation").description("manage relations").addCommands(subCmds.map(cmdGen));
21
+ });
22
+ const registerCmd = genRegisterCmd();
23
+ const updateCmd = genUpdateCmd();
24
+ const otherCmds = "upgrade,touch,transfer,owner,descriptor,snapshot,rule,admit".split(",").map(genRelationCmd);
25
+ export const relationCmd = new Command("relation")
26
+ .description("manage relations")
27
+ .addCommand(registerCmd)
28
+ .addCommand(updateCmd)
29
+ .addCommands(otherCmds);
30
+ function genRegisterCmd() {
31
+ const funcName = "relationRegister";
32
+ const abiFuncs = abi.funcs.omniRegistry.filter((i) => i.name == funcName);
33
+ const abiNonFuncs = abi.nonFuncs.omniRegistry;
34
+ const data = new Argument("<data>", "Matter hash of the relation data");
35
+ const rule = new Argument("<rule>", "Relation rule");
36
+ const adjs = new Argument("<adjs...>", "Relation adjacencies");
37
+ const options = [...writeOptions, ...outputOptions];
38
+ const args = [data, rule, adjs];
39
+ function getFuncArgs(cmd) {
40
+ const code = pad("0x", { size: 20 });
41
+ const data = cmd.processedArgs[0];
42
+ const rule = stringify(resolveRule(cmd.processedArgs[1]));
43
+ const adjs = stringify(resolveAdjs(cmd.processedArgs[2]));
44
+ const args = [code, data, rule, adjs].map((arg, i) => coerceValue(arg, abiFuncs[0].inputs[i]));
45
+ return args;
46
+ }
47
+ async function getCmdAction() {
48
+ const opts = this.opts();
49
+ const args = getFuncArgs(this);
50
+ const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
51
+ const address = conf.contracts.OmniRegistry;
52
+ const account = walletClient.account;
53
+ const simulation = {
54
+ address,
55
+ abi: [...abiFuncs, ...abiNonFuncs],
56
+ functionName: funcName,
57
+ args,
58
+ account,
59
+ };
60
+ await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
61
+ }
62
+ return new Command("register")
63
+ .description("Register a new relation")
64
+ .addOptions(options)
65
+ .addArguments(args)
66
+ .action(getCmdAction);
67
+ }
68
+ function genUpdateCmd() {
69
+ const funcName = "relationUpdate";
70
+ const abiFuncs = abi.funcs.omniRegistry.filter((i) => i.name == funcName);
71
+ // console.log(abiFuncs);
72
+ const abiNonFuncs = abi.nonFuncs.omniRegistry;
73
+ const rel = new Argument("<rel>", "Relation ID");
74
+ const data = new Argument("<data>", "Matter hash of the relation data");
75
+ const adjs = new Option("--adjs <adjacency...>", "Relation adjacencies");
76
+ const options = [adjs, ...writeOptions, ...outputOptions];
77
+ const args = [rel, data];
78
+ function getFuncArgs(cmd) {
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ const opts = cmd.opts();
81
+ let args = [cmd.processedArgs[0], cmd.processedArgs[1]];
82
+ let sig = "relationUpdate(uint64,bytes32)";
83
+ if (opts.adjs) {
84
+ sig = "relationUpdate(uint64,bytes32,(uint16,uint48)[])";
85
+ const adjs = stringify(resolveAdjs(opts.adjs));
86
+ args.push(adjs);
87
+ }
88
+ const abiFunc = abiFuncs.find((i) => i._metadata.signature == sig);
89
+ if (!abiFunc) {
90
+ throw new Error(`signature ${sig} not found in abi`);
91
+ }
92
+ args = args.map((arg, i) => coerceValue(arg, abiFunc.inputs[i]));
93
+ return { args, sig, abiFunc };
94
+ }
95
+ async function getCmdAction() {
96
+ const opts = this.opts();
97
+ const { args } = getFuncArgs(this);
98
+ const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
99
+ const address = conf.contracts.OmniRegistry;
100
+ const account = walletClient.account;
101
+ const simulation = {
102
+ address,
103
+ abi: [...abiFuncs, ...abiNonFuncs],
104
+ functionName: funcName,
105
+ args,
106
+ account,
107
+ };
108
+ await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
109
+ }
110
+ return new Command("update")
111
+ .description("Update an existing relation")
112
+ .addOptions(options)
113
+ .addArguments(args)
114
+ .action(getCmdAction);
115
+ }
116
+ const U48_MAX = (1n << 48n) - 1n;
117
+ const DEG_MAX = 0x7fff;
118
+ const AT_LEAST_ONE_FLAG = 0x8000;
119
+ function resolveAdjs(args) {
120
+ const kind = (s) => {
121
+ if (/^other$/i.test(s))
122
+ return 0n;
123
+ if (/^total$/i.test(s))
124
+ return U48_MAX;
125
+ const n = BigInt(s);
126
+ if (n < 0n || n > U48_MAX)
127
+ throw new Error("kind out of range");
128
+ return n;
129
+ };
130
+ const degs = (s) => {
131
+ const plus = s.endsWith("+"), t = plus ? s.slice(0, -1) : s, n = Number(t);
132
+ if (!/^\d+$/.test(t) || n < 0 || n > DEG_MAX)
133
+ throw new Error("degs out of range");
134
+ return plus ? n | AT_LEAST_ONE_FLAG : n;
135
+ };
136
+ return args.map((s) => {
137
+ const [k, d] = s.split(":");
138
+ if (!k || !d)
139
+ throw new Error(`bad "${s}"`);
140
+ return { kind: kind(k.trim()), degs: degs(d.trim()) };
141
+ });
142
+ }
143
+ function resolveRule(arg) {
144
+ const json = arg.startsWith("@") ? fs.readFileSync(arg.slice(1), "utf8") : arg;
145
+ const input = j11Parse(json);
146
+ return {
147
+ version: input.version ?? 1,
148
+ relateShift: parseEnum(input.relateShift, RelationOwnerShift, "relateShift"),
149
+ terminator: parseEnum(input.terminator, RelationTerminator, "terminator"),
150
+ unrelateShift: parseEnum(input.unrelateShift, RelationOwnerShift, "unrelateShift"),
151
+ unrelateDelay: input.unrelateDelay ?? 0,
152
+ extra: padHex(input.extra ?? "0x", { size: 20 }),
153
+ };
154
+ }
@@ -5,8 +5,8 @@ export const password = new Option("-p, --password [password]", "Password to dec
5
5
  export const passwordFile = new Option("-P, --password-file <file>", "File containing the keystore password");
6
6
  export const privateKey = new Option("-k, --private-key <key>", "Private key to sign the transaction");
7
7
  export const foundry = new Option("-f, --foundry", "Use foundry keystores (~/.foundry/keystores)");
8
- export const universe = new Option("-u, --universe <universe>", "Universe name").default("anvil");
9
- export const network = new Option("-n, --network <network>", "Network name").default("dev");
8
+ export const universe = new Option("-u, --universe <universe>", "Universe name").default("local");
9
+ export const network = new Option("-n, --network <network>", "Network name").default("local");
10
10
  export const json = new Option("-j, --json [file]", "Output result as JSON to stdout or file");
11
11
  export const quiet = new Option("-q, --quiet", "Suppress info messages");
12
12
  export const keystoreOptions = [account, password, passwordFile, foundry];
package/dist/enums.js ADDED
@@ -0,0 +1,41 @@
1
+ export var RelationTerminator;
2
+ (function (RelationTerminator) {
3
+ RelationTerminator[RelationTerminator["TailOwner"] = 0] = "TailOwner";
4
+ RelationTerminator[RelationTerminator["HeadOwner"] = 1] = "HeadOwner";
5
+ RelationTerminator[RelationTerminator["Either"] = 2] = "Either";
6
+ RelationTerminator[RelationTerminator["Neither"] = 3] = "Neither";
7
+ RelationTerminator[RelationTerminator["Anyone"] = 4] = "Anyone";
8
+ RelationTerminator[RelationTerminator["Nobody"] = 5] = "Nobody";
9
+ })(RelationTerminator || (RelationTerminator = {}));
10
+ export var RelationOwnerShift;
11
+ (function (RelationOwnerShift) {
12
+ RelationOwnerShift[RelationOwnerShift["Retain"] = 0] = "Retain";
13
+ RelationOwnerShift[RelationOwnerShift["TransferToTailOwner"] = 1] = "TransferToTailOwner";
14
+ RelationOwnerShift[RelationOwnerShift["TransferToHeadOwner"] = 2] = "TransferToHeadOwner";
15
+ RelationOwnerShift[RelationOwnerShift["TransferToCaller"] = 3] = "TransferToCaller";
16
+ RelationOwnerShift[RelationOwnerShift["TransferToPreset"] = 4] = "TransferToPreset";
17
+ RelationOwnerShift[RelationOwnerShift["TransferToBurned"] = 5] = "TransferToBurned";
18
+ RelationOwnerShift[RelationOwnerShift["TransferToResolved"] = 6] = "TransferToResolved";
19
+ RelationOwnerShift[RelationOwnerShift["TransferToIntended"] = 7] = "TransferToIntended";
20
+ RelationOwnerShift[RelationOwnerShift["HoldForTailOwner"] = 8] = "HoldForTailOwner";
21
+ RelationOwnerShift[RelationOwnerShift["HoldForHeadOwner"] = 9] = "HoldForHeadOwner";
22
+ RelationOwnerShift[RelationOwnerShift["HoldForCaller"] = 10] = "HoldForCaller";
23
+ RelationOwnerShift[RelationOwnerShift["HoldForPreset"] = 11] = "HoldForPreset";
24
+ RelationOwnerShift[RelationOwnerShift["HoldForBurned"] = 12] = "HoldForBurned";
25
+ RelationOwnerShift[RelationOwnerShift["HoldForResolved"] = 13] = "HoldForResolved";
26
+ RelationOwnerShift[RelationOwnerShift["HoldPending"] = 14] = "HoldPending";
27
+ })(RelationOwnerShift || (RelationOwnerShift = {}));
28
+ export function parseEnum(v, E, name) {
29
+ if (typeof v === "number") {
30
+ if (v in E)
31
+ return v;
32
+ }
33
+ else if (typeof v === "string") {
34
+ // accept exact key or case-insensitive key
35
+ const k = Object.keys(E).find((key) => key.toLowerCase() === v.toLowerCase());
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ if (k && typeof E[k] === "number")
38
+ return E[k];
39
+ }
40
+ throw new Error(`${name} must be a valid enum value or key`);
41
+ }
package/dist/matter.js ADDED
@@ -0,0 +1,479 @@
1
+ import * as fs from "fs";
2
+ import { loadBinary, loadJson } from "./utils.js";
3
+ import path from "path";
4
+ import csv from "csv-parser";
5
+ import { fromHex, pad, sha256 } from "viem";
6
+ import { memoize } from "lodash-es";
7
+ const MATTER_FORMS = {
8
+ // Simple
9
+ JSON: 0x01,
10
+ IMAGE: 0x02,
11
+ // Code
12
+ WASM: 0xc0,
13
+ // Data
14
+ ENUM: 0xd0,
15
+ PERM: 0xd1,
16
+ // Info
17
+ INFO: 0xff,
18
+ };
19
+ const CELL_TYPES = {
20
+ JSON: 0x01,
21
+ IMAGE: 0x02,
22
+ INFO: 0xff,
23
+ };
24
+ export function formValueByName(name) {
25
+ const value = MATTER_FORMS[name];
26
+ if (value === undefined) {
27
+ throw new Error(`Invalid form name: "${name}"`);
28
+ }
29
+ return value;
30
+ }
31
+ export function metaFromExt(file) {
32
+ const ext = path.extname(file);
33
+ switch (ext) {
34
+ case ".wasm":
35
+ return { mime: "application/wasm", form: MATTER_FORMS.WASM };
36
+ case ".json":
37
+ return { mime: "application/json", form: MATTER_FORMS.JSON };
38
+ case ".jpg":
39
+ return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
40
+ case ".jpeg":
41
+ return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
42
+ case ".png":
43
+ return { mime: "image/png", form: MATTER_FORMS.IMAGE };
44
+ case ".bin":
45
+ if (file.endsWith(".enum.bin")) {
46
+ return { mime: "application/vnd.every.enum", form: MATTER_FORMS.ENUM };
47
+ }
48
+ else if (file.endsWith(".perm.bin")) {
49
+ return { mime: "application/vnd.every.perm", form: MATTER_FORMS.PERM };
50
+ }
51
+ else {
52
+ throw new Error("unknown matter file extension");
53
+ }
54
+ default:
55
+ throw new Error("unknown matter file extension");
56
+ }
57
+ }
58
+ export function computeHash(form, mime, blob) {
59
+ if (form < 0 || form > 255) {
60
+ throw new Error("form must be uint8 (0..255)");
61
+ }
62
+ const mimeUTF8 = new TextEncoder().encode(mime);
63
+ if (mimeUTF8.length <= 0 || mimeUTF8.length > 31) {
64
+ throw new Error("form must be uint8 (0..255)");
65
+ }
66
+ const msg = new Uint8Array(32 + blob.length);
67
+ msg[0] = form & 0xff;
68
+ msg.set(pad(mimeUTF8, { size: 32, dir: "right" }), 1);
69
+ msg.set(blob, 32);
70
+ // SHA256(form:1 || mime:31 || blob:var)
71
+ return sha256(msg);
72
+ }
73
+ export function computeHashFromFile(file) {
74
+ const spec = parseFileSpec(file);
75
+ if (spec.type != "blob") {
76
+ throw new Error("not blob type");
77
+ }
78
+ let form, mime;
79
+ if (spec.form && spec.mime) {
80
+ form = spec.form;
81
+ mime = spec.mime;
82
+ }
83
+ else {
84
+ const g = metaFromExt(spec.path);
85
+ form = spec.form ?? g.form;
86
+ mime = spec.mime ?? g.mime;
87
+ }
88
+ const hash = computeHash(form, mime, loadBinary(spec.path));
89
+ return { path: spec.path, form, mime, hash };
90
+ }
91
+ export function parseFileSpec(file) {
92
+ if (file.startsWith("@")) {
93
+ const p = file.slice(1).trim();
94
+ if (!p)
95
+ throw new Error("Invalid manifest file spec (empty path)");
96
+ return { path: p, type: "manifest" };
97
+ }
98
+ const parts = file.split(":");
99
+ if (parts.length < 1 || parts.length > 3) {
100
+ throw new Error(`Invalid matter spec: ${file}`);
101
+ }
102
+ const pathPart = parts[0];
103
+ let form;
104
+ let mime;
105
+ parts.slice(1).forEach((part) => {
106
+ if (part.startsWith("form=")) {
107
+ form = Number(part.slice(5));
108
+ }
109
+ else if (part.startsWith("mime=")) {
110
+ mime = part.slice(5);
111
+ }
112
+ else {
113
+ throw new Error(`Invalid extra info: ${part}`);
114
+ }
115
+ });
116
+ const type = pathPart.endsWith(".enum.csv") || pathPart.endsWith(".perm.csv") ? "raw" : "blob";
117
+ return { path: pathPart, type, form, mime };
118
+ }
119
+ export async function specToInput(spec) {
120
+ if (spec.type == "blob") {
121
+ if (spec.form && spec.mime)
122
+ return [{ path: spec.path, form: spec.form, mime: spec.mime }];
123
+ const meta = metaFromExt(spec.path);
124
+ return [{ path: spec.path, form: spec.form ?? meta.form, mime: spec.mime ?? meta.mime }];
125
+ }
126
+ else if (spec.type == "manifest") {
127
+ return loadJson(spec.path).map((item) => {
128
+ const { path, form, mime } = item;
129
+ return { path, form, mime };
130
+ });
131
+ }
132
+ else if (spec.type == "raw") {
133
+ if (spec.path.endsWith(".enum.csv")) {
134
+ const { blob, deps } = await compileEnumCsv(spec.path);
135
+ const path = spec.path.slice(0, -9) + ".enum.bin";
136
+ const { form, mime } = metaFromExt(path);
137
+ return [...deps, { blob, form, mime, path }];
138
+ }
139
+ else if (spec.path.endsWith(".perm.csv")) {
140
+ const { blob, deps } = await compilePermCsv(spec.path);
141
+ const path = spec.path.slice(0, -9) + ".perm.bin";
142
+ const { form, mime } = metaFromExt(path);
143
+ return [...deps, { blob, form, mime, path }];
144
+ }
145
+ else {
146
+ throw new Error("unknown raw matter file extension");
147
+ }
148
+ }
149
+ else {
150
+ throw new Error("unknown matter file type");
151
+ }
152
+ }
153
+ export async function compileEnumCsv(csvFile) {
154
+ const deps = new Map();
155
+ const hashCellFileMemo = HashCellFileMemo(csvFile, deps);
156
+ const mapHeaders = ({ header }) => parseCsvHeader(header);
157
+ const mapValues = ({ header, value }) => {
158
+ const c = parseCsvCell(value, header);
159
+ if (c.type == "hex") {
160
+ const bin = fromHex(pad(c.hex, { size: 32, dir: "left" }), "bytes");
161
+ return bin;
162
+ }
163
+ else if (c.type == "file") {
164
+ const input = hashCellFileMemo(c.file);
165
+ const bin = fromHex(input.hash, "bytes");
166
+ return bin;
167
+ }
168
+ };
169
+ const aux = parseCsvAux(csvFile, hashCellFileMemo);
170
+ const rows = [];
171
+ const headers = [];
172
+ const blob = await new Promise((resolve, reject) => {
173
+ fs.createReadStream(csvFile)
174
+ .pipe(csv({ skipComments: true, strict: true, mapHeaders, mapValues }))
175
+ .on("error", (e) => reject(e))
176
+ .on("headers", (hs) => headers.push(...hs))
177
+ .on("data", (data) => rows.push(Object.values(data)))
178
+ .on("end", () => {
179
+ const auxTypes = Object.keys(aux ?? {});
180
+ const auxData = Object.values(aux ?? {});
181
+ const header = buildEnumBinHeader(auxTypes ?? [], headers, rows.length);
182
+ resolve(Buffer.concat([header, ...auxData, ...rows.flat()]));
183
+ });
184
+ });
185
+ return { blob, deps: Array.from(deps.values()) };
186
+ }
187
+ export async function compilePermCsv(csvFile) {
188
+ const deps = new Map();
189
+ const hashCellFileMemo = HashCellFileMemo(csvFile, deps);
190
+ const headers = [];
191
+ const columns = [];
192
+ const mapHeaders = (header) => {
193
+ const h = parsePermCsvHeader(header);
194
+ headers.push(h);
195
+ return h.header;
196
+ };
197
+ const mapValues = ({ header, value, index }) => {
198
+ const v = parsePermCsvCell(value, header);
199
+ const bin = v.type == "hex"
200
+ ? fromHex(pad(v.hex, { size: 32, dir: "left" }), "bytes")
201
+ : v.type == "file"
202
+ ? fromHex(hashCellFileMemo(v.file).hash, "bytes")
203
+ : null;
204
+ if (bin)
205
+ for (let i = 0; i < v.count; i++)
206
+ columns[index].push(bin);
207
+ return v.cell;
208
+ };
209
+ const aux = parseCsvAux(csvFile, hashCellFileMemo);
210
+ const blob = await new Promise((resolve, reject) => {
211
+ fs.createReadStream(csvFile)
212
+ .pipe(csv({ skipComments: true, strict: false, mapHeaders, mapValues }))
213
+ .on("error", (e) => reject(e))
214
+ .on("headers", (hs) => hs.forEach(() => columns.push([])))
215
+ .on("data", (data) => void data) // event must be subscribed, so that "end" works
216
+ .on("end", () => {
217
+ validatePermColumns(headers, columns);
218
+ const auxTypes = Object.keys(aux ?? {});
219
+ const auxData = Object.values(aux ?? {});
220
+ const header = buildPermBinHeader(auxTypes, headers, columns);
221
+ resolve(Buffer.concat([header, ...auxData, ...columns.flat()]));
222
+ });
223
+ });
224
+ return { blob, deps: Array.from(deps.values()) };
225
+ }
226
+ function validatePermColumns(headers, columns) {
227
+ const n = headers.length;
228
+ if (n !== columns.length)
229
+ throw new Error("headers/columns length mismatch");
230
+ const sizes = columns.map((c) => c.length);
231
+ const permIdxs = headers.map((h, i) => (h.perm ? i : -1)).filter((i) => i >= 0);
232
+ const prod = permIdxs.reduce((acc, cur) => acc * sizes[cur], 1);
233
+ if (prod === 0)
234
+ throw new Error("column height cannot be zero for permutation columns");
235
+ const ensureAllEqual = (idxs, expected) => {
236
+ if (idxs.length === 0)
237
+ return;
238
+ const ref = expected ?? sizes[idxs[0]];
239
+ for (const i of idxs) {
240
+ if (sizes[i] !== ref) {
241
+ throw new Error(`column heights mismatch: column ${i} has ${sizes[i]} but expected ${ref}`);
242
+ }
243
+ if (sizes[i] === 0) {
244
+ throw new Error(`column height cannot be zero (column ${i})`);
245
+ }
246
+ }
247
+ };
248
+ const nonPermIdxs = headers.map((h, i) => (!h.perm ? i : -1)).filter((i) => i >= 0);
249
+ ensureAllEqual(nonPermIdxs, permIdxs.length === 0 ? undefined : prod);
250
+ }
251
+ /**
252
+ * Build an EnumMatter header.
253
+ * Layout:
254
+ * [0..3] = "ENUM"
255
+ * [4] = ver, aux (u8.hi4, u8.lo4)
256
+ * [5] = cols (u8)
257
+ * [6..7] = rows (u16 LE)
258
+ * [8..15] = aux_types [u8; 8]
259
+ * [16..31] = col_types [u8; 16]
260
+ */
261
+ export function buildEnumBinHeader(auxTypes, colTypes, rows = 0) {
262
+ const colTypesLen = colTypes.length;
263
+ const auxTypesLen = auxTypes.length;
264
+ if (colTypesLen + auxTypesLen == 0) {
265
+ throw new Error("Empty header list");
266
+ }
267
+ if (auxTypesLen > 8) {
268
+ throw new Error(`Too many aux types (max 8): ${auxTypesLen}`);
269
+ }
270
+ if (colTypesLen > 16) {
271
+ throw new Error(`Too many column types (max 16): ${colTypesLen}`);
272
+ }
273
+ if (rows < 0 || rows > 0xffff) {
274
+ throw new Error(`Rows out of range: ${rows}`);
275
+ }
276
+ const buf = new Uint8Array(32);
277
+ buf.set([0x45, 0x4e, 0x55, 0x4d], 0); // magic "ENUM"
278
+ buf[4] = (0x01 << 4) | (auxTypesLen & 0x0f); // version(hi4), aux(lo4)
279
+ buf[5] = colTypesLen; // cols
280
+ buf[6] = rows & 0xff; // rows.lo
281
+ buf[7] = (rows >>> 8) & 0xff; // rows.hi
282
+ auxTypes.forEach((t, i) => (buf[8 + i] = formValueByName(t))); // aux types
283
+ colTypes.forEach((t, i) => (buf[16 + i] = formValueByName(t))); // col types
284
+ return buf;
285
+ }
286
+ /**
287
+ * Build a PermMatter header.
288
+ * Layout:
289
+ * [0..3] = "PERM"
290
+ * [4] = ver, aux (u8.hi4, u8.lo4)
291
+ * [5] = cols (u8)
292
+ * [6..7] = enum_cols (16 bits)
293
+ * [8..15] = aux_types [u8; 8]
294
+ * [16..31] = col_types [u8; 16]
295
+ * [32..64] = col_heights [u16; 16]
296
+ */
297
+ export function buildPermBinHeader(auxTypes, headers, columns) {
298
+ if (auxTypes.length > 8) {
299
+ throw new Error(`Too many aux types (max 8): ${auxTypes.length}`);
300
+ }
301
+ if (headers.length > 16) {
302
+ throw new Error(`Too many column types (max 16): ${headers.length}`);
303
+ }
304
+ if (columns.length != headers.length) {
305
+ throw new Error(`Headers and columns mismatch`);
306
+ }
307
+ const buf = new Uint8Array(columns.length > 0 ? 64 : 32);
308
+ buf.set([0x50, 0x45, 0x52, 0x4d], 0); // magic "ENUM"
309
+ buf[4] = (0x01 << 4) | (auxTypes.length & 0x0f); // version(hi4), aux(lo4)
310
+ buf[5] = headers.length; // cols
311
+ const bits = headers.reduce((prev, current, i) => {
312
+ return prev | (!current.perm ? 1 << (15 - i) : 0);
313
+ }, 0);
314
+ buf[6] = bits & 0xff; // bits.lo
315
+ buf[7] = (bits >>> 8) & 0xff; // bits.hi
316
+ auxTypes.forEach((t, i) => (buf[8 + i] = formValueByName(t))); // aux types
317
+ headers.forEach((h, i) => (buf[16 + i] = formValueByName(h.header))); // col types
318
+ columns.forEach((c, i) => {
319
+ const loByte = c.length & 0xff;
320
+ const hiByte = (c.length >>> 8) & 0xff;
321
+ buf[32 + i * 2] = loByte;
322
+ buf[32 + i * 2 + 1] = hiByte;
323
+ });
324
+ return buf;
325
+ }
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
+ function parseCsvAux(filePath, hashCellFileMemo) {
328
+ const lines = readPrologue(filePath)
329
+ .filter((l) => /^\s*#\s*@aux\s+/i.test(l))
330
+ .map((l) => l.replace(/^\s*#\s*@aux\s+/i, "").trim());
331
+ const mapHeaders = ({ header }) => parseCsvHeader(header);
332
+ const mapValues = ({ header, value }) => {
333
+ const c = parseCsvCell(value, header);
334
+ if (c.type == "hex") {
335
+ const bin = fromHex(pad(c.hex, { size: 32, dir: "left" }), "bytes");
336
+ return bin;
337
+ }
338
+ else {
339
+ const input = hashCellFileMemo(c.file);
340
+ const bin = fromHex(input.hash, "bytes");
341
+ return bin;
342
+ }
343
+ };
344
+ if (lines.length == 0) {
345
+ return undefined;
346
+ }
347
+ else if (lines.length == 2) {
348
+ const headers = lines[0].split(",").map((header, index) => mapHeaders({ header, index }));
349
+ const parts = lines[1].split(",");
350
+ if (parts.length != headers.length) {
351
+ throw new Error("aux header and values mismatch");
352
+ }
353
+ const values = parts.map((value, index) => mapValues({ header: headers[index], index, value }));
354
+ return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
355
+ }
356
+ else {
357
+ throw new Error("invalid aux data");
358
+ }
359
+ }
360
+ function readPrologue(filePath, maxBytes = 2048) {
361
+ const fd = fs.openSync(filePath, "r");
362
+ const buf = Buffer.alloc(maxBytes);
363
+ const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
364
+ fs.closeSync(fd);
365
+ const lines = buf.subarray(0, bytesRead).toString("utf8").split(/\r?\n/);
366
+ const prologue = [];
367
+ for (const line of lines) {
368
+ if (line.trim().startsWith("#")) {
369
+ prologue.push(line);
370
+ }
371
+ else if (line.trim().length > 0) {
372
+ break; // stop at the first non-comment, non-empty line
373
+ }
374
+ }
375
+ return prologue;
376
+ }
377
+ function parseCsvHeader(header) {
378
+ const h = header.trim().toUpperCase();
379
+ if (h in CELL_TYPES) {
380
+ return h;
381
+ }
382
+ else {
383
+ throw new Error(`Invalid element type: ${h}`);
384
+ }
385
+ }
386
+ function parseCsvCell(input, header) {
387
+ const cell = input.trim();
388
+ if (cell.length == 0)
389
+ throw new Error("empty cell not permitted");
390
+ // (0xHEX | FILE) [* NUMBER] with optional spaces around *
391
+ const re = /^(?:(0x[0-9A-Fa-f]+)|([^\s*]+))$/;
392
+ const m = cell.match(re);
393
+ if (!m)
394
+ throw new Error("invalid cell");
395
+ const [, hex, file] = m;
396
+ if (hex) {
397
+ if ((hex.length - 2) % 2 !== 0 || hex.length > 66)
398
+ throw new Error("invalid hex cell");
399
+ return { cell, type: "hex", hex: hex, file: undefined };
400
+ }
401
+ else {
402
+ if (header == "INFO") {
403
+ throw new Error("file not permitted for INFO cell");
404
+ }
405
+ return { cell, type: "file", hex: undefined, file };
406
+ }
407
+ }
408
+ function parsePermCsvHeader({ header, index }) {
409
+ let h = header.trim().toUpperCase();
410
+ let perm = true;
411
+ if (h.endsWith("!")) {
412
+ h = h.slice(0, -1);
413
+ perm = false;
414
+ }
415
+ if (h in CELL_TYPES) {
416
+ return { header: h, perm, index };
417
+ }
418
+ else {
419
+ throw new Error(`Invalid column type: ${h}`);
420
+ }
421
+ }
422
+ export function parsePermCsvCell(input, header) {
423
+ const cell = input.trim();
424
+ if (cell.length == 0)
425
+ return { cell, type: "empty", count: 0 };
426
+ // (0xHEX | FILE) [* NUMBER] with optional spaces around *
427
+ const re = /^(?:(0x[0-9A-Fa-f]+)|([^\s*]+))\s*(?:\*\s*(\d+))?$/;
428
+ const m = cell.match(re);
429
+ if (!m)
430
+ throw new Error("invalid cell");
431
+ const [, hex, file, num] = m;
432
+ if (hex) {
433
+ if ((hex.length - 2) % 2 !== 0 || hex.length > 66)
434
+ throw new Error("invalid hex cell");
435
+ return {
436
+ cell,
437
+ type: "hex",
438
+ hex: hex,
439
+ file: undefined,
440
+ count: num ? Number(num) : 1,
441
+ };
442
+ }
443
+ else {
444
+ if (header == "INFO") {
445
+ throw new Error("file cell not permitted for INFO column");
446
+ }
447
+ return {
448
+ cell,
449
+ type: "file",
450
+ hex: undefined,
451
+ file,
452
+ count: num ? Number(num) : 1,
453
+ };
454
+ }
455
+ }
456
+ export function resolveCellPath(csvFile) {
457
+ return function (cellFile) {
458
+ if (path.isAbsolute(cellFile))
459
+ return cellFile;
460
+ const dir = path.dirname(csvFile);
461
+ if (path.isAbsolute(csvFile)) {
462
+ return path.resolve(dir, cellFile);
463
+ }
464
+ else {
465
+ return path.normalize(path.join(dir, cellFile));
466
+ }
467
+ };
468
+ }
469
+ function HashCellFileMemo(csvFile, deps) {
470
+ const resolvePath = resolveCellPath(csvFile);
471
+ function hashCellFile(p) {
472
+ const input = computeHashFromFile(resolvePath(p));
473
+ if (!deps.has(input.path)) {
474
+ deps.set(input.path, input);
475
+ }
476
+ return input;
477
+ }
478
+ return memoize(hashCellFile);
479
+ }
package/dist/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
+ import * as JSON11 from "json11";
4
5
  import { isHex, hexToU8a } from "@polkadot/util";
5
6
  import { base64Decode } from "@polkadot/util-crypto/base64";
6
7
  import { decodePair } from "@polkadot/keyring/pair/decode";
@@ -190,3 +191,12 @@ export function toRelationId(v) {
190
191
  }
191
192
  return n;
192
193
  }
194
+ export function stringify(v, options) {
195
+ return JSON11.stringify(v, options ?? { quoteNames: true, quote: '"', withBigInt: false });
196
+ }
197
+ export function j11String(v, options) {
198
+ return JSON11.stringify(v, options);
199
+ }
200
+ export function j11Parse(str) {
201
+ return JSON11.parse(str);
202
+ }
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@everyprotocol/every-cli",
3
3
  "type": "module",
4
- "version": "0.1.8",
4
+ "version": "0.1.10",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/everyprotocol/cli.git"
8
+ },
5
9
  "files": [
6
10
  "dist/",
7
11
  "abis/",
@@ -48,8 +52,8 @@
48
52
  "scripts": {
49
53
  "build": "npm run tsc -b",
50
54
  "clean": "rm -rf dist",
51
- "bb": "bun build src/*.ts --target=node --outdir dist",
52
- "prepublishOnly": "npm run build"
55
+ "b": "bun build src/*.ts --target=node --outdir dist",
56
+ "prepack": "npm run build"
53
57
  },
54
58
  "publishConfig": {
55
59
  "access": "public"