@corpus-core/colibri-stateless 1.1.1 → 1.1.3

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/cjs/index.js ADDED
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 corpus.core
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ * this software and associated documentation files (the "Software"), to deal in
7
+ * the Software without restriction, including without limitation the rights to
8
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ * the Software, and to permit persons to whom the Software is furnished to do so,
10
+ * subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ *
22
+ * SPDX-License-Identifier: MIT
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.PrototypeProtection = exports.TransactionVerifier = exports.MethodType = exports.ProviderRpcError = exports.set_wasm_url = exports.Strategy = void 0;
26
+ // Import the Emscripten-generated module
27
+ const wasm_js_1 = require("./wasm.js");
28
+ const eventEmitter_js_1 = require("./eventEmitter.js");
29
+ const connectionState_js_1 = require("./connectionState.js");
30
+ const types_js_1 = require("./types.js");
31
+ Object.defineProperty(exports, "ProviderRpcError", { enumerable: true, get: function () { return types_js_1.ProviderRpcError; } });
32
+ Object.defineProperty(exports, "MethodType", { enumerable: true, get: function () { return types_js_1.MethodType; } });
33
+ const chains_js_1 = require("./chains.js");
34
+ const subscriptionManager_js_1 = require("./subscriptionManager.js");
35
+ const strategy_js_1 = require("./strategy.js");
36
+ exports.Strategy = strategy_js_1.default;
37
+ const transactionVerifier_js_1 = require("./transactionVerifier.js");
38
+ const http_js_1 = require("./http.js");
39
+ // Public helper for controlling WASM loading behavior in Node/bundlers.
40
+ var wasm_js_2 = require("./wasm.js");
41
+ Object.defineProperty(exports, "set_wasm_url", { enumerable: true, get: function () { return wasm_js_2.set_wasm_url; } });
42
+ // Re-export transaction verification utilities
43
+ var transactionVerifier_js_2 = require("./transactionVerifier.js");
44
+ Object.defineProperty(exports, "TransactionVerifier", { enumerable: true, get: function () { return transactionVerifier_js_2.TransactionVerifier; } });
45
+ Object.defineProperty(exports, "PrototypeProtection", { enumerable: true, get: function () { return transactionVerifier_js_2.PrototypeProtection; } });
46
+ // fetch_rpc und handle_request sind nach http.ts verschoben
47
+ // default_config, get_chain_id, chain_conf ausgelagert nach ./chains.ts
48
+ function cleanup_args(method, args) {
49
+ if (method == "eth_verifyLogs")
50
+ return args.map(arg => ({
51
+ transactionIndex: arg.transactionIndex,
52
+ blockNumber: arg.blockNumber
53
+ }));
54
+ return args;
55
+ }
56
+ class C4Client {
57
+ config;
58
+ eventEmitter;
59
+ connectionState;
60
+ subscriptionManager;
61
+ initMap = new Map();
62
+ flags = 0;
63
+ // Protect against prototype pollution by freezing critical methods
64
+ static CRITICAL_METHODS = ['rpc', 'request', 'verifyProof', 'createProof'];
65
+ static {
66
+ // Apply prototype pollution protection
67
+ transactionVerifier_js_1.PrototypeProtection.protectClass(C4Client, C4Client.CRITICAL_METHODS);
68
+ }
69
+ /**
70
+ * Creates a new Colibri client instance.
71
+ *
72
+ * Example:
73
+ * ```ts
74
+ * import Colibri from '@corpus-core/colibri-stateless';
75
+ * const client = new Colibri({ chainId: 1 });
76
+ * const blockNumber = await client.request({ method: 'eth_blockNumber' });
77
+ * ```
78
+ *
79
+ * @param config Optional partial configuration; user config overrides defaults.
80
+ */
81
+ constructor(config) {
82
+ const chainId = config?.chainId ? (0, chains_js_1.get_chain_id)(config?.chainId + '') : 1;
83
+ const chain_config = { ...chains_js_1.default_config[chainId + ''] };
84
+ // Protect against config manipulation (deep-freeze at sensitive boundaries)
85
+ const baseConfig = {
86
+ chains: {},
87
+ trusted_checkpoint: undefined,
88
+ rpcs: chain_config.rpcs || [],
89
+ beacon_apis: chain_config.beacon_apis || [],
90
+ prover: chain_config.prover || [],
91
+ checkpointz: chain_config.checkpointz || [],
92
+ ...config, // User config overrides defaults
93
+ chainId,
94
+ };
95
+ // Apply config immutability/protection
96
+ transactionVerifier_js_1.PrototypeProtection.protectConfig(baseConfig, ['rpcs', 'beacon_apis', 'prover', 'checkpointz']);
97
+ this.config = baseConfig;
98
+ if (!this.config.warningHandler)
99
+ this.config.warningHandler = async (req, message) => console.warn(message);
100
+ if (!this.config.proofStrategy)
101
+ this.config.proofStrategy = strategy_js_1.default.VerifyIfPossible;
102
+ this.eventEmitter = new eventEmitter_js_1.EventEmitter();
103
+ this.connectionState = new connectionState_js_1.ConnectionState({ chainId: parseInt(this.config.chainId + ''), debug: this.config.debug }, async () => this.rpc('eth_chainId', [], types_js_1.MethodType.LOCAL), // specific callback to detect chainId
104
+ this.eventEmitter);
105
+ this.subscriptionManager = new subscriptionManager_js_1.SubscriptionManager(async (method, params) => this.rpc(method, params), this.eventEmitter, {
106
+ debug: this.config.debug,
107
+ pollingInterval: this.config.pollingInterval || (0, chains_js_1.chain_conf)(this.config, this.config.chainId)?.pollingInterval || 12000
108
+ });
109
+ }
110
+ // Prover config helpers are moved to wasm.ts (get_prover_config_hex)
111
+ async fetch_checkpointz() {
112
+ let checkpoint = undefined;
113
+ for (const url of [...(this.config.checkpointz || []), ...(this.config.beacon_apis || []), ...(this.config.prover || [])]) {
114
+ const response = await fetch(url + (url.endsWith('/') ? '' : '/') + 'eth/v1/beacon/states/head/finality_checkpoints', {
115
+ method: 'GET',
116
+ headers: {
117
+ "Content-Type": "application/json"
118
+ }
119
+ });
120
+ if (response.ok) {
121
+ const res = await response.json();
122
+ checkpoint = res?.data?.finalized?.root;
123
+ if (checkpoint)
124
+ break;
125
+ }
126
+ }
127
+ if (!checkpoint)
128
+ throw new Error('No checkpoint found');
129
+ this.config.trusted_checkpoint = checkpoint;
130
+ // set trusted checkpoint in C state so we can use it in proof calls immediately
131
+ await (0, wasm_js_1.set_trusted_checkpoint)(this.config.chainId, checkpoint);
132
+ }
133
+ /**
134
+ * Checks whether the RPC method is supported or proofable.
135
+ * @param method - The method to check
136
+ * @returns The method type
137
+ */
138
+ async getMethodSupport(method) {
139
+ const c4w = await (0, wasm_js_1.getC4w)();
140
+ const free_buffers = [];
141
+ const method_type = c4w._c4w_get_method_type(BigInt(this.config.chainId), (0, wasm_js_1.as_char_ptr)(method, c4w, free_buffers));
142
+ free_buffers.forEach(ptr => c4w._free(ptr));
143
+ return method_type;
144
+ }
145
+ /**
146
+ * Creates a proof for the given method and arguments.
147
+ * @param method - The method to create a proof for
148
+ * @param args - The arguments to create a proof for
149
+ * @returns The proof
150
+ */
151
+ async createProof(method, args) {
152
+ const c4w = await (0, wasm_js_1.getC4w)();
153
+ const free_buffers = [];
154
+ let ctx = 0;
155
+ try {
156
+ // Call the C function
157
+ ctx = c4w._c4w_create_proof_ctx((0, wasm_js_1.as_char_ptr)(method, c4w, free_buffers), (0, wasm_js_1.as_char_ptr)(JSON.stringify(args), c4w, free_buffers), BigInt(this.config.chainId), this.flags);
158
+ while (true) {
159
+ const state = (0, wasm_js_1.as_json)(c4w._c4w_execute_proof_ctx(ctx), c4w, true);
160
+ switch (state.status) {
161
+ case "success":
162
+ return (0, wasm_js_1.as_bytes)(state.result, state.result_len, c4w);
163
+ case "error":
164
+ throw new Error(state.error);
165
+ case "waiting": {
166
+ await Promise.all(state.requests.map((req) => (0, http_js_1.handle_request)(req, this.config)));
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ finally {
173
+ free_buffers.forEach(ptr => c4w._free(ptr));
174
+ if (ctx)
175
+ c4w._c4w_free_proof_ctx(ctx);
176
+ }
177
+ }
178
+ /**
179
+ * Verifies a proof for the given method and arguments.
180
+ * @param method - The method to verify the proof for
181
+ * @param args - The arguments to verify the proof for
182
+ * @param proof - The proof to verify
183
+ * @returns The result
184
+ */
185
+ async verifyProof(method, args, proof) {
186
+ const c4w = await (0, wasm_js_1.getC4w)();
187
+ const free_buffers = [];
188
+ let ctx = 0;
189
+ try {
190
+ // Call the C function
191
+ const checkpoint_ptr = this.config.trusted_checkpoint
192
+ ? (0, wasm_js_1.as_char_ptr)(this.config.trusted_checkpoint, c4w, free_buffers)
193
+ : 0;
194
+ const witness_keys_ptr = this.config.checkpoint_witness_keys
195
+ ? (0, wasm_js_1.as_char_ptr)(this.config.checkpoint_witness_keys, c4w, free_buffers)
196
+ : 0;
197
+ ctx = c4w._c4w_create_verify_ctx((0, wasm_js_1.copy_to_c)(proof, c4w, free_buffers), proof.length, (0, wasm_js_1.as_char_ptr)(method, c4w, free_buffers), (0, wasm_js_1.as_char_ptr)(JSON.stringify(args), c4w, free_buffers), BigInt(this.config.chainId), checkpoint_ptr, witness_keys_ptr);
198
+ while (true) {
199
+ const state = (0, wasm_js_1.as_json)(c4w._c4w_verify_proof(ctx), c4w, true);
200
+ switch (state.status) {
201
+ case "success":
202
+ return state.result;
203
+ case "error":
204
+ throw new Error(state.error);
205
+ case "waiting": {
206
+ await Promise.all(state.requests.map((req) => (0, http_js_1.handle_request)(req, this.config)));
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ }
212
+ finally {
213
+ free_buffers.forEach(ptr => c4w._free(ptr));
214
+ if (ctx)
215
+ c4w._c4w_free_verify_ctx(ctx);
216
+ }
217
+ }
218
+ /**
219
+ * Executes an RPC method. This includes:
220
+ * - Creating or fetching the proof
221
+ * - Verifying the proof
222
+ * - Returning the result
223
+ * @param method - The method to execute
224
+ * @param args - The arguments to execute the method with
225
+ * @returns The result
226
+ */
227
+ async rpc(method, args, method_type) {
228
+ // eth_subscribe and eth_unsubscribe are handled by C4Client.request before this method is called.
229
+ // This rpc method is for the underlying data fetching/proving.
230
+ if (!this.initMap.get(this.config.chainId)) {
231
+ this.initMap.set(this.config.chainId, true);
232
+ if (!this.config.zk_proof && this.config.checkpointz && this.config.checkpointz.length > 0 && !this.config.trusted_checkpoint && (await (0, wasm_js_1.get_prover_config_hex)(this.config.chainId)).length == 2)
233
+ await this.fetch_checkpointz();
234
+ }
235
+ // Special handling for eth_sendTransaction with verification
236
+ if (method === 'eth_sendTransaction' && this.config.verifyTransactions) {
237
+ return await transactionVerifier_js_1.TransactionVerifier.verifyAndSendTransaction(args[0], this.config, (method, args, methodType) => this.rpc(method, args, methodType), http_js_1.fetch_rpc);
238
+ }
239
+ if (method_type === undefined)
240
+ method_type = await this.getMethodSupport(method);
241
+ switch (method_type) {
242
+ case types_js_1.MethodType.PROOFABLE: {
243
+ if (this.config.verify && !this.config.verify(method, args)) {
244
+ // skip verification and fetch directly
245
+ return await (0, http_js_1.fetch_rpc)(this.config.rpcs, { method, params: args }, false);
246
+ }
247
+ const proof = this.config.prover && this.config.prover.length
248
+ ? await (0, http_js_1.fetch_rpc)(this.config.prover, {
249
+ method,
250
+ params: cleanup_args(method, args),
251
+ c4: await (0, wasm_js_1.get_prover_config_hex)(this.config.chainId),
252
+ zk_proof: !!this.config.zk_proof,
253
+ signers: this.config.checkpoint_witness_keys || '0x'
254
+ }, true)
255
+ : await this.createProof(method, args);
256
+ return this.verifyProof(method, args, proof);
257
+ }
258
+ case types_js_1.MethodType.UNPROOFABLE:
259
+ return await (0, http_js_1.fetch_rpc)(this.config.rpcs, { method, params: args }, false);
260
+ case types_js_1.MethodType.NOT_SUPPORTED:
261
+ throw new types_js_1.ProviderRpcError(4200, `Method ${method} is not supported by C4Client.rpc core`);
262
+ case types_js_1.MethodType.LOCAL:
263
+ return this.verifyProof(method, args, new Uint8Array());
264
+ }
265
+ // Should be unreachable if MethodType enum is comprehensive and handled
266
+ throw new types_js_1.ProviderRpcError(-32603, `Internal error: Unhandled method type for ${method} in C4Client.rpc core`);
267
+ }
268
+ static async register_storage(storage) {
269
+ const c4w = await (0, wasm_js_1.getC4w)();
270
+ c4w.storage = storage;
271
+ }
272
+ async request(args) {
273
+ if (!this.connectionState.initialConnectionAttempted) {
274
+ await this.connectionState.attemptInitialConnection();
275
+ }
276
+ const { method, params } = args;
277
+ const paramsArray = Array.isArray(params) ? params : (params ? [params] : []);
278
+ // Attempt to handle with SubscriptionManager first
279
+ const subscriptionResult = this.subscriptionManager.handleRequest(method, paramsArray);
280
+ if (subscriptionResult !== null) {
281
+ // If handleRequest returns a Promise, it means it's handling the request
282
+ return subscriptionResult;
283
+ }
284
+ let conf = (0, chains_js_1.chain_conf)(this.config, this.config.chainId);
285
+ let strategy = conf?.proofStrategy || this.config.proofStrategy;
286
+ // If not handled by SubscriptionManager, proceed with standard RPC call logic
287
+ try {
288
+ const result = strategy
289
+ ? await strategy(this, args, this.config, http_js_1.fetch_rpc)
290
+ : await this.rpc(method, paramsArray);
291
+ this.connectionState.processSuccessfulRequest(method, result);
292
+ return result;
293
+ }
294
+ catch (error) {
295
+ const providerError = types_js_1.ProviderRpcError.createError(error, args);
296
+ this.connectionState.processFailedRequest(providerError);
297
+ throw providerError;
298
+ }
299
+ }
300
+ /**
301
+ * Registers an event listener on the Colibri client.
302
+ * @param event Event name (e.g. 'connect', 'disconnect', 'message')
303
+ * @param callback Callback invoked with event data
304
+ * @return This client for chaining
305
+ */
306
+ on(event, callback) {
307
+ this.eventEmitter.on(event, callback);
308
+ if ((event === 'connect' || event === 'disconnect') && !this.connectionState.initialConnectionAttempted) {
309
+ this.connectionState.attemptInitialConnection().catch(err => {
310
+ if (this.config.debug)
311
+ console.error("[C4Client] Error during lazy initial connection attempt triggered by 'on':", err);
312
+ });
313
+ }
314
+ return this;
315
+ }
316
+ /**
317
+ * Removes a previously registered event listener from the Colibri client.
318
+ * @param event Event name
319
+ * @param callback Same function reference passed to on()
320
+ * @return This client for chaining
321
+ */
322
+ removeListener(event, callback) {
323
+ this.eventEmitter.removeListener(event, callback);
324
+ return this;
325
+ }
326
+ }
327
+ exports.default = C4Client;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 corpus.core
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ * this software and associated documentation files (the "Software"), to deal in
7
+ * the Software without restriction, including without limitation the rights to
8
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ * the Software, and to permit persons to whom the Software is furnished to do so,
10
+ * subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ *
22
+ * SPDX-License-Identifier: MIT
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ const types_js_1 = require("./types.js");
26
+ function argsToArray(args) {
27
+ return Array.isArray(args) ? args : (args ? [args] : []);
28
+ }
29
+ function deepEqual(a, b) {
30
+ if (a === b)
31
+ return true;
32
+ if (a === null || b === null)
33
+ return false;
34
+ if (a.constructor !== b.constructor)
35
+ return false;
36
+ if (a instanceof Date)
37
+ return a.getTime() === b.getTime();
38
+ if (a instanceof RegExp)
39
+ return a.source === b.source && a.flags === b.flags;
40
+ if (a instanceof Uint8Array)
41
+ return a.length === b.length && a.every((value, index) => value === b[index]);
42
+ if (a instanceof Array)
43
+ return a.length === b.length && a.every((value, index) => deepEqual(value, b[index]));
44
+ if (a instanceof Object)
45
+ return Object.keys(a).every(key => b[key] === undefined || deepEqual(a[key], b[key]));
46
+ if (a instanceof String)
47
+ return a.toLowerCase() === b.toLowerCase();
48
+ if (a instanceof Number)
49
+ return a.toFixed(10) === b.toFixed(10);
50
+ if (a instanceof Boolean)
51
+ return a === b;
52
+ if (a instanceof BigInt)
53
+ return a.toString() === b.toString();
54
+ if (a instanceof Buffer)
55
+ return a.equals(b);
56
+ if (a instanceof Map)
57
+ return a.size === b.size && Array.from(a.entries()).every(([key, value]) => deepEqual(value, b.get(key)));
58
+ if (a instanceof Set)
59
+ return a.size === b.size && Array.from(a).every(value => b.has(value));
60
+ if (a instanceof Date)
61
+ return a.getTime() === b.getTime();
62
+ return a == b;
63
+ }
64
+ function fetch_unverified_rpc(config, req, fetch_rpc) {
65
+ const fallback_provider = config.fallback_provider;
66
+ if (fallback_provider)
67
+ return fallback_provider.request(req);
68
+ let conf = config.chains[config.chainId];
69
+ let rpcs = conf?.rpcs || config.rpcs;
70
+ if (!rpcs || !Array.isArray(rpcs) || rpcs.length === 0)
71
+ throw new Error("No RPC- Endpoint configured");
72
+ return fetch_rpc(rpcs, req, false);
73
+ }
74
+ function OnlyProofStrategy(client, req, config, fetch_rpc) {
75
+ return client.rpc(req.method, argsToArray(req.params));
76
+ }
77
+ async function ProofIfPossibleStrategy(client, req, config, fetch_rpc) {
78
+ const method_type = await client.getMethodSupport(req.method);
79
+ switch (method_type) {
80
+ case types_js_1.MethodType.PROOFABLE:
81
+ case types_js_1.MethodType.LOCAL:
82
+ return client.rpc(req.method, argsToArray(req.params), method_type);
83
+ default:
84
+ return fetch_unverified_rpc(config, req, fetch_rpc);
85
+ }
86
+ }
87
+ async function WarningStrategy(client, req, config, fetch_rpc) {
88
+ const method_type = await client.getMethodSupport(req.method);
89
+ switch (method_type) {
90
+ case types_js_1.MethodType.LOCAL:
91
+ return client.rpc(req.method, argsToArray(req.params), method_type);
92
+ case types_js_1.MethodType.UNPROOFABLE:
93
+ case types_js_1.MethodType.NOT_SUPPORTED:
94
+ return fetch_unverified_rpc(config, req, fetch_rpc);
95
+ case types_js_1.MethodType.PROOFABLE: {
96
+ const [verified_result, unverified_result] = await Promise.all([
97
+ client.rpc(req.method, argsToArray(req.params), types_js_1.MethodType.PROOFABLE)
98
+ .catch(async (err) => {
99
+ await config.warningHandler(req, `[Warning] ${req.method} failed to be verfiy: ${err.message}, falling back to Default`);
100
+ return undefined;
101
+ }),
102
+ fetch_unverified_rpc(config, req, fetch_rpc)
103
+ ]);
104
+ if (verified_result !== undefined && !deepEqual(verified_result, unverified_result))
105
+ await config.warningHandler(req, `[Warning] ${req.method} does not match the rpc-result`);
106
+ return unverified_result;
107
+ }
108
+ }
109
+ }
110
+ exports.default = {
111
+ VerifiedOnly: OnlyProofStrategy,
112
+ VerifyIfPossible: ProofIfPossibleStrategy,
113
+ WarningWithFallback: WarningStrategy
114
+ };