@safe-vanity-blockie/core 0.3.0
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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/address.d.ts +26 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +61 -0
- package/dist/address.js.map +1 -0
- package/dist/blo.d.ts +15 -0
- package/dist/blo.d.ts.map +1 -0
- package/dist/blo.js +86 -0
- package/dist/blo.js.map +1 -0
- package/dist/hex.d.ts +4 -0
- package/dist/hex.d.ts.map +1 -0
- package/dist/hex.js +26 -0
- package/dist/hex.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/keccak.d.ts +8 -0
- package/dist/keccak.d.ts.map +1 -0
- package/dist/keccak.js +15 -0
- package/dist/keccak.js.map +1 -0
- package/dist/miner.d.ts +45 -0
- package/dist/miner.d.ts.map +1 -0
- package/dist/miner.js +97 -0
- package/dist/miner.js.map +1 -0
- package/dist/scoring.d.ts +27 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +185 -0
- package/dist/scoring.js.map +1 -0
- package/dist/select.d.ts +47 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +49 -0
- package/dist/select.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +140 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
- package/src/address.ts +89 -0
- package/src/blo.ts +100 -0
- package/src/hex.ts +25 -0
- package/src/index.ts +60 -0
- package/src/keccak.ts +17 -0
- package/src/miner.ts +133 -0
- package/src/scoring.ts +209 -0
- package/src/select.ts +77 -0
- package/src/templates.ts +163 -0
- package/src/types.ts +66 -0
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Largest-remainder apportionment: split `budget` across `rawWeights` proportionally using
|
|
3
|
+
* integers that sum to exactly `budget`.
|
|
4
|
+
*
|
|
5
|
+
* The spec's `Math.round(raw / total * budget)` does not do this — it yields 57 for a 3-stroke
|
|
6
|
+
* mouth and 62 for a 2-stroke one, silently making some expressions worth more than others.
|
|
7
|
+
*/
|
|
8
|
+
export function apportion(rawWeights, budget) {
|
|
9
|
+
if (rawWeights.length === 0)
|
|
10
|
+
return [];
|
|
11
|
+
const total = rawWeights.reduce((a, b) => a + b, 0);
|
|
12
|
+
if (total <= 0)
|
|
13
|
+
throw new Error('apportion: raw weights must sum to a positive number');
|
|
14
|
+
const exact = rawWeights.map((weight) => (weight * budget) / total);
|
|
15
|
+
const shares = exact.map((value) => Math.floor(value));
|
|
16
|
+
const remainder = budget - shares.reduce((a, b) => a + b, 0);
|
|
17
|
+
const order = exact
|
|
18
|
+
.map((value, index) => ({ index, fraction: value - Math.floor(value) }))
|
|
19
|
+
.sort((a, b) => b.fraction - a.fraction || a.index - b.index);
|
|
20
|
+
for (let i = 0; i < remainder; i++)
|
|
21
|
+
shares[order[i].index] += 1;
|
|
22
|
+
return shares;
|
|
23
|
+
}
|
|
24
|
+
export function compileFace(spec) {
|
|
25
|
+
const used = new Set();
|
|
26
|
+
const claim = (index, where) => {
|
|
27
|
+
if (!Number.isInteger(index) || index < 0 || index > 31) {
|
|
28
|
+
throw new Error(`${where}: cell index ${index} must be an integer between 0 and 31`);
|
|
29
|
+
}
|
|
30
|
+
if (used.has(index))
|
|
31
|
+
throw new Error(`${where}: cell index ${index} is used more than once`);
|
|
32
|
+
used.add(index);
|
|
33
|
+
};
|
|
34
|
+
const fixedIndex = new Uint8Array(spec.fixed.length);
|
|
35
|
+
const fixedTarget = new Uint8Array(spec.fixed.length);
|
|
36
|
+
const fixedWeight = new Int32Array(spec.fixed.length);
|
|
37
|
+
let maxScore = 0;
|
|
38
|
+
spec.fixed.forEach((cell, i) => {
|
|
39
|
+
claim(cell.index, `fixed cell ${i}`);
|
|
40
|
+
fixedIndex[i] = cell.index;
|
|
41
|
+
fixedTarget[i] = cell.value;
|
|
42
|
+
fixedWeight[i] = cell.weight;
|
|
43
|
+
maxScore += cell.weight;
|
|
44
|
+
});
|
|
45
|
+
const regionIndexStart = new Int32Array(spec.regions.length);
|
|
46
|
+
const regionLength = new Int32Array(spec.regions.length);
|
|
47
|
+
const regionAltCount = new Int32Array(spec.regions.length);
|
|
48
|
+
const regionCellStart = new Int32Array(spec.regions.length);
|
|
49
|
+
const indices = [];
|
|
50
|
+
const cells = [];
|
|
51
|
+
const weights = [];
|
|
52
|
+
const regionNames = [];
|
|
53
|
+
const regionAltNames = [];
|
|
54
|
+
spec.regions.forEach((region, r) => {
|
|
55
|
+
regionIndexStart[r] = indices.length;
|
|
56
|
+
regionCellStart[r] = cells.length;
|
|
57
|
+
regionLength[r] = region.indices.length;
|
|
58
|
+
regionAltCount[r] = region.alternatives.length;
|
|
59
|
+
regionNames.push(region.name);
|
|
60
|
+
regionAltNames.push(region.alternatives.map((alternative) => alternative.name));
|
|
61
|
+
if (region.alternatives.length === 0) {
|
|
62
|
+
throw new Error(`region "${region.name}": needs at least one alternative`);
|
|
63
|
+
}
|
|
64
|
+
for (const index of region.indices)
|
|
65
|
+
claim(index, `region "${region.name}"`);
|
|
66
|
+
indices.push(...region.indices);
|
|
67
|
+
for (const alternative of region.alternatives) {
|
|
68
|
+
if (alternative.cells.length !== region.indices.length) {
|
|
69
|
+
throw new Error(`region "${region.name}", alternative "${alternative.name}": expected ` +
|
|
70
|
+
`${region.indices.length} cells, got ${alternative.cells.length}`);
|
|
71
|
+
}
|
|
72
|
+
const raw = alternative.cells.map((cell) => cell === 1 ? region.strokeWeight : region.bgWeight);
|
|
73
|
+
cells.push(...alternative.cells);
|
|
74
|
+
weights.push(...apportion(raw, region.budget));
|
|
75
|
+
}
|
|
76
|
+
maxScore += region.budget;
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
name: spec.name,
|
|
80
|
+
maxScore,
|
|
81
|
+
nFixed: spec.fixed.length,
|
|
82
|
+
fixedIndex,
|
|
83
|
+
fixedTarget,
|
|
84
|
+
fixedWeight,
|
|
85
|
+
nRegions: spec.regions.length,
|
|
86
|
+
regionIndexStart,
|
|
87
|
+
regionLength,
|
|
88
|
+
regionAltCount,
|
|
89
|
+
regionCellStart,
|
|
90
|
+
regionIndex: Uint8Array.from(indices),
|
|
91
|
+
regionCells: Uint8Array.from(cells),
|
|
92
|
+
regionWeights: Int32Array.from(weights),
|
|
93
|
+
regionNames,
|
|
94
|
+
regionAltNames,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Builds the hot-path scorer. Everything is hoisted into locals so the returned closure is
|
|
99
|
+
* monomorphic: integer-only, no allocation, no division, no property lookups per candidate.
|
|
100
|
+
*/
|
|
101
|
+
export function makeScorer(face) {
|
|
102
|
+
const { nFixed, fixedIndex, fixedTarget, fixedWeight, nRegions, regionIndexStart, regionLength, regionAltCount, regionCellStart, regionIndex, regionCells, regionWeights, } = face;
|
|
103
|
+
return function score(data) {
|
|
104
|
+
let total = 0;
|
|
105
|
+
for (let i = 0; i < nFixed; i++) {
|
|
106
|
+
if (data[fixedIndex[i]] === fixedTarget[i])
|
|
107
|
+
total += fixedWeight[i];
|
|
108
|
+
}
|
|
109
|
+
for (let r = 0; r < nRegions; r++) {
|
|
110
|
+
const indexBase = regionIndexStart[r];
|
|
111
|
+
const length = regionLength[r];
|
|
112
|
+
const altCount = regionAltCount[r];
|
|
113
|
+
const cellBase = regionCellStart[r];
|
|
114
|
+
let best = 0;
|
|
115
|
+
for (let a = 0; a < altCount; a++) {
|
|
116
|
+
const offset = cellBase + a * length;
|
|
117
|
+
let got = 0;
|
|
118
|
+
for (let j = 0; j < length; j++) {
|
|
119
|
+
if (data[regionIndex[indexBase + j]] === regionCells[offset + j]) {
|
|
120
|
+
got += regionWeights[offset + j];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (got > best)
|
|
124
|
+
best = got;
|
|
125
|
+
}
|
|
126
|
+
total += best;
|
|
127
|
+
}
|
|
128
|
+
return total;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Off the hot path: which alternative won in each region, plus the total score. */
|
|
132
|
+
export function describeMatch(face, data) {
|
|
133
|
+
let total = 0;
|
|
134
|
+
for (let i = 0; i < face.nFixed; i++) {
|
|
135
|
+
if (data[face.fixedIndex[i]] === face.fixedTarget[i])
|
|
136
|
+
total += face.fixedWeight[i];
|
|
137
|
+
}
|
|
138
|
+
const regions = {};
|
|
139
|
+
for (let r = 0; r < face.nRegions; r++) {
|
|
140
|
+
const indexBase = face.regionIndexStart[r];
|
|
141
|
+
const length = face.regionLength[r];
|
|
142
|
+
const cellBase = face.regionCellStart[r];
|
|
143
|
+
let best = -1;
|
|
144
|
+
let bestAlt = 0;
|
|
145
|
+
for (let a = 0; a < face.regionAltCount[r]; a++) {
|
|
146
|
+
const offset = cellBase + a * length;
|
|
147
|
+
let got = 0;
|
|
148
|
+
for (let j = 0; j < length; j++) {
|
|
149
|
+
if (data[face.regionIndex[indexBase + j]] === face.regionCells[offset + j]) {
|
|
150
|
+
got += face.regionWeights[offset + j];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (got > best) {
|
|
154
|
+
best = got;
|
|
155
|
+
bestAlt = a;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
total += best;
|
|
159
|
+
regions[face.regionNames[r]] = face.regionAltNames[r][bestAlt];
|
|
160
|
+
}
|
|
161
|
+
return { score: total, regions };
|
|
162
|
+
}
|
|
163
|
+
/** True when the grid never uses blo's spot colour, i.e. the blockie renders in two colours. */
|
|
164
|
+
export function isTwoColor(data) {
|
|
165
|
+
for (let i = 0; i < data.length; i++)
|
|
166
|
+
if (data[i] === 2)
|
|
167
|
+
return false;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
/** blo emits h 0-360, s 0-100, l 0-100. Returns r/g/b in 0-255. */
|
|
171
|
+
export function hslToRgb(h, s, l) {
|
|
172
|
+
const saturation = s / 100;
|
|
173
|
+
const lightness = l / 100;
|
|
174
|
+
const k = (n) => (n + h / 30) % 12;
|
|
175
|
+
const a = saturation * Math.min(lightness, 1 - lightness);
|
|
176
|
+
const f = (n) => lightness - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
|
|
177
|
+
return [f(0) * 255, f(8) * 255, f(4) * 255];
|
|
178
|
+
}
|
|
179
|
+
/** Euclidean RGB distance between two HSL colours. 0 = identical, ~441.7 = black vs white. */
|
|
180
|
+
export function colorContrast(a, b) {
|
|
181
|
+
const [r1, g1, b1] = hslToRgb(a[0], a[1], a[2]);
|
|
182
|
+
const [r2, g2, b2] = hslToRgb(b[0], b[1], b[2]);
|
|
183
|
+
return Math.hypot(r1 - r2, g1 - g2, b1 - b2);
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"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"}
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Candidate } from './miner.js';
|
|
2
|
+
/**
|
|
3
|
+
* A score as a percentage of the template's maximum, rounded to the one decimal a result is
|
|
4
|
+
* displayed with. `formatScore` renders exactly this number, and `filterCandidates` compares
|
|
5
|
+
* exactly this number against the match floor, so the figure on a tile and the figure the filter
|
|
6
|
+
* judges it by are the same figure: 89.96% displays as "90.0%" and must not then be dropped by a
|
|
7
|
+
* 90% floor over a difference nothing on screen can show.
|
|
8
|
+
*/
|
|
9
|
+
export declare function scorePercent(score: number, maxScore: number): number;
|
|
10
|
+
export declare function filterCandidates(candidates: Candidate[], filters: {
|
|
11
|
+
twoColor: boolean;
|
|
12
|
+
minContrast: number;
|
|
13
|
+
minMatch?: number;
|
|
14
|
+
}): Candidate[];
|
|
15
|
+
export interface SelectReportedResult {
|
|
16
|
+
reported: Candidate[];
|
|
17
|
+
/** How many candidates the filters removed. Zero when the fallback was used. */
|
|
18
|
+
droppedCount: number;
|
|
19
|
+
/** True when filtering removed everything and the unfiltered list is being shown instead. */
|
|
20
|
+
usedFallback: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retention is score-ranked and blind to twoColor/minContrast/minMatch, which are applied here —
|
|
24
|
+
* so callers must retain far more candidates than they intend to display, or the filters will
|
|
25
|
+
* have nothing left to choose from.
|
|
26
|
+
*
|
|
27
|
+
* `fallbackWhenEmpty` defaults to true, which shows the unfiltered list rather than nothing when
|
|
28
|
+
* the filters match no candidate at all. That suits a caller that can say so alongside the
|
|
29
|
+
* results — the CLI prints a notice when `usedFallback` comes back true. A caller that can render
|
|
30
|
+
* an explicit "nothing matches these filters" state should pass false instead: silently showing
|
|
31
|
+
* everything makes the filter look ignored rather than unsatisfied. With it off, `droppedCount`
|
|
32
|
+
* stops being a special case and always reports what the filters actually removed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function selectReported(candidates: Candidate[], options: {
|
|
35
|
+
twoColor: boolean;
|
|
36
|
+
minContrast: number;
|
|
37
|
+
/** Percentage floor, 0-100, judged as `scorePercent`. Omitted or 0 filters nothing. */
|
|
38
|
+
minMatch?: number;
|
|
39
|
+
keep: number;
|
|
40
|
+
fallbackWhenEmpty?: boolean;
|
|
41
|
+
}): SelectReportedResult;
|
|
42
|
+
/**
|
|
43
|
+
* A score as a percentage of the template's maximum. One decimal, because the interesting
|
|
44
|
+
* results sit in a narrow band near the top and whole percent would collapse distinct scores.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatScore(score: number, maxScore: number): string;
|
|
47
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,SAAS,EAAE,EACvB,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACrE,SAAS,EAAE,CAQb;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,SAAS,EAAE,CAAA;IACrB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAA;IACpB,6FAA6F;IAC7F,YAAY,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,SAAS,EAAE,EACvB,OAAO,EAAE;IACP,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,GACA,oBAAoB,CAWtB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
package/dist/select.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A score as a percentage of the template's maximum, rounded to the one decimal a result is
|
|
3
|
+
* displayed with. `formatScore` renders exactly this number, and `filterCandidates` compares
|
|
4
|
+
* exactly this number against the match floor, so the figure on a tile and the figure the filter
|
|
5
|
+
* judges it by are the same figure: 89.96% displays as "90.0%" and must not then be dropped by a
|
|
6
|
+
* 90% floor over a difference nothing on screen can show.
|
|
7
|
+
*/
|
|
8
|
+
export function scorePercent(score, maxScore) {
|
|
9
|
+
if (maxScore <= 0)
|
|
10
|
+
return 0;
|
|
11
|
+
return Number(((score / maxScore) * 100).toFixed(1));
|
|
12
|
+
}
|
|
13
|
+
export function filterCandidates(candidates, filters) {
|
|
14
|
+
const minMatch = filters.minMatch ?? 0;
|
|
15
|
+
return candidates.filter((candidate) => (!filters.twoColor || candidate.twoColor) &&
|
|
16
|
+
candidate.contrast >= filters.minContrast &&
|
|
17
|
+
scorePercent(candidate.score, candidate.maxScore) >= minMatch);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Retention is score-ranked and blind to twoColor/minContrast/minMatch, which are applied here —
|
|
21
|
+
* so callers must retain far more candidates than they intend to display, or the filters will
|
|
22
|
+
* have nothing left to choose from.
|
|
23
|
+
*
|
|
24
|
+
* `fallbackWhenEmpty` defaults to true, which shows the unfiltered list rather than nothing when
|
|
25
|
+
* the filters match no candidate at all. That suits a caller that can say so alongside the
|
|
26
|
+
* results — the CLI prints a notice when `usedFallback` comes back true. A caller that can render
|
|
27
|
+
* an explicit "nothing matches these filters" state should pass false instead: silently showing
|
|
28
|
+
* everything makes the filter look ignored rather than unsatisfied. With it off, `droppedCount`
|
|
29
|
+
* stops being a special case and always reports what the filters actually removed.
|
|
30
|
+
*/
|
|
31
|
+
export function selectReported(candidates, options) {
|
|
32
|
+
const filtered = filterCandidates(candidates, {
|
|
33
|
+
twoColor: options.twoColor,
|
|
34
|
+
minContrast: options.minContrast,
|
|
35
|
+
minMatch: options.minMatch,
|
|
36
|
+
});
|
|
37
|
+
const usedFallback = (options.fallbackWhenEmpty ?? true) && filtered.length === 0 && candidates.length > 0;
|
|
38
|
+
const usable = usedFallback ? candidates : filtered;
|
|
39
|
+
const droppedCount = usedFallback ? 0 : candidates.length - filtered.length;
|
|
40
|
+
return { reported: usable.slice(0, options.keep), droppedCount, usedFallback };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A score as a percentage of the template's maximum. One decimal, because the interesting
|
|
44
|
+
* results sit in a narrow band near the top and whole percent would collapse distinct scores.
|
|
45
|
+
*/
|
|
46
|
+
export function formatScore(score, maxScore) {
|
|
47
|
+
return `${scorePercent(score, maxScore).toFixed(1)}%`;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAgB;IAC1D,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,CAAC,CAAA;IAC3B,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAAuB,EACvB,OAAsE;IAEtE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAA;IACtC,OAAO,UAAU,CAAC,MAAM,CACtB,CAAC,SAAS,EAAE,EAAE,CACZ,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC;QACzC,SAAS,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW;QACzC,YAAY,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAChE,CAAA;AACH,CAAC;AAUD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAuB,EACvB,OAOC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,EAAE;QAC5C,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAA;IACF,MAAM,YAAY,GAChB,CAAC,OAAO,CAAC,iBAAiB,IAAI,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAA;IACvF,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAA;IACnD,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,CAAA;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,QAAgB;IACzD,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AACvD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FaceSpec, RegionAlternative } from './types.js';
|
|
2
|
+
export declare const MOUTH_BUDGET = 60;
|
|
3
|
+
export declare const MOUTH_STROKE_WEIGHT = 3;
|
|
4
|
+
export declare const MOUTH_BG_WEIGHT = 1;
|
|
5
|
+
/** Rows 0-4, index = row * 4 + col. 1 = the eye pixel (mirrored to column 5), 0 = background. */
|
|
6
|
+
export declare const BASE_TARGET: readonly (0 | 1)[];
|
|
7
|
+
/** 8 = the eye, 5 = cells hugging it (isolation, incl. col 3 to keep the two eyes apart), 3 = plain background. */
|
|
8
|
+
export declare const BASE_WEIGHTS: readonly number[];
|
|
9
|
+
/** Rows 5-7 of the left half: r5c0..r5c3, r6c0..r6c3, r7c0..r7c3. */
|
|
10
|
+
export declare const MOUTH_INDICES: readonly number[];
|
|
11
|
+
export declare const MOUTHS: readonly RegionAlternative[];
|
|
12
|
+
/** Builds a face with fixed eyes and the named subset of expressions accepted for the mouth. */
|
|
13
|
+
export declare function faceWithMouths(name: string, mouthNames: string[]): FaceSpec;
|
|
14
|
+
export declare const TEMPLATES: Record<string, FaceSpec>;
|
|
15
|
+
export declare function getTemplate(name: string): FaceSpec;
|
|
16
|
+
/** Validates untrusted JSON (a `--target file.json`, or a future web designer export). */
|
|
17
|
+
export declare function parseFaceSpec(input: unknown): FaceSpec;
|
|
18
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,QAAQ,EAAa,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEpF,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,eAAO,MAAM,mBAAmB,IAAI,CAAA;AACpC,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC,iGAAiG;AACjG,eAAO,MAAM,WAAW,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAEzC,CAAA;AAED,mHAAmH;AACnH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAEzC,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAqD,CAAA;AAEhG,eAAO,MAAM,MAAM,EAAE,SAAS,iBAAiB,EAO9C,CAAA;AAiBD,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,QAAQ,CAW3E;AAKD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAQ7C,CAAA;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAKlD;AAkBD,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAoEtD"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export const MOUTH_BUDGET = 60;
|
|
2
|
+
export const MOUTH_STROKE_WEIGHT = 3;
|
|
3
|
+
export const MOUTH_BG_WEIGHT = 1;
|
|
4
|
+
/** Rows 0-4, index = row * 4 + col. 1 = the eye pixel (mirrored to column 5), 0 = background. */
|
|
5
|
+
export const BASE_TARGET = [
|
|
6
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
7
|
+
];
|
|
8
|
+
/** 8 = the eye, 5 = cells hugging it (isolation, incl. col 3 to keep the two eyes apart), 3 = plain background. */
|
|
9
|
+
export const BASE_WEIGHTS = [
|
|
10
|
+
3, 3, 3, 3, 3, 3, 5, 3, 3, 5, 8, 5, 3, 3, 5, 3, 3, 3, 3, 3,
|
|
11
|
+
];
|
|
12
|
+
/** Rows 5-7 of the left half: r5c0..r5c3, r6c0..r6c3, r7c0..r7c3. */
|
|
13
|
+
export const MOUTH_INDICES = [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
|
14
|
+
export const MOUTHS = [
|
|
15
|
+
// r5: c0 c1 c2 c3 r6: c0 c1 c2 c3 r7: c0 c1 c2 c3
|
|
16
|
+
{ name: 'smile', cells: [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }, // corners up, dips centre
|
|
17
|
+
{ name: 'frown', cells: [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0] }, // corners down
|
|
18
|
+
{ name: 'neutral', cells: [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0] }, // straight line
|
|
19
|
+
{ name: 'open', cells: [0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1] }, // rounded "o" / surprised
|
|
20
|
+
{ name: 'small', cells: [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0] }, // little mouth
|
|
21
|
+
];
|
|
22
|
+
function baseFixedCells() {
|
|
23
|
+
return BASE_TARGET.map((value, index) => ({ index, value, weight: BASE_WEIGHTS[index] }));
|
|
24
|
+
}
|
|
25
|
+
function mouthRegion(alternatives) {
|
|
26
|
+
return {
|
|
27
|
+
name: 'mouth',
|
|
28
|
+
indices: [...MOUTH_INDICES],
|
|
29
|
+
budget: MOUTH_BUDGET,
|
|
30
|
+
strokeWeight: MOUTH_STROKE_WEIGHT,
|
|
31
|
+
bgWeight: MOUTH_BG_WEIGHT,
|
|
32
|
+
alternatives,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Builds a face with fixed eyes and the named subset of expressions accepted for the mouth. */
|
|
36
|
+
export function faceWithMouths(name, mouthNames) {
|
|
37
|
+
const alternatives = mouthNames.map((mouthName) => {
|
|
38
|
+
const mouth = MOUTHS.find((candidate) => candidate.name === mouthName);
|
|
39
|
+
if (!mouth) {
|
|
40
|
+
throw new Error(`unknown mouth "${mouthName}"; available: ${MOUTHS.map((m) => m.name).join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
return { name: mouth.name, cells: [...mouth.cells] };
|
|
43
|
+
});
|
|
44
|
+
return { name, fixed: baseFixedCells(), regions: [mouthRegion(alternatives)] };
|
|
45
|
+
}
|
|
46
|
+
// Built on a null prototype so a lookup by an attacker- or URL-controlled name can never resolve
|
|
47
|
+
// an inherited Object.prototype key (e.g. "constructor", "toString", "__proto__") instead of
|
|
48
|
+
// throwing the intended "unknown template" error.
|
|
49
|
+
export const TEMPLATES = Object.assign(Object.create(null), {
|
|
50
|
+
faces: faceWithMouths('faces', MOUTHS.map((mouth) => mouth.name)),
|
|
51
|
+
...Object.fromEntries(MOUTHS.map((mouth) => [mouth.name, faceWithMouths(mouth.name, [mouth.name])])),
|
|
52
|
+
});
|
|
53
|
+
export function getTemplate(name) {
|
|
54
|
+
if (!Object.hasOwn(TEMPLATES, name)) {
|
|
55
|
+
throw new Error(`unknown template "${name}"; available: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
return TEMPLATES[name];
|
|
58
|
+
}
|
|
59
|
+
/** Guards the `entry as Record<string, unknown>` casts below: a null or primitive entry would
|
|
60
|
+
* otherwise surface as a raw TypeError from the first property read. */
|
|
61
|
+
function requireObject(value, label) {
|
|
62
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
63
|
+
throw new Error(`${label} must be an object, got ${JSON.stringify(value)}`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function requirePositiveInteger(value, label) {
|
|
68
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
69
|
+
throw new Error(`${label} must be a positive integer, got ${JSON.stringify(value)}`);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
/** Validates untrusted JSON (a `--target file.json`, or a future web designer export). */
|
|
74
|
+
export function parseFaceSpec(input) {
|
|
75
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
76
|
+
throw new Error('FaceSpec must be an object');
|
|
77
|
+
}
|
|
78
|
+
const raw = input;
|
|
79
|
+
const name = typeof raw.name === 'string' && raw.name ? raw.name : 'custom';
|
|
80
|
+
const fixedInput = Array.isArray(raw.fixed) ? raw.fixed : [];
|
|
81
|
+
const fixed = fixedInput.map((entry, i) => {
|
|
82
|
+
const cell = requireObject(entry, `fixed[${i}]`);
|
|
83
|
+
const index = cell.index;
|
|
84
|
+
if (!Number.isInteger(index) || index < 0 || index > 31) {
|
|
85
|
+
throw new Error(`fixed[${i}].index must be an integer between 0 and 31`);
|
|
86
|
+
}
|
|
87
|
+
if (cell.value !== 0 && cell.value !== 1) {
|
|
88
|
+
throw new Error(`fixed[${i}].value must be 0 or 1`);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
index: index,
|
|
92
|
+
value: cell.value,
|
|
93
|
+
weight: requirePositiveInteger(cell.weight, `fixed[${i}].weight`),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const regionsInput = Array.isArray(raw.regions) ? raw.regions : [];
|
|
97
|
+
const regions = regionsInput.map((entry, r) => {
|
|
98
|
+
const label = `regions[${r}]`;
|
|
99
|
+
const region = requireObject(entry, label);
|
|
100
|
+
if (!Array.isArray(region.indices) || region.indices.length === 0) {
|
|
101
|
+
throw new Error(`${label}.indices must be a non-empty array`);
|
|
102
|
+
}
|
|
103
|
+
for (const index of region.indices) {
|
|
104
|
+
if (!Number.isInteger(index) || index < 0 || index > 31) {
|
|
105
|
+
throw new Error(`${label}.indices must all be integers between 0 and 31`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!Array.isArray(region.alternatives) || region.alternatives.length === 0) {
|
|
109
|
+
throw new Error(`${label}.alternatives must contain at least one alternative`);
|
|
110
|
+
}
|
|
111
|
+
const alternatives = region.alternatives.map((altEntry, a) => {
|
|
112
|
+
const alternative = requireObject(altEntry, `${label}.alternatives[${a}]`);
|
|
113
|
+
if (!Array.isArray(alternative.cells)) {
|
|
114
|
+
throw new Error(`${label}.alternatives[${a}].cells must be an array`);
|
|
115
|
+
}
|
|
116
|
+
for (const cell of alternative.cells) {
|
|
117
|
+
if (cell !== 0 && cell !== 1) {
|
|
118
|
+
throw new Error(`${label}.alternatives[${a}].cells value must be 0 or 1`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
name: typeof alternative.name === 'string' ? alternative.name : `alt${a}`,
|
|
123
|
+
cells: alternative.cells,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
name: typeof region.name === 'string' ? region.name : `region${r}`,
|
|
128
|
+
indices: region.indices,
|
|
129
|
+
budget: requirePositiveInteger(region.budget, `${label}.budget`),
|
|
130
|
+
strokeWeight: requirePositiveInteger(region.strokeWeight, `${label}.strokeWeight`),
|
|
131
|
+
bgWeight: requirePositiveInteger(region.bgWeight, `${label}.bgWeight`),
|
|
132
|
+
alternatives,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
if (fixed.length === 0 && regions.length === 0) {
|
|
136
|
+
throw new Error('FaceSpec must define at least one fixed cell or region');
|
|
137
|
+
}
|
|
138
|
+
return { name, fixed, regions };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Hue 0-360, saturation 0-100, lightness 0-100 — the ranges blo emits. */
|
|
2
|
+
export type Hsl = readonly [number, number, number];
|
|
3
|
+
/** blo's palette, in the order blo returns it: background, color, spot. */
|
|
4
|
+
export type Palette = readonly [Hsl, Hsl, Hsl];
|
|
5
|
+
export interface BloImage {
|
|
6
|
+
/** 32 values in {0,1,2} for the left half of the 8x8 grid; column c mirrors to 7-c. */
|
|
7
|
+
readonly data: Uint8Array;
|
|
8
|
+
readonly colors: Palette;
|
|
9
|
+
}
|
|
10
|
+
/** A cell whose target value is pinned. `index` is 0..31 in the left half of the grid. */
|
|
11
|
+
export interface FixedCell {
|
|
12
|
+
index: number;
|
|
13
|
+
value: 0 | 1;
|
|
14
|
+
weight: number;
|
|
15
|
+
}
|
|
16
|
+
/** One accepted shape for a region, e.g. a single mouth expression. */
|
|
17
|
+
export interface RegionAlternative {
|
|
18
|
+
name: string;
|
|
19
|
+
/** Aligned 1:1 with the region's `indices`. */
|
|
20
|
+
cells: (0 | 1)[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A group of cells scored as `max` over a list of alternatives. Every alternative is normalised
|
|
24
|
+
* to the same `budget`, so no shape wins just for having more stroke pixels.
|
|
25
|
+
*/
|
|
26
|
+
export interface FaceRegion {
|
|
27
|
+
name: string;
|
|
28
|
+
indices: number[];
|
|
29
|
+
budget: number;
|
|
30
|
+
/** Relative weight of a cell that should be foreground. */
|
|
31
|
+
strokeWeight: number;
|
|
32
|
+
/** Relative weight of a cell that should stay background. */
|
|
33
|
+
bgWeight: number;
|
|
34
|
+
alternatives: RegionAlternative[];
|
|
35
|
+
}
|
|
36
|
+
export interface FaceSpec {
|
|
37
|
+
name: string;
|
|
38
|
+
fixed: FixedCell[];
|
|
39
|
+
regions: FaceRegion[];
|
|
40
|
+
}
|
|
41
|
+
/** Flattened, integer-only form of a FaceSpec. Built once; read millions of times. */
|
|
42
|
+
export interface CompiledFace {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly maxScore: number;
|
|
45
|
+
readonly nFixed: number;
|
|
46
|
+
readonly fixedIndex: Uint8Array;
|
|
47
|
+
readonly fixedTarget: Uint8Array;
|
|
48
|
+
readonly fixedWeight: Int32Array;
|
|
49
|
+
readonly nRegions: number;
|
|
50
|
+
readonly regionIndexStart: Int32Array;
|
|
51
|
+
readonly regionLength: Int32Array;
|
|
52
|
+
readonly regionAltCount: Int32Array;
|
|
53
|
+
readonly regionCellStart: Int32Array;
|
|
54
|
+
readonly regionIndex: Uint8Array;
|
|
55
|
+
readonly regionCells: Uint8Array;
|
|
56
|
+
readonly regionWeights: Int32Array;
|
|
57
|
+
readonly regionNames: readonly string[];
|
|
58
|
+
readonly regionAltNames: readonly (readonly string[])[];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,2EAA2E;AAC3E,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAE9C,MAAM,WAAW,QAAQ;IACvB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,CAAC,GAAG,CAAC,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,iBAAiB,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB;AAED,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAA;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAA;IACrC,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAA;IACjC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAA;IACnC,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAA;IACpC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAA;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAA;IAChC,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;CACxD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@safe-vanity-blockie/core",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Isomorphic blo identicon, CREATE2 address derivation and mining primitives for Safe vanity blockies",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/tmjssz/safe-vanity-blockie.git",
|
|
11
|
+
"directory": "packages/core"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"safe",
|
|
17
|
+
"safe-global",
|
|
18
|
+
"blockie",
|
|
19
|
+
"blo",
|
|
20
|
+
"identicon",
|
|
21
|
+
"vanity-address",
|
|
22
|
+
"create2",
|
|
23
|
+
"ethereum"
|
|
24
|
+
],
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"hash-wasm": "^4.12.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@noble/hashes": "^2.3.0",
|
|
44
|
+
"blo": "2.0.0",
|
|
45
|
+
"viem": "^2.55.11"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|