@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/README.md +26 -0
- package/c4w.js +1 -1
- package/c4w.wasm +0 -0
- package/cjs/chains.js +84 -0
- package/cjs/connectionState.js +156 -0
- package/cjs/eventEmitter.js +54 -0
- package/cjs/http.js +172 -0
- package/cjs/index.js +327 -0
- package/cjs/package.json +3 -0
- package/cjs/strategy.js +114 -0
- package/cjs/subscriptionManager.js +314 -0
- package/cjs/transactionVerifier.js +202 -0
- package/cjs/types.js +71 -0
- package/cjs/wasm.js +65 -0
- package/cjs/wasm_cjs.js +65 -0
- package/cjs/wasm_shared.js +200 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +9 -5
- package/wasm.d.ts +8 -52
- package/wasm.js +38 -152
- package/wasm_cjs.d.ts +30 -0
- package/wasm_cjs.js +58 -0
- package/wasm_shared.d.ts +68 -0
- package/wasm_shared.js +190 -0
|
@@ -0,0 +1,314 @@
|
|
|
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.SubscriptionManager = void 0;
|
|
26
|
+
const types_js_1 = require("./types.js"); // Removed unused Config, DataRequest, RequestArguments
|
|
27
|
+
const DEFAULT_POLLING_INTERVAL = 12000; // 12 seconds
|
|
28
|
+
class SubscriptionManager {
|
|
29
|
+
activeSubscriptions = new Map();
|
|
30
|
+
eventEmitter;
|
|
31
|
+
rpcCall;
|
|
32
|
+
config;
|
|
33
|
+
nextSubscriptionId = 0;
|
|
34
|
+
constructor(rpcCallCallback, eventEmitter, config = {}) {
|
|
35
|
+
this.rpcCall = rpcCallCallback;
|
|
36
|
+
this.eventEmitter = eventEmitter;
|
|
37
|
+
this.config = {
|
|
38
|
+
pollingInterval: DEFAULT_POLLING_INTERVAL,
|
|
39
|
+
...config,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// New method to handle requests related to subscriptions and filters
|
|
43
|
+
handleRequest(method, paramsArray) {
|
|
44
|
+
switch (method) {
|
|
45
|
+
case 'eth_subscribe':
|
|
46
|
+
if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
|
|
47
|
+
return Promise.reject(new types_js_1.ProviderRpcError(-32602, 'Invalid params for eth_subscribe: missing subscription type.'));
|
|
48
|
+
}
|
|
49
|
+
return this.subscribe(paramsArray[0], paramsArray.slice(1));
|
|
50
|
+
case 'eth_unsubscribe':
|
|
51
|
+
if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
|
|
52
|
+
return Promise.reject(new types_js_1.ProviderRpcError(-32602, 'Invalid params for eth_unsubscribe: missing or invalid subscription ID.'));
|
|
53
|
+
}
|
|
54
|
+
return this.unsubscribe(paramsArray[0]);
|
|
55
|
+
case 'eth_newBlockFilter':
|
|
56
|
+
return this.newFilter('block');
|
|
57
|
+
case 'eth_newPendingTransactionFilter':
|
|
58
|
+
// This will internally throw, but we return the promise from newFilter
|
|
59
|
+
return this.newFilter('pendingTransaction');
|
|
60
|
+
case 'eth_newFilter': // For log filters
|
|
61
|
+
return this.newFilter('log', paramsArray);
|
|
62
|
+
case 'eth_getFilterChanges':
|
|
63
|
+
if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
|
|
64
|
+
return Promise.reject(new types_js_1.ProviderRpcError(-32602, 'Invalid params for eth_getFilterChanges: missing or invalid filter ID.'));
|
|
65
|
+
}
|
|
66
|
+
return this.getFilterChanges(paramsArray[0]);
|
|
67
|
+
case 'eth_uninstallFilter':
|
|
68
|
+
if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
|
|
69
|
+
return Promise.reject(new types_js_1.ProviderRpcError(-32602, 'Invalid params for eth_uninstallFilter: missing or invalid filter ID.'));
|
|
70
|
+
}
|
|
71
|
+
return this.uninstallFilter(paramsArray[0]);
|
|
72
|
+
default:
|
|
73
|
+
// Not a method handled by SubscriptionManager
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
generateSubscriptionId() {
|
|
78
|
+
this.nextSubscriptionId++;
|
|
79
|
+
// Simple hex string, similar to what nodes return
|
|
80
|
+
return `0x${this.nextSubscriptionId.toString(16)}`;
|
|
81
|
+
}
|
|
82
|
+
// For eth_subscribe
|
|
83
|
+
async subscribe(kind, params = []) {
|
|
84
|
+
if (this.config.debug) {
|
|
85
|
+
console.log(`[SubMan] eth_subscribe: kind=${kind}, params=`, params);
|
|
86
|
+
}
|
|
87
|
+
const subId = this.generateSubscriptionId();
|
|
88
|
+
let subscriptionDetails = {
|
|
89
|
+
id: subId,
|
|
90
|
+
subscriptionMethod: 'eth_subscribe',
|
|
91
|
+
kind: kind,
|
|
92
|
+
};
|
|
93
|
+
switch (kind) {
|
|
94
|
+
case 'newHeads':
|
|
95
|
+
// No specific params needed from client for newHeads
|
|
96
|
+
break;
|
|
97
|
+
case 'logs':
|
|
98
|
+
subscriptionDetails.params = params[0] || {};
|
|
99
|
+
break;
|
|
100
|
+
default:
|
|
101
|
+
// Should not happen if kind is correctly typed
|
|
102
|
+
throw new types_js_1.ProviderRpcError(4200, `Subscription kind "${kind}" is not supported by eth_subscribe.`);
|
|
103
|
+
}
|
|
104
|
+
const finalSubscription = subscriptionDetails;
|
|
105
|
+
this.activeSubscriptions.set(subId, finalSubscription);
|
|
106
|
+
this.schedulePoll(finalSubscription, 0); // Start active polling
|
|
107
|
+
if (this.config.debug) {
|
|
108
|
+
console.log(`[SubMan] Created eth_subscribe subscription: ${subId}`, finalSubscription);
|
|
109
|
+
}
|
|
110
|
+
return subId;
|
|
111
|
+
}
|
|
112
|
+
// For eth_newFilter, eth_newBlockFilter, eth_newPendingTransactionFilter
|
|
113
|
+
async newFilter(kind, params = []) {
|
|
114
|
+
if (this.config.debug) {
|
|
115
|
+
console.log(`[SubMan] eth_newFilter: kind=${kind}, params=`, params);
|
|
116
|
+
}
|
|
117
|
+
if (kind === 'pendingTransaction') {
|
|
118
|
+
if (this.config.debug)
|
|
119
|
+
console.warn('[SubMan] eth_newPendingTransactionFilter is not supported.');
|
|
120
|
+
throw new types_js_1.ProviderRpcError(4200, 'eth_newPendingTransactionFilter is not supported.');
|
|
121
|
+
}
|
|
122
|
+
const filterId = this.generateSubscriptionId();
|
|
123
|
+
const filterDetails = {
|
|
124
|
+
id: filterId,
|
|
125
|
+
subscriptionMethod: 'eth_newFilter',
|
|
126
|
+
kind: kind,
|
|
127
|
+
params: kind === 'log' ? (params[0] || {}) : undefined,
|
|
128
|
+
// No timeoutId as polling is manual via getFilterChanges
|
|
129
|
+
};
|
|
130
|
+
this.activeSubscriptions.set(filterId, filterDetails);
|
|
131
|
+
if (this.config.debug) {
|
|
132
|
+
console.log(`[SubMan] Created eth_newFilter filter: ${filterId}`, filterDetails);
|
|
133
|
+
}
|
|
134
|
+
return filterId;
|
|
135
|
+
}
|
|
136
|
+
// For eth_getFilterChanges
|
|
137
|
+
async getFilterChanges(filterId) {
|
|
138
|
+
const sub = this.activeSubscriptions.get(filterId);
|
|
139
|
+
if (!sub || sub.subscriptionMethod !== 'eth_newFilter') {
|
|
140
|
+
if (this.config.debug)
|
|
141
|
+
console.warn(`[SubMan] getFilterChanges: Filter not found or not a filter type for ID ${filterId}`);
|
|
142
|
+
throw new types_js_1.ProviderRpcError(-32000, 'Filter not found.'); // Standard Ethereum error for missing filter
|
|
143
|
+
}
|
|
144
|
+
if (this.config.debug) {
|
|
145
|
+
console.log(`[SubMan] getFilterChanges for filter: ${filterId}, kind: ${sub.kind}`);
|
|
146
|
+
}
|
|
147
|
+
// Perform a one-time poll based on the filter kind
|
|
148
|
+
return this.executePollLogic(sub, true); // Pass true to indicate results should be returned, not emitted
|
|
149
|
+
}
|
|
150
|
+
// For eth_uninstallFilter
|
|
151
|
+
async uninstallFilter(filterId) {
|
|
152
|
+
const sub = this.activeSubscriptions.get(filterId);
|
|
153
|
+
if (sub && sub.subscriptionMethod === 'eth_newFilter') {
|
|
154
|
+
this.activeSubscriptions.delete(filterId);
|
|
155
|
+
// No timeout to clear for filters
|
|
156
|
+
if (this.config.debug) {
|
|
157
|
+
console.log(`[SubMan] Uninstalled filter: ${filterId}`);
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (this.config.debug) {
|
|
162
|
+
console.warn(`[SubMan] UninstallFilter failed: Filter not found or not a filter type for ID ${filterId}`);
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
// Common polling logic, can return results or emit them via eventEmitter
|
|
167
|
+
async executePollLogic(sub, returnResults = false) {
|
|
168
|
+
let results = [];
|
|
169
|
+
let currentVerifiableBlockNumber = null;
|
|
170
|
+
try {
|
|
171
|
+
const blockNumberHex = await this.rpcCall('eth_blockNumber', []);
|
|
172
|
+
if (blockNumberHex)
|
|
173
|
+
currentVerifiableBlockNumber = BigInt(blockNumberHex);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
if (this.config.debug)
|
|
177
|
+
console.warn('[SubMan] Could not fetch eth_blockNumber for polling context:', e);
|
|
178
|
+
}
|
|
179
|
+
if (sub.kind === 'newHeads' || sub.kind === 'block') { // block is for eth_newBlockFilter
|
|
180
|
+
if (currentVerifiableBlockNumber) {
|
|
181
|
+
let fromBlock = sub.lastPolledBlockNumber ? sub.lastPolledBlockNumber + 1n : currentVerifiableBlockNumber;
|
|
182
|
+
if (sub.kind === 'newHeads') { // For newHeads, we only care if the latest is newer
|
|
183
|
+
if (!sub.lastPolledBlockNumber)
|
|
184
|
+
sub.lastPolledBlockNumber = currentVerifiableBlockNumber;
|
|
185
|
+
if (currentVerifiableBlockNumber <= sub.lastPolledBlockNumber)
|
|
186
|
+
return []; // No new head yet for eth_subscribe('newHeads')
|
|
187
|
+
}
|
|
188
|
+
for (let blockNum = fromBlock; blockNum <= currentVerifiableBlockNumber; blockNum++) {
|
|
189
|
+
try {
|
|
190
|
+
const block = await this.rpcCall('eth_getBlockByNumber', ['0x' + blockNum.toString(16), sub.kind === 'newHeads' /* full block for newHeads */]);
|
|
191
|
+
if (block && this.activeSubscriptions.has(sub.id)) {
|
|
192
|
+
if (sub.kind === 'newHeads')
|
|
193
|
+
results.push(block);
|
|
194
|
+
else if (sub.kind === 'block')
|
|
195
|
+
results.push(block.hash); // eth_newBlockFilter returns block hashes
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
if (this.config.debug)
|
|
200
|
+
console.error(`[SubMan] Error fetching block ${blockNum} for sub/filter ${sub.id}:`, e);
|
|
201
|
+
if (sub.kind === 'block')
|
|
202
|
+
break; // For block filters, stop if a block in sequence fails
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (results.length > 0 && sub.kind === 'newHeads' && !returnResults && this.activeSubscriptions.has(sub.id)) {
|
|
206
|
+
// For newHeads subscription, emit each block individually or as an array?
|
|
207
|
+
// EIP-1193 usually implies one result per message for newHeads
|
|
208
|
+
results.forEach(blockResult => {
|
|
209
|
+
this.eventEmitter.emit('message', {
|
|
210
|
+
type: 'eth_subscription',
|
|
211
|
+
data: { subscription: sub.id, result: blockResult }
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
if (this.config.debug)
|
|
215
|
+
console.log(`[SubMan] Emitted ${results.length} newHead(s) for sub ${sub.id}`);
|
|
216
|
+
}
|
|
217
|
+
sub.lastPolledBlockNumber = currentVerifiableBlockNumber;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (sub.kind === 'logs' || sub.kind === 'log') { // 'log' is for eth_newFilter(logs)
|
|
221
|
+
const filterParams = { ...(sub.params || {}) };
|
|
222
|
+
if (sub.lastPolledBlockNumber) {
|
|
223
|
+
filterParams.fromBlock = '0x' + (sub.lastPolledBlockNumber + 1n).toString(16);
|
|
224
|
+
}
|
|
225
|
+
else if (!filterParams.fromBlock && currentVerifiableBlockNumber) {
|
|
226
|
+
filterParams.fromBlock = '0x' + currentVerifiableBlockNumber.toString(16);
|
|
227
|
+
}
|
|
228
|
+
if (currentVerifiableBlockNumber) {
|
|
229
|
+
filterParams.toBlock = '0x' + currentVerifiableBlockNumber.toString(16);
|
|
230
|
+
}
|
|
231
|
+
else if (!filterParams.toBlock) {
|
|
232
|
+
filterParams.toBlock = 'latest';
|
|
233
|
+
}
|
|
234
|
+
if (filterParams.fromBlock && filterParams.toBlock && filterParams.toBlock !== 'latest' && BigInt(filterParams.fromBlock) > BigInt(filterParams.toBlock)) {
|
|
235
|
+
// Skip if fromBlock is ahead
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
try {
|
|
239
|
+
const logs = await this.rpcCall('eth_getLogs', [filterParams]);
|
|
240
|
+
if (logs && logs.length > 0 && this.activeSubscriptions.has(sub.id)) {
|
|
241
|
+
results = logs;
|
|
242
|
+
if (!returnResults) { // For eth_subscribe('logs')
|
|
243
|
+
this.eventEmitter.emit('message', {
|
|
244
|
+
type: 'eth_subscription',
|
|
245
|
+
data: { subscription: sub.id, result: logs } // Usually an array of logs
|
|
246
|
+
});
|
|
247
|
+
if (this.config.debug)
|
|
248
|
+
console.log(`[SubMan] Emitted ${logs.length} logs for sub ${sub.id}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (filterParams.toBlock !== 'latest')
|
|
252
|
+
sub.lastPolledBlockNumber = BigInt(filterParams.toBlock);
|
|
253
|
+
else if (currentVerifiableBlockNumber)
|
|
254
|
+
sub.lastPolledBlockNumber = currentVerifiableBlockNumber;
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
if (this.config.debug)
|
|
258
|
+
console.error(`[SubMan] Error fetching logs for sub/filter ${sub.id}:`, e);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
schedulePoll(sub, delay) {
|
|
265
|
+
if (!this.activeSubscriptions.has(sub.id) || sub.subscriptionMethod === 'eth_newFilter')
|
|
266
|
+
return;
|
|
267
|
+
sub.timeoutId = setTimeout(async () => {
|
|
268
|
+
try {
|
|
269
|
+
if (!this.activeSubscriptions.has(sub.id))
|
|
270
|
+
return;
|
|
271
|
+
await this.pollSubscriptionAndEmit(sub); // Renamed from pollSubscription for clarity
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error(`[SubMan] Unexpected error in poll scheduling for subscription ${sub.id}:`, error);
|
|
275
|
+
}
|
|
276
|
+
}, delay);
|
|
277
|
+
}
|
|
278
|
+
// Wrapper for eth_subscribe polling that emits results and reschedules
|
|
279
|
+
async pollSubscriptionAndEmit(sub) {
|
|
280
|
+
if (!this.activeSubscriptions.has(sub.id)) {
|
|
281
|
+
if (this.config.debug)
|
|
282
|
+
console.log(`[SubMan] Subscription ${sub.id} was unsubscribed. Skipping poll.`);
|
|
283
|
+
if (sub.timeoutId)
|
|
284
|
+
clearTimeout(sub.timeoutId);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (this.config.debug) {
|
|
288
|
+
console.log(`[SubMan] Polling for eth_subscribe: ${sub.id}, kind: ${sub.kind}`);
|
|
289
|
+
}
|
|
290
|
+
await this.executePollLogic(sub, false); // false means emit results, don't return them
|
|
291
|
+
if (this.activeSubscriptions.has(sub.id)) {
|
|
292
|
+
this.schedulePoll(sub, this.config.pollingInterval || DEFAULT_POLLING_INTERVAL);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// For eth_unsubscribe (specific to eth_subscribe subscriptions)
|
|
296
|
+
async unsubscribe(subscriptionId) {
|
|
297
|
+
const sub = this.activeSubscriptions.get(subscriptionId);
|
|
298
|
+
if (sub && sub.subscriptionMethod === 'eth_subscribe') {
|
|
299
|
+
if (sub.timeoutId) {
|
|
300
|
+
clearTimeout(sub.timeoutId);
|
|
301
|
+
}
|
|
302
|
+
this.activeSubscriptions.delete(subscriptionId);
|
|
303
|
+
if (this.config.debug) {
|
|
304
|
+
console.log(`[SubMan] Unsubscribed (eth_subscribe): ${subscriptionId}`);
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (this.config.debug) {
|
|
309
|
+
console.warn(`[SubMan] Unsubscribe failed: Subscription not found or not an eth_subscribe type for ID ${subscriptionId}`);
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
exports.SubscriptionManager = SubscriptionManager;
|
|
@@ -0,0 +1,202 @@
|
|
|
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.TransactionUtils = exports.PrototypeProtection = exports.TransactionVerifier = void 0;
|
|
26
|
+
const types_js_1 = require("./types.js");
|
|
27
|
+
class TransactionVerifier {
|
|
28
|
+
/**
|
|
29
|
+
* Verifizierte Ausführung von eth_sendTransaction
|
|
30
|
+
* 1. Transaction über fallbackProvider signieren lassen
|
|
31
|
+
* 2. Signierte Transaction mit colibri_decodeTransaction dekodieren
|
|
32
|
+
* 3. Dekodierte Parameter mit Original vergleichen
|
|
33
|
+
* 4. eth_sendRawTransaction ausführen
|
|
34
|
+
*/
|
|
35
|
+
static async verifyAndSendTransaction(txObject, config, rpcMethod, fetchRpc) {
|
|
36
|
+
if (config.debug)
|
|
37
|
+
console.log('[TransactionVerifier] Verifying transaction before sending:', txObject);
|
|
38
|
+
if (!config.fallback_provider) {
|
|
39
|
+
throw new types_js_1.ProviderRpcError(4203, 'Transaction verification requires fallback_provider to be configured');
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// 1. Transaction über fallbackProvider signieren lassen
|
|
43
|
+
const signedRawTx = await config.fallback_provider.request({
|
|
44
|
+
method: 'eth_signTransaction',
|
|
45
|
+
params: [txObject]
|
|
46
|
+
});
|
|
47
|
+
// 2. Signierte Transaction dekodieren
|
|
48
|
+
const decodedTx = await rpcMethod('colibri_decodeTransaction', [signedRawTx], types_js_1.MethodType.LOCAL);
|
|
49
|
+
// 3. Dekodierte Parameter mit Original vergleichen
|
|
50
|
+
const isValid = this.compareTransactionParameters(txObject, decodedTx, config);
|
|
51
|
+
if (!isValid) {
|
|
52
|
+
throw new types_js_1.ProviderRpcError(4201, 'Signed transaction does not match original parameters');
|
|
53
|
+
}
|
|
54
|
+
// 4. Verifizierte Transaction senden
|
|
55
|
+
return await fetchRpc(config.rpcs, {
|
|
56
|
+
method: 'eth_sendRawTransaction',
|
|
57
|
+
params: [signedRawTx]
|
|
58
|
+
}, false);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (config.debug)
|
|
62
|
+
console.error('[TransactionVerifier] Transaction verification failed:', error);
|
|
63
|
+
throw new types_js_1.ProviderRpcError(4202, `Transaction verification failed: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Vergleicht die Parameter einer ursprünglichen Transaction mit einer dekodierten signierten Transaction
|
|
68
|
+
*/
|
|
69
|
+
static compareTransactionParameters(original, decoded, config) {
|
|
70
|
+
if (config.debug) {
|
|
71
|
+
console.log('[TransactionVerifier] Comparing transaction parameters:');
|
|
72
|
+
console.log('Original:', original);
|
|
73
|
+
console.log('Decoded:', decoded);
|
|
74
|
+
}
|
|
75
|
+
// Kritische Parameter vergleichen
|
|
76
|
+
const criticalFields = ['to', 'value', 'data', 'gas', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas', 'nonce'];
|
|
77
|
+
for (const field of criticalFields) {
|
|
78
|
+
if (original[field] !== undefined) {
|
|
79
|
+
// Normalisierung für Hex-Werte (0x-Prefix, Leading Zeros)
|
|
80
|
+
const originalValue = this.normalizeHexValue(original[field]);
|
|
81
|
+
const decodedValue = this.normalizeHexValue(decoded[field]);
|
|
82
|
+
if (originalValue !== decodedValue) {
|
|
83
|
+
if (config.debug) {
|
|
84
|
+
console.warn(`[TransactionVerifier] Transaction parameter mismatch in '${field}': original='${originalValue}', decoded='${decodedValue}'`);
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ChainId prüfen
|
|
91
|
+
if (decoded.chainId && parseInt(decoded.chainId, 16) !== parseInt(config.chainId)) {
|
|
92
|
+
if (config.debug) {
|
|
93
|
+
console.warn(`[TransactionVerifier] ChainId mismatch: expected=${config.chainId}, decoded=${parseInt(decoded.chainId, 16)}`);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Normalisiert Hex-Werte für Vergleiche (entfernt führende Nullen, stellt 0x-Prefix sicher)
|
|
101
|
+
*/
|
|
102
|
+
static normalizeHexValue(value) {
|
|
103
|
+
if (value === undefined || value === null)
|
|
104
|
+
return '';
|
|
105
|
+
if (typeof value === 'number')
|
|
106
|
+
return '0x' + value.toString(16);
|
|
107
|
+
const str = value.toString();
|
|
108
|
+
if (!str.startsWith('0x'))
|
|
109
|
+
return '0x' + str;
|
|
110
|
+
// Führende Nullen entfernen, aber mindestens eine Ziffer behalten
|
|
111
|
+
const withoutPrefix = str.slice(2);
|
|
112
|
+
const normalized = withoutPrefix.replace(/^0+/, '') || '0';
|
|
113
|
+
return '0x' + normalized;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.TransactionVerifier = TransactionVerifier;
|
|
117
|
+
/**
|
|
118
|
+
* Prototype Protection - Schutz vor NPM Supply-Chain-Attacks
|
|
119
|
+
*
|
|
120
|
+
* Friert kritische Prototypen und Methoden ein, um Manipulation zu verhindern
|
|
121
|
+
*/
|
|
122
|
+
class PrototypeProtection {
|
|
123
|
+
/**
|
|
124
|
+
* Schützt eine Klasse vor Prototype Pollution
|
|
125
|
+
*/
|
|
126
|
+
static protectClass(targetClass, criticalMethods = []) {
|
|
127
|
+
// Einfrieren des Prototypes
|
|
128
|
+
Object.freeze(targetClass.prototype);
|
|
129
|
+
// Kritische Methoden gegen Überschreibung schützen
|
|
130
|
+
criticalMethods.forEach(method => {
|
|
131
|
+
const descriptor = Object.getOwnPropertyDescriptor(targetClass.prototype, method);
|
|
132
|
+
if (descriptor) {
|
|
133
|
+
Object.defineProperty(targetClass.prototype, method, {
|
|
134
|
+
...descriptor,
|
|
135
|
+
writable: false,
|
|
136
|
+
configurable: false
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Schützt ein Konfigurationsobjekt vor Manipulation
|
|
143
|
+
*/
|
|
144
|
+
static protectConfig(config, criticalArrays = []) {
|
|
145
|
+
// Kritische Arrays einfrieren
|
|
146
|
+
criticalArrays.forEach(arrayName => {
|
|
147
|
+
if (config[arrayName] && Array.isArray(config[arrayName])) {
|
|
148
|
+
Object.freeze(config[arrayName]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.PrototypeProtection = PrototypeProtection;
|
|
154
|
+
/**
|
|
155
|
+
* Transaction Utilities - Hilfsfunktionen für Transaction-Handling
|
|
156
|
+
*/
|
|
157
|
+
class TransactionUtils {
|
|
158
|
+
/**
|
|
159
|
+
* Validiert ein Transaction-Objekt
|
|
160
|
+
*/
|
|
161
|
+
static validateTransactionObject(txObject) {
|
|
162
|
+
if (!txObject || typeof txObject !== 'object')
|
|
163
|
+
return false;
|
|
164
|
+
// Mindestanforderungen
|
|
165
|
+
if (!txObject.to && !txObject.data)
|
|
166
|
+
return false; // Entweder to-Adresse oder data für Contract-Creation
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Bereinigt Transaction-Parameter für verschiedene Standards
|
|
171
|
+
*/
|
|
172
|
+
static cleanupTransactionParams(txObject) {
|
|
173
|
+
const cleaned = { ...txObject };
|
|
174
|
+
// EIP-1559 vs Legacy Gas-Handling
|
|
175
|
+
if (cleaned.maxFeePerGas || cleaned.maxPriorityFeePerGas) {
|
|
176
|
+
// EIP-1559 - gasPrice entfernen
|
|
177
|
+
delete cleaned.gasPrice;
|
|
178
|
+
}
|
|
179
|
+
else if (cleaned.gasPrice) {
|
|
180
|
+
// Legacy - EIP-1559 Felder entfernen
|
|
181
|
+
delete cleaned.maxFeePerGas;
|
|
182
|
+
delete cleaned.maxPriorityFeePerGas;
|
|
183
|
+
}
|
|
184
|
+
return cleaned;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Erstellt eine Zusammenfassung einer Transaction für Logging
|
|
188
|
+
*/
|
|
189
|
+
static summarizeTransaction(txObject) {
|
|
190
|
+
const to = txObject.to || 'Contract Creation';
|
|
191
|
+
const value = txObject.value ? `${parseInt(txObject.value, 16)} wei` : '0 wei';
|
|
192
|
+
const gas = txObject.gas ? parseInt(txObject.gas, 16) : 'auto';
|
|
193
|
+
return `Transaction(to: ${to}, value: ${value}, gas: ${gas})`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.TransactionUtils = TransactionUtils;
|
|
197
|
+
// Export aller Klassen als Default
|
|
198
|
+
exports.default = {
|
|
199
|
+
TransactionVerifier,
|
|
200
|
+
PrototypeProtection,
|
|
201
|
+
TransactionUtils
|
|
202
|
+
};
|
package/cjs/types.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
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.MethodType = exports.ProviderRpcError = void 0;
|
|
26
|
+
// --- EIP-1193 & C4Client Specific Types ---
|
|
27
|
+
// From EIP-1193: Error structure
|
|
28
|
+
class ProviderRpcError extends Error {
|
|
29
|
+
code;
|
|
30
|
+
data;
|
|
31
|
+
constructor(code, message, data) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'ProviderRpcError';
|
|
34
|
+
this.code = code;
|
|
35
|
+
if (data !== undefined) {
|
|
36
|
+
this.data = data;
|
|
37
|
+
}
|
|
38
|
+
Object.setPrototypeOf(this, ProviderRpcError.prototype);
|
|
39
|
+
}
|
|
40
|
+
static createError(error, args) {
|
|
41
|
+
let providerError;
|
|
42
|
+
if (error instanceof ProviderRpcError) {
|
|
43
|
+
providerError = error;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
let code;
|
|
47
|
+
let message = (error?.message || 'An unknown error occurred');
|
|
48
|
+
const originalData = error?.data;
|
|
49
|
+
if (typeof message === 'string' && message.startsWith('Method ') && message.endsWith(' is not supported')) {
|
|
50
|
+
code = 4200;
|
|
51
|
+
}
|
|
52
|
+
else if (typeof message === 'string' && (message.includes('HTTP error!') || message.includes('All nodes failed') || message.includes('Failed to fetch'))) {
|
|
53
|
+
code = 4900;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
code = (typeof error?.code === 'number') ? error.code : -32603;
|
|
57
|
+
}
|
|
58
|
+
providerError = new ProviderRpcError(code, 'Error in rpc call ' + (args?.method || '') + JSON.stringify(args?.params || []) + ' : ' + message, originalData);
|
|
59
|
+
}
|
|
60
|
+
return providerError;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.ProviderRpcError = ProviderRpcError;
|
|
64
|
+
// Enum for RPC method types
|
|
65
|
+
var MethodType;
|
|
66
|
+
(function (MethodType) {
|
|
67
|
+
MethodType[MethodType["PROOFABLE"] = 1] = "PROOFABLE";
|
|
68
|
+
MethodType[MethodType["UNPROOFABLE"] = 2] = "UNPROOFABLE";
|
|
69
|
+
MethodType[MethodType["NOT_SUPPORTED"] = 3] = "NOT_SUPPORTED";
|
|
70
|
+
MethodType[MethodType["LOCAL"] = 4] = "LOCAL";
|
|
71
|
+
})(MethodType || (exports.MethodType = MethodType = {}));
|
package/cjs/wasm.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
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.set_trusted_checkpoint = exports.get_prover_config_hex = exports.getC4w = exports.loadC4WModule = exports.set_wasm_url = exports.get_default_storage = exports.copy_to_c = exports.as_json = exports.as_char_ptr = exports.as_bytes = void 0;
|
|
26
|
+
// This file is used only for the CommonJS build output. It must not use
|
|
27
|
+
// `import.meta` because that is a parse error in CommonJS environments (e.g. Jest).
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
const node_url_1 = require("node:url");
|
|
30
|
+
const wasm_shared_js_1 = require("./wasm_shared.js");
|
|
31
|
+
Object.defineProperty(exports, "as_bytes", { enumerable: true, get: function () { return wasm_shared_js_1.as_bytes; } });
|
|
32
|
+
Object.defineProperty(exports, "as_char_ptr", { enumerable: true, get: function () { return wasm_shared_js_1.as_char_ptr; } });
|
|
33
|
+
Object.defineProperty(exports, "as_json", { enumerable: true, get: function () { return wasm_shared_js_1.as_json; } });
|
|
34
|
+
Object.defineProperty(exports, "copy_to_c", { enumerable: true, get: function () { return wasm_shared_js_1.copy_to_c; } });
|
|
35
|
+
Object.defineProperty(exports, "get_default_storage", { enumerable: true, get: function () { return wasm_shared_js_1.get_default_storage; } });
|
|
36
|
+
async function dynamicImport(specifier) {
|
|
37
|
+
// Prevent TypeScript from transforming `import()` into `require()` in CJS output.
|
|
38
|
+
// Node supports dynamic import in CommonJS.
|
|
39
|
+
const importer = new Function('p', 'return import(p)');
|
|
40
|
+
return importer(specifier);
|
|
41
|
+
}
|
|
42
|
+
const api = (0, wasm_shared_js_1.createC4wApi)({
|
|
43
|
+
importC4wModule: async () => {
|
|
44
|
+
const pkgRoot = (0, node_path_1.join)(__dirname, '..');
|
|
45
|
+
const glueUrl = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(pkgRoot, 'c4w.js')).href;
|
|
46
|
+
return await dynamicImport(glueUrl);
|
|
47
|
+
},
|
|
48
|
+
resolveWasmLocation: (override) => {
|
|
49
|
+
// In CJS/Node we provide wasmBinary, so we don't need locateFile here.
|
|
50
|
+
// Keep override support for browser-like loaders (if someone uses it).
|
|
51
|
+
if (override)
|
|
52
|
+
return override;
|
|
53
|
+
return null;
|
|
54
|
+
},
|
|
55
|
+
getWasmBinary: async (override) => {
|
|
56
|
+
const fs = await dynamicImport('node:fs');
|
|
57
|
+
const wasmPath = override ?? (0, node_path_1.join)(__dirname, '..', 'c4w.wasm');
|
|
58
|
+
return fs.readFileSync(wasmPath);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
exports.set_wasm_url = api.set_wasm_url;
|
|
62
|
+
exports.loadC4WModule = api.loadC4WModule;
|
|
63
|
+
exports.getC4w = api.getC4w;
|
|
64
|
+
exports.get_prover_config_hex = api.get_prover_config_hex;
|
|
65
|
+
exports.set_trusted_checkpoint = api.set_trusted_checkpoint;
|