@artblocks/abx-storage 0.1.0-alpha.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.
@@ -0,0 +1,105 @@
1
+ import type { Hex } from 'viem';
2
+ import type { DirEntry, StorageBackend, StoredContent } from './backend.js';
3
+ /**
4
+ * S3-compatible **cloud** object storage — one adapter for AWS S3, Cloudflare R2,
5
+ * Backblaze B2, MinIO, and anything else that speaks the S3 API. It signs plain
6
+ * `fetch` requests with AWS Signature V4 using only `node:crypto`, so it adds
7
+ * **zero dependencies** (no AWS SDK, no native module) — the toolkit's minimal-deps
8
+ * ethos, kept. Content is stored path-style and addressed by its commitment hash.
9
+ */
10
+ export interface CloudStorageConfig {
11
+ /** S3 endpoint origin, e.g. `https://<acct>.r2.cloudflarestorage.com` or `https://s3.us-east-1.amazonaws.com`. */
12
+ endpoint: string;
13
+ bucket: string;
14
+ /** `auto` for R2; the bucket's region for AWS (e.g. `us-east-1`). */
15
+ region: string;
16
+ accessKeyId: string;
17
+ secretAccessKey: string;
18
+ /** Key prefix for stored objects. Default `abx/content/`. */
19
+ prefix?: string;
20
+ /** SigV4 service name. Default `s3`. */
21
+ service?: string;
22
+ /**
23
+ * Public READ base for objects — distinct from the signed-API `endpoint`. The bucket's
24
+ * public URL or a CDN in front of it (e.g. `https://<bucket>.s3.<region>.amazonaws.com`,
25
+ * `https://s3.<region>.amazonaws.com/<bucket>`, or `https://cdn.you.xyz`), such that
26
+ * `<publicBase>/<key>` fetches the object. Required to bake direct object URLs on-chain
27
+ * (the `url`/`url-template` image patterns) or to serve images off this node's `/image` route.
28
+ * Unset → node-served only (the resolver serves via `get()`); no direct URLs.
29
+ */
30
+ publicBase?: string;
31
+ }
32
+ /** The AWS SigV4 signing key: an HMAC chain over date → region → service. */
33
+ export declare function deriveSigningKey(secret: string, dateStamp: string, region: string, service: string): Buffer;
34
+ export interface SigV4Input {
35
+ method: string;
36
+ canonicalUri: string;
37
+ canonicalQuery: string;
38
+ headers: Record<string, string>;
39
+ payloadHash: string;
40
+ amzDate: string;
41
+ dateStamp: string;
42
+ region: string;
43
+ service: string;
44
+ accessKeyId: string;
45
+ secretAccessKey: string;
46
+ }
47
+ /**
48
+ * Compute an AWS Signature V4 authorization for a request. Pure and deterministic
49
+ * (date is an input), so it verifies against AWS's published test vectors.
50
+ */
51
+ export declare function sigv4Signature(p: SigV4Input): {
52
+ authorization: string;
53
+ signature: string;
54
+ signedHeaders: string;
55
+ };
56
+ export declare class CloudStorageBackend implements StorageBackend {
57
+ private readonly cfg;
58
+ readonly id = "cloud";
59
+ /** The public READ base (`<publicBase>/<key>`), when configured — see {@link StorageBackend.publicBase}. */
60
+ readonly publicBase?: string;
61
+ private readonly origin;
62
+ private readonly prefix;
63
+ private readonly service;
64
+ constructor(cfg: CloudStorageConfig);
65
+ private key;
66
+ private send;
67
+ /** Signed S3 request against an arbitrary object key (the key-based core of {@link send}). */
68
+ private sendKey;
69
+ put(hash: Hex, content: StoredContent): Promise<void>;
70
+ get(hash: Hex): Promise<StoredContent | null>;
71
+ has(hash: Hex): Promise<boolean>;
72
+ /**
73
+ * The public READ URL for the object (`<publicBase>/<key>`), or null if no public base is
74
+ * configured (then the resolver serves the bytes through its own `/image` route). Note: S3 is
75
+ * NOT content-addressed, so this URL has no on-chain integrity anchor of its own — the on-chain
76
+ * keccak256 remains the anchor (verify any served bytes against it).
77
+ */
78
+ locator(hash: Hex): Promise<string | null>;
79
+ /**
80
+ * Write bytes at an EXACT key (not content-addressed), overwriting in place — the
81
+ * deterministic-thumbnail primitive. `key` is the object path relative to the bucket root
82
+ * (and to {@link publicBase}), e.g. `orbit-weave/0.png`, so `<publicBase>/<key>` is the stable
83
+ * URL an on-chain `url-template` names. No `prefix` is prepended: the key IS the public path,
84
+ * fully determined by what was baked on-chain.
85
+ */
86
+ putObject(key: string, content: StoredContent): Promise<void>;
87
+ /** Read bytes previously written by {@link putObject} at `key`, or null (404). */
88
+ getObject(key: string): Promise<StoredContent | null>;
89
+ /**
90
+ * Upload a directory of files under a content-derived prefix and return its public base
91
+ * (`<publicBase>/<dirPrefix>`), so `<base>/0.png` resolves — the S3 form of the O(1) directory
92
+ * pattern (one on-chain `url-template`). The prefix is a hash of the (name, bytes) set, so the
93
+ * same inputs re-upload to the same location (idempotent) and distinct collections never collide.
94
+ */
95
+ putDirectory(entries: DirEntry[]): Promise<{
96
+ base: string;
97
+ }>;
98
+ health(): Promise<{
99
+ ok: boolean;
100
+ detail?: string;
101
+ }>;
102
+ }
103
+ /** Build a cloud-storage config from the environment (works for AWS S3, R2, B2, MinIO). */
104
+ export declare function cloudConfigFromEnv(): CloudStorageConfig;
105
+ //# sourceMappingURL=cloud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,MAAM,CAAC;AAC9B,OAAO,KAAK,EAAC,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,kHAAkH;IAClH,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAYD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAK3G;AAUD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,GAAG;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,CAmB/G;AAED,qBAAa,mBAAoB,YAAW,cAAc;IAQ5C,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPhC,QAAQ,CAAC,EAAE,WAAW;IACtB,4GAA4G;IAC5G,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEJ,GAAG,EAAE,kBAAkB;IAOpD,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,IAAI;IAIZ,8FAA8F;YAChF,OAAO;IA+Bf,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAQ7C,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAOtC;;;;;OAKG;IACG,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIhD;;;;;;OAMG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnE,kFAAkF;IAC5E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAQ3D;;;;;OAKG;IACG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC;IAiB1D,MAAM,IAAI,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CAWxD;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,IAAI,kBAAkB,CAgBvD"}
package/dist/cloud.js ADDED
@@ -0,0 +1,203 @@
1
+ import { createHash, createHmac } from 'node:crypto';
2
+ const EMPTY_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
3
+ function sha256Hex(data) {
4
+ return createHash('sha256').update(data).digest('hex');
5
+ }
6
+ function hmac(key, data) {
7
+ return createHmac('sha256', key).update(data, 'utf8').digest();
8
+ }
9
+ /** The AWS SigV4 signing key: an HMAC chain over date → region → service. */
10
+ export function deriveSigningKey(secret, dateStamp, region, service) {
11
+ const kDate = hmac('AWS4' + secret, dateStamp);
12
+ const kRegion = hmac(kDate, region);
13
+ const kService = hmac(kRegion, service);
14
+ return hmac(kService, 'aws4_request');
15
+ }
16
+ /** RFC-3986 encode one path segment (encodeURIComponent leaves `!'()*` alone). */
17
+ function rfc3986(segment) {
18
+ return encodeURIComponent(segment).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
19
+ }
20
+ function encodePath(path) {
21
+ return path.split('/').map(rfc3986).join('/');
22
+ }
23
+ /**
24
+ * Compute an AWS Signature V4 authorization for a request. Pure and deterministic
25
+ * (date is an input), so it verifies against AWS's published test vectors.
26
+ */
27
+ export function sigv4Signature(p) {
28
+ const lower = {};
29
+ for (const [k, v] of Object.entries(p.headers))
30
+ lower[k.toLowerCase()] = v.trim().replace(/\s+/g, ' ');
31
+ const names = Object.keys(lower).sort();
32
+ const canonicalHeaders = names.map((n) => `${n}:${lower[n]}\n`).join('');
33
+ const signedHeaders = names.join(';');
34
+ const canonicalRequest = [p.method, p.canonicalUri, p.canonicalQuery, canonicalHeaders, signedHeaders, p.payloadHash].join('\n');
35
+ const scope = `${p.dateStamp}/${p.region}/${p.service}/aws4_request`;
36
+ const stringToSign = ['AWS4-HMAC-SHA256', p.amzDate, scope, sha256Hex(canonicalRequest)].join('\n');
37
+ const signature = createHmac('sha256', deriveSigningKey(p.secretAccessKey, p.dateStamp, p.region, p.service))
38
+ .update(stringToSign, 'utf8')
39
+ .digest('hex');
40
+ return {
41
+ authorization: `AWS4-HMAC-SHA256 Credential=${p.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
42
+ signature,
43
+ signedHeaders,
44
+ };
45
+ }
46
+ export class CloudStorageBackend {
47
+ cfg;
48
+ id = 'cloud';
49
+ /** The public READ base (`<publicBase>/<key>`), when configured — see {@link StorageBackend.publicBase}. */
50
+ publicBase;
51
+ origin;
52
+ prefix;
53
+ service;
54
+ constructor(cfg) {
55
+ this.cfg = cfg;
56
+ this.origin = cfg.endpoint.replace(/\/+$/, '');
57
+ this.prefix = cfg.prefix ?? 'abx/content/';
58
+ this.service = cfg.service ?? 's3';
59
+ this.publicBase = cfg.publicBase ? cfg.publicBase.replace(/\/+$/, '') : undefined;
60
+ }
61
+ key(hash) {
62
+ return `${this.prefix}${hash.toLowerCase().replace(/^0x/, '')}`;
63
+ }
64
+ send(method, hash, body, contentType) {
65
+ return this.sendKey(method, this.key(hash), body, contentType);
66
+ }
67
+ /** Signed S3 request against an arbitrary object key (the key-based core of {@link send}). */
68
+ async sendKey(method, key, body, contentType) {
69
+ const path = `/${this.cfg.bucket}/${key}`;
70
+ const url = `${this.origin}${path}`;
71
+ const host = new URL(url).host;
72
+ const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
73
+ const dateStamp = amzDate.slice(0, 8);
74
+ const payloadHash = body ? sha256Hex(body) : EMPTY_SHA256;
75
+ const signHeaders = { host, 'x-amz-content-sha256': payloadHash, 'x-amz-date': amzDate };
76
+ if (contentType)
77
+ signHeaders['content-type'] = contentType;
78
+ const { authorization } = sigv4Signature({
79
+ method,
80
+ canonicalUri: encodePath(path),
81
+ canonicalQuery: '',
82
+ headers: signHeaders,
83
+ payloadHash,
84
+ amzDate,
85
+ dateStamp,
86
+ region: this.cfg.region,
87
+ service: this.service,
88
+ accessKeyId: this.cfg.accessKeyId,
89
+ secretAccessKey: this.cfg.secretAccessKey,
90
+ });
91
+ // `host` is managed by fetch; the rest must match what we signed.
92
+ const headers = { authorization, 'x-amz-content-sha256': payloadHash, 'x-amz-date': amzDate };
93
+ if (contentType)
94
+ headers['content-type'] = contentType;
95
+ return fetch(url, { method, headers, body });
96
+ }
97
+ async put(hash, content) {
98
+ const res = await this.send('PUT', hash, content.bytes, content.contentType);
99
+ if (!res.ok)
100
+ throw new Error(`Cloud storage PUT failed (${res.status}): ${await res.text()}`);
101
+ }
102
+ async get(hash) {
103
+ const res = await this.send('GET', hash);
104
+ if (res.status === 404)
105
+ return null;
106
+ if (!res.ok)
107
+ throw new Error(`Cloud storage GET failed (${res.status}): ${await res.text()}`);
108
+ const bytes = new Uint8Array(await res.arrayBuffer());
109
+ return { bytes, contentType: res.headers.get('content-type') ?? 'application/octet-stream' };
110
+ }
111
+ async has(hash) {
112
+ const res = await this.send('HEAD', hash);
113
+ if (res.status === 404)
114
+ return false;
115
+ if (!res.ok)
116
+ throw new Error(`Cloud storage HEAD failed (${res.status})`);
117
+ return true;
118
+ }
119
+ /**
120
+ * The public READ URL for the object (`<publicBase>/<key>`), or null if no public base is
121
+ * configured (then the resolver serves the bytes through its own `/image` route). Note: S3 is
122
+ * NOT content-addressed, so this URL has no on-chain integrity anchor of its own — the on-chain
123
+ * keccak256 remains the anchor (verify any served bytes against it).
124
+ */
125
+ async locator(hash) {
126
+ return this.publicBase ? `${this.publicBase}/${this.key(hash)}` : null;
127
+ }
128
+ /**
129
+ * Write bytes at an EXACT key (not content-addressed), overwriting in place — the
130
+ * deterministic-thumbnail primitive. `key` is the object path relative to the bucket root
131
+ * (and to {@link publicBase}), e.g. `orbit-weave/0.png`, so `<publicBase>/<key>` is the stable
132
+ * URL an on-chain `url-template` names. No `prefix` is prepended: the key IS the public path,
133
+ * fully determined by what was baked on-chain.
134
+ */
135
+ async putObject(key, content) {
136
+ const res = await this.sendKey('PUT', key.replace(/^\/+/, ''), content.bytes, content.contentType);
137
+ if (!res.ok)
138
+ throw new Error(`Cloud storage PUT (${key}) failed (${res.status}): ${await res.text()}`);
139
+ }
140
+ /** Read bytes previously written by {@link putObject} at `key`, or null (404). */
141
+ async getObject(key) {
142
+ const res = await this.sendKey('GET', key.replace(/^\/+/, ''));
143
+ if (res.status === 404)
144
+ return null;
145
+ if (!res.ok)
146
+ throw new Error(`Cloud storage GET (${key}) failed (${res.status}): ${await res.text()}`);
147
+ const bytes = new Uint8Array(await res.arrayBuffer());
148
+ return { bytes, contentType: res.headers.get('content-type') ?? 'application/octet-stream' };
149
+ }
150
+ /**
151
+ * Upload a directory of files under a content-derived prefix and return its public base
152
+ * (`<publicBase>/<dirPrefix>`), so `<base>/0.png` resolves — the S3 form of the O(1) directory
153
+ * pattern (one on-chain `url-template`). The prefix is a hash of the (name, bytes) set, so the
154
+ * same inputs re-upload to the same location (idempotent) and distinct collections never collide.
155
+ */
156
+ async putDirectory(entries) {
157
+ if (!this.cfg.publicBase) {
158
+ throw new Error('cloud directory upload needs a public read base — set ABX_S3_PUBLIC_BASE (or --public-base) to your ' +
159
+ 'bucket public URL / CDN, e.g. https://<bucket>.s3.<region>.amazonaws.com or https://cdn.you.xyz. ' +
160
+ "It's baked on-chain, so prefer a domain/CDN you control (moving it is then DNS, not a tx).");
161
+ }
162
+ const dirHash = sha256Hex(entries.map((e) => `${e.name}:${sha256Hex(e.bytes)}`).join('|')).slice(0, 32);
163
+ const dirPrefix = `${this.prefix}dir/${dirHash}`;
164
+ for (const e of entries) {
165
+ const res = await this.sendKey('PUT', `${dirPrefix}/${e.name}`, e.bytes, e.contentType);
166
+ if (!res.ok)
167
+ throw new Error(`Cloud storage directory PUT failed for ${e.name} (${res.status}): ${await res.text()}`);
168
+ }
169
+ return { base: `${this.cfg.publicBase.replace(/\/+$/, '')}/${dirPrefix}` };
170
+ }
171
+ async health() {
172
+ // A signed read of a key that won't exist: 404 → endpoint reachable + credentials
173
+ // accepted; a 401/403 → bad credentials; a network error → wrong endpoint.
174
+ const probe = ('0x' + '00'.repeat(32));
175
+ try {
176
+ await this.get(probe);
177
+ return { ok: true, detail: `${this.origin}/${this.cfg.bucket} (region ${this.cfg.region})` };
178
+ }
179
+ catch (e) {
180
+ return { ok: false, detail: e.message };
181
+ }
182
+ }
183
+ }
184
+ /** Build a cloud-storage config from the environment (works for AWS S3, R2, B2, MinIO). */
185
+ export function cloudConfigFromEnv() {
186
+ const need = (k) => {
187
+ const v = process.env[k];
188
+ if (!v)
189
+ throw new Error(`Cloud storage backend needs ${k} (set ABX_S3_ENDPOINT/BUCKET/ACCESS_KEY_ID/SECRET_ACCESS_KEY).`);
190
+ return v;
191
+ };
192
+ return {
193
+ endpoint: need('ABX_S3_ENDPOINT'),
194
+ bucket: need('ABX_S3_BUCKET'),
195
+ region: process.env.ABX_S3_REGION ?? 'auto',
196
+ accessKeyId: need('ABX_S3_ACCESS_KEY_ID'),
197
+ secretAccessKey: need('ABX_S3_SECRET_ACCESS_KEY'),
198
+ prefix: process.env.ABX_S3_PREFIX ?? 'abx/content/',
199
+ service: 's3',
200
+ publicBase: process.env.ABX_S3_PUBLIC_BASE,
201
+ };
202
+ }
203
+ //# sourceMappingURL=cloud.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Hex } from 'viem';
2
+ /**
3
+ * The off-chain `keccak → pointer` index used by backends whose native content id
4
+ * isn't the commitment hash (IPFS CID, Arweave txid). It's the necessary bridge
5
+ * until a pointer commitment is recorded on-chain — small JSON next to the
6
+ * projection, and removable the day the pointer lives on-chain.
7
+ */
8
+ export interface IndexEntry {
9
+ /** The backend's native id for the content — an IPFS CID or an Arweave txid. */
10
+ pointer: string;
11
+ contentType: string;
12
+ }
13
+ export declare class ContentIndex {
14
+ readonly path: string;
15
+ private data;
16
+ constructor(dataDir?: string);
17
+ private key;
18
+ get(hash: Hex): IndexEntry | null;
19
+ has(hash: Hex): boolean;
20
+ set(hash: Hex, entry: IndexEntry): void;
21
+ }
22
+ //# sourceMappingURL=content-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-index.d.ts","sourceRoot":"","sources":["../src/content-index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,MAAM,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,IAAI,CAA6B;gBAE7B,OAAO,CAAC,EAAE,MAAM;IAO5B,OAAO,CAAC,GAAG;IAIX,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,UAAU,GAAG,IAAI;IAIjC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO;IAIvB,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;CAIxC"}
@@ -0,0 +1,26 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ export class ContentIndex {
4
+ path;
5
+ data;
6
+ constructor(dataDir) {
7
+ const base = dataDir ?? process.env.ABX_DATA_DIR ?? resolve(process.cwd(), '.abx-self-host');
8
+ mkdirSync(base, { recursive: true });
9
+ this.path = resolve(base, 'content-index.json');
10
+ this.data = existsSync(this.path) ? JSON.parse(readFileSync(this.path, 'utf8')) : {};
11
+ }
12
+ key(hash) {
13
+ return hash.toLowerCase();
14
+ }
15
+ get(hash) {
16
+ return this.data[this.key(hash)] ?? null;
17
+ }
18
+ has(hash) {
19
+ return this.key(hash) in this.data;
20
+ }
21
+ set(hash, entry) {
22
+ this.data[this.key(hash)] = entry;
23
+ writeFileSync(this.path, JSON.stringify(this.data, null, 2));
24
+ }
25
+ }
26
+ //# sourceMappingURL=content-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-index.js","sourceRoot":"","sources":["../src/content-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAC,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAelC,MAAM,OAAO,YAAY;IACd,IAAI,CAAS;IACd,IAAI,CAA6B;IAEzC,YAAY,OAAgB;QAC1B,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC7F,SAAS,CAAC,IAAI,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvH,CAAC;IAEO,GAAG,CAAC,IAAS;QACnB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAED,GAAG,CAAC,IAAS;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,IAAS;QACX,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,IAAS,EAAE,KAAiB;QAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC;QAClC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF"}
package/dist/fs.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Hex } from 'viem';
2
+ import type { StorageBackend, StoredContent } from './backend.js';
3
+ /**
4
+ * The reference backend: content-addressed files on the local disk — "store the
5
+ * bytes on your droplet and serve them." Zero dependencies, the natural floor for
6
+ * a self-hoster. Bytes live at `<dataDir>/content/<hash>`, with the MIME type in a
7
+ * sidecar so `get` can replay it. Deleting the directory loses the content (it is
8
+ * the source of truth) — exactly why custody is switchable but never lapsable.
9
+ */
10
+ export declare class LocalFsBackend implements StorageBackend {
11
+ readonly id = "fs";
12
+ readonly dir: string;
13
+ constructor(dataDir?: string);
14
+ private path;
15
+ put(hash: Hex, content: StoredContent): Promise<void>;
16
+ get(hash: Hex): Promise<StoredContent | null>;
17
+ has(hash: Hex): Promise<boolean>;
18
+ health(): Promise<{
19
+ ok: boolean;
20
+ detail?: string;
21
+ }>;
22
+ }
23
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,MAAM,CAAC;AAC9B,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhE;;;;;;GAMG;AACH,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,EAAE,QAAQ;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,CAAC,EAAE,MAAM;IAM5B,OAAO,CAAC,IAAI;IAIN,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAS7C,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAIhC,MAAM,IAAI,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CAWxD"}
package/dist/fs.js ADDED
@@ -0,0 +1,50 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ /**
4
+ * The reference backend: content-addressed files on the local disk — "store the
5
+ * bytes on your droplet and serve them." Zero dependencies, the natural floor for
6
+ * a self-hoster. Bytes live at `<dataDir>/content/<hash>`, with the MIME type in a
7
+ * sidecar so `get` can replay it. Deleting the directory loses the content (it is
8
+ * the source of truth) — exactly why custody is switchable but never lapsable.
9
+ */
10
+ export class LocalFsBackend {
11
+ id = 'fs';
12
+ dir;
13
+ constructor(dataDir) {
14
+ const base = dataDir ?? process.env.ABX_DATA_DIR ?? resolve(process.cwd(), '.abx-self-host');
15
+ this.dir = resolve(base, 'content');
16
+ mkdirSync(this.dir, { recursive: true });
17
+ }
18
+ path(hash) {
19
+ return join(this.dir, hash.toLowerCase().replace(/^0x/, ''));
20
+ }
21
+ async put(hash, content) {
22
+ writeFileSync(this.path(hash), content.bytes);
23
+ writeFileSync(this.path(hash) + '.type', content.contentType, 'utf8');
24
+ }
25
+ async get(hash) {
26
+ const p = this.path(hash);
27
+ if (!existsSync(p))
28
+ return null;
29
+ const bytes = new Uint8Array(readFileSync(p));
30
+ const typePath = p + '.type';
31
+ const contentType = existsSync(typePath) ? readFileSync(typePath, 'utf8') : 'application/octet-stream';
32
+ return { bytes, contentType };
33
+ }
34
+ async has(hash) {
35
+ return existsSync(this.path(hash));
36
+ }
37
+ async health() {
38
+ try {
39
+ const probe = join(this.dir, '.probe');
40
+ writeFileSync(probe, 'ok');
41
+ readFileSync(probe);
42
+ rmSync(probe);
43
+ return { ok: true, detail: `writable: ${this.dir}` };
44
+ }
45
+ catch (e) {
46
+ return { ok: false, detail: e.message };
47
+ }
48
+ }
49
+ }
50
+ //# sourceMappingURL=fs.js.map
package/dist/fs.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAC,MAAM,SAAS,CAAC;AACnF,OAAO,EAAC,IAAI,EAAE,OAAO,EAAC,MAAM,WAAW,CAAC;AAIxC;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IAChB,EAAE,GAAG,IAAI,CAAC;IACV,GAAG,CAAS;IAErB,YAAY,OAAgB;QAC1B,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC7F,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACpC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;IACzC,CAAC;IAEO,IAAI,CAAC,IAAS;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAS,EAAE,OAAsB;QACzC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAS;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,CAAC,GAAG,OAAO,CAAC;QAC7B,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC;QACvG,OAAO,EAAC,KAAK,EAAE,WAAW,EAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAS;QACjB,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACvC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC3B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC;YACd,OAAO,EAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,IAAI,CAAC,GAAG,EAAE,EAAC,CAAC;QACrD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC;QACnD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @artblocks/abx-storage — Layer 3 reference byte custody.
3
+ *
4
+ * Holds the source-of-truth bytes a token's on-chain content commitment points
5
+ * at, content-addressed by that hash, behind a pluggable backend. The reference
6
+ * backend is local disk; cloud / content-addressed backends slot in behind the
7
+ * same interface.
8
+ */
9
+ export type { StorageBackend, StoredContent } from './backend.js';
10
+ export { hashContent, contentTypeFromPath } from './backend.js';
11
+ export { LocalFsBackend } from './fs.js';
12
+ export { CloudStorageBackend, cloudConfigFromEnv, deriveSigningKey, sigv4Signature, type CloudStorageConfig, type SigV4Input, } from './cloud.js';
13
+ export { ContentIndex, type IndexEntry } from './content-index.js';
14
+ export { IpfsBackend, ipfsConfigFromEnv, type IpfsConfig } from './ipfs.js';
15
+ export { ArweaveBackend, HttpBundlerUploader, TurboUploader, arweaveConfigFromEnv, arweaveFunding, arweaveProvider, turboIdentity, resolveTurboIdentity, turboIdentityAddress, isEthIdentity, ARWEAVE_FREE_UPLOAD_LIMIT, turboUploadCostUsd, turboUploadWinc, turboUploadId, turboBalanceForAddress, type ArweaveConfig, type ArweaveProvider, type ArweaveUploader, type ArweaveFunding, type TurboIdentity, } from './arweave.js';
16
+ export { generateArweaveJwk, arweaveAddress, type ArweaveJwk } from './arweave-identity.js';
17
+ export { resolveBackend, type ResolveStorageOptions } from './resolve.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,YAAY,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAChE,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,YAAY,EAAE,KAAK,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,kBAAkB,EAAE,cAAc,EAAE,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAC,cAAc,EAAE,KAAK,qBAAqB,EAAC,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { hashContent, contentTypeFromPath } from './backend.js';
2
+ export { LocalFsBackend } from './fs.js';
3
+ export { CloudStorageBackend, cloudConfigFromEnv, deriveSigningKey, sigv4Signature, } from './cloud.js';
4
+ export { ContentIndex } from './content-index.js';
5
+ export { IpfsBackend, ipfsConfigFromEnv } from './ipfs.js';
6
+ export { ArweaveBackend, HttpBundlerUploader, TurboUploader, arweaveConfigFromEnv, arweaveFunding, arweaveProvider, turboIdentity, resolveTurboIdentity, turboIdentityAddress, isEthIdentity, ARWEAVE_FREE_UPLOAD_LIMIT, turboUploadCostUsd, turboUploadWinc, turboUploadId, turboBalanceForAddress, } from './arweave.js';
7
+ export { generateArweaveJwk, arweaveAddress } from './arweave-identity.js';
8
+ export { resolveBackend } from './resolve.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,GAGf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAC,WAAW,EAAE,iBAAiB,EAAkB,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,sBAAsB,GAMvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,kBAAkB,EAAE,cAAc,EAAkB,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAC,cAAc,EAA6B,MAAM,cAAc,CAAC"}
package/dist/ipfs.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ import type { Hex } from 'viem';
2
+ import type { DirEntry, StorageBackend, StoredContent } from './backend.js';
3
+ /**
4
+ * IPFS byte custody, two modes:
5
+ * - **`kubo`** — add + pin to an IPFS node you run (the Kubo HTTP API), retrieve
6
+ * via your gateway. Self-hosted, dep-free.
7
+ * - **`pinata`** — pin through a managed service (Pinata), retrieve via a gateway.
8
+ * Same shape for other pinning services.
9
+ *
10
+ * IPFS addresses by **CID**, but the toolkit keys content by its keccak256
11
+ * commitment. Until a CID commitment is recorded on-chain, this backend keeps an
12
+ * off-chain `keccak → {cid, contentType}` index next to the projection — localized
13
+ * here, and removable the day the CID lives on-chain (the commitment extension
14
+ * already allows multiple kinds).
15
+ */
16
+ export interface IpfsConfig {
17
+ mode: 'kubo' | 'pinata';
18
+ /** Gateway base for retrieval, e.g. `http://127.0.0.1:8080` or `https://gateway.pinata.cloud`. */
19
+ gateway: string;
20
+ /** Kubo API base, e.g. `http://127.0.0.1:5001`. */
21
+ apiUrl?: string;
22
+ /** Pinata API base. Default `https://api.pinata.cloud`. */
23
+ pinataEndpoint?: string;
24
+ /** Pinata JWT — secret, from env, never persisted to config. */
25
+ pinataJwt?: string;
26
+ }
27
+ export declare class IpfsBackend implements StorageBackend {
28
+ private readonly cfg;
29
+ readonly id = "ipfs";
30
+ private readonly index;
31
+ constructor(cfg: IpfsConfig, dataDir?: string);
32
+ put(hash: Hex, content: StoredContent): Promise<void>;
33
+ get(hash: Hex): Promise<StoredContent | null>;
34
+ has(hash: Hex): Promise<boolean>;
35
+ /**
36
+ * The **gateway HTTPS URL** for the pinned bytes (`<gateway>/ipfs/<cid>`) — the production-safe
37
+ * form an NFT `image` needs, since browsers, wallets, and most marketplaces can't render a raw
38
+ * `ipfs://<cid>`. The CID is content-addressed so the bytes are resolvable through *any* gateway
39
+ * (and verifiable against the on-chain keccak256); we serve a concrete, reachable one. A pinning
40
+ * service + a public gateway is therefore part of operating IPFS custody — a local gateway
41
+ * (`127.0.0.1`/`localhost`) produces a URL only this machine can resolve. Null until pinned.
42
+ */
43
+ locator(hash: Hex): Promise<string | null>;
44
+ /**
45
+ * Pin a directory of files as ONE IPFS object and return its gateway base
46
+ * (`<gateway>/ipfs/<dirCID>`), so `<base>/0.png` resolves. The CID is content-addressed →
47
+ * gateway-agnostic integrity for the whole set. Pinata only (kubo directory add is deferred).
48
+ */
49
+ putDirectory(entries: DirEntry[]): Promise<{
50
+ base: string;
51
+ }>;
52
+ health(): Promise<{
53
+ ok: boolean;
54
+ detail?: string;
55
+ }>;
56
+ private form;
57
+ private addKubo;
58
+ private addPinata;
59
+ }
60
+ /** Build an IPFS config from the environment (non-secret values; PINATA_JWT is the secret). */
61
+ export declare function ipfsConfigFromEnv(): IpfsConfig;
62
+ //# sourceMappingURL=ipfs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipfs.d.ts","sourceRoot":"","sources":["../src/ipfs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,MAAM,CAAC;AAC9B,OAAO,KAAK,EAAC,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAG1E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,kGAAkG;IAClG,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,qBAAa,WAAY,YAAW,cAAc;IAIpC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAHhC,QAAQ,CAAC,EAAE,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAER,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM;IAIxD,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAU7C,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAItC;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKhD;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC;IAuB1D,MAAM,IAAI,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;IAiBvD,OAAO,CAAC,IAAI;YAME,OAAO;YAUP,SAAS;CAYxB;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,IAAI,UAAU,CAS9C"}