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