@aztec/blob-client 0.0.1-commit.03f7ef2

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.
Files changed (92) hide show
  1. package/README.md +62 -0
  2. package/dest/archive/blobscan_archive_client.d.ts +146 -0
  3. package/dest/archive/blobscan_archive_client.d.ts.map +1 -0
  4. package/dest/archive/blobscan_archive_client.js +138 -0
  5. package/dest/archive/config.d.ts +7 -0
  6. package/dest/archive/config.d.ts.map +1 -0
  7. package/dest/archive/config.js +11 -0
  8. package/dest/archive/factory.d.ts +4 -0
  9. package/dest/archive/factory.d.ts.map +1 -0
  10. package/dest/archive/factory.js +7 -0
  11. package/dest/archive/index.d.ts +3 -0
  12. package/dest/archive/index.d.ts.map +1 -0
  13. package/dest/archive/index.js +2 -0
  14. package/dest/archive/instrumentation.d.ts +11 -0
  15. package/dest/archive/instrumentation.d.ts.map +1 -0
  16. package/dest/archive/instrumentation.js +33 -0
  17. package/dest/archive/interface.d.ts +13 -0
  18. package/dest/archive/interface.d.ts.map +1 -0
  19. package/dest/archive/interface.js +1 -0
  20. package/dest/blobstore/blob_store_test_suite.d.ts +3 -0
  21. package/dest/blobstore/blob_store_test_suite.d.ts.map +1 -0
  22. package/dest/blobstore/blob_store_test_suite.js +133 -0
  23. package/dest/blobstore/index.d.ts +3 -0
  24. package/dest/blobstore/index.d.ts.map +1 -0
  25. package/dest/blobstore/index.js +2 -0
  26. package/dest/blobstore/interface.d.ts +12 -0
  27. package/dest/blobstore/interface.d.ts.map +1 -0
  28. package/dest/blobstore/interface.js +1 -0
  29. package/dest/blobstore/memory_blob_store.d.ts +8 -0
  30. package/dest/blobstore/memory_blob_store.d.ts.map +1 -0
  31. package/dest/blobstore/memory_blob_store.js +24 -0
  32. package/dest/client/bin/index.d.ts +3 -0
  33. package/dest/client/bin/index.d.ts.map +1 -0
  34. package/dest/client/bin/index.js +30 -0
  35. package/dest/client/config.d.ts +50 -0
  36. package/dest/client/config.d.ts.map +1 -0
  37. package/dest/client/config.js +55 -0
  38. package/dest/client/factory.d.ts +39 -0
  39. package/dest/client/factory.d.ts.map +1 -0
  40. package/dest/client/factory.js +53 -0
  41. package/dest/client/http.d.ts +63 -0
  42. package/dest/client/http.d.ts.map +1 -0
  43. package/dest/client/http.js +533 -0
  44. package/dest/client/index.d.ts +6 -0
  45. package/dest/client/index.d.ts.map +1 -0
  46. package/dest/client/index.js +5 -0
  47. package/dest/client/interface.d.ts +22 -0
  48. package/dest/client/interface.d.ts.map +1 -0
  49. package/dest/client/interface.js +1 -0
  50. package/dest/client/local.d.ts +11 -0
  51. package/dest/client/local.d.ts.map +1 -0
  52. package/dest/client/local.js +16 -0
  53. package/dest/client/tests.d.ts +11 -0
  54. package/dest/client/tests.d.ts.map +1 -0
  55. package/dest/client/tests.js +51 -0
  56. package/dest/encoding/index.d.ts +15 -0
  57. package/dest/encoding/index.d.ts.map +1 -0
  58. package/dest/encoding/index.js +19 -0
  59. package/dest/filestore/factory.d.ts +50 -0
  60. package/dest/filestore/factory.d.ts.map +1 -0
  61. package/dest/filestore/factory.js +67 -0
  62. package/dest/filestore/filestore_blob_client.d.ts +55 -0
  63. package/dest/filestore/filestore_blob_client.d.ts.map +1 -0
  64. package/dest/filestore/filestore_blob_client.js +91 -0
  65. package/dest/filestore/index.d.ts +3 -0
  66. package/dest/filestore/index.d.ts.map +1 -0
  67. package/dest/filestore/index.js +2 -0
  68. package/package.json +94 -0
  69. package/src/archive/blobscan_archive_client.ts +176 -0
  70. package/src/archive/config.ts +14 -0
  71. package/src/archive/factory.ts +11 -0
  72. package/src/archive/fixtures/blobscan_get_blob_data.json +1 -0
  73. package/src/archive/fixtures/blobscan_get_block.json +56 -0
  74. package/src/archive/index.ts +2 -0
  75. package/src/archive/instrumentation.ts +41 -0
  76. package/src/archive/interface.ts +9 -0
  77. package/src/blobstore/blob_store_test_suite.ts +110 -0
  78. package/src/blobstore/index.ts +2 -0
  79. package/src/blobstore/interface.ts +12 -0
  80. package/src/blobstore/memory_blob_store.ts +31 -0
  81. package/src/client/bin/index.ts +35 -0
  82. package/src/client/config.ts +117 -0
  83. package/src/client/factory.ts +88 -0
  84. package/src/client/http.ts +616 -0
  85. package/src/client/index.ts +5 -0
  86. package/src/client/interface.ts +23 -0
  87. package/src/client/local.ts +25 -0
  88. package/src/client/tests.ts +62 -0
  89. package/src/encoding/index.ts +21 -0
  90. package/src/filestore/factory.ts +145 -0
  91. package/src/filestore/filestore_blob_client.ts +121 -0
  92. package/src/filestore/index.ts +2 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ ## Blob Client
2
+
3
+ A client library for storing and retrieving blob data from various sources.
4
+
5
+ ## When is this used?
6
+
7
+ The blob client is used by nodes to store and retrieve blob data that accompanies `propose` transactions on L1.
8
+
9
+ ### Why?
10
+
11
+ Blob data is only available in the L1 consensus layer for a limited period (~3 weeks). The blob client provides a unified interface for:
12
+ - Fetching blobs from L1 consensus layer (beacon nodes)
13
+ - Storing/retrieving blobs from file stores (local files, S3, GCS)
14
+ - Caching blobs locally for faster access
15
+
16
+ ### How?
17
+
18
+ The blob client supports multiple blob sources:
19
+ 1. **File Store**: Local filesystem (`file://`), S3 (`s3://`), or GCS (`gs://`)
20
+ 2. **L1 Consensus**: Beacon node API for recent blobs
21
+ 3. **Archive**: Blobscan API for historical blobs
22
+
23
+ ### Configurations
24
+
25
+ **File Store URLs** (`BLOB_FILE_STORE_URLS`):
26
+ Comma-separated list of URLs to read blobs from. Tried in order until blobs are found.
27
+
28
+ **File Store Upload URL** (`BLOB_FILE_STORE_UPLOAD_URL`):
29
+ URL for uploading blobs to a file store.
30
+
31
+ **L1 Consensus Host URLs** (`L1_CONSENSUS_HOST_URLS`):
32
+ Beacon node URLs for fetching recent blobs directly from L1.
33
+
34
+ **Archive API URL** (`BLOB_SINK_ARCHIVE_API_URL`):
35
+ Blobscan or similar archive API for historical blob data.
36
+
37
+ ### Example Usage
38
+
39
+ ```typescript
40
+ import { createBlobClient, createBlobClientWithFileStores } from '@aztec/blob-client/client';
41
+
42
+ // Simple client with L1 consensus and archive sources
43
+ const client = createBlobClient({
44
+ l1ConsensusHostUrls: ['https://beacon.example.com'],
45
+ archiveApiUrl: 'https://api.blobscan.com',
46
+ });
47
+
48
+ // Client with file store support
49
+ const clientWithFileStore = await createBlobClientWithFileStores({
50
+ l1ChainId: 1,
51
+ rollupVersion: 1,
52
+ l1Contracts: { rollupAddress: { toString: () => '0x...' } },
53
+ blobFileStoreUrls: ['s3://bucket/blobs', 'file:///local/blobs'],
54
+ blobFileStoreUploadUrl: 's3://bucket/blobs',
55
+ });
56
+
57
+ // Fetch blobs
58
+ const blobs = await client.getBlobSidecar(blockHash, blobHashes);
59
+
60
+ // Upload blobs to file store
61
+ await client.sendBlobsToFilestore(blobs);
62
+ ```
@@ -0,0 +1,146 @@
1
+ import type { BlobJson } from '@aztec/blob-lib/types';
2
+ import { type TelemetryClient } from '@aztec/telemetry-client';
3
+ import { z } from 'zod';
4
+ import type { BlobArchiveClient } from './interface.js';
5
+ export declare const BlobscanBlockResponseSchema: z.ZodEffects<z.ZodObject<{
6
+ hash: z.ZodString;
7
+ slot: z.ZodNumber;
8
+ number: z.ZodNumber;
9
+ transactions: z.ZodArray<z.ZodObject<{
10
+ hash: z.ZodString;
11
+ blobs: z.ZodArray<z.ZodObject<{
12
+ versionedHash: z.ZodString;
13
+ data: z.ZodString;
14
+ commitment: z.ZodString;
15
+ proof: z.ZodString;
16
+ size: z.ZodNumber;
17
+ index: z.ZodOptional<z.ZodNumber>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ versionedHash: string;
20
+ data: string;
21
+ commitment: string;
22
+ proof: string;
23
+ size: number;
24
+ index?: number | undefined;
25
+ }, {
26
+ versionedHash: string;
27
+ data: string;
28
+ commitment: string;
29
+ proof: string;
30
+ size: number;
31
+ index?: number | undefined;
32
+ }>, "many">;
33
+ }, "strip", z.ZodTypeAny, {
34
+ hash: string;
35
+ blobs: {
36
+ versionedHash: string;
37
+ data: string;
38
+ commitment: string;
39
+ proof: string;
40
+ size: number;
41
+ index?: number | undefined;
42
+ }[];
43
+ }, {
44
+ hash: string;
45
+ blobs: {
46
+ versionedHash: string;
47
+ data: string;
48
+ commitment: string;
49
+ proof: string;
50
+ size: number;
51
+ index?: number | undefined;
52
+ }[];
53
+ }>, "many">;
54
+ }, "strip", z.ZodTypeAny, {
55
+ hash: string;
56
+ slot: number;
57
+ number: number;
58
+ transactions: {
59
+ hash: string;
60
+ blobs: {
61
+ versionedHash: string;
62
+ data: string;
63
+ commitment: string;
64
+ proof: string;
65
+ size: number;
66
+ index?: number | undefined;
67
+ }[];
68
+ }[];
69
+ }, {
70
+ hash: string;
71
+ slot: number;
72
+ number: number;
73
+ transactions: {
74
+ hash: string;
75
+ blobs: {
76
+ versionedHash: string;
77
+ data: string;
78
+ commitment: string;
79
+ proof: string;
80
+ size: number;
81
+ index?: number | undefined;
82
+ }[];
83
+ }[];
84
+ }>, {
85
+ blob: string;
86
+ kzg_commitment: string;
87
+ }[], {
88
+ hash: string;
89
+ slot: number;
90
+ number: number;
91
+ transactions: {
92
+ hash: string;
93
+ blobs: {
94
+ versionedHash: string;
95
+ data: string;
96
+ commitment: string;
97
+ proof: string;
98
+ size: number;
99
+ index?: number | undefined;
100
+ }[];
101
+ }[];
102
+ }>;
103
+ export declare const BlobscanBlocksResponseSchema: z.ZodObject<{
104
+ blocks: z.ZodArray<z.ZodObject<{
105
+ hash: z.ZodString;
106
+ slot: z.ZodNumber;
107
+ number: z.ZodNumber;
108
+ }, "strip", z.ZodTypeAny, {
109
+ hash: string;
110
+ slot: number;
111
+ number: number;
112
+ }, {
113
+ hash: string;
114
+ slot: number;
115
+ number: number;
116
+ }>, "many">;
117
+ }, "strip", z.ZodTypeAny, {
118
+ blocks: {
119
+ hash: string;
120
+ slot: number;
121
+ number: number;
122
+ }[];
123
+ }, {
124
+ blocks: {
125
+ hash: string;
126
+ slot: number;
127
+ number: number;
128
+ }[];
129
+ }>;
130
+ export declare class BlobscanArchiveClient implements BlobArchiveClient {
131
+ private readonly logger;
132
+ private readonly fetchOpts;
133
+ private readonly fetch;
134
+ private readonly baseUrl;
135
+ private instrumentation;
136
+ constructor(baseUrl: string, telemetry?: TelemetryClient);
137
+ getLatestBlock(): Promise<{
138
+ hash: string;
139
+ number: number;
140
+ slot: number;
141
+ }>;
142
+ getBaseUrl(): string;
143
+ getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined>;
144
+ getBlobData(id: string): Promise<Buffer | undefined>;
145
+ }
146
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYnNjYW5fYXJjaGl2ZV9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcmNoaXZlL2Jsb2JzY2FuX2FyY2hpdmVfY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBSXRELE9BQU8sRUFBRSxLQUFLLGVBQWUsRUFBc0IsTUFBTSx5QkFBeUIsQ0FBQztBQUVuRixPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBR3hCLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEQsZUFBTyxNQUFNLDJCQUEyQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztFQStCdkMsQ0FBQztBQUdGLGVBQU8sTUFBTSw0QkFBNEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0VBUXZDLENBQUM7QUFFSCxxQkFBYSxxQkFBc0IsWUFBVyxpQkFBaUI7SUFDN0QsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQXVEO0lBQzlFLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUErQztJQUN6RSxPQUFPLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FTcEI7SUFFRixPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBTTtJQUU5QixPQUFPLENBQUMsZUFBZSxDQUFtQztJQUUxRCxZQUFZLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxHQUFFLGVBQXNDLEVBVTdFO0lBRVksY0FBYyxJQUFJLE9BQU8sQ0FBQztRQUFFLElBQUksRUFBRSxNQUFNLENBQUM7UUFBQyxNQUFNLEVBQUUsTUFBTSxDQUFDO1FBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQTtLQUFFLENBQUMsQ0EyQnJGO0lBRU0sVUFBVSxJQUFJLE1BQU0sQ0FFMUI7SUFFWSxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0E0Qi9FO0lBRVksV0FBVyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0EwQmhFO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blobscan_archive_client.d.ts","sourceRoot":"","sources":["../../src/archive/blobscan_archive_client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BvC,CAAC;AAGF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;EAQvC,CAAC;AAEH,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuD;IAC9E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+C;IACzE,OAAO,CAAC,QAAQ,CAAC,KAAK,CASpB;IAEF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;IAE9B,OAAO,CAAC,eAAe,CAAmC;IAE1D,YAAY,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,eAAsC,EAU7E;IAEY,cAAc,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CA2BrF;IAEM,UAAU,IAAI,MAAM,CAE1B;IAEY,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,CA4B/E;IAEY,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0BhE;CACF"}
@@ -0,0 +1,138 @@
1
+ import { createLogger } from '@aztec/foundation/log';
2
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
3
+ import { schemas, zodFor } from '@aztec/foundation/schemas';
4
+ import { getTelemetryClient } from '@aztec/telemetry-client';
5
+ import { z } from 'zod';
6
+ import { BlobArchiveClientInstrumentation } from './instrumentation.js';
7
+ export const BlobscanBlockResponseSchema = zodFor()(z.object({
8
+ hash: z.string(),
9
+ slot: z.number().int(),
10
+ number: z.number().int(),
11
+ transactions: z.array(z.object({
12
+ hash: z.string(),
13
+ blobs: z.array(z.object({
14
+ versionedHash: z.string(),
15
+ data: z.string(),
16
+ commitment: z.string(),
17
+ proof: z.string(),
18
+ size: z.number().int(),
19
+ index: z.number().int().optional()
20
+ }))
21
+ }))
22
+ }).transform((data)=>data.transactions.flatMap((tx)=>tx.blobs.map((blob)=>({
23
+ blob: blob.data,
24
+ // eslint-disable-next-line camelcase
25
+ kzg_commitment: blob.commitment
26
+ })))));
27
+ // Response from https://api.blobscan.com/blocks?sort=desc&type=canonical
28
+ export const BlobscanBlocksResponseSchema = z.object({
29
+ blocks: z.array(z.object({
30
+ hash: z.string(),
31
+ slot: z.number().int(),
32
+ number: z.number().int()
33
+ }))
34
+ });
35
+ export class BlobscanArchiveClient {
36
+ logger = createLogger('blob-client:blobscan-archive-client');
37
+ fetchOpts = {
38
+ headers: {
39
+ accept: 'application/json'
40
+ }
41
+ };
42
+ fetch = async (...args)=>{
43
+ return await retry(()=>fetch(...args), // eslint-disable-next-line @typescript-eslint/no-base-to-string
44
+ `Fetching ${args[0]}`, makeBackoff([
45
+ 1,
46
+ 1,
47
+ 3
48
+ ]), this.logger, /*failSilently=*/ false);
49
+ };
50
+ baseUrl;
51
+ instrumentation;
52
+ constructor(baseUrl, telemetry = getTelemetryClient()){
53
+ this.baseUrl = new URL(baseUrl);
54
+ if (this.baseUrl.protocol !== 'https:') {
55
+ throw new TypeError('BaseURL must be secure: ' + baseUrl);
56
+ }
57
+ this.instrumentation = new BlobArchiveClientInstrumentation(telemetry, this.baseUrl.origin, 'BlobscanArchiveClient');
58
+ }
59
+ async getLatestBlock() {
60
+ const url = new URL('blocks', this.baseUrl);
61
+ url.searchParams.set('sort', 'desc');
62
+ url.searchParams.set('type', 'canonical');
63
+ url.searchParams.set('p', '1');
64
+ url.searchParams.set('ps', '1');
65
+ this.logger.trace(`Fetching latest block from ${url.href}`);
66
+ const response = await this.fetch(url, this.fetchOpts);
67
+ if (response.status !== 200) {
68
+ throw new Error(`Failed to fetch latest block: ${response.statusText} (${response.status})`, {
69
+ cause: {
70
+ httpResponse: {
71
+ status: response.status,
72
+ body: await response.text().catch(()=>'Failed to read response body')
73
+ }
74
+ }
75
+ });
76
+ }
77
+ const parsed = await response.json().then((data)=>BlobscanBlocksResponseSchema.parse(data));
78
+ if (parsed.blocks.length === 0) {
79
+ throw new Error(`No blocks found at ${this.baseUrl}`);
80
+ }
81
+ return parsed.blocks[0];
82
+ }
83
+ getBaseUrl() {
84
+ return this.baseUrl.href;
85
+ }
86
+ async getBlobsFromBlock(blockId) {
87
+ const url = new URL(`blocks/${blockId}`, this.baseUrl);
88
+ url.searchParams.set('type', 'canonical');
89
+ url.searchParams.set('expand', 'blob,blob_data');
90
+ this.logger.trace(`Fetching blobs for block ${blockId} from ${url.href}`);
91
+ const response = await this.fetch(url, this.fetchOpts);
92
+ this.instrumentation.incRequest('blocks', response.status);
93
+ if (response.status === 404) {
94
+ this.logger.debug(`No blobs found for block ${blockId} at ${this.baseUrl}`);
95
+ return undefined;
96
+ } else if (response.status !== 200) {
97
+ throw new Error(`Failed to fetch blobs for block ${blockId}: ${response.statusText} (${response.status})`, {
98
+ cause: {
99
+ httpResponse: {
100
+ status: response.status,
101
+ body: await response.text().catch(()=>'Failed to read response body')
102
+ }
103
+ }
104
+ });
105
+ } else {
106
+ const result = await response.json().then((data)=>BlobscanBlockResponseSchema.parse(data));
107
+ this.logger.debug(`Fetched ${result.length} blobs for block ${blockId} from ${this.baseUrl}`);
108
+ this.instrumentation.incRetrievedBlobs(result.length);
109
+ return result;
110
+ }
111
+ }
112
+ async getBlobData(id) {
113
+ const url = new URL(`blobs/${id}/data`, this.baseUrl);
114
+ const response = await this.fetch(url, this.fetchOpts);
115
+ this.instrumentation.incRequest('blobs', response.status);
116
+ if (response.status === 404) {
117
+ return undefined;
118
+ } else if (response.status !== 200) {
119
+ throw new Error(`Failed to fetch blob data for blob ${id}: ${response.statusText} (${response.status})`, {
120
+ cause: {
121
+ httpResponse: {
122
+ status: response.status,
123
+ body: await response.text().catch((err)=>{
124
+ this.logger.warn('Failed to read response body', err);
125
+ return '';
126
+ })
127
+ }
128
+ }
129
+ });
130
+ } else {
131
+ return await response.json().then((data)=>{
132
+ const blob = schemas.BufferHex.parse(data);
133
+ this.instrumentation.incRetrievedBlobs(1);
134
+ return blob;
135
+ });
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,7 @@
1
+ import { type L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
+ import { type ConfigMappingsType } from '@aztec/foundation/config';
3
+ export type BlobArchiveApiConfig = {
4
+ archiveApiUrl?: string;
5
+ } & Partial<Pick<L1ReaderConfig, 'l1ChainId'>>;
6
+ export declare const blobArchiveApiConfigMappings: ConfigMappingsType<BlobArchiveApiConfig>;
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssY0FBYyxFQUEwQixNQUFNLDJCQUEyQixDQUFDO0FBQ3hGLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUFzQixNQUFNLDBCQUEwQixDQUFDO0FBRXZGLE1BQU0sTUFBTSxvQkFBb0IsR0FBRztJQUNqQyxhQUFhLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDeEIsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0FBRS9DLGVBQU8sTUFBTSw0QkFBNEIsRUFBRSxrQkFBa0IsQ0FBQyxvQkFBb0IsQ0FNakYsQ0FBQyJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/archive/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,2BAA2B,CAAC;AACxF,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,0BAA0B,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;AAE/C,eAAO,MAAM,4BAA4B,EAAE,kBAAkB,CAAC,oBAAoB,CAMjF,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { l1ReaderConfigMappings } from '@aztec/ethereum/l1-reader';
2
+ import { pickConfigMappings } from '@aztec/foundation/config';
3
+ export const blobArchiveApiConfigMappings = {
4
+ archiveApiUrl: {
5
+ env: 'BLOB_SINK_ARCHIVE_API_URL',
6
+ description: 'The URL of the archive API'
7
+ },
8
+ ...pickConfigMappings(l1ReaderConfigMappings, [
9
+ 'l1ChainId'
10
+ ])
11
+ };
@@ -0,0 +1,4 @@
1
+ import type { BlobClientConfig } from '../client/config.js';
2
+ import type { BlobArchiveClient } from './interface.js';
3
+ export declare function createBlobArchiveClient(config: BlobClientConfig): BlobArchiveClient | undefined;
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FyY2hpdmUvZmFjdG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRTVELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEQsd0JBQWdCLHVCQUF1QixDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsR0FBRyxpQkFBaUIsR0FBRyxTQUFTLENBTS9GIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/archive/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,CAM/F"}
@@ -0,0 +1,7 @@
1
+ import { BlobscanArchiveClient } from './blobscan_archive_client.js';
2
+ export function createBlobArchiveClient(config) {
3
+ if (config.archiveApiUrl) {
4
+ return new BlobscanArchiveClient(config.archiveApiUrl);
5
+ }
6
+ return undefined;
7
+ }
@@ -0,0 +1,3 @@
1
+ export * from './interface.js';
2
+ export * from './factory.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcmNoaXZlL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxjQUFjLENBQUMifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/archive/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './interface.js';
2
+ export * from './factory.js';
@@ -0,0 +1,11 @@
1
+ import { type TelemetryClient } from '@aztec/telemetry-client';
2
+ export declare class BlobArchiveClientInstrumentation {
3
+ private httpHost;
4
+ private blockRequestCounter;
5
+ private blobRequestCounter;
6
+ private retrievedBlobs;
7
+ constructor(client: TelemetryClient, httpHost: string, name: string);
8
+ incRequest(type: 'blocks' | 'blobs', status: number): void;
9
+ incRetrievedBlobs(count: number): void;
10
+ }
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdHJ1bWVudGF0aW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9pbnN0cnVtZW50YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUF1QixLQUFLLGVBQWUsRUFBaUMsTUFBTSx5QkFBeUIsQ0FBQztBQUVuSCxxQkFBYSxnQ0FBZ0M7SUFPekMsT0FBTyxDQUFDLFFBQVE7SUFObEIsT0FBTyxDQUFDLG1CQUFtQixDQUFnQjtJQUMzQyxPQUFPLENBQUMsa0JBQWtCLENBQWdCO0lBQzFDLE9BQU8sQ0FBQyxjQUFjLENBQWdCO0lBRXRDLFlBQ0UsTUFBTSxFQUFFLGVBQWUsRUFDZixRQUFRLEVBQUUsTUFBTSxFQUN4QixJQUFJLEVBQUUsTUFBTSxFQWlCYjtJQUVELFVBQVUsQ0FBQyxJQUFJLEVBQUUsUUFBUSxHQUFHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQU1sRDtJQUVELGlCQUFpQixDQUFDLEtBQUssRUFBRSxNQUFNLFFBRTlCO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/archive/instrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,eAAe,EAAiC,MAAM,yBAAyB,CAAC;AAEnH,qBAAa,gCAAgC;IAOzC,OAAO,CAAC,QAAQ;IANlB,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAgB;IAEtC,YACE,MAAM,EAAE,eAAe,EACf,QAAQ,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,EAiBb;IAED,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,QAMlD;IAED,iBAAiB,CAAC,KAAK,EAAE,MAAM,QAE9B;CACF"}
@@ -0,0 +1,33 @@
1
+ import { Attributes, Metrics, ValueType } from '@aztec/telemetry-client';
2
+ export class BlobArchiveClientInstrumentation {
3
+ httpHost;
4
+ blockRequestCounter;
5
+ blobRequestCounter;
6
+ retrievedBlobs;
7
+ constructor(client, httpHost, name){
8
+ this.httpHost = httpHost;
9
+ const meter = client.getMeter(name);
10
+ this.blockRequestCounter = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOCK_REQUEST_COUNT, {
11
+ description: 'Number of requests made to retrieve blocks from the blob archive',
12
+ valueType: ValueType.INT
13
+ });
14
+ this.blobRequestCounter = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOB_REQUEST_COUNT, {
15
+ description: 'Number of requests made to retrieve blobs from the blob archive',
16
+ valueType: ValueType.INT
17
+ });
18
+ this.retrievedBlobs = meter.createUpDownCounter(Metrics.BLOB_SINK_ARCHIVE_BLOB_COUNT, {
19
+ description: 'Number of blobs retrieved from the blob archive',
20
+ valueType: ValueType.INT
21
+ });
22
+ }
23
+ incRequest(type, status) {
24
+ const counter = type === 'blocks' ? this.blockRequestCounter : this.blobRequestCounter;
25
+ counter.add(1, {
26
+ [Attributes.HTTP_RESPONSE_STATUS_CODE]: status,
27
+ [Attributes.HTTP_REQUEST_HOST]: this.httpHost
28
+ });
29
+ }
30
+ incRetrievedBlobs(count) {
31
+ this.retrievedBlobs.add(count);
32
+ }
33
+ }
@@ -0,0 +1,13 @@
1
+ import type { BlobJson } from '@aztec/blob-lib/types';
2
+ /** Interface to an blob archiving service. */
3
+ export interface BlobArchiveClient {
4
+ getBlobData(id: string): Promise<Buffer | undefined>;
5
+ getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined>;
6
+ getLatestBlock(): Promise<{
7
+ hash: string;
8
+ number: number;
9
+ slot: number;
10
+ }>;
11
+ getBaseUrl(): string;
12
+ }
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZS9pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFdEQsOENBQThDO0FBQzlDLE1BQU0sV0FBVyxpQkFBaUI7SUFDaEMsV0FBVyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FBQztJQUNyRCxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0FBQztJQUNwRSxjQUFjLElBQUksT0FBTyxDQUFDO1FBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQztRQUFDLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxJQUFJLEVBQUUsTUFBTSxDQUFBO0tBQUUsQ0FBQyxDQUFDO0lBQzFFLFVBQVUsSUFBSSxNQUFNLENBQUM7Q0FDdEIifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/archive/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,CAAC;IACpE,cAAc,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,UAAU,IAAI,MAAM,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ /** Interface to an blob archiving service. */ export { };
@@ -0,0 +1,3 @@
1
+ import type { BlobStore } from './interface.js';
2
+ export declare function describeBlobStore(getBlobStore: () => Promise<BlobStore>): void;
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYl9zdG9yZV90ZXN0X3N1aXRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmxvYnN0b3JlL2Jsb2Jfc3RvcmVfdGVzdF9zdWl0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFHQSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVoRCx3QkFBZ0IsaUJBQWlCLENBQUMsWUFBWSxFQUFFLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxRQXdHdkUifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob_store_test_suite.d.ts","sourceRoot":"","sources":["../../src/blobstore/blob_store_test_suite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,QAwGvE"}
@@ -0,0 +1,133 @@
1
+ import { Blob } from '@aztec/blob-lib';
2
+ import { Fr } from '@aztec/foundation/curves/bn254';
3
+ export function describeBlobStore(getBlobStore) {
4
+ let blobStore;
5
+ beforeEach(async ()=>{
6
+ blobStore = await getBlobStore();
7
+ });
8
+ it('should store and retrieve a blob by hash', async ()=>{
9
+ // Create a test blob with random fields
10
+ const testFields = [
11
+ Fr.random(),
12
+ Fr.random(),
13
+ Fr.random()
14
+ ];
15
+ const blob = Blob.fromFields(testFields);
16
+ const blobHash = blob.getEthVersionedBlobHash();
17
+ // Store the blob
18
+ await blobStore.addBlobs([
19
+ blob
20
+ ]);
21
+ // Retrieve the blob by hash
22
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
23
+ blobHash
24
+ ]);
25
+ // Verify the blob was retrieved and matches
26
+ expect(retrievedBlobs.length).toBe(1);
27
+ expect(retrievedBlobs[0]).toEqual(blob);
28
+ });
29
+ it('should handle multiple blobs stored and retrieved by their hashes', async ()=>{
30
+ // Create two different blobs
31
+ const blob1 = Blob.fromFields([
32
+ Fr.random(),
33
+ Fr.random()
34
+ ]);
35
+ const blob2 = Blob.fromFields([
36
+ Fr.random(),
37
+ Fr.random(),
38
+ Fr.random()
39
+ ]);
40
+ const blobHash1 = blob1.getEthVersionedBlobHash();
41
+ const blobHash2 = blob2.getEthVersionedBlobHash();
42
+ // Store both blobs
43
+ await blobStore.addBlobs([
44
+ blob1,
45
+ blob2
46
+ ]);
47
+ // Retrieve and verify both blobs
48
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
49
+ blobHash1,
50
+ blobHash2
51
+ ]);
52
+ expect(retrievedBlobs.length).toBe(2);
53
+ expect(retrievedBlobs[0]).toEqual(blob1);
54
+ expect(retrievedBlobs[1]).toEqual(blob2);
55
+ });
56
+ it('should return empty array for non-existent blob hash', async ()=>{
57
+ // Create a random hash that doesn't exist
58
+ const nonExistentHash = Buffer.alloc(32);
59
+ nonExistentHash.fill(0xff);
60
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
61
+ nonExistentHash
62
+ ]);
63
+ expect(retrievedBlobs).toEqual([]);
64
+ });
65
+ it('should handle retrieving subset of stored blobs', async ()=>{
66
+ // Store multiple blobs
67
+ const blob1 = Blob.fromFields([
68
+ Fr.random()
69
+ ]);
70
+ const blob2 = Blob.fromFields([
71
+ Fr.random()
72
+ ]);
73
+ const blob3 = Blob.fromFields([
74
+ Fr.random()
75
+ ]);
76
+ await blobStore.addBlobs([
77
+ blob1,
78
+ blob2,
79
+ blob3
80
+ ]);
81
+ // Retrieve only some of them
82
+ const blobHash1 = blob1.getEthVersionedBlobHash();
83
+ const blobHash3 = blob3.getEthVersionedBlobHash();
84
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
85
+ blobHash1,
86
+ blobHash3
87
+ ]);
88
+ expect(retrievedBlobs.length).toBe(2);
89
+ expect(retrievedBlobs[0]).toEqual(blob1);
90
+ expect(retrievedBlobs[1]).toEqual(blob3);
91
+ });
92
+ it('should handle duplicate blob hashes in request', async ()=>{
93
+ const blob = Blob.fromFields([
94
+ Fr.random()
95
+ ]);
96
+ const blobHash = blob.getEthVersionedBlobHash();
97
+ await blobStore.addBlobs([
98
+ blob
99
+ ]);
100
+ // Request the same blob hash multiple times
101
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
102
+ blobHash,
103
+ blobHash
104
+ ]);
105
+ // Implementation may return duplicates or deduplicate - both are valid
106
+ expect(retrievedBlobs.length).toBeGreaterThanOrEqual(1);
107
+ expect(retrievedBlobs[0]).toEqual(blob);
108
+ });
109
+ it('should overwrite blob when storing with same hash', async ()=>{
110
+ // Create two blobs that will have the same hash (same content)
111
+ const fields = [
112
+ Fr.random(),
113
+ Fr.random()
114
+ ];
115
+ const blob1 = Blob.fromFields(fields);
116
+ const blob2 = Blob.fromFields(fields);
117
+ const blobHash = blob1.getEthVersionedBlobHash();
118
+ // Store first blob
119
+ await blobStore.addBlobs([
120
+ blob1
121
+ ]);
122
+ // Overwrite with second blob (same hash)
123
+ await blobStore.addBlobs([
124
+ blob2
125
+ ]);
126
+ // Retrieve and verify it exists
127
+ const retrievedBlobs = await blobStore.getBlobsByHashes([
128
+ blobHash
129
+ ]);
130
+ expect(retrievedBlobs.length).toBe(1);
131
+ expect(retrievedBlobs[0]).toEqual(blob1); // Same content
132
+ });
133
+ }
@@ -0,0 +1,3 @@
1
+ export * from './memory_blob_store.js';
2
+ export * from './interface.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ibG9ic3RvcmUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGdCQUFnQixDQUFDIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blobstore/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './memory_blob_store.js';
2
+ export * from './interface.js';
@@ -0,0 +1,12 @@
1
+ import type { Blob } from '@aztec/blob-lib';
2
+ export interface BlobStore {
3
+ /**
4
+ * Get blobs by their hashes
5
+ */
6
+ getBlobsByHashes: (blobHashes: Buffer[]) => Promise<Blob[]>;
7
+ /**
8
+ * Add blobs to the store, indexed by their hashes
9
+ */
10
+ addBlobs: (blobs: Blob[]) => Promise<void>;
11
+ }
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmxvYnN0b3JlL2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUU1QyxNQUFNLFdBQVcsU0FBUztJQUN4Qjs7T0FFRztJQUNILGdCQUFnQixFQUFFLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxLQUFLLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQzVEOztPQUVHO0lBQ0gsUUFBUSxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztDQUM1QyJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/blobstore/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
@@ -0,0 +1 @@
1
+ export { };