@aztec/blob-client 3.0.0-nightly.20251223
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/README.md +62 -0
- package/dest/archive/blobscan_archive_client.d.ts +147 -0
- package/dest/archive/blobscan_archive_client.d.ts.map +1 -0
- package/dest/archive/blobscan_archive_client.js +141 -0
- package/dest/archive/config.d.ts +7 -0
- package/dest/archive/config.d.ts.map +1 -0
- package/dest/archive/config.js +11 -0
- package/dest/archive/factory.d.ts +4 -0
- package/dest/archive/factory.d.ts.map +1 -0
- package/dest/archive/factory.js +7 -0
- package/dest/archive/index.d.ts +3 -0
- package/dest/archive/index.d.ts.map +1 -0
- package/dest/archive/index.js +2 -0
- package/dest/archive/instrumentation.d.ts +11 -0
- package/dest/archive/instrumentation.d.ts.map +1 -0
- package/dest/archive/instrumentation.js +33 -0
- package/dest/archive/interface.d.ts +13 -0
- package/dest/archive/interface.d.ts.map +1 -0
- package/dest/archive/interface.js +1 -0
- package/dest/blobstore/blob_store_test_suite.d.ts +3 -0
- package/dest/blobstore/blob_store_test_suite.d.ts.map +1 -0
- package/dest/blobstore/blob_store_test_suite.js +164 -0
- package/dest/blobstore/index.d.ts +3 -0
- package/dest/blobstore/index.d.ts.map +1 -0
- package/dest/blobstore/index.js +2 -0
- package/dest/blobstore/interface.d.ts +12 -0
- package/dest/blobstore/interface.d.ts.map +1 -0
- package/dest/blobstore/interface.js +1 -0
- package/dest/blobstore/memory_blob_store.d.ts +8 -0
- package/dest/blobstore/memory_blob_store.d.ts.map +1 -0
- package/dest/blobstore/memory_blob_store.js +24 -0
- package/dest/client/bin/index.d.ts +3 -0
- package/dest/client/bin/index.d.ts.map +1 -0
- package/dest/client/bin/index.js +30 -0
- package/dest/client/config.d.ts +50 -0
- package/dest/client/config.d.ts.map +1 -0
- package/dest/client/config.js +55 -0
- package/dest/client/factory.d.ts +39 -0
- package/dest/client/factory.d.ts.map +1 -0
- package/dest/client/factory.js +53 -0
- package/dest/client/http.d.ts +63 -0
- package/dest/client/http.d.ts.map +1 -0
- package/dest/client/http.js +536 -0
- package/dest/client/index.d.ts +6 -0
- package/dest/client/index.d.ts.map +1 -0
- package/dest/client/index.js +5 -0
- package/dest/client/interface.d.ts +23 -0
- package/dest/client/interface.d.ts.map +1 -0
- package/dest/client/interface.js +1 -0
- package/dest/client/local.d.ts +12 -0
- package/dest/client/local.d.ts.map +1 -0
- package/dest/client/local.js +18 -0
- package/dest/client/tests.d.ts +11 -0
- package/dest/client/tests.d.ts.map +1 -0
- package/dest/client/tests.js +65 -0
- package/dest/encoding/index.d.ts +15 -0
- package/dest/encoding/index.d.ts.map +1 -0
- package/dest/encoding/index.js +19 -0
- package/dest/filestore/factory.d.ts +50 -0
- package/dest/filestore/factory.d.ts.map +1 -0
- package/dest/filestore/factory.js +67 -0
- package/dest/filestore/filestore_blob_client.d.ts +56 -0
- package/dest/filestore/filestore_blob_client.d.ts.map +1 -0
- package/dest/filestore/filestore_blob_client.js +99 -0
- package/dest/filestore/index.d.ts +3 -0
- package/dest/filestore/index.d.ts.map +1 -0
- package/dest/filestore/index.js +2 -0
- package/dest/types/api.d.ts +65 -0
- package/dest/types/api.d.ts.map +1 -0
- package/dest/types/api.js +22 -0
- package/dest/types/blob_with_index.d.ts +25 -0
- package/dest/types/blob_with_index.d.ts.map +1 -0
- package/dest/types/blob_with_index.js +43 -0
- package/dest/types/index.d.ts +2 -0
- package/dest/types/index.d.ts.map +1 -0
- package/dest/types/index.js +1 -0
- package/package.json +95 -0
- package/src/archive/blobscan_archive_client.ts +178 -0
- package/src/archive/config.ts +14 -0
- package/src/archive/factory.ts +11 -0
- package/src/archive/fixtures/blobscan_get_blob_data.json +1 -0
- package/src/archive/fixtures/blobscan_get_block.json +56 -0
- package/src/archive/index.ts +2 -0
- package/src/archive/instrumentation.ts +41 -0
- package/src/archive/interface.ts +9 -0
- package/src/blobstore/blob_store_test_suite.ts +137 -0
- package/src/blobstore/index.ts +2 -0
- package/src/blobstore/interface.ts +12 -0
- package/src/blobstore/memory_blob_store.ts +31 -0
- package/src/client/bin/index.ts +35 -0
- package/src/client/config.ts +117 -0
- package/src/client/factory.ts +88 -0
- package/src/client/http.ts +620 -0
- package/src/client/index.ts +5 -0
- package/src/client/interface.ts +30 -0
- package/src/client/local.ts +32 -0
- package/src/client/tests.ts +78 -0
- package/src/encoding/index.ts +21 -0
- package/src/filestore/factory.ts +145 -0
- package/src/filestore/filestore_blob_client.ts +129 -0
- package/src/filestore/index.ts +2 -0
- package/src/types/api.ts +50 -0
- package/src/types/blob_with_index.ts +48 -0
- package/src/types/index.ts +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aztec/blob-client",
|
|
3
|
+
"version": "3.0.0-nightly.20251223",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": "./dest/client/bin/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./client": "./dest/client/index.js",
|
|
8
|
+
"./client/config": "./dest/client/config.js",
|
|
9
|
+
"./encoding": "./dest/encoding/index.js",
|
|
10
|
+
"./types": "./dest/types/index.js",
|
|
11
|
+
"./filestore": "./dest/filestore/index.js"
|
|
12
|
+
},
|
|
13
|
+
"inherits": [
|
|
14
|
+
"../package.common.json"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
18
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
19
|
+
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
20
|
+
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
21
|
+
},
|
|
22
|
+
"jest": {
|
|
23
|
+
"moduleNameMapper": {
|
|
24
|
+
"^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
|
|
25
|
+
},
|
|
26
|
+
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
|
|
27
|
+
"rootDir": "./src",
|
|
28
|
+
"transform": {
|
|
29
|
+
"^.+\\.tsx?$": [
|
|
30
|
+
"@swc/jest",
|
|
31
|
+
{
|
|
32
|
+
"jsc": {
|
|
33
|
+
"parser": {
|
|
34
|
+
"syntax": "typescript",
|
|
35
|
+
"decorators": true
|
|
36
|
+
},
|
|
37
|
+
"transform": {
|
|
38
|
+
"decoratorVersion": "2022-03"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"extensionsToTreatAsEsm": [
|
|
45
|
+
".ts"
|
|
46
|
+
],
|
|
47
|
+
"reporters": [
|
|
48
|
+
"default"
|
|
49
|
+
],
|
|
50
|
+
"testTimeout": 120000,
|
|
51
|
+
"setupFiles": [
|
|
52
|
+
"../../foundation/src/jest/setup.mjs"
|
|
53
|
+
],
|
|
54
|
+
"testEnvironment": "../../foundation/src/jest/env.mjs",
|
|
55
|
+
"setupFilesAfterEnv": [
|
|
56
|
+
"../../foundation/src/jest/setupAfterEnv.mjs"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@aztec/blob-lib": "3.0.0-nightly.20251223",
|
|
61
|
+
"@aztec/ethereum": "3.0.0-nightly.20251223",
|
|
62
|
+
"@aztec/foundation": "3.0.0-nightly.20251223",
|
|
63
|
+
"@aztec/kv-store": "3.0.0-nightly.20251223",
|
|
64
|
+
"@aztec/stdlib": "3.0.0-nightly.20251223",
|
|
65
|
+
"@aztec/telemetry-client": "3.0.0-nightly.20251223",
|
|
66
|
+
"express": "^4.21.2",
|
|
67
|
+
"snappy": "^7.2.2",
|
|
68
|
+
"source-map-support": "^0.5.21",
|
|
69
|
+
"tslib": "^2.4.0",
|
|
70
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
71
|
+
"zod": "^3.23.8"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@jest/globals": "^30.0.0",
|
|
75
|
+
"@types/jest": "^30.0.0",
|
|
76
|
+
"@types/node": "^22.15.17",
|
|
77
|
+
"@types/source-map-support": "^0.5.10",
|
|
78
|
+
"@types/supertest": "^6.0.2",
|
|
79
|
+
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
|
80
|
+
"jest": "^30.0.0",
|
|
81
|
+
"jest-mock-extended": "^4.0.0",
|
|
82
|
+
"supertest": "^7.0.0",
|
|
83
|
+
"ts-node": "^10.9.1",
|
|
84
|
+
"typescript": "^5.3.3"
|
|
85
|
+
},
|
|
86
|
+
"files": [
|
|
87
|
+
"dest",
|
|
88
|
+
"src",
|
|
89
|
+
"!*.test.*"
|
|
90
|
+
],
|
|
91
|
+
"types": "./dest/index.d.ts",
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": ">=20.10"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { BlobJson } from '@aztec/blob-lib/types';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
4
|
+
import { schemas, zodFor } from '@aztec/foundation/schemas';
|
|
5
|
+
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import { BlobArchiveClientInstrumentation } from './instrumentation.js';
|
|
10
|
+
import type { BlobArchiveClient } from './interface.js';
|
|
11
|
+
|
|
12
|
+
export const BlobscanBlockResponseSchema = zodFor<BlobJson[]>()(
|
|
13
|
+
z
|
|
14
|
+
.object({
|
|
15
|
+
hash: z.string(),
|
|
16
|
+
slot: z.number().int(),
|
|
17
|
+
number: z.number().int(),
|
|
18
|
+
transactions: z.array(
|
|
19
|
+
z.object({
|
|
20
|
+
hash: z.string(),
|
|
21
|
+
blobs: z.array(
|
|
22
|
+
z.object({
|
|
23
|
+
versionedHash: z.string(),
|
|
24
|
+
data: z.string(),
|
|
25
|
+
commitment: z.string(),
|
|
26
|
+
proof: z.string(),
|
|
27
|
+
size: z.number().int(),
|
|
28
|
+
index: z.number().int().optional(), // This is the index within the tx, not within the block!
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
})
|
|
34
|
+
.transform(data =>
|
|
35
|
+
data.transactions
|
|
36
|
+
.flatMap(tx =>
|
|
37
|
+
tx.blobs.map(blob => ({
|
|
38
|
+
blob: blob.data,
|
|
39
|
+
// eslint-disable-next-line camelcase
|
|
40
|
+
kzg_commitment: blob.commitment,
|
|
41
|
+
})),
|
|
42
|
+
)
|
|
43
|
+
.map((blob, index) => ({ ...blob, index: index.toString() })),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Response from https://api.blobscan.com/blocks?sort=desc&type=canonical
|
|
48
|
+
export const BlobscanBlocksResponseSchema = z.object({
|
|
49
|
+
blocks: z.array(
|
|
50
|
+
z.object({
|
|
51
|
+
hash: z.string(),
|
|
52
|
+
slot: z.number().int(),
|
|
53
|
+
number: z.number().int(),
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export class BlobscanArchiveClient implements BlobArchiveClient {
|
|
59
|
+
private readonly logger = createLogger('blob-client:blobscan-archive-client');
|
|
60
|
+
private readonly fetchOpts = { headers: { accept: 'application/json' } };
|
|
61
|
+
private readonly fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {
|
|
62
|
+
return await retry(
|
|
63
|
+
() => fetch(...args),
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
65
|
+
`Fetching ${args[0]}`,
|
|
66
|
+
makeBackoff([1, 1, 3]),
|
|
67
|
+
this.logger,
|
|
68
|
+
/*failSilently=*/ false,
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
private readonly baseUrl: URL;
|
|
73
|
+
|
|
74
|
+
private instrumentation: BlobArchiveClientInstrumentation;
|
|
75
|
+
|
|
76
|
+
constructor(baseUrl: string, telemetry: TelemetryClient = getTelemetryClient()) {
|
|
77
|
+
this.baseUrl = new URL(baseUrl);
|
|
78
|
+
if (this.baseUrl.protocol !== 'https:') {
|
|
79
|
+
throw new TypeError('BaseURL must be secure: ' + baseUrl);
|
|
80
|
+
}
|
|
81
|
+
this.instrumentation = new BlobArchiveClientInstrumentation(
|
|
82
|
+
telemetry,
|
|
83
|
+
this.baseUrl.origin,
|
|
84
|
+
'BlobscanArchiveClient',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public async getLatestBlock(): Promise<{ hash: string; number: number; slot: number }> {
|
|
89
|
+
const url = new URL('blocks', this.baseUrl);
|
|
90
|
+
url.searchParams.set('sort', 'desc');
|
|
91
|
+
url.searchParams.set('type', 'canonical');
|
|
92
|
+
url.searchParams.set('p', '1');
|
|
93
|
+
url.searchParams.set('ps', '1');
|
|
94
|
+
|
|
95
|
+
this.logger.trace(`Fetching latest block from ${url.href}`);
|
|
96
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
97
|
+
|
|
98
|
+
if (response.status !== 200) {
|
|
99
|
+
throw new Error(`Failed to fetch latest block: ${response.statusText} (${response.status})`, {
|
|
100
|
+
cause: {
|
|
101
|
+
httpResponse: {
|
|
102
|
+
status: response.status,
|
|
103
|
+
body: await response.text().catch(() => 'Failed to read response body'),
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const parsed = await response.json().then((data: any) => BlobscanBlocksResponseSchema.parse(data));
|
|
110
|
+
if (parsed.blocks.length === 0) {
|
|
111
|
+
throw new Error(`No blocks found at ${this.baseUrl}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return parsed.blocks[0];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public getBaseUrl(): string {
|
|
118
|
+
return this.baseUrl.href;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public async getBlobsFromBlock(blockId: string): Promise<BlobJson[] | undefined> {
|
|
122
|
+
const url = new URL(`blocks/${blockId}`, this.baseUrl);
|
|
123
|
+
url.searchParams.set('type', 'canonical');
|
|
124
|
+
url.searchParams.set('expand', 'blob,blob_data');
|
|
125
|
+
|
|
126
|
+
this.logger.trace(`Fetching blobs for block ${blockId} from ${url.href}`);
|
|
127
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
128
|
+
|
|
129
|
+
this.instrumentation.incRequest('blocks', response.status);
|
|
130
|
+
|
|
131
|
+
if (response.status === 404) {
|
|
132
|
+
this.logger.debug(`No blobs found for block ${blockId} at ${this.baseUrl}`);
|
|
133
|
+
return undefined;
|
|
134
|
+
} else if (response.status !== 200) {
|
|
135
|
+
throw new Error(`Failed to fetch blobs for block ${blockId}: ${response.statusText} (${response.status})`, {
|
|
136
|
+
cause: {
|
|
137
|
+
httpResponse: {
|
|
138
|
+
status: response.status,
|
|
139
|
+
body: await response.text().catch(() => 'Failed to read response body'),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
const result = await response.json().then((data: any) => BlobscanBlockResponseSchema.parse(data));
|
|
145
|
+
this.logger.debug(`Fetched ${result.length} blobs for block ${blockId} from ${this.baseUrl}`);
|
|
146
|
+
this.instrumentation.incRetrievedBlobs(result.length);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public async getBlobData(id: string): Promise<Buffer | undefined> {
|
|
152
|
+
const url = new URL(`blobs/${id}/data`, this.baseUrl);
|
|
153
|
+
|
|
154
|
+
const response = await this.fetch(url, this.fetchOpts);
|
|
155
|
+
this.instrumentation.incRequest('blobs', response.status);
|
|
156
|
+
if (response.status === 404) {
|
|
157
|
+
return undefined;
|
|
158
|
+
} else if (response.status !== 200) {
|
|
159
|
+
throw new Error(`Failed to fetch blob data for blob ${id}: ${response.statusText} (${response.status})`, {
|
|
160
|
+
cause: {
|
|
161
|
+
httpResponse: {
|
|
162
|
+
status: response.status,
|
|
163
|
+
body: await response.text().catch(err => {
|
|
164
|
+
this.logger.warn('Failed to read response body', err);
|
|
165
|
+
return '';
|
|
166
|
+
}),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
return await response.json().then((data: any) => {
|
|
172
|
+
const blob = schemas.BufferHex.parse(data);
|
|
173
|
+
this.instrumentation.incRetrievedBlobs(1);
|
|
174
|
+
return blob;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type L1ReaderConfig, l1ReaderConfigMappings } from '@aztec/ethereum/l1-reader';
|
|
2
|
+
import { type ConfigMappingsType, pickConfigMappings } from '@aztec/foundation/config';
|
|
3
|
+
|
|
4
|
+
export type BlobArchiveApiConfig = {
|
|
5
|
+
archiveApiUrl?: string;
|
|
6
|
+
} & Partial<Pick<L1ReaderConfig, 'l1ChainId'>>;
|
|
7
|
+
|
|
8
|
+
export const blobArchiveApiConfigMappings: ConfigMappingsType<BlobArchiveApiConfig> = {
|
|
9
|
+
archiveApiUrl: {
|
|
10
|
+
env: 'BLOB_SINK_ARCHIVE_API_URL',
|
|
11
|
+
description: 'The URL of the archive API',
|
|
12
|
+
},
|
|
13
|
+
...pickConfigMappings(l1ReaderConfigMappings, ['l1ChainId']),
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BlobClientConfig } from '../client/config.js';
|
|
2
|
+
import { BlobscanArchiveClient } from './blobscan_archive_client.js';
|
|
3
|
+
import type { BlobArchiveClient } from './interface.js';
|
|
4
|
+
|
|
5
|
+
export function createBlobArchiveClient(config: BlobClientConfig): BlobArchiveClient | undefined {
|
|
6
|
+
if (config.archiveApiUrl) {
|
|
7
|
+
return new BlobscanArchiveClient(config.archiveApiUrl);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|