@everyprotocol/every-cli 0.1.3 → 0.1.5
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 +6 -6
- package/dist/abi.js +1 -0
- package/dist/cmdgen.js +51 -139
- package/dist/cmds/balance.js +43 -0
- package/dist/cmds/config.js +46 -0
- package/dist/cmds/kind.js +14 -0
- package/dist/cmds/matter.js +432 -0
- package/dist/cmds/minter.js +31 -0
- package/dist/cmds/object.js +175 -0
- package/dist/cmds/relation.js +16 -0
- package/dist/cmds/set.js +31 -0
- package/dist/cmds/unique.js +14 -0
- package/dist/cmds/universe.js +21 -0
- package/dist/cmds/value.js +14 -0
- package/dist/{wallet.js → cmds/wallet.js} +19 -19
- package/dist/commander-patch.js +64 -0
- package/dist/config.js +18 -93
- package/dist/ethereum.js +33 -0
- package/dist/from-opts.js +161 -0
- package/dist/index.js +3 -2
- package/dist/logger.js +39 -0
- package/dist/parsers.js +70 -0
- package/dist/program.js +28 -39
- package/dist/substrate.js +24 -18
- package/dist/utils.js +131 -184
- package/package.json +5 -1
- package/dist/cmds.js +0 -132
- package/dist/matter.js +0 -96
- package/dist/mint.js +0 -78
- package/dist/options.js +0 -26
- package/dist/relate.js +0 -104
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import "@polkadot/api-augment/substrate";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as JSON11 from "json11";
|
|
5
|
+
import columify from "columnify";
|
|
6
|
+
import { loadBinary, loadJson } from "../utils.js";
|
|
7
|
+
import { submitTransaction } from "../substrate.js";
|
|
8
|
+
import { network } from "../commander-patch.js";
|
|
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";
|
|
14
|
+
const matterRegisterCmd = new Command("register")
|
|
15
|
+
.description("Register matters")
|
|
16
|
+
.argument("<files...>", "Paths of matter blob files")
|
|
17
|
+
.addOption(network)
|
|
18
|
+
.addKeystoreOptions()
|
|
19
|
+
.addOutputOptions()
|
|
20
|
+
.subWriteAction(async function (api, pair, files) {
|
|
21
|
+
const opts = this.opts();
|
|
22
|
+
const console = new Logger(opts);
|
|
23
|
+
const deduper = new Set();
|
|
24
|
+
const inputs = [];
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
const spec = parseFileSpec(file);
|
|
27
|
+
const mi = await toRegisterInput(spec);
|
|
28
|
+
mi.forEach((input) => {
|
|
29
|
+
if (!deduper.has(input.path)) {
|
|
30
|
+
inputs.push(input);
|
|
31
|
+
deduper.add(input.path);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const txns = [];
|
|
36
|
+
for (const { blob, form, mime, path: filePath } of inputs) {
|
|
37
|
+
const content = blob || fs.readFileSync(filePath);
|
|
38
|
+
console.log(`Register matter: form=${form} mime=${mime} blob=${content.length}B ${filePath}`);
|
|
39
|
+
const contentRaw = api.createType("Raw", content, content.length);
|
|
40
|
+
const call = api.tx.every.matterRegister(form, mime, contentRaw);
|
|
41
|
+
console.log(`Transaction submitting...`);
|
|
42
|
+
const txn = await submitTransaction(api, call, pair);
|
|
43
|
+
console.log(`Transaction submitted: ${txn.txHash}`);
|
|
44
|
+
txns.push({ txn, filePath });
|
|
45
|
+
}
|
|
46
|
+
console.log("Waiting for confirmation...");
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const { txn, filePath } of txns) {
|
|
50
|
+
const r = await txn.receipt;
|
|
51
|
+
const header = await api.rpc.chain.getHeader(r.blockHash);
|
|
52
|
+
console.log(`Transaction confirmed: ${txn.txHash} ${filePath}`);
|
|
53
|
+
console.log(`Confirmed in: block ${header.number}, hash ${header.hash}`);
|
|
54
|
+
// 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())]);
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
const receipt = r.events.map((e) => ({ event: e.event.method, data: e.event.data.toJSON() }));
|
|
58
|
+
console.log(columify(events, { showHeaders: false }));
|
|
59
|
+
result.push({
|
|
60
|
+
file: filePath,
|
|
61
|
+
transaction: txn.txHash,
|
|
62
|
+
events: receipt,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
console.result(result);
|
|
66
|
+
});
|
|
67
|
+
const matterCompileCmd = new Command("compile")
|
|
68
|
+
.description("Compile matters from human-readable to binary")
|
|
69
|
+
.argument("<files...>", "Paths to matter files")
|
|
70
|
+
.option("-o, --out <dir>", "Output directory")
|
|
71
|
+
.addOutputOptions()
|
|
72
|
+
.action(async function (files) {
|
|
73
|
+
const opts = this.opts();
|
|
74
|
+
const console = new Logger(opts);
|
|
75
|
+
const specs = files.map((f) => parseFileSpec(f));
|
|
76
|
+
if (specs.find((spec) => spec.type != "raw")) {
|
|
77
|
+
throw new Error("invalid input, only .enum.csv or .perm.csv files supported");
|
|
78
|
+
}
|
|
79
|
+
for (const spec of specs) {
|
|
80
|
+
const { binFile, depsFile, outDir, compile } = getOutFiles(spec.path, opts.out);
|
|
81
|
+
const { blob, deps } = await compile(spec.path);
|
|
82
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
83
|
+
fs.writeFileSync(binFile, blob, "binary");
|
|
84
|
+
console.log(`blob saved to ${binFile}`);
|
|
85
|
+
fs.writeFileSync(depsFile, JSON.stringify(deps));
|
|
86
|
+
console.log(`deps saved to ${depsFile}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const matterHashCmd = new Command("hash")
|
|
90
|
+
.description("Compute matter hashes")
|
|
91
|
+
.argument("<files...>", "Paths of matter blob files")
|
|
92
|
+
.addOutputOptions()
|
|
93
|
+
.action(async function (files) {
|
|
94
|
+
const opts = this.opts();
|
|
95
|
+
const console = new Logger(opts);
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
const outputs = [];
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
const spec = parseFileSpec(file);
|
|
100
|
+
const inputs = await toRegisterInput(spec);
|
|
101
|
+
inputs.forEach((input) => {
|
|
102
|
+
if (!seen.has(input.path)) {
|
|
103
|
+
seen.add(input.path);
|
|
104
|
+
const { form, mime, path } = input;
|
|
105
|
+
const blob = input.blob || loadBinary(path);
|
|
106
|
+
const hash = computeHash(form, mime, blob);
|
|
107
|
+
outputs.push({ hash, form, mime, path });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
console.log(columify(outputs));
|
|
112
|
+
console.result(outputs);
|
|
113
|
+
});
|
|
114
|
+
export const matterPickCmd = new Command("pick")
|
|
115
|
+
.description("Pick matter hashes from a hash output file")
|
|
116
|
+
.argument("<files...>", "Paths of the matter files")
|
|
117
|
+
.option("--from <file>", "Path to the hash output file", "register/hashes.json")
|
|
118
|
+
.action(function (files) {
|
|
119
|
+
const opts = this.opts();
|
|
120
|
+
const list = loadJson(opts.from);
|
|
121
|
+
if (!Array.isArray(list))
|
|
122
|
+
throw new Error(`Expected an array in ${opts.from}`);
|
|
123
|
+
const hashes = list;
|
|
124
|
+
const result = files.map((file) => {
|
|
125
|
+
const rec = hashes.find((r) => r.path === file);
|
|
126
|
+
if (!rec)
|
|
127
|
+
throw new Error(`No hash found for: ${file}`);
|
|
128
|
+
return rec.hash;
|
|
129
|
+
});
|
|
130
|
+
console.log(result.join(" "));
|
|
131
|
+
});
|
|
132
|
+
export const matterCmd = new Command("matter")
|
|
133
|
+
.description("matter utilities")
|
|
134
|
+
.addCommand(matterRegisterCmd)
|
|
135
|
+
.addCommand(matterCompileCmd)
|
|
136
|
+
.addCommand(matterHashCmd)
|
|
137
|
+
.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
|
+
function getOutFiles(file, outDir) {
|
|
201
|
+
const dir = outDir ? path.resolve(outDir) : path.dirname(path.resolve(file));
|
|
202
|
+
if (file.endsWith(".enum.csv")) {
|
|
203
|
+
const baseName = path.basename(file).slice(0, -9);
|
|
204
|
+
const binFile = path.join(dir, `${baseName}.enum.bin`);
|
|
205
|
+
const depsFile = path.join(dir, `${baseName}.enum.deps`);
|
|
206
|
+
return { binFile, depsFile, outDir: dir, compile: compileEnumMatter };
|
|
207
|
+
}
|
|
208
|
+
else if (file.endsWith(".perm.csv")) {
|
|
209
|
+
const baseName = path.basename(file).slice(0, -9);
|
|
210
|
+
const binFile = path.join(dir, `${baseName}.perm.bin`);
|
|
211
|
+
const depsFile = path.join(dir, `${baseName}.perm.deps`);
|
|
212
|
+
return { binFile, depsFile, outDir: dir, compile: compilePermMatter };
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
throw new Error("Unsupported raw matter file extension");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Argument, Command } from "commander";
|
|
2
|
+
import { abi } from "../abi.js";
|
|
3
|
+
import { CommandGenDefaults, getCommandGen, makeFuncName } from "../cmdgen.js";
|
|
4
|
+
const adminCmdConfig = {
|
|
5
|
+
getFuncName: (cmdName) => `${cmdName}MintPolicy`,
|
|
6
|
+
getAbiFuncs: (funcName) => abi.funcs.objectMinterAdmin.filter((i) => i.name == funcName),
|
|
7
|
+
// eslint-disable-next-line
|
|
8
|
+
getAbiNonFuncs: (funcName) => abi.nonFuncs.objectMinter,
|
|
9
|
+
// eslint-disable-next-line
|
|
10
|
+
getContract: (conf, args, abiFunc) => args[0],
|
|
11
|
+
// eslint-disable-next-line
|
|
12
|
+
getFuncArgs: (args, abiFunc) => args.slice(1),
|
|
13
|
+
getCmdArgs: (abiFunc) => [
|
|
14
|
+
new Argument(`<contract>`, "address of the set contract"),
|
|
15
|
+
...CommandGenDefaults.getCmdArgs(abiFunc),
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
const userCmdConfig = {
|
|
19
|
+
getFuncName: (cmdName) => makeFuncName(cmdName, `mintPolicy`),
|
|
20
|
+
getAbiFuncs: (funcName) => abi.funcs.objectMinter.filter((i) => i.name == funcName),
|
|
21
|
+
// eslint-disable-next-line
|
|
22
|
+
getAbiNonFuncs: (funcName) => abi.nonFuncs.objectMinter,
|
|
23
|
+
// eslint-disable-next-line
|
|
24
|
+
getContract: (conf, args, abiFunc) => conf.contracts.ObjectMinter,
|
|
25
|
+
};
|
|
26
|
+
const adminCmds = "add,enable,disable".split(",");
|
|
27
|
+
const userCmds = "count,get,search".split(",");
|
|
28
|
+
export const minterCmd = new Command("minter")
|
|
29
|
+
.description("manage mint policies")
|
|
30
|
+
.addCommands(adminCmds.map(getCommandGen(adminCmdConfig)))
|
|
31
|
+
.addCommands(userCmds.map(getCommandGen(userCmdConfig)));
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Argument, Command } from "commander";
|
|
2
|
+
import { createPublicClient, http, parseUnits } from "viem";
|
|
3
|
+
import { parseAbiItem, erc1155Abi, erc721Abi } from "viem";
|
|
4
|
+
import { abi } from "../abi.js";
|
|
5
|
+
import { submitSimulation } from "../ethereum.js";
|
|
6
|
+
import { Logger } from "../logger.js";
|
|
7
|
+
import { parseBigInt, parseNode3, parseNode4, parseSID } from "../parsers.js";
|
|
8
|
+
import { CommandGenDefaults, getCommandGen } from "../cmdgen.js";
|
|
9
|
+
import { FromOpts } from "../from-opts.js";
|
|
10
|
+
import { coerceValue } from "../utils.js";
|
|
11
|
+
const objectRelateCmd = new Command()
|
|
12
|
+
.name("relate")
|
|
13
|
+
.description("Link a tail object to a head object through a relation")
|
|
14
|
+
.addWriteOptions()
|
|
15
|
+
.argument("<tail>", "tail node, in form of [[data.]grant.]set.id", parseNode4)
|
|
16
|
+
.argument("<rel>", "relation ID", parseBigInt)
|
|
17
|
+
.argument("<head>", "head node in form of [grant.]set.id, ", parseNode3)
|
|
18
|
+
.action(async function () {
|
|
19
|
+
await relateAction(this, "relate");
|
|
20
|
+
});
|
|
21
|
+
const objectUnrelateCmd = new Command()
|
|
22
|
+
.name("unrelate")
|
|
23
|
+
.description("Unlinks a tail object from a head object")
|
|
24
|
+
.addWriteOptions()
|
|
25
|
+
.argument("<tail>", "tail node, in form of [[data.]grant.]set.id", parseNode4)
|
|
26
|
+
.argument("<rel>", "relation ID", parseBigInt)
|
|
27
|
+
.argument("<head>", "head node in form of [grant.]set.id, ", parseNode3)
|
|
28
|
+
.action(async function () {
|
|
29
|
+
await relateAction(this, "unrelate");
|
|
30
|
+
});
|
|
31
|
+
const objectMintCmd = new Command()
|
|
32
|
+
.name("mint")
|
|
33
|
+
.description("Mint an object via the object minter or directly from the set")
|
|
34
|
+
.option("--to <address>", "specify the recipient")
|
|
35
|
+
.option("--value <amount>", "the amount of ETH to send together", "0")
|
|
36
|
+
.option("--auth <data>", "authorization data for a permissioned mint", "0x")
|
|
37
|
+
.option("--policy <index>", "the index number of the mint policy", "0")
|
|
38
|
+
.option("--no-minter", "mint directly from set contract instead of using ObjectMinter")
|
|
39
|
+
.addWriteOptions()
|
|
40
|
+
.argument("<sid>", "scoped object ID, in form of set.id (e.g., 17.1)")
|
|
41
|
+
.argument("[data]", "additional input data", "0x")
|
|
42
|
+
.action(mintAction);
|
|
43
|
+
const objectSendCmd = new Command("send")
|
|
44
|
+
.description("Call a function by signature (dry-run: prints calldata)")
|
|
45
|
+
.option("--sig <sig>", "Function signature, e.g. 'transfer(address,uint256)'")
|
|
46
|
+
.argument("<args...>", "Function arguments (arrays/tuples as JSON)")
|
|
47
|
+
.addWriteOptions()
|
|
48
|
+
.action(sendAction);
|
|
49
|
+
const cmdGenConfig = {
|
|
50
|
+
getFuncName: (cmdName) => cmdName,
|
|
51
|
+
getAbiFuncs: (funcName) => abi.funcs.setContract.filter((i) => i.name == funcName),
|
|
52
|
+
// eslint-disable-next-line
|
|
53
|
+
getAbiNonFuncs: (funcName) => [...abi.nonFuncs.setContract],
|
|
54
|
+
// eslint-disable-next-line
|
|
55
|
+
getContract: async function (conf, args, abiFunc) {
|
|
56
|
+
const publicClient = createPublicClient({ transport: http(conf.rpc) });
|
|
57
|
+
const address = await publicClient.readContract({
|
|
58
|
+
address: conf.contracts.SetRegistry,
|
|
59
|
+
abi: abi.setContract,
|
|
60
|
+
functionName: "setContract",
|
|
61
|
+
args: [args[0].set],
|
|
62
|
+
});
|
|
63
|
+
return address;
|
|
64
|
+
},
|
|
65
|
+
// eslint-disable-next-line
|
|
66
|
+
getFuncArgs: function (args, abiFunc) {
|
|
67
|
+
return abiFunc.name == "uri" ? args.slice(1) : [args[0].id, ...args.slice(1)];
|
|
68
|
+
},
|
|
69
|
+
getCmdArgs: function (abiFunc) {
|
|
70
|
+
const sid = new Argument(`<sid>`, "sid of the object").argParser(parseSID);
|
|
71
|
+
const args0 = CommandGenDefaults.getCmdArgs(abiFunc);
|
|
72
|
+
return abiFunc.name == "uri" ? [sid] : [sid, ...args0.slice(1)];
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const cmdGen = getCommandGen(cmdGenConfig);
|
|
76
|
+
const writeCmds = "upgrade,touch,transfer".split(",");
|
|
77
|
+
const readCmds = "owner,descriptor,snapshot,uri".split(",");
|
|
78
|
+
export const objectCmd = new Command("object")
|
|
79
|
+
.description("manage objects")
|
|
80
|
+
.addCommands(writeCmds.map(cmdGen))
|
|
81
|
+
.addCommand(objectMintCmd)
|
|
82
|
+
.addCommand(objectRelateCmd)
|
|
83
|
+
.addCommand(objectUnrelateCmd)
|
|
84
|
+
.addCommand(objectSendCmd)
|
|
85
|
+
.addCommands(readCmds.map(cmdGen));
|
|
86
|
+
async function mintAction() {
|
|
87
|
+
const opts = this.opts();
|
|
88
|
+
const args0 = this.args;
|
|
89
|
+
const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
|
|
90
|
+
const setRegistry = conf.contracts["SetRegistry"];
|
|
91
|
+
const account = walletClient.account;
|
|
92
|
+
const [set, id] = args0[0].split(".");
|
|
93
|
+
const setContract = (await publicClient.readContract({
|
|
94
|
+
address: setRegistry,
|
|
95
|
+
abi: abi.setContract,
|
|
96
|
+
functionName: "setContract",
|
|
97
|
+
args: [BigInt(set)],
|
|
98
|
+
}));
|
|
99
|
+
const value = parseUnits(opts.value || "0", 18);
|
|
100
|
+
const recipientAddress = (opts.to || account.address);
|
|
101
|
+
const mintData = (args0[1] || "0x");
|
|
102
|
+
const simulation = opts.minter
|
|
103
|
+
? {
|
|
104
|
+
address: conf.contracts["ObjectMinter"],
|
|
105
|
+
abi: abi.mint,
|
|
106
|
+
functionName: "mint",
|
|
107
|
+
args: [recipientAddress, setContract, BigInt(id), mintData, opts.auth || "0x", Number(opts.policy || "0")],
|
|
108
|
+
account,
|
|
109
|
+
value,
|
|
110
|
+
}
|
|
111
|
+
: {
|
|
112
|
+
address: setContract,
|
|
113
|
+
abi: abi.create,
|
|
114
|
+
functionName: "create",
|
|
115
|
+
args: [recipientAddress, BigInt(id), mintData],
|
|
116
|
+
account,
|
|
117
|
+
value,
|
|
118
|
+
};
|
|
119
|
+
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
120
|
+
}
|
|
121
|
+
async function relateAction(cmd, functionName) {
|
|
122
|
+
const opts = cmd.opts();
|
|
123
|
+
const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
|
|
124
|
+
const address = conf.contracts["OmniRegistry"];
|
|
125
|
+
const simulation = {
|
|
126
|
+
address,
|
|
127
|
+
abi: abi.relation,
|
|
128
|
+
functionName,
|
|
129
|
+
args: cmd.args,
|
|
130
|
+
account: walletClient.account,
|
|
131
|
+
};
|
|
132
|
+
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
133
|
+
}
|
|
134
|
+
async function sendAction() {
|
|
135
|
+
const opts = this.opts();
|
|
136
|
+
const args0 = this.args;
|
|
137
|
+
const { sig } = this.opts();
|
|
138
|
+
if (!sig) {
|
|
139
|
+
console.error("Error: --sig is required (e.g. --sig 'transfer(address,uint256)')");
|
|
140
|
+
this.exitOverride();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const item = parseAbiItem(`function ${sig}`);
|
|
144
|
+
if (item.type !== "function")
|
|
145
|
+
throw new Error(`Not a function signature: ${sig}`);
|
|
146
|
+
const abiFunc = item;
|
|
147
|
+
const params = abiFunc.inputs ?? [];
|
|
148
|
+
if (args0.length !== params.length)
|
|
149
|
+
throw new Error(`Argument count mismatch: expected ${params.length}, got ${args0.length}`);
|
|
150
|
+
const sidIndex = params.findIndex((p) => p.type == "uint64");
|
|
151
|
+
if (sidIndex == -1)
|
|
152
|
+
throw new Error("SID type(uint64) not found in signature");
|
|
153
|
+
const [setId, objectId] = args0[sidIndex].split(".");
|
|
154
|
+
args0[sidIndex] = objectId;
|
|
155
|
+
const args = args0.map((a, i) => coerceValue(a, params[i]));
|
|
156
|
+
const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
|
|
157
|
+
const setRegistry = conf.contracts["SetRegistry"];
|
|
158
|
+
const account = walletClient.account;
|
|
159
|
+
const setContract = (await publicClient.readContract({
|
|
160
|
+
address: setRegistry,
|
|
161
|
+
abi: abi.funcs.setRegistry,
|
|
162
|
+
functionName: "setContract",
|
|
163
|
+
args: [BigInt(setId)],
|
|
164
|
+
}));
|
|
165
|
+
const value = parseUnits(opts.value ?? "0", 18);
|
|
166
|
+
const simulation = {
|
|
167
|
+
address: setContract,
|
|
168
|
+
abi: [abiFunc, ...abi.nonFuncs.setContract, ...erc1155Abi, ...erc721Abi],
|
|
169
|
+
functionName: abiFunc.name,
|
|
170
|
+
args: args,
|
|
171
|
+
account,
|
|
172
|
+
value,
|
|
173
|
+
};
|
|
174
|
+
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
175
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { abi } from "../abi.js";
|
|
3
|
+
import { getCommandGen, makeFuncName } from "../cmdgen.js";
|
|
4
|
+
const cmdGenConfig = {
|
|
5
|
+
getFuncName: (cmdName) => makeFuncName(cmdName, `relation`),
|
|
6
|
+
getAbiFuncs: (funcName) => abi.funcs.omniRegistry.filter((i) => i.name == funcName),
|
|
7
|
+
// eslint-disable-next-line
|
|
8
|
+
getAbiNonFuncs: (funcName) => abi.nonFuncs.omniRegistry,
|
|
9
|
+
// eslint-disable-next-line
|
|
10
|
+
getContract: (conf, args, abiFunc) => conf.contracts.OmniRegistry,
|
|
11
|
+
// eslint-disable-next-line
|
|
12
|
+
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));
|
package/dist/cmds/set.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Argument, Command } from "commander";
|
|
2
|
+
import { abi } from "../abi.js";
|
|
3
|
+
import { CommandGenDefaults, getCommandGen, makeFuncName } from "../cmdgen.js";
|
|
4
|
+
const adminCmdConfig = {
|
|
5
|
+
getFuncName: (cmdName) => `${cmdName}Set`,
|
|
6
|
+
getAbiFuncs: (funcName) => abi.funcs.setRegistryAdmin.filter((i) => i.name == funcName),
|
|
7
|
+
// eslint-disable-next-line
|
|
8
|
+
getAbiNonFuncs: (funcName) => abi.nonFuncs.setRegistry,
|
|
9
|
+
// eslint-disable-next-line
|
|
10
|
+
getContract: (conf, args, abiFunc) => args[0],
|
|
11
|
+
// eslint-disable-next-line
|
|
12
|
+
getFuncArgs: (args, abiFunc) => args.slice(1),
|
|
13
|
+
getCmdArgs: (abiFunc) => [
|
|
14
|
+
new Argument(`<contract>`, "address of the set contract"),
|
|
15
|
+
...CommandGenDefaults.getCmdArgs(abiFunc),
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
const userCmdConfig = {
|
|
19
|
+
getFuncName: (cmdName) => makeFuncName(cmdName, `set`),
|
|
20
|
+
getAbiFuncs: (funcName) => abi.funcs.setRegistry.filter((i) => i.name == funcName),
|
|
21
|
+
// eslint-disable-next-line
|
|
22
|
+
getAbiNonFuncs: (funcName) => abi.nonFuncs.setRegistry,
|
|
23
|
+
// eslint-disable-next-line
|
|
24
|
+
getContract: (conf, args, abiFunc) => conf.contracts.SetRegistry,
|
|
25
|
+
};
|
|
26
|
+
const userCmds = "owner,descriptor,snapshot".split(",");
|
|
27
|
+
const adminCmds = "register,update,upgrade,touch".split(",");
|
|
28
|
+
export const setCmd = new Command("set")
|
|
29
|
+
.description("manage sets")
|
|
30
|
+
.addCommands(adminCmds.map(getCommandGen(adminCmdConfig)))
|
|
31
|
+
.addCommands(userCmds.map(getCommandGen(userCmdConfig)));
|