@everyprotocol/every-cli 0.1.7 → 0.1.9

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/dist/cmdgen.js CHANGED
@@ -10,7 +10,7 @@ const getReadAction = (config, funName, abiFunc, abi) => async function readActi
10
10
  const opts = this.opts();
11
11
  const conf = FromOpts.getUniverseConfig(opts);
12
12
  const address = await config.getContract(conf, this.processedArgs, abiFunc);
13
- const args = (config.getFuncArgs ?? CommandGenDefaults.getFuncArgs)(this.processedArgs, abiFunc);
13
+ const args = (config.getFuncArgs ?? CommandGenDefaults.getFuncArgs)(this.processedArgs, abiFunc, opts);
14
14
  const console = new Logger(opts);
15
15
  const publicClient = createPublicClient({ transport: http(conf.rpc) });
16
16
  const result = await publicClient.readContract({ address, abi, functionName: funName, args });
@@ -20,11 +20,12 @@ const getReadAction = (config, funName, abiFunc, abi) => async function readActi
20
20
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
21
  const getWriteAction = (config, funcName, abiFunc, abi) => async function writeAction() {
22
22
  const opts = this.opts();
23
- const args = (config.getFuncArgs ?? CommandGenDefaults.getFuncArgs)(this.args, abiFunc);
23
+ const args = (config.getFuncArgs ?? CommandGenDefaults.getFuncArgs)(this.args, abiFunc, opts);
24
24
  const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
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) {
@@ -36,7 +37,8 @@ export const getCommandGen = (config) => function genCmd(cmdName) {
36
37
  const abiFuncDoc = abiFuncs[0];
37
38
  const description = abiFuncDoc._metadata?.notice || "";
38
39
  const isRead = abiFuncDoc.stateMutability == "view" || abiFuncDoc.stateMutability == "pure";
39
- const options = isRead ? [universe, ...outputOptions] : [...writeOptions, ...outputOptions];
40
+ // const options = isRead ? [universe, ...outputOptions] : [...writeOptions, ...outputOptions];
41
+ const options = (config.getCmdOpts ?? CommandGenDefaults.getCmdOpts)(abiFuncDoc);
40
42
  const args = (config.getCmdArgs ?? CommandGenDefaults.getCmdArgs)(abiFuncDoc);
41
43
  const abiContract = [...abiFuncs, ...abiNonFuncs];
42
44
  const action = isRead
@@ -48,7 +50,12 @@ export function makeFuncName(cmdName, prefix) {
48
50
  return `${prefix}${cmdName[0].toUpperCase()}${cmdName.slice(1)}`;
49
51
  }
50
52
  export const CommandGenDefaults = {
51
- getFuncArgs: (args, abiFunc) => args.map((arg, i) => coerceValue(arg, abiFunc.inputs[i])),
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
54
+ getFuncArgs: (args, abiFunc, opts) => args.map((arg, i) => coerceValue(arg, abiFunc.inputs[i])),
55
+ getCmdOpts: (abiFunc) => {
56
+ const isRead = abiFunc.stateMutability == "view" || abiFunc.stateMutability == "pure";
57
+ return isRead ? [universe, ...outputOptions] : [...writeOptions, ...outputOptions];
58
+ },
52
59
  getCmdArgs: (abiFunc) => abiFunc.inputs.map((input) => {
53
60
  const desc = abiFunc._metadata?.params?.[input.name] || `${input.type} parameter`;
54
61
  return new Argument(`<${input.name}>`, desc);
package/dist/cmds/kind.js CHANGED
@@ -1,14 +1,132 @@
1
- import { Command } from "commander";
1
+ import { Argument, Command, Option } from "commander";
2
2
  import { abi } from "../abi.js";
3
3
  import { getCommandGen, makeFuncName } from "../cmdgen.js";
4
- const cmdGenConfig = {
4
+ import { toHex } from "viem";
5
+ import { coerceValue, readKindElements, toElementType, toRelationId } from "../utils.js";
6
+ import { outputOptions, writeOptions } from "../commander-patch.js";
7
+ import { submitSimulation } from "../ethereum.js";
8
+ import { Logger } from "../logger.js";
9
+ import { FromOpts } from "../from-opts.js";
10
+ const genKindCmd = getCommandGen({
5
11
  getFuncName: (cmdName) => makeFuncName(cmdName, `kind`),
6
12
  getAbiFuncs: (funcName) => abi.funcs.kindRegistry.filter((i) => i.name == funcName),
7
13
  // eslint-disable-next-line
8
14
  getAbiNonFuncs: (funcName) => abi.nonFuncs.kindRegistry,
9
15
  // eslint-disable-next-line
10
16
  getContract: (conf, args, abiFunc) => conf.contracts.KindRegistry,
11
- };
12
- const cmdGen = getCommandGen(cmdGenConfig);
13
- const subCmds = "register,upgrade,touch,transfer,owner,descriptor,snapshot".split(",");
14
- export const kindCmd = new Command("kind").description("manage kinds").addCommands(subCmds.map(cmdGen));
17
+ });
18
+ const registerCmd = genRegisterCmd();
19
+ const updateCmd = genUpdateCmd();
20
+ const otherCmds = "upgrade,touch,transfer,owner,descriptor,snapshot".split(",").map(genKindCmd);
21
+ export const kindCmd = new Command("kind")
22
+ .description("manage kinds")
23
+ .addCommand(registerCmd)
24
+ .addCommand(updateCmd)
25
+ .addCommands(otherCmds);
26
+ function genRegisterCmd() {
27
+ const funcName = "kindRegister";
28
+ const abiFuncs = abi.funcs.kindRegistry.filter((i) => i.name == funcName);
29
+ const abiNonFuncs = abi.nonFuncs.kindRegistry;
30
+ const code = new Argument("<code>", "Matter hash of the kind code");
31
+ const data = new Argument("<data>", "Matter hash of the kind data");
32
+ const etys = new Option("--elements <ety...>", "Element types");
33
+ const rels = new Option("--relations <rel...>", "IDs of relations supported");
34
+ const options = [etys, rels, ...writeOptions, ...outputOptions];
35
+ const args = [code, data];
36
+ function resolveElementTypes(args) {
37
+ return args.length == 0
38
+ ? []
39
+ : args.length == 1 && /\.wasm$/i.test(args[0])
40
+ ? readKindElements(args[0])
41
+ : args.map(toElementType);
42
+ }
43
+ function getFuncArgs(cmd) {
44
+ const opts = cmd.opts();
45
+ const etys = resolveElementTypes(opts.elements ?? []);
46
+ const rels = (opts.relations ?? []).map(toRelationId);
47
+ const args = cmd.args.map((arg, i) => coerceValue(arg, abiFuncs[0].inputs[i]));
48
+ args.push(etys, rels);
49
+ return args;
50
+ }
51
+ async function getCmdAction(cmd) {
52
+ const opts = cmd.opts();
53
+ const args = getFuncArgs(cmd);
54
+ const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
55
+ const address = conf.contracts.KindRegistry;
56
+ const account = walletClient.account;
57
+ const simulation = {
58
+ address,
59
+ abi: [...abiFuncs, ...abiNonFuncs],
60
+ functionName: funcName,
61
+ args,
62
+ account,
63
+ };
64
+ await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
65
+ }
66
+ return new Command("register")
67
+ .description("Register a new kind")
68
+ .addOptions(options)
69
+ .addArguments(args)
70
+ .action(getCmdAction);
71
+ }
72
+ function genUpdateCmd() {
73
+ const funcName = "kindUpdate";
74
+ const abiFuncs = abi.funcs.kindRegistry.filter((i) => i.name == funcName);
75
+ const abiNonFuncs = abi.nonFuncs.kindRegistry;
76
+ const code = new Option("--code <code>", "Matter hash of the kind code");
77
+ const data = new Option("--data <data>", "Matter hash of the kind data");
78
+ const rels = new Option("--relations [rel...]", "IDs of relations supported");
79
+ const id = new Argument(`<id>`, "Kind id");
80
+ const options = [code, data, rels, ...writeOptions, ...outputOptions];
81
+ const args = [id];
82
+ function getFuncArgs(cmd) {
83
+ const opts = cmd.opts();
84
+ const args = [cmd.args[0]];
85
+ const ZERO32 = toHex(0, { size: 32 });
86
+ let sig;
87
+ if (opts.relations) {
88
+ if (opts.code || opts.data) {
89
+ sig = "kindUpdate(uint64,bytes32,bytes32,uint64[])";
90
+ args.push(opts.code ?? ZERO32);
91
+ args.push(opts.data ?? ZERO32);
92
+ args.push(typeof opts.relations == "boolean" ? [] : opts.relations);
93
+ }
94
+ else {
95
+ sig = "kindUpdate(uint64,uint64[])";
96
+ args.push(typeof opts.relations == "boolean" ? [] : opts.relations);
97
+ }
98
+ }
99
+ else {
100
+ if (opts.code || opts.data) {
101
+ sig = "kindUpdate(uint64,bytes32,bytes32)";
102
+ args.push(opts.code ?? ZERO32);
103
+ args.push(opts.data ?? ZERO32);
104
+ }
105
+ else {
106
+ throw new Error("");
107
+ }
108
+ }
109
+ const abiFunc = abiFuncs.filter((f) => f._metadata.signature == sig)[0];
110
+ return [args, abiFunc];
111
+ }
112
+ async function getCmdAction(cmd) {
113
+ const opts = cmd.opts();
114
+ const [args, abiFunc] = getFuncArgs(cmd);
115
+ const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
116
+ const address = conf.contracts.KindRegistry;
117
+ const account = walletClient.account;
118
+ const simulation = {
119
+ address,
120
+ abi: [abiFunc, ...abiNonFuncs],
121
+ functionName: "kindUpdate",
122
+ args,
123
+ account,
124
+ };
125
+ await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
126
+ }
127
+ return new Command("update")
128
+ .description("Update an existing kind")
129
+ .addOptions(options)
130
+ .addArguments(args)
131
+ .action(getCmdAction);
132
+ }
@@ -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,298 +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
- const mapValues = ({ header, value }) => {
233
- const v = value.trim();
234
- if (v.startsWith("0x")) {
235
- return fromHex(pad(v, { size: 32, dir: "left" }), "bytes");
236
- }
237
- else if (header == "IMAGE" || header == "JSON") {
238
- const { hash, mime, form, path } = computeHashMemo(v);
239
- if (!deps.has(path)) {
240
- deps.set(path, { hash, mime, form, path });
241
- }
242
- return fromHex(hash, "bytes");
243
- }
244
- else {
245
- throw new Error("expect hex strings for cols other than IMAGE, JSON");
246
- }
247
- };
248
- const aux = parseAuxData(file, { mapValues, mapHeaders });
249
- const parser = csv({ skipComments: true, strict: true, mapHeaders, mapValues });
250
- const blob = await new Promise((resolve, reject) => {
251
- const content = [];
252
- fs.createReadStream(file)
253
- .pipe(parser)
254
- .on("headers", (headers) => {
255
- colTypes = headers;
256
- })
257
- .on("data", (data) => {
258
- Object.values(data).forEach((value) => content.push(value));
259
- rows += 1;
260
- })
261
- .on("end", () => {
262
- const auxTypes = Object.keys(aux ?? {});
263
- const auxValues = Object.values(aux ?? {});
264
- const enumHeader = buildEnumHeader(auxTypes, colTypes, rows);
265
- resolve(Buffer.concat([enumHeader, ...auxValues, ...content]));
266
- })
267
- .on("error", (e /* eslint-disable-line */) => {
268
- reject(e);
269
- });
270
- });
271
- return { blob, deps: Array.from(deps.values()) };
272
- }
273
- async function compilePermMatter(file) {
274
- void file;
275
- throw new Error("unimplemented");
276
- }
277
- function computeHash(form, mime, blob) {
278
- if (form < 0 || form > 255) {
279
- throw new Error("form must be uint8 (0..255)");
280
- }
281
- const mimeUTF8 = new TextEncoder().encode(mime);
282
- if (mimeUTF8.length <= 0 || mimeUTF8.length > 31) {
283
- throw new Error("form must be uint8 (0..255)");
284
- }
285
- const msg = new Uint8Array(32 + blob.length);
286
- msg[0] = form & 0xff;
287
- msg.set(pad(mimeUTF8, { size: 32, dir: "right" }), 1);
288
- msg.set(blob, 32);
289
- // SHA256(form:1 || mime:31 || blob:var)
290
- return sha256(msg);
291
- }
292
- function computeHashFile(file) {
293
- const spec = parseFileSpec(file);
294
- if (spec.type != "blob") {
295
- throw new Error("not blob type");
296
- }
297
- let form, mime;
298
- if (spec.form && spec.mime) {
299
- form = spec.form;
300
- mime = spec.mime;
301
- }
302
- else {
303
- const g = inferMatterMeta(spec.path);
304
- form = spec.form ?? g.form;
305
- mime = spec.mime ?? g.mime;
306
- }
307
- const hash = computeHash(form, mime, loadBinary(spec.path));
308
- return { path: spec.path, form, mime, hash };
309
- }
310
- /**
311
- * Build a 32-byte EnumMatter header.
312
- * Layout:
313
- * [0..3] = "ENUM"
314
- * [4] = ver, aux (u8.hi4, u8.lo4)
315
- * [5] = cols (u8)
316
- * [6..7] = rows (u16 LE)
317
- * [8..15] = aux_types [u8; 8]
318
- * [16..31] = col_types [u8; 16]
319
- */
320
- function buildEnumHeader(auxTypes, colTypes, rows = 0) {
321
- if (colTypes.length === 0 && auxTypes.length === 0) {
322
- throw new Error("Empty header list");
323
- }
324
- if (auxTypes.length > 8) {
325
- throw new Error(`Too many aux types (max 8): ${auxTypes.length}`);
326
- }
327
- if (colTypes.length > 16) {
328
- throw new Error(`Too many column types (max 16): ${colTypes.length}`);
329
- }
330
- if (rows < 0 || rows > 0xffff) {
331
- throw new Error(`Rows out of range: ${rows}`);
332
- }
333
- const buf = new Uint8Array(32);
334
- buf.set([0x45, 0x4e, 0x55, 0x4d], 0); // magic "ENUM"
335
- buf[4] = (0x01 << 4) | (auxTypes.length & 0x0f); // version(hi4), aux(lo4)
336
- buf[5] = colTypes.length; // cols
337
- buf[6] = rows & 0xff; // rows.lo
338
- buf[7] = (rows >>> 8) & 0xff; // rows.hi
339
- auxTypes.forEach((t, i) => (buf[8 + i] = formByName(t))); // aux types
340
- colTypes.forEach((t, i) => (buf[16 + i] = formByName(t))); // col types
341
- return buf;
342
- }
343
- const MATTER_FORMS = {
344
- // Simple
345
- JSON: 0x01,
346
- IMAGE: 0x02,
347
- // Code
348
- WASM: 0xc0,
349
- // Data
350
- ENUM: 0xd0,
351
- PERM: 0xd1,
352
- // Info
353
- INFO: 0xff,
354
- };
355
- const CELL_FORMS = {
356
- JSON: 0x01,
357
- IMAGE: 0x02,
358
- INFO: 0xff,
359
- };
360
- function formByName(name) {
361
- const value = MATTER_FORMS[name];
362
- if (value === undefined) {
363
- throw new Error(`Invalid form name: "${name}"`);
364
- }
365
- return value;
366
- }
367
- function inferMatterMeta(filePath) {
368
- const ext = path.extname(filePath);
369
- switch (ext) {
370
- case ".wasm":
371
- return { mime: "application/wasm", form: MATTER_FORMS.WASM };
372
- case ".json":
373
- return { mime: "application/json", form: MATTER_FORMS.JSON };
374
- case ".jpg":
375
- return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
376
- case ".jpeg":
377
- return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
378
- case ".png":
379
- return { mime: "image/png", form: MATTER_FORMS.IMAGE };
380
- case ".bin":
381
- if (filePath.endsWith(".enum.bin")) {
382
- return { mime: "application/vnd.every.enum", form: MATTER_FORMS.ENUM };
383
- }
384
- else if (filePath.endsWith(".perm.bin")) {
385
- return { mime: "application/vnd.every.perm", form: MATTER_FORMS.PERM };
386
- }
387
- else {
388
- throw new Error("unknown matter file extension");
389
- }
390
- default:
391
- throw new Error("unknown matter file extension");
392
- }
393
- }
394
- function parseAuxData(filePath, options) {
395
- const lines = readPrologue(filePath)
396
- .filter((l) => /^\s*#\s*@aux\s+/i.test(l))
397
- .map((l) => l.replace(/^\s*#\s*@aux\s+/i, "").trim());
398
- const mapHeaders = options.mapHeaders ?? (({ header }) => header);
399
- const mapValues = options.mapValues ?? (({ value }) => value);
400
- if (lines.length == 0) {
401
- return undefined;
402
- }
403
- else if (lines.length == 2) {
404
- const headers = lines[0].split(",").map((header, index) => mapHeaders({ header, index }));
405
- const parts = lines[1].split(",");
406
- if (parts.length != headers.length) {
407
- throw new Error("aux header and values mismatch");
408
- }
409
- const values = parts.map((value, index) => mapValues({ header: headers[index], index, value }));
410
- return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
411
- }
412
- else {
413
- throw new Error("invalid aux data");
414
- }
415
- }
416
- function readPrologue(filePath, maxBytes = 2048) {
417
- const fd = fs.openSync(filePath, "r");
418
- const buf = Buffer.alloc(maxBytes);
419
- const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
420
- fs.closeSync(fd);
421
- const lines = buf.subarray(0, bytesRead).toString("utf8").split(/\r?\n/);
422
- const prologue = [];
423
- for (const line of lines) {
424
- if (line.trim().startsWith("#")) {
425
- prologue.push(line);
426
- }
427
- else if (line.trim().length > 0) {
428
- break; // stop at the first non-comment, non-empty line
429
- }
430
- }
431
- return prologue;
432
- }
@@ -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
+ }
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";
@@ -167,3 +168,35 @@ export function loadBinary(file) {
167
168
  const buf = fs.readFileSync(file);
168
169
  return buf;
169
170
  }
171
+ export function readKindElements(file) {
172
+ const buf = fs.readFileSync(file);
173
+ const mod = new WebAssembly.Module(buf); // sync
174
+ const [section] = WebAssembly.Module.customSections(mod, "kindelms");
175
+ if (!section)
176
+ throw new Error(`Section "kindelms" not found in ${file}`);
177
+ return Array.from(new Uint8Array(section));
178
+ }
179
+ export function toElementType(v) {
180
+ const n = typeof v === "number" ? v : v.startsWith("0x") ? Number.parseInt(v.slice(2), 16) : Number.parseInt(v, 10);
181
+ if (!Number.isInteger(n) || n < 1 || n > 255) {
182
+ throw new Error(`Invalid element type: ${v} (must be 1–255)`);
183
+ }
184
+ return n;
185
+ }
186
+ export function toRelationId(v) {
187
+ const n = typeof v === "bigint" ? v : BigInt(v);
188
+ const U48_MAX = (1n << 48n) - 1n;
189
+ if (n < 1n || n > U48_MAX) {
190
+ throw new Error(`Invalid relation id: ${String(v)} (must be between 1 and ${U48_MAX})`);
191
+ }
192
+ return n;
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,7 @@
1
1
  {
2
2
  "name": "@everyprotocol/every-cli",
3
3
  "type": "module",
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "files": [
6
6
  "dist/",
7
7
  "abis/",