quantumcoin 7.0.2 → 7.0.4
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/.github/workflows/publish-npmjs.yaml +22 -22
- package/.gitignore +15 -15
- package/LICENSE +21 -21
- package/README-SDK.md +756 -756
- package/README.md +165 -152
- package/SPEC.md +3845 -3845
- package/config.d.ts +50 -50
- package/config.js +115 -115
- package/examples/AllSolidityTypes.sol +184 -184
- package/examples/SimpleIERC20.sol +74 -74
- package/examples/events.js +41 -35
- package/examples/events.ts +35 -0
- package/examples/example-generator-sdk-js.js +100 -95
- package/examples/example-generator-sdk-js.ts +77 -0
- package/examples/example-generator-sdk-ts.js +100 -95
- package/examples/example-generator-sdk-ts.ts +77 -0
- package/examples/example.js +72 -61
- package/examples/example.ts +61 -0
- package/examples/offline-signing.js +79 -0
- package/examples/offline-signing.ts +66 -0
- package/examples/package-lock.json +48 -57
- package/examples/package.json +32 -16
- package/examples/read-operations.js +32 -27
- package/examples/read-operations.ts +31 -0
- package/examples/sdk-generator-erc20.inline.json +251 -251
- package/examples/solidity-types.ts +43 -43
- package/examples/wallet-offline.js +35 -29
- package/examples/wallet-offline.ts +34 -0
- package/generate-sdk.js +1824 -1383
- package/index.js +12 -12
- package/package.json +95 -75
- package/scripts/copy-declarations.js +31 -0
- package/scripts/run-all-one-by-one.js +151 -0
- package/src/abi/fragments.d.ts +42 -42
- package/src/abi/fragments.js +63 -63
- package/src/abi/index.d.ts +13 -13
- package/src/abi/index.js +9 -9
- package/src/abi/interface.d.ts +128 -132
- package/src/abi/interface.js +590 -590
- package/src/abi/js-abi-coder.d.ts +8 -0
- package/src/abi/js-abi-coder.js +474 -474
- package/src/constants.d.ts +66 -61
- package/src/constants.js +101 -94
- package/src/contract/contract-factory.d.ts +28 -28
- package/src/contract/contract-factory.js +105 -105
- package/src/contract/contract.d.ts +113 -105
- package/src/contract/contract.js +354 -312
- package/src/contract/index.d.ts +9 -9
- package/src/contract/index.js +9 -9
- package/src/errors/index.d.ts +92 -92
- package/src/errors/index.js +188 -188
- package/src/generator/index.d.ts +74 -0
- package/src/generator/index.js +1404 -1201
- package/src/index.d.ts +125 -127
- package/src/index.js +41 -41
- package/src/internal/hex.d.ts +61 -61
- package/src/internal/hex.js +144 -144
- package/src/providers/extra-providers.d.ts +139 -128
- package/src/providers/extra-providers.js +600 -575
- package/src/providers/index.d.ts +17 -16
- package/src/providers/index.js +10 -10
- package/src/providers/json-rpc-provider.d.ts +12 -12
- package/src/providers/json-rpc-provider.js +79 -79
- package/src/providers/provider.d.ts +207 -196
- package/src/providers/provider.js +392 -359
- package/src/types/index.d.ts +214 -462
- package/src/types/index.js +9 -9
- package/src/utils/address.d.ts +72 -72
- package/src/utils/address.js +181 -182
- package/src/utils/encoding.d.ts +120 -120
- package/src/utils/encoding.js +306 -306
- package/src/utils/hashing.d.ts +82 -76
- package/src/utils/hashing.js +313 -298
- package/src/utils/index.d.ts +65 -55
- package/src/utils/index.js +13 -13
- package/src/utils/result.d.ts +57 -57
- package/src/utils/result.js +128 -128
- package/src/utils/rlp.d.ts +12 -12
- package/src/utils/rlp.js +200 -200
- package/src/utils/units.d.ts +29 -29
- package/src/utils/units.js +107 -107
- package/src/wallet/index.d.ts +10 -10
- package/src/wallet/index.js +8 -8
- package/src/wallet/wallet.d.ts +160 -160
- package/src/wallet/wallet.js +483 -489
- package/test/e2e/all-solidity-types.dynamic.test.js +207 -200
- package/test/e2e/all-solidity-types.dynamic.test.ts +191 -0
- package/test/e2e/all-solidity-types.fixtures.js +231 -231
- package/test/e2e/all-solidity-types.generated-sdks.e2e.test.js +387 -361
- package/test/e2e/all-solidity-types.generated-sdks.e2e.test.ts +350 -0
- package/test/e2e/helpers.js +59 -47
- package/test/e2e/signing-context-and-fee.e2e.test.js +137 -0
- package/test/e2e/signing-context-and-fee.e2e.test.ts +128 -0
- package/test/e2e/simple-erc20.generated-sdks.e2e.test.js +168 -144
- package/test/e2e/simple-erc20.generated-sdks.e2e.test.ts +141 -0
- package/test/e2e/transactional.test.js +245 -191
- package/test/e2e/transactional.test.ts +208 -0
- package/test/e2e/typed-generator.e2e.test.js +407 -402
- package/test/e2e/typed-generator.e2e.test.ts +337 -0
- package/test/fixtures/ConstructorParam.sol +23 -23
- package/test/fixtures/MultiContracts.sol +37 -37
- package/test/fixtures/SimpleStorage.sol +18 -18
- package/test/fixtures/StakingContract.abi.json +1 -1
- package/test/integration/ipc-provider.test.js +49 -44
- package/test/integration/ipc-provider.test.ts +44 -0
- package/test/integration/provider.test.js +88 -72
- package/test/integration/provider.test.ts +85 -0
- package/test/integration/ws-provider.test.js +41 -33
- package/test/integration/ws-provider.test.ts +38 -0
- package/test/security/malformed-input.test.js +37 -31
- package/test/security/malformed-input.test.ts +35 -0
- package/test/unit/_encrypted-output.txt +6 -0
- package/test/unit/_log-encrypted-jsons.js +45 -0
- package/test/unit/_write-keystore-fixture.js +16 -0
- package/test/unit/abi-interface.test.js +103 -98
- package/test/unit/abi-interface.test.ts +102 -0
- package/test/unit/address-wallet.test.js +355 -257
- package/test/unit/address-wallet.test.ts +342 -0
- package/test/unit/browser-provider.test.js +85 -82
- package/test/unit/browser-provider.test.ts +79 -0
- package/test/unit/contract.test.js +85 -82
- package/test/unit/contract.test.ts +83 -0
- package/test/unit/encoding-units-rlp.test.js +92 -89
- package/test/unit/encoding-units-rlp.test.ts +91 -0
- package/test/unit/errors.test.js +77 -74
- package/test/unit/errors.test.ts +76 -0
- package/test/unit/filter-by-blockhash.test.js +55 -52
- package/test/unit/filter-by-blockhash.test.ts +54 -0
- package/test/unit/fixtures/encrypted-keystores-48-32-36.js +9 -0
- package/test/unit/generate-contract-cli.test.js +42 -39
- package/test/unit/generate-contract-cli.test.ts +41 -0
- package/test/unit/generate-sdk-artifacts-json.test.js +113 -110
- package/test/unit/generate-sdk-artifacts-json.test.ts +110 -0
- package/test/unit/generator.test.js +102 -98
- package/test/unit/generator.test.ts +101 -0
- package/test/unit/hashing.test.js +68 -54
- package/test/unit/hashing.test.ts +67 -0
- package/test/unit/init.test.js +39 -36
- package/test/unit/init.test.ts +38 -0
- package/test/unit/interface.test.js +56 -53
- package/test/unit/interface.test.ts +54 -0
- package/test/unit/internal-hex.test.js +50 -47
- package/test/unit/internal-hex.test.ts +49 -0
- package/test/unit/populate-transaction.test.js +65 -0
- package/test/unit/populate-transaction.test.ts +64 -0
- package/test/unit/providers.test.js +200 -144
- package/test/unit/providers.test.ts +196 -0
- package/test/unit/result.test.js +80 -77
- package/test/unit/result.test.ts +79 -0
- package/test/unit/solidity-types.test.js +49 -46
- package/test/unit/solidity-types.test.ts +39 -0
- package/test/unit/utils.test.js +57 -54
- package/test/unit/utils.test.ts +56 -0
- package/test/verbose-logger.js +74 -0
- package/tsconfig.build.json +14 -0
|
@@ -1,575 +1,600 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Additional provider classes (compatibility stubs).
|
|
3
|
-
*
|
|
4
|
-
* These are included to match the API shape described in SPEC.md. Only
|
|
5
|
-
* JsonRpcProvider is fully implemented for network operations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { AbstractProvider } = require("./provider");
|
|
9
|
-
const { JsonRpcProvider } = require("./json-rpc-provider");
|
|
10
|
-
const { makeError } = require("../errors");
|
|
11
|
-
const net = require("node:net");
|
|
12
|
-
const { JsonRpcSigner } = require("../wallet/wallet");
|
|
13
|
-
const { normalizeHex, isHexString } = require("../internal/hex");
|
|
14
|
-
|
|
15
|
-
let _wsRpcId = 1;
|
|
16
|
-
let _ipcRpcId = 1;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Extract the first complete JSON object from a stream buffer.
|
|
20
|
-
*
|
|
21
|
-
* geth's IPC JSON-RPC typically uses newline-delimited JSON, but we also support
|
|
22
|
-
* non-newline framing by scanning for balanced braces and respecting strings.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} buffer
|
|
25
|
-
* @returns {{ json: string, rest: string } | null}
|
|
26
|
-
*/
|
|
27
|
-
function _extractFirstJsonObject(buffer) {
|
|
28
|
-
const start = buffer.indexOf("{");
|
|
29
|
-
if (start === -1) return null;
|
|
30
|
-
|
|
31
|
-
let depth = 0;
|
|
32
|
-
let inString = false;
|
|
33
|
-
let escape = false;
|
|
34
|
-
|
|
35
|
-
for (let i = start; i < buffer.length; i++) {
|
|
36
|
-
const ch = buffer[i];
|
|
37
|
-
|
|
38
|
-
if (inString) {
|
|
39
|
-
if (escape) {
|
|
40
|
-
escape = false;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (ch === "\\") {
|
|
44
|
-
escape = true;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (ch === '"') {
|
|
48
|
-
inString = false;
|
|
49
|
-
}
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (ch === '"') {
|
|
54
|
-
inString = true;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (ch === "{") depth++;
|
|
59
|
-
if (ch === "}") depth--;
|
|
60
|
-
|
|
61
|
-
if (depth === 0) {
|
|
62
|
-
const json = buffer.slice(start, i + 1);
|
|
63
|
-
const rest = buffer.slice(i + 1);
|
|
64
|
-
return { json, rest };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class WebSocketProvider extends AbstractProvider {
|
|
72
|
-
/**
|
|
73
|
-
* Create a WebSocket JSON-RPC provider.
|
|
74
|
-
*
|
|
75
|
-
* This uses the built-in global `WebSocket` available in recent Node.js
|
|
76
|
-
* versions (via undici). No additional npm dependencies are required.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} url WebSocket endpoint (e.g. ws://127.0.0.1:8546)
|
|
79
|
-
* @param {number=} chainId Optional chain id (compat)
|
|
80
|
-
*/
|
|
81
|
-
constructor(url, chainId) {
|
|
82
|
-
super();
|
|
83
|
-
if (typeof url !== "string" || url.trim().length === 0) {
|
|
84
|
-
throw makeError("missing WebSocket url", "INVALID_ARGUMENT", { url });
|
|
85
|
-
}
|
|
86
|
-
this.url = url.trim();
|
|
87
|
-
this.chainId = chainId == null ? 123123 : chainId;
|
|
88
|
-
|
|
89
|
-
/** @type {any|null} */
|
|
90
|
-
this._ws = null;
|
|
91
|
-
/** @type {Promise<void>|null} */
|
|
92
|
-
this._wsReady = null;
|
|
93
|
-
/** @type {Map<number, { resolve: Function, reject: Function, timer: any }>} */
|
|
94
|
-
this._pending = new Map();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Close the underlying WebSocket connection and reject any pending requests.
|
|
99
|
-
* This is important for tests so the Node.js event loop can exit cleanly.
|
|
100
|
-
*/
|
|
101
|
-
destroy() {
|
|
102
|
-
try {
|
|
103
|
-
if (this._ws && typeof this._ws.close === "function") this._ws.close();
|
|
104
|
-
} catch {
|
|
105
|
-
// ignore
|
|
106
|
-
}
|
|
107
|
-
this._ws = null;
|
|
108
|
-
this._wsReady = null;
|
|
109
|
-
this._rejectAllPending(makeError("WebSocket closed", "UNKNOWN_ERROR", { url: this.url }));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
_rejectAllPending(err) {
|
|
113
|
-
for (const [id, p] of this._pending.entries()) {
|
|
114
|
-
try {
|
|
115
|
-
clearTimeout(p.timer);
|
|
116
|
-
p.reject(err);
|
|
117
|
-
} catch {
|
|
118
|
-
// ignore
|
|
119
|
-
}
|
|
120
|
-
this._pending.delete(id);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async _connect() {
|
|
125
|
-
if (this._ws && this._ws.readyState === 1) return;
|
|
126
|
-
if (this._wsReady) return this._wsReady;
|
|
127
|
-
|
|
128
|
-
const WS = globalThis.WebSocket;
|
|
129
|
-
if (typeof WS !== "function") {
|
|
130
|
-
throw makeError("WebSocket not available in this Node.js runtime", "NOT_IMPLEMENTED", {});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this._wsReady = new Promise((resolve, reject) => {
|
|
134
|
-
const ws = new WS(this.url);
|
|
135
|
-
this._ws = ws;
|
|
136
|
-
|
|
137
|
-
const connectTimer = setTimeout(() => {
|
|
138
|
-
try {
|
|
139
|
-
ws.close();
|
|
140
|
-
} catch {
|
|
141
|
-
// ignore
|
|
142
|
-
}
|
|
143
|
-
reject(makeError("WebSocket connect timeout", "UNKNOWN_ERROR", { url: this.url }));
|
|
144
|
-
}, 10_000);
|
|
145
|
-
|
|
146
|
-
const onOpen = () => {
|
|
147
|
-
clearTimeout(connectTimer);
|
|
148
|
-
resolve();
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const onError = (event) => {
|
|
152
|
-
clearTimeout(connectTimer);
|
|
153
|
-
reject(
|
|
154
|
-
makeError("WebSocket error", "UNKNOWN_ERROR", {
|
|
155
|
-
url: this.url,
|
|
156
|
-
error: event && event.message ? event.message : String(event),
|
|
157
|
-
}),
|
|
158
|
-
);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const onClose = () => {
|
|
162
|
-
clearTimeout(connectTimer);
|
|
163
|
-
this._ws = null;
|
|
164
|
-
this._wsReady = null;
|
|
165
|
-
this._rejectAllPending(makeError("WebSocket closed", "UNKNOWN_ERROR", { url: this.url }));
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const onMessage = (event) => {
|
|
169
|
-
const data = event && event.data != null ? event.data : "";
|
|
170
|
-
const text = typeof data === "string" ? data : data.toString();
|
|
171
|
-
let msg;
|
|
172
|
-
try {
|
|
173
|
-
msg = JSON.parse(text);
|
|
174
|
-
} catch {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (!msg || typeof msg !== "object") return;
|
|
178
|
-
if (typeof msg.id !== "number") return;
|
|
179
|
-
|
|
180
|
-
const pending = this._pending.get(msg.id);
|
|
181
|
-
if (!pending) return;
|
|
182
|
-
this._pending.delete(msg.id);
|
|
183
|
-
clearTimeout(pending.timer);
|
|
184
|
-
|
|
185
|
-
if (msg.error) {
|
|
186
|
-
pending.reject(makeError("JSON-RPC error", "UNKNOWN_ERROR", { error: msg.error }));
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
pending.resolve(msg.result);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Prefer addEventListener API, but fall back to on* properties if needed.
|
|
193
|
-
if (typeof ws.addEventListener === "function") {
|
|
194
|
-
ws.addEventListener("open", onOpen);
|
|
195
|
-
ws.addEventListener("error", onError);
|
|
196
|
-
ws.addEventListener("close", onClose);
|
|
197
|
-
ws.addEventListener("message", onMessage);
|
|
198
|
-
} else {
|
|
199
|
-
ws.onopen = onOpen;
|
|
200
|
-
ws.onerror = onError;
|
|
201
|
-
ws.onclose = onClose;
|
|
202
|
-
ws.onmessage = onMessage;
|
|
203
|
-
}
|
|
204
|
-
}).finally(() => {
|
|
205
|
-
// If connection failed, allow retries.
|
|
206
|
-
if (!this._ws || this._ws.readyState !== 1) {
|
|
207
|
-
this._ws = null;
|
|
208
|
-
this._wsReady = null;
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return this._wsReady;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* @param {string} method
|
|
217
|
-
* @param {any[]=} params
|
|
218
|
-
* @returns {Promise<any>}
|
|
219
|
-
*/
|
|
220
|
-
async _perform(method, params) {
|
|
221
|
-
await this._connect();
|
|
222
|
-
const ws = this._ws;
|
|
223
|
-
if (!ws || ws.readyState !== 1) {
|
|
224
|
-
throw makeError("WebSocket not connected", "UNKNOWN_ERROR", { url: this.url, method });
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const id = _wsRpcId++;
|
|
228
|
-
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: params || [] });
|
|
229
|
-
|
|
230
|
-
return new Promise((resolve, reject) => {
|
|
231
|
-
const timer = setTimeout(() => {
|
|
232
|
-
this._pending.delete(id);
|
|
233
|
-
reject(makeError("WebSocket JSON-RPC timeout", "UNKNOWN_ERROR", { url: this.url, method }));
|
|
234
|
-
}, 30_000);
|
|
235
|
-
|
|
236
|
-
this._pending.set(id, { resolve, reject, timer });
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
ws.send(body);
|
|
240
|
-
} catch (e) {
|
|
241
|
-
clearTimeout(timer);
|
|
242
|
-
this._pending.delete(id);
|
|
243
|
-
reject(
|
|
244
|
-
makeError("WebSocket send failed", "UNKNOWN_ERROR", {
|
|
245
|
-
url: this.url,
|
|
246
|
-
method,
|
|
247
|
-
error: e && e.message ? e.message : String(e),
|
|
248
|
-
}),
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
class IpcSocketProvider extends AbstractProvider {
|
|
256
|
-
/**
|
|
257
|
-
* Create an IPC provider.
|
|
258
|
-
*
|
|
259
|
-
* On Windows, use a named pipe path like: `\\\\.\\pipe\\geth.ipc`
|
|
260
|
-
* On Unix, use a domain socket path like: `/path/to/geth.ipc`
|
|
261
|
-
*
|
|
262
|
-
* @param {string} path IPC socket path
|
|
263
|
-
*/
|
|
264
|
-
constructor(path) {
|
|
265
|
-
super();
|
|
266
|
-
if (typeof path !== "string" || path.length === 0) {
|
|
267
|
-
throw makeError("missing IPC path", "INVALID_ARGUMENT", { path });
|
|
268
|
-
}
|
|
269
|
-
this.path = path;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* @param {string} method
|
|
274
|
-
* @param {any[]=} params
|
|
275
|
-
* @returns {Promise<any>}
|
|
276
|
-
*/
|
|
277
|
-
async _perform(method, params) {
|
|
278
|
-
const id = _ipcRpcId++;
|
|
279
|
-
const body =
|
|
280
|
-
JSON.stringify({
|
|
281
|
-
jsonrpc: "2.0",
|
|
282
|
-
id,
|
|
283
|
-
method,
|
|
284
|
-
params: params || [],
|
|
285
|
-
}) + "\n";
|
|
286
|
-
|
|
287
|
-
return new Promise((resolve, reject) => {
|
|
288
|
-
/** @type {boolean} */
|
|
289
|
-
let done = false;
|
|
290
|
-
/** @type {string} */
|
|
291
|
-
let buffer = "";
|
|
292
|
-
|
|
293
|
-
const socket = net.createConnection(this.path);
|
|
294
|
-
socket.setEncoding("utf8");
|
|
295
|
-
socket.setTimeout(30_000);
|
|
296
|
-
|
|
297
|
-
function finish(err, value) {
|
|
298
|
-
if (done) return;
|
|
299
|
-
done = true;
|
|
300
|
-
try {
|
|
301
|
-
socket.destroy();
|
|
302
|
-
} catch {
|
|
303
|
-
// ignore
|
|
304
|
-
}
|
|
305
|
-
if (err) reject(err);
|
|
306
|
-
else resolve(value);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
socket.on("connect", () => {
|
|
310
|
-
try {
|
|
311
|
-
socket.write(body);
|
|
312
|
-
} catch (e) {
|
|
313
|
-
finish(
|
|
314
|
-
makeError("IPC write failed", "UNKNOWN_ERROR", {
|
|
315
|
-
method,
|
|
316
|
-
path: this.path,
|
|
317
|
-
error: e && e.message ? e.message : String(e),
|
|
318
|
-
}),
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
socket.on("timeout", () => {
|
|
324
|
-
finish(makeError("IPC request timeout", "UNKNOWN_ERROR", { method, path: this.path }));
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
socket.on("error", (e) => {
|
|
328
|
-
finish(
|
|
329
|
-
makeError("IPC socket error", "UNKNOWN_ERROR", {
|
|
330
|
-
method,
|
|
331
|
-
path: this.path,
|
|
332
|
-
error: e && e.message ? e.message : String(e),
|
|
333
|
-
}),
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
socket.on("data", (chunk) => {
|
|
338
|
-
buffer += String(chunk);
|
|
339
|
-
|
|
340
|
-
// Fast path: newline-delimited JSON responses.
|
|
341
|
-
while (buffer.includes("\n")) {
|
|
342
|
-
const idx = buffer.indexOf("\n");
|
|
343
|
-
const line = buffer.slice(0, idx).trim();
|
|
344
|
-
buffer = buffer.slice(idx + 1);
|
|
345
|
-
if (!line) continue;
|
|
346
|
-
try {
|
|
347
|
-
const json = JSON.parse(line);
|
|
348
|
-
if (json && json.id === id) {
|
|
349
|
-
if (json.error) {
|
|
350
|
-
finish(makeError("JSON-RPC error", "UNKNOWN_ERROR", { method, error: json.error }));
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
finish(null, json.result);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
// If parsing fails, fall through to brace-balanced extraction on the accumulated buffer.
|
|
358
|
-
buffer = line + "\n" + buffer;
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Robust path: find a complete JSON object by balanced braces.
|
|
364
|
-
while (true) {
|
|
365
|
-
const extracted = _extractFirstJsonObject(buffer);
|
|
366
|
-
if (!extracted) return;
|
|
367
|
-
buffer = extracted.rest;
|
|
368
|
-
try {
|
|
369
|
-
const json = JSON.parse(extracted.json);
|
|
370
|
-
if (json && json.id === id) {
|
|
371
|
-
if (json.error) {
|
|
372
|
-
finish(makeError("JSON-RPC error", "UNKNOWN_ERROR", { method, error: json.error }));
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
finish(null, json.result);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
} catch {
|
|
379
|
-
// ignore malformed object and continue scanning remainder
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
socket.on("end", () => {
|
|
385
|
-
if (!done) finish(makeError("IPC connection ended before response", "UNKNOWN_ERROR", { method, path: this.path }));
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
class BrowserProvider extends AbstractProvider {
|
|
392
|
-
/**
|
|
393
|
-
* Create a BrowserProvider from an EIP-1193 provider (e.g. MetaMask).
|
|
394
|
-
*
|
|
395
|
-
* This is a lightweight implementation that focuses on the core behaviors:
|
|
396
|
-
* - `send(method, params)` dispatches EIP-1193 requests
|
|
397
|
-
* - `getSigner()` resolves the connected account
|
|
398
|
-
* - emits ethers-like `debug` events for request/response tracking
|
|
399
|
-
*
|
|
400
|
-
* @param {{ request: Function }} eip1193Provider
|
|
401
|
-
* @param {any=} network Unused (compat)
|
|
402
|
-
* @param {{ providerInfo?: any }=} options
|
|
403
|
-
*/
|
|
404
|
-
constructor(eip1193Provider, network, options) {
|
|
405
|
-
super();
|
|
406
|
-
void network;
|
|
407
|
-
this.provider = eip1193Provider;
|
|
408
|
-
this.providerInfo = options && options.providerInfo ? options.providerInfo : null;
|
|
409
|
-
|
|
410
|
-
if (!this.provider || typeof this.provider.request !== "function") {
|
|
411
|
-
throw makeError("invalid EIP-1193 provider (missing request)", "INVALID_ARGUMENT", { provider: this.provider });
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Send an EIP-1193 JSON-RPC request.
|
|
417
|
-
* @param {string} method
|
|
418
|
-
* @param {any[]|Record<string, any>=} params
|
|
419
|
-
* @returns {Promise<any>}
|
|
420
|
-
*/
|
|
421
|
-
async send(method, params) {
|
|
422
|
-
const payload = { method, params: params == null ? [] : params };
|
|
423
|
-
this.emit("debug", { action: "sendEip1193Payload", payload });
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
const result = await this.provider.request(payload);
|
|
427
|
-
this.emit("debug", { action: "receiveEip1193Result", result });
|
|
428
|
-
return result;
|
|
429
|
-
} catch (e) {
|
|
430
|
-
const err = this.getRpcError(payload, e);
|
|
431
|
-
this.emit("debug", { action: "receiveEip1193Error", error: err });
|
|
432
|
-
throw err;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Map an EIP-1193 error into a normalized Error.
|
|
438
|
-
* @param {{ method: string, params?: any }} payload
|
|
439
|
-
* @param {any} error
|
|
440
|
-
* @returns {Error}
|
|
441
|
-
*/
|
|
442
|
-
getRpcError(payload, error) {
|
|
443
|
-
if (error instanceof Error) return error;
|
|
444
|
-
return makeError("EIP-1193 error", "UNKNOWN_ERROR", { payload, error });
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Ethers compatibility: internal send for single or batched payloads.
|
|
449
|
-
* @param {any|any[]} payload
|
|
450
|
-
* @returns {Promise<any>}
|
|
451
|
-
*/
|
|
452
|
-
async _send(payload) {
|
|
453
|
-
if (Array.isArray(payload)) {
|
|
454
|
-
const out = [];
|
|
455
|
-
for (const p of payload) {
|
|
456
|
-
// eslint-disable-next-line no-await-in-loop
|
|
457
|
-
out.push(await this.send(p.method, p.params));
|
|
458
|
-
}
|
|
459
|
-
return out;
|
|
460
|
-
}
|
|
461
|
-
return this.send(payload.method, payload.params);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* @param {string} method
|
|
466
|
-
* @param {any[]=} params
|
|
467
|
-
* @returns {Promise<any>}
|
|
468
|
-
*/
|
|
469
|
-
async _perform(method, params) {
|
|
470
|
-
return this.send(method, params || []);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Returns a signer for the specified account index or address.
|
|
475
|
-
* @param {number|string=} address
|
|
476
|
-
* @returns {Promise<JsonRpcSigner>}
|
|
477
|
-
*/
|
|
478
|
-
async getSigner(address) {
|
|
479
|
-
const accounts = await this.send("eth_accounts", []);
|
|
480
|
-
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
481
|
-
throw makeError("no accounts available from EIP-1193 provider", "UNKNOWN_ERROR", {});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (address == null) {
|
|
485
|
-
return new JsonRpcSigner(this, accounts[0]);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (typeof address === "number") {
|
|
489
|
-
const a = accounts[address];
|
|
490
|
-
if (!a) throw makeError("account index out of range", "INVALID_ARGUMENT", { address });
|
|
491
|
-
return new JsonRpcSigner(this, a);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (typeof address === "string") {
|
|
495
|
-
const found = accounts.find((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
496
|
-
if (!found) throw makeError("account not found", "INVALID_ARGUMENT", { address });
|
|
497
|
-
return new JsonRpcSigner(this, found);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
throw makeError("invalid signer address/index", "INVALID_ARGUMENT", { address });
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Resolve whether this provider manages the address/index.
|
|
505
|
-
* @param {number|string} address
|
|
506
|
-
* @returns {Promise<boolean>}
|
|
507
|
-
*/
|
|
508
|
-
async hasSigner(address) {
|
|
509
|
-
const accounts = await this.send("eth_accounts", []);
|
|
510
|
-
if (!Array.isArray(accounts)) return false;
|
|
511
|
-
if (typeof address === "number") return address >= 0 && address < accounts.length;
|
|
512
|
-
if (typeof address === "string") return accounts.some((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* FallbackProvider - uses the first provider in the list.
|
|
519
|
-
*/
|
|
520
|
-
class FallbackProvider extends AbstractProvider {
|
|
521
|
-
/**
|
|
522
|
-
* @param {AbstractProvider[]|AbstractProvider} providers
|
|
523
|
-
*/
|
|
524
|
-
constructor(providers) {
|
|
525
|
-
super();
|
|
526
|
-
this.providers = Array.isArray(providers) ? providers : [providers];
|
|
527
|
-
if (!this.providers.length) throw makeError("no providers provided", "INVALID_ARGUMENT", {});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
async _perform(method, params) {
|
|
531
|
-
// Simple strategy: try providers in order.
|
|
532
|
-
let lastErr;
|
|
533
|
-
for (const p of this.providers) {
|
|
534
|
-
try {
|
|
535
|
-
// eslint-disable-next-line no-await-in-loop
|
|
536
|
-
return await p._perform(method, params);
|
|
537
|
-
} catch (e) {
|
|
538
|
-
lastErr = e;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
throw lastErr || makeError("all providers failed", "UNKNOWN_ERROR", { method });
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* FilterByBlockHash placeholder.
|
|
547
|
-
*/
|
|
548
|
-
class FilterByBlockHash {
|
|
549
|
-
constructor(blockHash, address, topics) {
|
|
550
|
-
if (typeof blockHash !== "string" || !isHexString(blockHash, 32)) {
|
|
551
|
-
throw makeError("invalid blockHash", "INVALID_ARGUMENT", { blockHash });
|
|
552
|
-
}
|
|
553
|
-
this.blockHash = normalizeHex(blockHash);
|
|
554
|
-
if (address != null) this.address = address;
|
|
555
|
-
if (topics != null) this.topics = topics;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
toJSON() {
|
|
559
|
-
// Ensure JSON serialization includes only the filter fields.
|
|
560
|
-
return {
|
|
561
|
-
blockHash: this.blockHash,
|
|
562
|
-
address: this.address,
|
|
563
|
-
topics: this.topics,
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Additional provider classes (compatibility stubs).
|
|
3
|
+
*
|
|
4
|
+
* These are included to match the API shape described in SPEC.md. Only
|
|
5
|
+
* JsonRpcProvider is fully implemented for network operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { AbstractProvider } = require("./provider");
|
|
9
|
+
const { JsonRpcProvider } = require("./json-rpc-provider");
|
|
10
|
+
const { makeError } = require("../errors");
|
|
11
|
+
const net = require("node:net");
|
|
12
|
+
const { JsonRpcSigner } = require("../wallet/wallet");
|
|
13
|
+
const { normalizeHex, isHexString } = require("../internal/hex");
|
|
14
|
+
|
|
15
|
+
let _wsRpcId = 1;
|
|
16
|
+
let _ipcRpcId = 1;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract the first complete JSON object from a stream buffer.
|
|
20
|
+
*
|
|
21
|
+
* geth's IPC JSON-RPC typically uses newline-delimited JSON, but we also support
|
|
22
|
+
* non-newline framing by scanning for balanced braces and respecting strings.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} buffer
|
|
25
|
+
* @returns {{ json: string, rest: string } | null}
|
|
26
|
+
*/
|
|
27
|
+
function _extractFirstJsonObject(buffer) {
|
|
28
|
+
const start = buffer.indexOf("{");
|
|
29
|
+
if (start === -1) return null;
|
|
30
|
+
|
|
31
|
+
let depth = 0;
|
|
32
|
+
let inString = false;
|
|
33
|
+
let escape = false;
|
|
34
|
+
|
|
35
|
+
for (let i = start; i < buffer.length; i++) {
|
|
36
|
+
const ch = buffer[i];
|
|
37
|
+
|
|
38
|
+
if (inString) {
|
|
39
|
+
if (escape) {
|
|
40
|
+
escape = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === "\\") {
|
|
44
|
+
escape = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (ch === '"') {
|
|
48
|
+
inString = false;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (ch === '"') {
|
|
54
|
+
inString = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (ch === "{") depth++;
|
|
59
|
+
if (ch === "}") depth--;
|
|
60
|
+
|
|
61
|
+
if (depth === 0) {
|
|
62
|
+
const json = buffer.slice(start, i + 1);
|
|
63
|
+
const rest = buffer.slice(i + 1);
|
|
64
|
+
return { json, rest };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class WebSocketProvider extends AbstractProvider {
|
|
72
|
+
/**
|
|
73
|
+
* Create a WebSocket JSON-RPC provider.
|
|
74
|
+
*
|
|
75
|
+
* This uses the built-in global `WebSocket` available in recent Node.js
|
|
76
|
+
* versions (via undici). No additional npm dependencies are required.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} url WebSocket endpoint (e.g. ws://127.0.0.1:8546)
|
|
79
|
+
* @param {number=} chainId Optional chain id (compat)
|
|
80
|
+
*/
|
|
81
|
+
constructor(url, chainId) {
|
|
82
|
+
super();
|
|
83
|
+
if (typeof url !== "string" || url.trim().length === 0) {
|
|
84
|
+
throw makeError("missing WebSocket url", "INVALID_ARGUMENT", { url });
|
|
85
|
+
}
|
|
86
|
+
this.url = url.trim();
|
|
87
|
+
this.chainId = chainId == null ? 123123 : chainId;
|
|
88
|
+
|
|
89
|
+
/** @type {any|null} */
|
|
90
|
+
this._ws = null;
|
|
91
|
+
/** @type {Promise<void>|null} */
|
|
92
|
+
this._wsReady = null;
|
|
93
|
+
/** @type {Map<number, { resolve: Function, reject: Function, timer: any }>} */
|
|
94
|
+
this._pending = new Map();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Close the underlying WebSocket connection and reject any pending requests.
|
|
99
|
+
* This is important for tests so the Node.js event loop can exit cleanly.
|
|
100
|
+
*/
|
|
101
|
+
destroy() {
|
|
102
|
+
try {
|
|
103
|
+
if (this._ws && typeof this._ws.close === "function") this._ws.close();
|
|
104
|
+
} catch {
|
|
105
|
+
// ignore
|
|
106
|
+
}
|
|
107
|
+
this._ws = null;
|
|
108
|
+
this._wsReady = null;
|
|
109
|
+
this._rejectAllPending(makeError("WebSocket closed", "UNKNOWN_ERROR", { url: this.url }));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_rejectAllPending(err) {
|
|
113
|
+
for (const [id, p] of this._pending.entries()) {
|
|
114
|
+
try {
|
|
115
|
+
clearTimeout(p.timer);
|
|
116
|
+
p.reject(err);
|
|
117
|
+
} catch {
|
|
118
|
+
// ignore
|
|
119
|
+
}
|
|
120
|
+
this._pending.delete(id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async _connect() {
|
|
125
|
+
if (this._ws && this._ws.readyState === 1) return;
|
|
126
|
+
if (this._wsReady) return this._wsReady;
|
|
127
|
+
|
|
128
|
+
const WS = globalThis.WebSocket;
|
|
129
|
+
if (typeof WS !== "function") {
|
|
130
|
+
throw makeError("WebSocket not available in this Node.js runtime", "NOT_IMPLEMENTED", {});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this._wsReady = new Promise((resolve, reject) => {
|
|
134
|
+
const ws = new WS(this.url);
|
|
135
|
+
this._ws = ws;
|
|
136
|
+
|
|
137
|
+
const connectTimer = setTimeout(() => {
|
|
138
|
+
try {
|
|
139
|
+
ws.close();
|
|
140
|
+
} catch {
|
|
141
|
+
// ignore
|
|
142
|
+
}
|
|
143
|
+
reject(makeError("WebSocket connect timeout", "UNKNOWN_ERROR", { url: this.url }));
|
|
144
|
+
}, 10_000);
|
|
145
|
+
|
|
146
|
+
const onOpen = () => {
|
|
147
|
+
clearTimeout(connectTimer);
|
|
148
|
+
resolve();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const onError = (event) => {
|
|
152
|
+
clearTimeout(connectTimer);
|
|
153
|
+
reject(
|
|
154
|
+
makeError("WebSocket error", "UNKNOWN_ERROR", {
|
|
155
|
+
url: this.url,
|
|
156
|
+
error: event && event.message ? event.message : String(event),
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const onClose = () => {
|
|
162
|
+
clearTimeout(connectTimer);
|
|
163
|
+
this._ws = null;
|
|
164
|
+
this._wsReady = null;
|
|
165
|
+
this._rejectAllPending(makeError("WebSocket closed", "UNKNOWN_ERROR", { url: this.url }));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const onMessage = (event) => {
|
|
169
|
+
const data = event && event.data != null ? event.data : "";
|
|
170
|
+
const text = typeof data === "string" ? data : data.toString();
|
|
171
|
+
let msg;
|
|
172
|
+
try {
|
|
173
|
+
msg = JSON.parse(text);
|
|
174
|
+
} catch {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!msg || typeof msg !== "object") return;
|
|
178
|
+
if (typeof msg.id !== "number") return;
|
|
179
|
+
|
|
180
|
+
const pending = this._pending.get(msg.id);
|
|
181
|
+
if (!pending) return;
|
|
182
|
+
this._pending.delete(msg.id);
|
|
183
|
+
clearTimeout(pending.timer);
|
|
184
|
+
|
|
185
|
+
if (msg.error) {
|
|
186
|
+
pending.reject(makeError("JSON-RPC error", "UNKNOWN_ERROR", { error: msg.error }));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
pending.resolve(msg.result);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Prefer addEventListener API, but fall back to on* properties if needed.
|
|
193
|
+
if (typeof ws.addEventListener === "function") {
|
|
194
|
+
ws.addEventListener("open", onOpen);
|
|
195
|
+
ws.addEventListener("error", onError);
|
|
196
|
+
ws.addEventListener("close", onClose);
|
|
197
|
+
ws.addEventListener("message", onMessage);
|
|
198
|
+
} else {
|
|
199
|
+
ws.onopen = onOpen;
|
|
200
|
+
ws.onerror = onError;
|
|
201
|
+
ws.onclose = onClose;
|
|
202
|
+
ws.onmessage = onMessage;
|
|
203
|
+
}
|
|
204
|
+
}).finally(() => {
|
|
205
|
+
// If connection failed, allow retries.
|
|
206
|
+
if (!this._ws || this._ws.readyState !== 1) {
|
|
207
|
+
this._ws = null;
|
|
208
|
+
this._wsReady = null;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return this._wsReady;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {string} method
|
|
217
|
+
* @param {any[]=} params
|
|
218
|
+
* @returns {Promise<any>}
|
|
219
|
+
*/
|
|
220
|
+
async _perform(method, params) {
|
|
221
|
+
await this._connect();
|
|
222
|
+
const ws = this._ws;
|
|
223
|
+
if (!ws || ws.readyState !== 1) {
|
|
224
|
+
throw makeError("WebSocket not connected", "UNKNOWN_ERROR", { url: this.url, method });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const id = _wsRpcId++;
|
|
228
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: params || [] });
|
|
229
|
+
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
const timer = setTimeout(() => {
|
|
232
|
+
this._pending.delete(id);
|
|
233
|
+
reject(makeError("WebSocket JSON-RPC timeout", "UNKNOWN_ERROR", { url: this.url, method }));
|
|
234
|
+
}, 30_000);
|
|
235
|
+
|
|
236
|
+
this._pending.set(id, { resolve, reject, timer });
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
ws.send(body);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
clearTimeout(timer);
|
|
242
|
+
this._pending.delete(id);
|
|
243
|
+
reject(
|
|
244
|
+
makeError("WebSocket send failed", "UNKNOWN_ERROR", {
|
|
245
|
+
url: this.url,
|
|
246
|
+
method,
|
|
247
|
+
error: e && e.message ? e.message : String(e),
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class IpcSocketProvider extends AbstractProvider {
|
|
256
|
+
/**
|
|
257
|
+
* Create an IPC provider.
|
|
258
|
+
*
|
|
259
|
+
* On Windows, use a named pipe path like: `\\\\.\\pipe\\geth.ipc`
|
|
260
|
+
* On Unix, use a domain socket path like: `/path/to/geth.ipc`
|
|
261
|
+
*
|
|
262
|
+
* @param {string} path IPC socket path
|
|
263
|
+
*/
|
|
264
|
+
constructor(path) {
|
|
265
|
+
super();
|
|
266
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
267
|
+
throw makeError("missing IPC path", "INVALID_ARGUMENT", { path });
|
|
268
|
+
}
|
|
269
|
+
this.path = path;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @param {string} method
|
|
274
|
+
* @param {any[]=} params
|
|
275
|
+
* @returns {Promise<any>}
|
|
276
|
+
*/
|
|
277
|
+
async _perform(method, params) {
|
|
278
|
+
const id = _ipcRpcId++;
|
|
279
|
+
const body =
|
|
280
|
+
JSON.stringify({
|
|
281
|
+
jsonrpc: "2.0",
|
|
282
|
+
id,
|
|
283
|
+
method,
|
|
284
|
+
params: params || [],
|
|
285
|
+
}) + "\n";
|
|
286
|
+
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
/** @type {boolean} */
|
|
289
|
+
let done = false;
|
|
290
|
+
/** @type {string} */
|
|
291
|
+
let buffer = "";
|
|
292
|
+
|
|
293
|
+
const socket = net.createConnection(this.path);
|
|
294
|
+
socket.setEncoding("utf8");
|
|
295
|
+
socket.setTimeout(30_000);
|
|
296
|
+
|
|
297
|
+
function finish(err, value) {
|
|
298
|
+
if (done) return;
|
|
299
|
+
done = true;
|
|
300
|
+
try {
|
|
301
|
+
socket.destroy();
|
|
302
|
+
} catch {
|
|
303
|
+
// ignore
|
|
304
|
+
}
|
|
305
|
+
if (err) reject(err);
|
|
306
|
+
else resolve(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
socket.on("connect", () => {
|
|
310
|
+
try {
|
|
311
|
+
socket.write(body);
|
|
312
|
+
} catch (e) {
|
|
313
|
+
finish(
|
|
314
|
+
makeError("IPC write failed", "UNKNOWN_ERROR", {
|
|
315
|
+
method,
|
|
316
|
+
path: this.path,
|
|
317
|
+
error: e && e.message ? e.message : String(e),
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
socket.on("timeout", () => {
|
|
324
|
+
finish(makeError("IPC request timeout", "UNKNOWN_ERROR", { method, path: this.path }));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
socket.on("error", (e) => {
|
|
328
|
+
finish(
|
|
329
|
+
makeError("IPC socket error", "UNKNOWN_ERROR", {
|
|
330
|
+
method,
|
|
331
|
+
path: this.path,
|
|
332
|
+
error: e && e.message ? e.message : String(e),
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
socket.on("data", (chunk) => {
|
|
338
|
+
buffer += String(chunk);
|
|
339
|
+
|
|
340
|
+
// Fast path: newline-delimited JSON responses.
|
|
341
|
+
while (buffer.includes("\n")) {
|
|
342
|
+
const idx = buffer.indexOf("\n");
|
|
343
|
+
const line = buffer.slice(0, idx).trim();
|
|
344
|
+
buffer = buffer.slice(idx + 1);
|
|
345
|
+
if (!line) continue;
|
|
346
|
+
try {
|
|
347
|
+
const json = JSON.parse(line);
|
|
348
|
+
if (json && json.id === id) {
|
|
349
|
+
if (json.error) {
|
|
350
|
+
finish(makeError("JSON-RPC error", "UNKNOWN_ERROR", { method, error: json.error }));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
finish(null, json.result);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
} catch {
|
|
357
|
+
// If parsing fails, fall through to brace-balanced extraction on the accumulated buffer.
|
|
358
|
+
buffer = line + "\n" + buffer;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Robust path: find a complete JSON object by balanced braces.
|
|
364
|
+
while (true) {
|
|
365
|
+
const extracted = _extractFirstJsonObject(buffer);
|
|
366
|
+
if (!extracted) return;
|
|
367
|
+
buffer = extracted.rest;
|
|
368
|
+
try {
|
|
369
|
+
const json = JSON.parse(extracted.json);
|
|
370
|
+
if (json && json.id === id) {
|
|
371
|
+
if (json.error) {
|
|
372
|
+
finish(makeError("JSON-RPC error", "UNKNOWN_ERROR", { method, error: json.error }));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
finish(null, json.result);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
// ignore malformed object and continue scanning remainder
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
socket.on("end", () => {
|
|
385
|
+
if (!done) finish(makeError("IPC connection ended before response", "UNKNOWN_ERROR", { method, path: this.path }));
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
class BrowserProvider extends AbstractProvider {
|
|
392
|
+
/**
|
|
393
|
+
* Create a BrowserProvider from an EIP-1193 provider (e.g. MetaMask).
|
|
394
|
+
*
|
|
395
|
+
* This is a lightweight implementation that focuses on the core behaviors:
|
|
396
|
+
* - `send(method, params)` dispatches EIP-1193 requests
|
|
397
|
+
* - `getSigner()` resolves the connected account
|
|
398
|
+
* - emits ethers-like `debug` events for request/response tracking
|
|
399
|
+
*
|
|
400
|
+
* @param {{ request: Function }} eip1193Provider
|
|
401
|
+
* @param {any=} network Unused (compat)
|
|
402
|
+
* @param {{ providerInfo?: any }=} options
|
|
403
|
+
*/
|
|
404
|
+
constructor(eip1193Provider, network, options) {
|
|
405
|
+
super();
|
|
406
|
+
void network;
|
|
407
|
+
this.provider = eip1193Provider;
|
|
408
|
+
this.providerInfo = options && options.providerInfo ? options.providerInfo : null;
|
|
409
|
+
|
|
410
|
+
if (!this.provider || typeof this.provider.request !== "function") {
|
|
411
|
+
throw makeError("invalid EIP-1193 provider (missing request)", "INVALID_ARGUMENT", { provider: this.provider });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Send an EIP-1193 JSON-RPC request.
|
|
417
|
+
* @param {string} method
|
|
418
|
+
* @param {any[]|Record<string, any>=} params
|
|
419
|
+
* @returns {Promise<any>}
|
|
420
|
+
*/
|
|
421
|
+
async send(method, params) {
|
|
422
|
+
const payload = { method, params: params == null ? [] : params };
|
|
423
|
+
this.emit("debug", { action: "sendEip1193Payload", payload });
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const result = await this.provider.request(payload);
|
|
427
|
+
this.emit("debug", { action: "receiveEip1193Result", result });
|
|
428
|
+
return result;
|
|
429
|
+
} catch (e) {
|
|
430
|
+
const err = this.getRpcError(payload, e);
|
|
431
|
+
this.emit("debug", { action: "receiveEip1193Error", error: err });
|
|
432
|
+
throw err;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Map an EIP-1193 error into a normalized Error.
|
|
438
|
+
* @param {{ method: string, params?: any }} payload
|
|
439
|
+
* @param {any} error
|
|
440
|
+
* @returns {Error}
|
|
441
|
+
*/
|
|
442
|
+
getRpcError(payload, error) {
|
|
443
|
+
if (error instanceof Error) return error;
|
|
444
|
+
return makeError("EIP-1193 error", "UNKNOWN_ERROR", { payload, error });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Ethers compatibility: internal send for single or batched payloads.
|
|
449
|
+
* @param {any|any[]} payload
|
|
450
|
+
* @returns {Promise<any>}
|
|
451
|
+
*/
|
|
452
|
+
async _send(payload) {
|
|
453
|
+
if (Array.isArray(payload)) {
|
|
454
|
+
const out = [];
|
|
455
|
+
for (const p of payload) {
|
|
456
|
+
// eslint-disable-next-line no-await-in-loop
|
|
457
|
+
out.push(await this.send(p.method, p.params));
|
|
458
|
+
}
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
return this.send(payload.method, payload.params);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @param {string} method
|
|
466
|
+
* @param {any[]=} params
|
|
467
|
+
* @returns {Promise<any>}
|
|
468
|
+
*/
|
|
469
|
+
async _perform(method, params) {
|
|
470
|
+
return this.send(method, params || []);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Returns a signer for the specified account index or address.
|
|
475
|
+
* @param {number|string=} address
|
|
476
|
+
* @returns {Promise<JsonRpcSigner>}
|
|
477
|
+
*/
|
|
478
|
+
async getSigner(address) {
|
|
479
|
+
const accounts = await this.send("eth_accounts", []);
|
|
480
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
481
|
+
throw makeError("no accounts available from EIP-1193 provider", "UNKNOWN_ERROR", {});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (address == null) {
|
|
485
|
+
return new JsonRpcSigner(this, accounts[0]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (typeof address === "number") {
|
|
489
|
+
const a = accounts[address];
|
|
490
|
+
if (!a) throw makeError("account index out of range", "INVALID_ARGUMENT", { address });
|
|
491
|
+
return new JsonRpcSigner(this, a);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (typeof address === "string") {
|
|
495
|
+
const found = accounts.find((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
496
|
+
if (!found) throw makeError("account not found", "INVALID_ARGUMENT", { address });
|
|
497
|
+
return new JsonRpcSigner(this, found);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
throw makeError("invalid signer address/index", "INVALID_ARGUMENT", { address });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Resolve whether this provider manages the address/index.
|
|
505
|
+
* @param {number|string} address
|
|
506
|
+
* @returns {Promise<boolean>}
|
|
507
|
+
*/
|
|
508
|
+
async hasSigner(address) {
|
|
509
|
+
const accounts = await this.send("eth_accounts", []);
|
|
510
|
+
if (!Array.isArray(accounts)) return false;
|
|
511
|
+
if (typeof address === "number") return address >= 0 && address < accounts.length;
|
|
512
|
+
if (typeof address === "string") return accounts.some((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* FallbackProvider - uses the first provider in the list.
|
|
519
|
+
*/
|
|
520
|
+
class FallbackProvider extends AbstractProvider {
|
|
521
|
+
/**
|
|
522
|
+
* @param {AbstractProvider[]|AbstractProvider} providers
|
|
523
|
+
*/
|
|
524
|
+
constructor(providers) {
|
|
525
|
+
super();
|
|
526
|
+
this.providers = Array.isArray(providers) ? providers : [providers];
|
|
527
|
+
if (!this.providers.length) throw makeError("no providers provided", "INVALID_ARGUMENT", {});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async _perform(method, params) {
|
|
531
|
+
// Simple strategy: try providers in order.
|
|
532
|
+
let lastErr;
|
|
533
|
+
for (const p of this.providers) {
|
|
534
|
+
try {
|
|
535
|
+
// eslint-disable-next-line no-await-in-loop
|
|
536
|
+
return await p._perform(method, params);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
lastErr = e;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
throw lastErr || makeError("all providers failed", "UNKNOWN_ERROR", { method });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* FilterByBlockHash placeholder.
|
|
547
|
+
*/
|
|
548
|
+
class FilterByBlockHash {
|
|
549
|
+
constructor(blockHash, address, topics) {
|
|
550
|
+
if (typeof blockHash !== "string" || !isHexString(blockHash, 32)) {
|
|
551
|
+
throw makeError("invalid blockHash", "INVALID_ARGUMENT", { blockHash });
|
|
552
|
+
}
|
|
553
|
+
this.blockHash = normalizeHex(blockHash);
|
|
554
|
+
if (address != null) this.address = address;
|
|
555
|
+
if (topics != null) this.topics = topics;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
toJSON() {
|
|
559
|
+
// Ensure JSON serialization includes only the filter fields.
|
|
560
|
+
return {
|
|
561
|
+
blockHash: this.blockHash,
|
|
562
|
+
address: this.address,
|
|
563
|
+
topics: this.topics,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Create a provider from an endpoint string. Detects connection type by scheme/path:
|
|
570
|
+
* - http:// or https:// → JsonRpcProvider
|
|
571
|
+
* - ws:// or wss:// → WebSocketProvider
|
|
572
|
+
* - otherwise (e.g. \\\\.\\pipe\\geth.ipc or /path/to/geth.ipc) → IpcSocketProvider
|
|
573
|
+
*
|
|
574
|
+
* @param {string=} endpoint - RPC URL (http/https), WebSocket URL (ws/wss), or IPC path. If omitted or empty, uses default from Config (HTTP).
|
|
575
|
+
* @param {number=} chainId - Chain ID (default 123123). Used for HTTP and WebSocket; ignored for IPC.
|
|
576
|
+
* @returns {AbstractProvider}
|
|
577
|
+
*/
|
|
578
|
+
function getProvider(endpoint, chainId) {
|
|
579
|
+
const url = typeof endpoint === "string" ? endpoint.trim() : "";
|
|
580
|
+
const lower = url.toLowerCase();
|
|
581
|
+
if (!url || lower.startsWith("http://") || lower.startsWith("https://")) {
|
|
582
|
+
return new JsonRpcProvider(url || undefined, chainId);
|
|
583
|
+
}
|
|
584
|
+
if (lower.startsWith("ws://") || lower.startsWith("wss://")) {
|
|
585
|
+
return new WebSocketProvider(url, chainId);
|
|
586
|
+
}
|
|
587
|
+
// IPC path (e.g. \\.\pipe\geth.ipc on Windows or /path/to/geth.ipc on Unix)
|
|
588
|
+
if (!url) throw makeError("missing endpoint", "INVALID_ARGUMENT", { endpoint });
|
|
589
|
+
return new IpcSocketProvider(url);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
module.exports = {
|
|
593
|
+
WebSocketProvider,
|
|
594
|
+
IpcSocketProvider,
|
|
595
|
+
BrowserProvider,
|
|
596
|
+
FallbackProvider,
|
|
597
|
+
FilterByBlockHash,
|
|
598
|
+
getProvider,
|
|
599
|
+
};
|
|
600
|
+
|