@awarizon/web3 1.0.0 → 1.0.2

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/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createPublicClient, http } from 'viem';
2
2
  import { WalletEngine } from '@awarizon/wallet-engine';
3
3
  export { ChainSwitchError, InvalidMnemonicError, InvalidPrivateKeyError, WalletEngine, WalletNotConnectedError } from '@awarizon/wallet-engine';
4
+ import { TransactionEngine } from '@awarizon/tx-engine';
5
+ export { ContractExecutionError, GasEstimationError, SimulationError, TransactionEngine, TransactionTimeoutError } from '@awarizon/tx-engine';
4
6
  import { moonbeam, fantom, gnosis, celo, mantle, scroll, linea, zora, sepolia, avalanche, bsc, optimismSepolia, optimism, arbitrumSepolia, arbitrum, polygonAmoy, polygon, baseSepolia, base, mainnet } from 'viem/chains';
5
7
  import { parseABI } from '@awarizon/abi-engine';
6
8
  export { DuplicateFunctionError, InvalidABIError, UnsupportedABIItemError, generateAllMethodSignatures, generateMethodSignature, isPayableFunction, isWriteFunction, parseABI } from '@awarizon/abi-engine';
7
- import { TransactionEngine } from '@awarizon/tx-engine';
8
- export { ContractExecutionError, GasEstimationError, SimulationError, TransactionEngine, TransactionTimeoutError } from '@awarizon/tx-engine';
9
9
 
10
10
  // src/sdk.ts
11
11
  var CHAINS = {
@@ -58,6 +58,7 @@ var CHAINS = {
58
58
  fantom,
59
59
  moonbeam
60
60
  };
61
+ var _chainAliases = Object.keys(CHAINS).sort().join(", ");
61
62
  function resolveChain(chain) {
62
63
  if (typeof chain === "object" && "id" in chain) {
63
64
  return chain;
@@ -65,10 +66,9 @@ function resolveChain(chain) {
65
66
  if (typeof chain === "string") {
66
67
  const resolved = CHAINS[chain.toLowerCase()] ?? CHAINS[chain];
67
68
  if (!resolved) {
68
- const available = Object.keys(CHAINS).filter((k, i, arr) => arr.indexOf(k) === i).sort().join(", ");
69
69
  throw new Error(
70
70
  `[awarizon/web3] Unsupported chain: "${chain}".
71
- Available chains: ${available}`
71
+ Available chains: ${_chainAliases}`
72
72
  );
73
73
  }
74
74
  return resolved;
@@ -83,37 +83,58 @@ function getSupportedChainIds() {
83
83
  return true;
84
84
  }).map((c) => c.id);
85
85
  }
86
- function buildContractInstance(address, abi, publicClient, getWalletClient) {
86
+ function trackEvent(telemetry, type, chain, functionName, success, start) {
87
+ telemetry?.track({
88
+ type,
89
+ chain: chain?.name ?? "unknown",
90
+ chainId: chain?.id ?? 0,
91
+ functionName,
92
+ success,
93
+ durationMs: Date.now() - start,
94
+ ts: Date.now()
95
+ });
96
+ }
97
+ function buildContractInstance(address, abi, publicClient, txEngine, telemetry, chain) {
87
98
  const parsed = parseABI(abi);
88
- const txEngine = new TransactionEngine(publicClient, getWalletClient);
99
+ const eventsMap = new Map(parsed.events.map((e) => [e.name, e]));
89
100
  const instance = {
90
101
  _address: address,
91
102
  _abi: abi
92
103
  };
93
104
  for (const fn of parsed.readFunctions) {
94
- instance[fn.name] = buildReadMethod(fn, address, abi, txEngine);
105
+ instance[fn.name] = buildReadMethod(fn, address, abi, txEngine, telemetry, chain);
95
106
  }
96
107
  for (const fn of parsed.writeFunctions) {
97
- instance[fn.name] = buildWriteMethod(fn, address, abi, txEngine);
108
+ instance[fn.name] = buildWriteMethod(fn, address, abi, txEngine, telemetry, chain);
98
109
  }
99
- instance["on"] = buildEventSubscription(address, abi, parsed.events, publicClient);
110
+ instance["on"] = buildEventSubscription(address, abi, eventsMap, publicClient);
100
111
  instance["estimateGas"] = async (method, ...args) => {
101
- const estimate = await txEngine.estimateGas({
102
- address,
103
- abi,
104
- functionName: method,
105
- args
106
- });
107
- return estimate.gas;
112
+ const start = Date.now();
113
+ try {
114
+ const estimate = await txEngine.estimateGas({ address, abi, functionName: method, args });
115
+ trackEvent(telemetry, "contract.gas_estimate", chain, method, true, start);
116
+ return estimate.gas;
117
+ } catch (error) {
118
+ trackEvent(telemetry, "contract.gas_estimate", chain, method, false, start);
119
+ throw error;
120
+ }
108
121
  };
109
122
  return instance;
110
123
  }
111
- function buildReadMethod(fn, address, abi, txEngine) {
124
+ function buildReadMethod(fn, address, abi, txEngine, telemetry, chain) {
112
125
  return async (...args) => {
113
- return txEngine.read({ address, abi, functionName: fn.name, args });
126
+ const start = Date.now();
127
+ try {
128
+ const result = await txEngine.read({ address, abi, functionName: fn.name, args });
129
+ trackEvent(telemetry, "contract.read", chain, fn.name, true, start);
130
+ return result;
131
+ } catch (error) {
132
+ trackEvent(telemetry, "contract.read", chain, fn.name, false, start);
133
+ throw error;
134
+ }
114
135
  };
115
136
  }
116
- function buildWriteMethod(fn, address, abi, txEngine) {
137
+ function buildWriteMethod(fn, address, abi, txEngine, telemetry, chain) {
117
138
  const isPayable = fn.stateMutability === "payable";
118
139
  return async (...args) => {
119
140
  let callArgs = args;
@@ -127,14 +148,22 @@ function buildWriteMethod(fn, address, abi, txEngine) {
127
148
  gas = last.gas;
128
149
  }
129
150
  }
130
- return txEngine.write({
131
- address,
132
- abi,
133
- functionName: fn.name,
134
- args: callArgs,
135
- value,
136
- gas
137
- });
151
+ const start = Date.now();
152
+ try {
153
+ const result = await txEngine.write({
154
+ address,
155
+ abi,
156
+ functionName: fn.name,
157
+ args: callArgs,
158
+ value,
159
+ gas
160
+ });
161
+ trackEvent(telemetry, "contract.write", chain, fn.name, true, start);
162
+ return result;
163
+ } catch (error) {
164
+ trackEvent(telemetry, "contract.write", chain, fn.name, false, start);
165
+ throw error;
166
+ }
138
167
  };
139
168
  }
140
169
  function isPayableOptions(val) {
@@ -144,11 +173,11 @@ function isPayableOptions(val) {
144
173
  const hasGas = "gas" in obj && (obj.gas === void 0 || typeof obj.gas === "bigint");
145
174
  return hasValue || hasGas;
146
175
  }
147
- function buildEventSubscription(address, abi, events, publicClient) {
176
+ function buildEventSubscription(address, abi, eventsMap, publicClient) {
148
177
  return (eventName, callback) => {
149
- const abiEvent = events.find((e) => e.name === eventName);
178
+ const abiEvent = eventsMap.get(eventName);
150
179
  if (!abiEvent) {
151
- const available = events.map((e) => e.name).join(", ") || "none";
180
+ const available = [...eventsMap.keys()].join(", ") || "none";
152
181
  throw new Error(
153
182
  `[awarizon/web3] Event "${eventName}" not found on contract ${address}.
154
183
  Available events: ${available}`
@@ -185,7 +214,7 @@ var ProviderError = class extends Error {
185
214
  };
186
215
  var ContractNotLoadedError = class extends Error {
187
216
  constructor(address) {
188
- super(`[awarizon/web3] Contract at ${address} is not loaded. Call sdk.contract({ address, abi }) first.`);
217
+ super(`[awarizon/web3] Contract at ${address} is not loaded. Call awarizon.contract({ address, abi }) first.`);
189
218
  this.code = "CONTRACT_NOT_LOADED";
190
219
  this.name = "ContractNotLoadedError";
191
220
  }
@@ -198,104 +227,434 @@ var UnsupportedChainError = class extends Error {
198
227
  this.chain = chain;
199
228
  }
200
229
  };
230
+ var ApiKeyRequiredError = class extends Error {
231
+ constructor() {
232
+ super(
233
+ '[awarizon/web3] An API key is required. Pass apiKey: "awz_live_..." to AwarizonWeb3({ ... }). Get yours at https://awarizon.com/dashboard/api-keys'
234
+ );
235
+ this.code = "API_KEY_REQUIRED";
236
+ this.name = "ApiKeyRequiredError";
237
+ }
238
+ };
239
+ var InvalidApiKeyError = class extends Error {
240
+ constructor(message = "Invalid or revoked API key.") {
241
+ super(`[awarizon/web3] ${message}`);
242
+ this.code = "INVALID_API_KEY";
243
+ this.name = "InvalidApiKeyError";
244
+ }
245
+ };
201
246
 
202
- // src/sdk.ts
247
+ // src/telemetry.ts
248
+ var DEFAULT_BASE_URL = "https://awarizon.com";
249
+ var FLUSH_INTERVAL_MS = 1e4;
250
+ var MAX_BATCH_SIZE = 20;
251
+ var VALIDATION_TIMEOUT = 5e3;
252
+ var TelemetryClient = class {
253
+ constructor(apiKey, baseUrl = DEFAULT_BASE_URL) {
254
+ this.validated = false;
255
+ this.validationPromise = null;
256
+ this.queue = [];
257
+ this.flushTimer = null;
258
+ this.unloadListener = null;
259
+ this.apiKey = apiKey;
260
+ this.baseUrl = baseUrl.replace(/\/$/, "");
261
+ }
262
+ /**
263
+ * Ensures the API key has been validated against the Awarizon API.
264
+ * Idempotent — safe to call on every operation; only hits the network once.
265
+ * Times out after 5 seconds to avoid hanging in degraded network conditions.
266
+ *
267
+ * @throws {InvalidApiKeyError} if the key is invalid, revoked, or unreachable
268
+ */
269
+ async ensureValidated() {
270
+ if (this.validated) return;
271
+ if (this.validationPromise) return this.validationPromise;
272
+ this.validationPromise = this._validate().catch((err) => {
273
+ this.validationPromise = null;
274
+ throw err;
275
+ });
276
+ return this.validationPromise;
277
+ }
278
+ /**
279
+ * Queue a usage event. Events are batched and sent periodically.
280
+ * Must only be called after ensureValidated() has resolved.
281
+ */
282
+ track(event) {
283
+ if (!this.validated) return;
284
+ this.queue.push(event);
285
+ if (this.queue.length >= MAX_BATCH_SIZE) this._flush(false);
286
+ }
287
+ /**
288
+ * Flush pending events and stop background timers.
289
+ * Call when tearing down a long-lived SDK instance (e.g. server shutdown).
290
+ */
291
+ async destroy() {
292
+ this._stopTimers();
293
+ await this._flush(true);
294
+ }
295
+ // ─── Private ─────────────────────────────────────────────────────────────────
296
+ async _validate() {
297
+ const controller = new AbortController();
298
+ const timeout = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT);
299
+ let res;
300
+ try {
301
+ res = await fetch(`${this.baseUrl}/api/v1/auth`, {
302
+ method: "GET",
303
+ headers: { Authorization: `Bearer ${this.apiKey}` },
304
+ signal: controller.signal
305
+ });
306
+ } catch (err) {
307
+ const msg = controller.signal.aborted ? `Awarizon API did not respond within ${VALIDATION_TIMEOUT / 1e3}s.` : `Could not reach Awarizon API: ${err.message}`;
308
+ throw new InvalidApiKeyError(msg);
309
+ } finally {
310
+ clearTimeout(timeout);
311
+ }
312
+ if (!res.ok) {
313
+ const body = await res.json().catch(() => ({}));
314
+ throw new InvalidApiKeyError(body.error ?? "Invalid or revoked API key.");
315
+ }
316
+ this.validated = true;
317
+ this._startTimers();
318
+ }
319
+ _startTimers() {
320
+ this.flushTimer = setInterval(() => {
321
+ if (this.queue.length > 0) this._flush(false);
322
+ }, FLUSH_INTERVAL_MS);
323
+ const timer = this.flushTimer;
324
+ if (typeof timer.unref === "function") timer.unref();
325
+ if (typeof window !== "undefined" && typeof navigator !== "undefined") {
326
+ this.unloadListener = () => this._flushBeacon();
327
+ window.addEventListener("beforeunload", this.unloadListener);
328
+ }
329
+ }
330
+ _stopTimers() {
331
+ if (this.flushTimer !== null) {
332
+ clearInterval(this.flushTimer);
333
+ this.flushTimer = null;
334
+ }
335
+ if (this.unloadListener !== null && typeof window !== "undefined") {
336
+ window.removeEventListener("beforeunload", this.unloadListener);
337
+ this.unloadListener = null;
338
+ }
339
+ }
340
+ _flush(awaited) {
341
+ if (this.queue.length === 0) return awaited ? Promise.resolve() : void 0;
342
+ const batch = this.queue.splice(0, MAX_BATCH_SIZE);
343
+ const promise = fetch(`${this.baseUrl}/api/v1/usage`, {
344
+ method: "POST",
345
+ headers: {
346
+ Authorization: `Bearer ${this.apiKey}`,
347
+ "Content-Type": "application/json"
348
+ },
349
+ body: JSON.stringify({ events: batch })
350
+ }).then(() => {
351
+ }, () => {
352
+ });
353
+ return awaited ? promise : void 0;
354
+ }
355
+ /** Uses sendBeacon for guaranteed delivery on page unload */
356
+ _flushBeacon() {
357
+ if (this.queue.length === 0 || typeof navigator === "undefined") return;
358
+ const batch = this.queue.splice(0);
359
+ navigator.sendBeacon(
360
+ `${this.baseUrl}/api/v1/usage`,
361
+ new Blob([JSON.stringify({ events: batch })], { type: "application/json" })
362
+ );
363
+ }
364
+ };
365
+ var WalletProxy = class {
366
+ constructor(engine, ensureReady) {
367
+ this.engine = engine;
368
+ this.ensureReady = ensureReady;
369
+ }
370
+ // ── Gated async operations ──────────────────────────────────────────────────
371
+ async create() {
372
+ await this.ensureReady();
373
+ return this.engine.create();
374
+ }
375
+ async importMnemonic(mnemonic, accountIndex) {
376
+ await this.ensureReady();
377
+ return this.engine.importMnemonic(mnemonic, accountIndex);
378
+ }
379
+ async importPrivateKey(privateKey) {
380
+ await this.ensureReady();
381
+ return this.engine.importPrivateKey(privateKey);
382
+ }
383
+ // ── Pass-throughs (sync, no network) ───────────────────────────────────────
384
+ address() {
385
+ return this.engine.address();
386
+ }
387
+ getSigner() {
388
+ return this.engine.getWalletClient();
389
+ }
390
+ getWalletClient() {
391
+ return this.engine.getWalletClient();
392
+ }
393
+ isConnected() {
394
+ return this.engine.isConnected();
395
+ }
396
+ hasExternalWallet() {
397
+ return this.engine.hasExternalWallet();
398
+ }
399
+ hasInternalWallet() {
400
+ return this.engine.hasInternalWallet();
401
+ }
402
+ connectExternal(c) {
403
+ this.engine.connectExternal(c);
404
+ }
405
+ disconnectExternal() {
406
+ this.engine.disconnectExternal();
407
+ }
408
+ disconnect() {
409
+ this.engine.disconnect();
410
+ }
411
+ async switchChain(chain) {
412
+ return this.engine.switchChain(chain);
413
+ }
414
+ };
203
415
  var AwarizonWeb3 = class {
204
416
  constructor(config) {
417
+ // Contract instances are cached by address — same address returns same instance.
418
+ // Cache is cleared on switchChain() since contracts are chain-specific.
419
+ this._contractCache = /* @__PURE__ */ new Map();
420
+ if (!config.apiKey) throw new ApiKeyRequiredError();
205
421
  this.chain = resolveChain(config.chain);
206
422
  this.publicClient = createPublicClient({
207
423
  chain: this.chain,
208
424
  transport: http(config.rpcUrl)
209
425
  });
210
- this.wallet = new WalletEngine({
426
+ this._engine = new WalletEngine({
211
427
  chain: this.chain,
212
428
  publicClient: this.publicClient,
213
429
  rpcUrl: config.rpcUrl
214
430
  });
431
+ this._txEngine = new TransactionEngine(
432
+ this.publicClient,
433
+ () => this._engine.getWalletClient()
434
+ );
435
+ this._telemetry = new TelemetryClient(config.apiKey, config.baseUrl);
436
+ this.wallet = new WalletProxy(
437
+ this._engine,
438
+ () => this._telemetry.ensureValidated()
439
+ );
215
440
  if (config.signer) {
216
- this.wallet.connectExternal(config.signer);
441
+ this._engine.connectExternal(config.signer);
217
442
  }
218
443
  }
219
444
  // ─── Wallet API surface ─────────────────────────────────────────────────────
220
- /**
221
- * Connect an external wallet client (wagmi, WalletConnect, viem, EIP-1193).
222
- * The connected signer takes priority over any internal wallet.
223
- *
224
- * @example
225
- * sdk.connectWallet(walletClient)
226
- */
227
445
  connectWallet(walletClient) {
228
- this.wallet.connectExternal(walletClient);
446
+ this._engine.connectExternal(walletClient);
229
447
  return this;
230
448
  }
231
- /**
232
- * Disconnect the externally-connected wallet.
233
- * Falls back to the internal wallet if one is loaded.
234
- */
235
449
  disconnectWallet() {
236
- this.wallet.disconnectExternal();
450
+ this._engine.disconnectExternal();
237
451
  return this;
238
452
  }
239
- /**
240
- * Switch the active chain.
241
- * Rebuilds the PublicClient and internal WalletClient for the new chain.
242
- */
243
453
  async switchChain(chain) {
454
+ await this._telemetry.ensureValidated();
244
455
  const resolved = resolveChain(chain);
245
- await this.wallet.switchChain(resolved);
456
+ await this._engine.switchChain(resolved);
457
+ this._contractCache.clear();
246
458
  return this;
247
459
  }
248
- /**
249
- * Returns a summary of the currently active wallet.
250
- * @throws {WalletNotConnectedError} if no wallet is connected
251
- */
252
460
  getWalletInfo() {
253
461
  return {
254
- address: this.wallet.address(),
462
+ address: this._engine.address(),
255
463
  chain: this.chain,
256
- isExternal: this.wallet.hasExternalWallet()
464
+ isExternal: this._engine.hasExternalWallet()
257
465
  };
258
466
  }
259
467
  // ─── Contract API ───────────────────────────────────────────────────────────
260
468
  /**
261
- * Load a deployed smart contract and return a fully typed, ABI-powered
262
- * instance. Every function in the ABI is exposed as a direct method.
263
- *
264
- * **Read functions** (view / pure) use the PublicClient — no signer needed.
265
- * **Write functions** (nonpayable / payable) use the active WalletClient.
469
+ * Load a deployed contract and return a fully typed, ABI-powered instance.
470
+ * Validates the API key on the first call subsequent calls are instant.
266
471
  *
267
- * @example
268
- * const token = await sdk.contract({ address: "0x...", abi: ERC20_ABI })
269
- * const balance = await token.balanceOf(address) // read
270
- * await token.transfer(to, 100n) // write
271
- * token.on("Transfer", handler) // events
472
+ * Results are cached by address — calling contract() twice with the same
473
+ * address and ABI object returns the same instance without rebuilding.
272
474
  */
273
475
  async contract(config) {
476
+ await this._telemetry.ensureValidated();
274
477
  if (!config.address || !config.abi) {
275
- throw new Error("[awarizon/web3] sdk.contract() requires both address and abi");
478
+ throw new Error("[awarizon/web3] awarizon.contract() requires both address and abi");
276
479
  }
277
- return buildContractInstance(
480
+ const key = config.address.toLowerCase();
481
+ const cached = this._contractCache.get(key);
482
+ if (cached && cached.abiRef === config.abi) return cached.instance;
483
+ const instance = buildContractInstance(
278
484
  config.address,
279
485
  config.abi,
280
486
  this.publicClient,
281
- () => this.wallet.getWalletClient()
487
+ this._txEngine,
488
+ this._telemetry,
489
+ this.chain
282
490
  );
491
+ this._contractCache.set(key, { abiRef: config.abi, instance });
492
+ return instance;
283
493
  }
284
494
  /**
285
- * Returns the chain ID of the current chain.
286
- * Useful for runtime network validation.
495
+ * Load multiple contracts in parallel. Validates the API key once, then
496
+ * instantiates all contracts concurrently.
497
+ *
498
+ * @example
499
+ * ```ts
500
+ * const [token, staking, nft] = await awarizon.contracts([
501
+ * { address: tokenAddr, abi: ERC20_ABI },
502
+ * { address: stakingAddr, abi: STAKING_ABI },
503
+ * { address: nftAddr, abi: ERC721_ABI },
504
+ * ])
505
+ * ```
506
+ */
507
+ async contracts(configs) {
508
+ return Promise.all(configs.map((c) => this.contract(c)));
509
+ }
510
+ /**
511
+ * Call a read-only (view/pure) contract function directly without loading
512
+ * a full contract instance. Best for one-off reads.
513
+ *
514
+ * @example
515
+ * ```ts
516
+ * const balance = await awarizon.read<bigint>({
517
+ * address: tokenAddress,
518
+ * abi: ERC20_ABI,
519
+ * method: "balanceOf",
520
+ * args: [userAddress],
521
+ * })
522
+ * ```
523
+ */
524
+ async read(params) {
525
+ await this._telemetry.ensureValidated();
526
+ const start = Date.now();
527
+ try {
528
+ const result = await this._txEngine.read({
529
+ address: params.address,
530
+ abi: params.abi,
531
+ functionName: params.method,
532
+ args: params.args ?? []
533
+ });
534
+ this._telemetry.track({
535
+ type: "contract.read",
536
+ chain: this.chain.name,
537
+ chainId: this.chain.id,
538
+ functionName: params.method,
539
+ success: true,
540
+ durationMs: Date.now() - start,
541
+ ts: Date.now()
542
+ });
543
+ return result;
544
+ } catch (err) {
545
+ this._telemetry.track({
546
+ type: "contract.read",
547
+ chain: this.chain.name,
548
+ chainId: this.chain.id,
549
+ functionName: params.method,
550
+ success: false,
551
+ durationMs: Date.now() - start,
552
+ ts: Date.now()
553
+ });
554
+ throw err;
555
+ }
556
+ }
557
+ /**
558
+ * Execute a state-mutating contract function directly without loading a full
559
+ * contract instance. Best for one-off writes.
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * const { hash, receipt } = await awarizon.write({
564
+ * address: tokenAddress,
565
+ * abi: ERC20_ABI,
566
+ * method: "transfer",
567
+ * args: [recipient, 100n],
568
+ * })
569
+ * ```
287
570
  */
571
+ async write(params) {
572
+ await this._telemetry.ensureValidated();
573
+ const start = Date.now();
574
+ try {
575
+ const result = await this._txEngine.write({
576
+ address: params.address,
577
+ abi: params.abi,
578
+ functionName: params.method,
579
+ args: params.args ?? [],
580
+ value: params.value,
581
+ gas: params.gas
582
+ });
583
+ this._telemetry.track({
584
+ type: "contract.write",
585
+ chain: this.chain.name,
586
+ chainId: this.chain.id,
587
+ functionName: params.method,
588
+ success: true,
589
+ durationMs: Date.now() - start,
590
+ ts: Date.now()
591
+ });
592
+ return result;
593
+ } catch (err) {
594
+ this._telemetry.track({
595
+ type: "contract.write",
596
+ chain: this.chain.name,
597
+ chainId: this.chain.id,
598
+ functionName: params.method,
599
+ success: false,
600
+ durationMs: Date.now() - start,
601
+ ts: Date.now()
602
+ });
603
+ throw err;
604
+ }
605
+ }
606
+ /**
607
+ * Batch multiple read calls into a single RPC round-trip using multicall3.
608
+ * Falls back to parallel individual reads on chains where multicall3 is
609
+ * not deployed.
610
+ *
611
+ * @example
612
+ * ```ts
613
+ * const [balance, supply, allowance] = await awarizon.multicall([
614
+ * { address: tokenAddr, abi: ERC20_ABI, method: "balanceOf", args: [userAddr] },
615
+ * { address: tokenAddr, abi: ERC20_ABI, method: "totalSupply" },
616
+ * { address: tokenAddr, abi: ERC20_ABI, method: "allowance", args: [userAddr, spenderAddr] },
617
+ * ])
618
+ * ```
619
+ */
620
+ async multicall(calls) {
621
+ await this._telemetry.ensureValidated();
622
+ const contracts = calls.map((c) => ({
623
+ address: c.address,
624
+ abi: c.abi,
625
+ functionName: c.method,
626
+ args: c.args ?? []
627
+ }));
628
+ try {
629
+ const results = await this.publicClient.multicall({
630
+ contracts,
631
+ allowFailure: false
632
+ });
633
+ return results;
634
+ } catch {
635
+ return Promise.all(
636
+ contracts.map(
637
+ (c) => this.publicClient.readContract(c)
638
+ )
639
+ );
640
+ }
641
+ }
642
+ // ─── Accessors ──────────────────────────────────────────────────────────────
288
643
  get chainId() {
289
644
  return this.chain.id;
290
645
  }
646
+ get isConnected() {
647
+ return this._engine.isConnected();
648
+ }
291
649
  /**
292
- * Check whether any wallet (internal or external) is connected.
650
+ * Flush pending telemetry and stop background timers.
651
+ * Call when tearing down a long-lived SDK instance (e.g. on server shutdown).
293
652
  */
294
- get isConnected() {
295
- return this.wallet.isConnected();
653
+ async destroy() {
654
+ await this._telemetry.destroy();
296
655
  }
297
656
  };
298
657
 
299
- export { AwarizonWeb3, CHAINS, ContractNotLoadedError, NetworkMismatchError, ProviderError, UnsupportedChainError, getSupportedChainIds, resolveChain };
658
+ export { ApiKeyRequiredError, AwarizonWeb3, CHAINS, ContractNotLoadedError, InvalidApiKeyError, NetworkMismatchError, ProviderError, TelemetryClient, UnsupportedChainError, getSupportedChainIds, resolveChain };
300
659
  //# sourceMappingURL=index.mjs.map
301
660
  //# sourceMappingURL=index.mjs.map