@cavos/cli 0.0.1

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.js ADDED
@@ -0,0 +1,1151 @@
1
+ 'use strict';
2
+
3
+ var starknet = require('starknet');
4
+ var crypto = require('crypto');
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+ var os = require('os');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
28
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
29
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
30
+
31
+ // src/CavosAgent.ts
32
+ function getRandomBytes(length) {
33
+ return new Uint8Array(crypto.randomBytes(length));
34
+ }
35
+ function randomFieldElement() {
36
+ const bytes = getRandomBytes(32);
37
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
38
+ return BigInt("0x" + hex) % 2n ** 251n;
39
+ }
40
+ function base64UrlToBase64(base64url) {
41
+ return base64url.replace(/-/g, "+").replace(/_/g, "/").padEnd(base64url.length + (4 - base64url.length % 4) % 4, "=");
42
+ }
43
+ function base64UrlToBytes(base64url) {
44
+ const base64 = base64UrlToBase64(base64url);
45
+ const buf = Buffer.from(base64, "base64");
46
+ return new Uint8Array(buf);
47
+ }
48
+ function bytesToU128Limbs(bytes) {
49
+ const limbs = [];
50
+ for (let i = 15; i >= 0; i--) {
51
+ let limb = 0n;
52
+ for (let j = 0; j < 16; j++) {
53
+ const byteIdx = i * 16 + j;
54
+ if (byteIdx < bytes.length) {
55
+ limb = limb * 256n + BigInt(bytes[byteIdx]);
56
+ }
57
+ }
58
+ limbs.push(starknet.num.toHex(limb));
59
+ }
60
+ return limbs;
61
+ }
62
+ function subToFelt(sub) {
63
+ try {
64
+ const subBigInt = BigInt(sub);
65
+ if (subBigInt < 2n ** 251n) {
66
+ return starknet.num.toHex(subBigInt);
67
+ }
68
+ } catch {
69
+ }
70
+ return stringToFelt(sub);
71
+ }
72
+ function stringToFelt(str) {
73
+ const bytes = Buffer.from(str, "utf-8");
74
+ let result = 0n;
75
+ for (let i = 0; i < bytes.length && i < 31; i++) {
76
+ result = result * 256n + BigInt(bytes[i]);
77
+ }
78
+ return starknet.num.toHex(result);
79
+ }
80
+ function parseJWT(jwt) {
81
+ const parts = jwt.split(".");
82
+ if (parts.length !== 3) {
83
+ throw new Error("Invalid JWT format");
84
+ }
85
+ const payload = JSON.parse(Buffer.from(base64UrlToBase64(parts[1]), "base64").toString("utf-8"));
86
+ return {
87
+ sub: payload.sub,
88
+ nonce: payload.nonce,
89
+ exp: payload.exp,
90
+ iss: payload.iss,
91
+ aud: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud
92
+ };
93
+ }
94
+ function extractKidFromJwt(jwt) {
95
+ const parts = jwt.split(".");
96
+ const header = JSON.parse(Buffer.from(base64UrlToBase64(parts[0]), "base64").toString("utf-8"));
97
+ return header.kid || "";
98
+ }
99
+ function findClaimOffsets(jwt) {
100
+ const parts = jwt.split(".");
101
+ const headerJson = JSON.parse(Buffer.from(base64UrlToBase64(parts[0]), "base64").toString("utf-8"));
102
+ const payloadJson = JSON.parse(Buffer.from(base64UrlToBase64(parts[1]), "base64").toString("utf-8"));
103
+ const subValue = payloadJson.sub || "";
104
+ const nonceValue = payloadJson.nonce || "";
105
+ const kidValue = headerJson.kid || "";
106
+ const decodedPayload = Buffer.from(base64UrlToBase64(parts[1]), "base64").toString("utf-8");
107
+ const decodedHeader = Buffer.from(base64UrlToBase64(parts[0]), "base64").toString("utf-8");
108
+ const findClaimValueOffset = (decoded, key, value) => {
109
+ const exactPattern = `"${key}":"${value}"`;
110
+ let idx = decoded.indexOf(exactPattern);
111
+ if (idx >= 0) return idx + key.length + 4;
112
+ const spacedPattern = `"${key}": "${value}"`;
113
+ idx = decoded.indexOf(spacedPattern);
114
+ if (idx >= 0) return idx + key.length + 5;
115
+ const keyPattern = `"${key}"`;
116
+ idx = decoded.indexOf(keyPattern);
117
+ if (idx >= 0) {
118
+ const colonIdx = decoded.indexOf(":", idx + key.length + 2);
119
+ if (colonIdx >= 0) {
120
+ const valueQuoteIdx = decoded.indexOf('"', colonIdx + 1);
121
+ if (valueQuoteIdx >= 0) return valueQuoteIdx + 1;
122
+ }
123
+ }
124
+ return -1;
125
+ };
126
+ const subValueStart = findClaimValueOffset(decodedPayload, "sub", subValue);
127
+ if (subValueStart < 0) throw new Error("Failed to find sub claim in JWT payload");
128
+ const nonceValueStart = findClaimValueOffset(decodedPayload, "nonce", nonceValue);
129
+ if (nonceValueStart < 0) throw new Error("Failed to find nonce claim in JWT payload");
130
+ const kidValueStart = findClaimValueOffset(decodedHeader, "kid", kidValue);
131
+ if (kidValueStart < 0) throw new Error("Failed to find kid claim in JWT header");
132
+ return {
133
+ sub_offset: subValueStart,
134
+ sub_len: subValue.length,
135
+ nonce_offset: nonceValueStart,
136
+ nonce_len: nonceValue.length,
137
+ kid_offset: kidValueStart,
138
+ kid_len: kidValue.length
139
+ };
140
+ }
141
+ function computeMerkleRoot(contracts) {
142
+ if (contracts.length === 0) return "0x0";
143
+ let leaves = contracts.map(
144
+ (c) => starknet.hash.computePoseidonHashOnElements([starknet.num.toHex(c)])
145
+ );
146
+ leaves.sort((a, b) => {
147
+ const aBig = BigInt(a);
148
+ const bBig = BigInt(b);
149
+ if (aBig < bBig) return -1;
150
+ if (aBig > bBig) return 1;
151
+ return 0;
152
+ });
153
+ while (leaves.length > 1) {
154
+ const nextLevel = [];
155
+ for (let i = 0; i < leaves.length; i += 2) {
156
+ if (i + 1 < leaves.length) {
157
+ const left = leaves[i];
158
+ const right = leaves[i + 1];
159
+ const leftBig = BigInt(left);
160
+ const rightBig = BigInt(right);
161
+ if (leftBig < rightBig) {
162
+ nextLevel.push(starknet.hash.computePoseidonHashOnElements([left, right]));
163
+ } else {
164
+ nextLevel.push(starknet.hash.computePoseidonHashOnElements([right, left]));
165
+ }
166
+ } else {
167
+ nextLevel.push(leaves[i]);
168
+ }
169
+ }
170
+ leaves = nextLevel;
171
+ }
172
+ return leaves[0];
173
+ }
174
+ function computeMerkleProof(contracts, targetContract) {
175
+ if (contracts.length === 0) return [];
176
+ let leaves = contracts.map(
177
+ (c) => starknet.hash.computePoseidonHashOnElements([starknet.num.toHex(c)])
178
+ );
179
+ leaves.sort((a, b) => {
180
+ const aBig = BigInt(a);
181
+ const bBig = BigInt(b);
182
+ if (aBig < bBig) return -1;
183
+ if (aBig > bBig) return 1;
184
+ return 0;
185
+ });
186
+ const targetLeaf = starknet.hash.computePoseidonHashOnElements([starknet.num.toHex(targetContract)]);
187
+ let targetIdx = leaves.indexOf(targetLeaf);
188
+ if (targetIdx === -1) return [];
189
+ const proof = [];
190
+ let currentLevel = [...leaves];
191
+ while (currentLevel.length > 1) {
192
+ const nextLevel = [];
193
+ let nextTargetIdx = -1;
194
+ for (let i = 0; i < currentLevel.length; i += 2) {
195
+ if (i + 1 < currentLevel.length) {
196
+ const left = currentLevel[i];
197
+ const right = currentLevel[i + 1];
198
+ if (i === targetIdx || i + 1 === targetIdx) {
199
+ proof.push(i === targetIdx ? right : left);
200
+ nextTargetIdx = Math.floor(i / 2);
201
+ }
202
+ const leftBig = BigInt(left);
203
+ const rightBig = BigInt(right);
204
+ if (leftBig < rightBig) {
205
+ nextLevel.push(starknet.hash.computePoseidonHashOnElements([left, right]));
206
+ } else {
207
+ nextLevel.push(starknet.hash.computePoseidonHashOnElements([right, left]));
208
+ }
209
+ } else {
210
+ if (i === targetIdx) {
211
+ nextTargetIdx = Math.floor(i / 2);
212
+ }
213
+ nextLevel.push(currentLevel[i]);
214
+ }
215
+ }
216
+ currentLevel = nextLevel;
217
+ targetIdx = nextTargetIdx;
218
+ }
219
+ return proof;
220
+ }
221
+
222
+ // src/utils/constants.ts
223
+ var OAUTH_JWT_V1_MAGIC = "0x4f415554485f4a57545f5631";
224
+ var SESSION_V1_MAGIC = "0x53455353494f4e5f5631";
225
+ var STARK_CURVE_ORDER = BigInt("0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f");
226
+ var TOKENS_SEPOLIA = {
227
+ STRK: "0x04718f5a0Fc34cC1AF16A1cdee98fFB20C31f5cD61D6Ab07201858f4287c938D",
228
+ ETH: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"
229
+ };
230
+ var TOKENS_MAINNET = {
231
+ STRK: "0x04718f5a0Fc34cC1AF16A1cdee98fFB20C31f5cD61D6Ab07201858f4287c938D",
232
+ ETH: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"
233
+ };
234
+ var DEFAULT_RPC = {
235
+ mainnet: "https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_10/dql5pMT88iueZWl7L0yzT56uVk0EBU4L",
236
+ sepolia: "https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/dql5pMT88iueZWl7L0yzT56uVk0EBU4L"
237
+ };
238
+ var DEFAULT_OAUTH_CONFIG_SEPOLIA = {
239
+ jwksRegistryAddress: "0x05a19f14719dec9e27eb2aa38c5b68277bdb5c41570e548504722f737a3da6c6",
240
+ cavosAccountClassHash: "0x40f4075372d7b9b964910755dcdf96935280c8b675272f656b2d43d1ae4bbf4"
241
+ };
242
+ var DEFAULT_OAUTH_CONFIG_MAINNET = {
243
+ jwksRegistryAddress: "0x07787f624d6869ae306dc17b49174b284dbadd1e999c1c8733ce72eb7ac518c2",
244
+ cavosAccountClassHash: "0x40f4075372d7b9b964910755dcdf96935280c8b675272f656b2d43d1ae4bbf4"
245
+ };
246
+ var DEFAULT_BACKEND_URL = "https://cavos.xyz";
247
+ var DEFAULT_PAYMASTER_KEY = "c37c52b7-ea5a-4426-8121-329a78354b0b";
248
+
249
+ // src/core/SessionKeyManager.ts
250
+ function generateSessionKeyPair() {
251
+ const randomBytes2 = getRandomBytes(32);
252
+ let pk = BigInt("0x" + Array.from(randomBytes2).map((b) => b.toString(16).padStart(2, "0")).join(""));
253
+ pk = pk % (STARK_CURVE_ORDER - 1n) + 1n;
254
+ const privateKey = "0x" + pk.toString(16);
255
+ const publicKey = starknet.ec.starkCurve.getStarkKey(privateKey);
256
+ return { privateKey, publicKey };
257
+ }
258
+ function buildSessionSignature(transactionHash, sessionPrivateKey, sessionPubKey, calls, policy) {
259
+ const signature = starknet.ec.starkCurve.sign(transactionHash, sessionPrivateKey);
260
+ const sig = [
261
+ SESSION_V1_MAGIC,
262
+ starknet.num.toHex(signature.r),
263
+ starknet.num.toHex(signature.s),
264
+ sessionPubKey
265
+ ];
266
+ if (calls && policy?.allowedContracts?.length) {
267
+ for (const call of calls) {
268
+ const proof = computeMerkleProof(policy.allowedContracts, call.contractAddress);
269
+ sig.push(starknet.num.toHex(proof.length));
270
+ sig.push(...proof);
271
+ }
272
+ }
273
+ return sig;
274
+ }
275
+ async function buildJWTSignatureData(transactionHash, session, salt, backendUrl) {
276
+ const { jwt, sessionPrivateKey, sessionPubKey, nonceParams, jwtClaims } = session;
277
+ const signature = starknet.ec.starkCurve.sign(transactionHash, sessionPrivateKey);
278
+ const jwtParts = jwt.split(".");
279
+ const rsaSignature = base64UrlToBytes(jwtParts[2]);
280
+ const rsaLimbs = bytesToU128Limbs(rsaSignature);
281
+ const signedData = `${jwtParts[0]}.${jwtParts[1]}`;
282
+ const signedDataBytes = Buffer.from(signedData, "utf-8");
283
+ const offsets = findClaimOffsets(jwt);
284
+ const jwt_sub_felt = subToFelt(jwtClaims.sub);
285
+ const salt_hex = starknet.num.toHex(salt);
286
+ const packedBytes = [];
287
+ const PACK_SIZE = 31;
288
+ for (let i = 0; i < signedDataBytes.length; i += PACK_SIZE) {
289
+ let chunk = 0n;
290
+ const end = Math.min(i + PACK_SIZE, signedDataBytes.length);
291
+ for (let j = i; j < end; j++) {
292
+ chunk = chunk * 256n + BigInt(signedDataBytes[j]);
293
+ }
294
+ packedBytes.push(starknet.num.toHex(chunk));
295
+ }
296
+ const kid = extractKidFromJwt(jwt);
297
+ const iss = jwtClaims.iss;
298
+ const modulusLimbs = await fetchModulusForKid(kid, iss, backendUrl);
299
+ const { n_prime, r_sq } = calculateMontgomeryConstants(modulusLimbs);
300
+ const sig = [
301
+ OAUTH_JWT_V1_MAGIC,
302
+ starknet.num.toHex(signature.r),
303
+ starknet.num.toHex(signature.s),
304
+ sessionPubKey,
305
+ starknet.num.toHex(nonceParams.validUntil),
306
+ starknet.num.toHex(nonceParams.randomness),
307
+ jwt_sub_felt,
308
+ session.nonce,
309
+ starknet.num.toHex(jwtClaims.exp),
310
+ stringToFelt(kid),
311
+ stringToFelt(jwtClaims.iss),
312
+ stringToFelt(jwtClaims.aud),
313
+ salt_hex,
314
+ starknet.num.toHex(offsets.sub_offset),
315
+ starknet.num.toHex(offsets.sub_len),
316
+ starknet.num.toHex(offsets.nonce_offset),
317
+ starknet.num.toHex(offsets.nonce_len),
318
+ starknet.num.toHex(offsets.kid_offset),
319
+ starknet.num.toHex(offsets.kid_len),
320
+ starknet.num.toHex(16),
321
+ ...rsaLimbs,
322
+ starknet.num.toHex(16),
323
+ ...n_prime,
324
+ starknet.num.toHex(16),
325
+ ...r_sq,
326
+ starknet.num.toHex(signedDataBytes.length),
327
+ ...packedBytes
328
+ ];
329
+ sig.push(starknet.num.toHex(nonceParams.validAfter));
330
+ const policy = session.sessionPolicy;
331
+ if (policy) {
332
+ const merkleRoot = policy.allowedContracts.length > 0 ? computeMerkleRoot(policy.allowedContracts) : "0x0";
333
+ sig.push(merkleRoot);
334
+ sig.push(starknet.num.toHex(policy.maxCallsPerTx));
335
+ sig.push(starknet.num.toHex(policy.spendingLimits.length));
336
+ for (const limit of policy.spendingLimits) {
337
+ sig.push(starknet.num.toHex(limit.token));
338
+ const limitBig = BigInt(limit.limit);
339
+ sig.push(starknet.num.toHex(limitBig & (1n << 128n) - 1n));
340
+ sig.push(starknet.num.toHex(limitBig >> 128n));
341
+ }
342
+ } else {
343
+ sig.push("0x0");
344
+ sig.push(starknet.num.toHex(10));
345
+ sig.push(starknet.num.toHex(0));
346
+ }
347
+ return sig;
348
+ }
349
+ async function fetchModulusForKid(kid, issuer, backendUrl) {
350
+ let jwksUrl = "https://www.googleapis.com/oauth2/v3/certs";
351
+ if (issuer === "https://appleid.apple.com") {
352
+ jwksUrl = "https://appleid.apple.com/auth/keys";
353
+ } else if (issuer === "https://cavos.app/firebase") {
354
+ jwksUrl = `${backendUrl}/api/jwks/firebase`;
355
+ }
356
+ const response = await fetch(jwksUrl);
357
+ const data = await response.json();
358
+ const jwks = data.jwks || data;
359
+ if (!jwks.keys || !Array.isArray(jwks.keys)) {
360
+ throw new Error(`Invalid JWKS response from ${jwksUrl}`);
361
+ }
362
+ const key = jwks.keys.find((k) => k.kid === kid);
363
+ if (!key || !key.n) {
364
+ throw new Error(`Key not found for kid: ${kid}`);
365
+ }
366
+ const modulusBytes = base64UrlToBytes(key.n);
367
+ const limbs = bytesToU128Limbs(modulusBytes);
368
+ return limbs.map((l) => BigInt(l));
369
+ }
370
+ function calculateMontgomeryConstants(n_limbs) {
371
+ let n = 0n;
372
+ for (let i = 0; i < n_limbs.length; i++) {
373
+ n += n_limbs[i] * (1n << BigInt(i) * 128n);
374
+ }
375
+ const R = 1n << 2048n;
376
+ function modInverse(a, mod) {
377
+ let t = 0n, newt = 1n, r = mod, newr = a;
378
+ while (newr !== 0n) {
379
+ const q = r / newr;
380
+ [t, newt] = [newt, t - q * newt];
381
+ [r, newr] = [newr, r - q * newr];
382
+ }
383
+ if (r > 1n) throw new Error("n is not invertible");
384
+ if (t < 0n) t += mod;
385
+ return t;
386
+ }
387
+ const n_inv = modInverse(n, R);
388
+ const n_prime_val = (R - n_inv) % R;
389
+ const r_sq_val = R * R % n;
390
+ const toLimbs = (val) => {
391
+ const limbs = [];
392
+ for (let i = 0; i < 16; i++) {
393
+ const limb = val >> BigInt(i) * 128n & (1n << 128n) - 1n;
394
+ limbs.push(starknet.num.toHex(limb));
395
+ }
396
+ return limbs;
397
+ };
398
+ return { n_prime: toLimbs(n_prime_val), r_sq: toLimbs(r_sq_val) };
399
+ }
400
+ function computeNonce(params) {
401
+ return starknet.hash.computePoseidonHashOnElements([
402
+ params.sessionPubKey,
403
+ starknet.num.toHex(params.validUntil),
404
+ starknet.num.toHex(params.randomness)
405
+ ]);
406
+ }
407
+ function generateNonceParams(sessionPubKey, currentTimestamp, sessionDurationSeconds = 86400n, renewalGraceSeconds = 172800n) {
408
+ const randomness = randomFieldElement();
409
+ return {
410
+ sessionPubKey,
411
+ validAfter: currentTimestamp,
412
+ validUntil: currentTimestamp + sessionDurationSeconds,
413
+ renewalDeadline: currentTimestamp + renewalGraceSeconds,
414
+ randomness
415
+ };
416
+ }
417
+ function computeAddressSeed(sub, salt, walletName) {
418
+ const subFeltVal = subToFelt(sub);
419
+ let saltFelt = starknet.num.toHex(salt);
420
+ if (walletName) {
421
+ const nameFelt = stringToFelt(walletName);
422
+ saltFelt = starknet.hash.computePoseidonHashOnElements([saltFelt, nameFelt]);
423
+ }
424
+ return starknet.hash.computePoseidonHashOnElements([subFeltVal, saltFelt]);
425
+ }
426
+ function computeContractAddress(sub, salt, classHash, jwksRegistryAddress, walletName) {
427
+ const addressSeed = computeAddressSeed(sub, salt, walletName);
428
+ const constructorCalldata = [addressSeed, jwksRegistryAddress];
429
+ return starknet.hash.calculateContractAddressFromHash(
430
+ addressSeed,
431
+ classHash,
432
+ constructorCalldata,
433
+ 0
434
+ );
435
+ }
436
+ async function isDeployed(provider, address) {
437
+ try {
438
+ const classHash = await provider.getClassHashAt(address, "latest");
439
+ return !!classHash;
440
+ } catch {
441
+ return false;
442
+ }
443
+ }
444
+ async function getSessionStatus(provider, walletAddress, sessionPubKey) {
445
+ try {
446
+ const result = await provider.callContract({
447
+ contractAddress: walletAddress,
448
+ entrypoint: "get_session",
449
+ calldata: [sessionPubKey]
450
+ }, "latest");
451
+ const nonce = BigInt(result[0]);
452
+ const validUntil = BigInt(result[2]);
453
+ const renewalDeadline = BigInt(result[3]);
454
+ const registered = nonce !== 0n;
455
+ if (!registered) {
456
+ return { registered: false, expired: false, canRenew: false };
457
+ }
458
+ const block = await provider.getBlock("latest");
459
+ const now = BigInt(block.timestamp);
460
+ const expired = now >= validUntil;
461
+ const canRenew = expired && now < renewalDeadline;
462
+ return { registered, expired, canRenew, validUntil, renewalDeadline };
463
+ } catch {
464
+ return { registered: false, expired: false, canRenew: false };
465
+ }
466
+ }
467
+ async function deployAccount(provider, session, classHash, jwksRegistryAddress, salt, paymasterApiKey, network, backendUrl) {
468
+ const deployed = await isDeployed(provider, session.walletAddress);
469
+ if (deployed) return "already-deployed";
470
+ const constructorCalldata = [
471
+ starknet.num.toHex(session.addressSeed),
472
+ starknet.num.toHex(jwksRegistryAddress)
473
+ ];
474
+ const baseUrl = network === "mainnet" ? "https://starknet.api.avnu.fi" : "https://sepolia.api.avnu.fi";
475
+ const buildResponse = await fetch(`${baseUrl}/paymaster/v1/build-typed-data`, {
476
+ method: "POST",
477
+ headers: {
478
+ "Content-Type": "application/json",
479
+ "api-key": paymasterApiKey
480
+ },
481
+ body: JSON.stringify({
482
+ userAddress: session.walletAddress,
483
+ calls: [],
484
+ accountClassHash: classHash,
485
+ accountCalldata: constructorCalldata
486
+ })
487
+ });
488
+ if (!buildResponse.ok) {
489
+ const errText = await buildResponse.text();
490
+ if (errText.includes("already deployed")) return "already-deployed";
491
+ throw new Error(`Deploy build-typed-data failed: ${errText}`);
492
+ }
493
+ const paymasterTypedData = await buildResponse.json();
494
+ const messageHash = computeTypedDataHash(paymasterTypedData, session.walletAddress);
495
+ const signature = await buildJWTSignatureData(messageHash, session, salt, backendUrl);
496
+ const executeResponse = await fetch(`${baseUrl}/paymaster/v1/execute`, {
497
+ method: "POST",
498
+ headers: {
499
+ "Content-Type": "application/json",
500
+ "api-key": paymasterApiKey
501
+ },
502
+ body: JSON.stringify({
503
+ userAddress: session.walletAddress,
504
+ typedData: JSON.stringify(paymasterTypedData),
505
+ signature
506
+ })
507
+ });
508
+ if (!executeResponse.ok) {
509
+ const errText = await executeResponse.text();
510
+ if (errText.includes("already deployed")) return "already-deployed";
511
+ throw new Error(`Deploy execute failed: ${errText}`);
512
+ }
513
+ const result = await executeResponse.json();
514
+ await provider.waitForTransaction(result.transactionHash);
515
+ return result.transactionHash;
516
+ }
517
+ async function execute(provider, session, calls, salt, paymasterApiKey, network, backendUrl) {
518
+ const callsArray = Array.isArray(calls) ? calls : [calls];
519
+ const status = await getSessionStatus(provider, session.walletAddress, session.sessionPubKey);
520
+ if (!status.registered) {
521
+ return executeWithAVNU(provider, session, callsArray, salt, paymasterApiKey, network, backendUrl, true);
522
+ }
523
+ if (status.expired && status.canRenew) {
524
+ throw new Error("SESSION_RENEWABLE: Session expired but can be renewed. Call renewSession() first.");
525
+ }
526
+ if (status.expired && !status.canRenew) {
527
+ throw new Error("SESSION_EXPIRED: Session expired outside grace period. Please login again.");
528
+ }
529
+ return executeWithAVNU(provider, session, callsArray, salt, paymasterApiKey, network, backendUrl, false);
530
+ }
531
+ async function executeWithAVNU(provider, session, calls, salt, paymasterApiKey, network, backendUrl, forceJWT) {
532
+ const baseUrl = network === "mainnet" ? "https://starknet.api.avnu.fi" : "https://sepolia.api.avnu.fi";
533
+ const formattedCalls = calls.map((call) => ({
534
+ contractAddress: call.contractAddress,
535
+ entrypoint: call.entrypoint,
536
+ calldata: call.calldata ? call.calldata.map((c) => starknet.num.toHex(c)) : []
537
+ }));
538
+ const buildResponse = await fetch(`${baseUrl}/paymaster/v1/build-typed-data`, {
539
+ method: "POST",
540
+ headers: {
541
+ "Content-Type": "application/json",
542
+ "api-key": paymasterApiKey
543
+ },
544
+ body: JSON.stringify({
545
+ userAddress: session.walletAddress,
546
+ calls: formattedCalls
547
+ })
548
+ });
549
+ if (!buildResponse.ok) {
550
+ throw new Error(`Build typed data failed: ${await buildResponse.text()}`);
551
+ }
552
+ const paymasterTypedData = await buildResponse.json();
553
+ const messageHash = computeTypedDataHash(paymasterTypedData, session.walletAddress);
554
+ const signature = forceJWT ? await buildJWTSignatureData(messageHash, session, salt, backendUrl) : buildSessionSignature(
555
+ messageHash,
556
+ session.sessionPrivateKey,
557
+ session.sessionPubKey,
558
+ calls.map((c) => ({ contractAddress: c.contractAddress })),
559
+ session.sessionPolicy
560
+ );
561
+ const executeResponse = await fetch(`${baseUrl}/paymaster/v1/execute`, {
562
+ method: "POST",
563
+ headers: {
564
+ "Content-Type": "application/json",
565
+ "api-key": paymasterApiKey
566
+ },
567
+ body: JSON.stringify({
568
+ userAddress: session.walletAddress,
569
+ typedData: JSON.stringify(paymasterTypedData),
570
+ signature
571
+ })
572
+ });
573
+ if (!executeResponse.ok) {
574
+ const errorText = await executeResponse.text();
575
+ if (errorText.includes("Session expired")) {
576
+ throw new Error("SESSION_EXPIRED: Session has expired. Call renewSession() first.");
577
+ }
578
+ throw new Error(`Execute failed: ${errorText}`);
579
+ }
580
+ const result = await executeResponse.json();
581
+ return result.transactionHash;
582
+ }
583
+ async function renewSession(provider, oldSession, newSession, paymasterApiKey, network) {
584
+ const policy = newSession.sessionPolicy;
585
+ const allowedContractsRoot = policy?.allowedContracts?.length ? computeMerkleRoot(policy.allowedContracts) : "0x0";
586
+ const maxCallsPerTx = policy?.maxCallsPerTx ?? 10;
587
+ const message = starknet.hash.computePoseidonHashOnElements([
588
+ newSession.sessionPubKey,
589
+ newSession.nonce,
590
+ starknet.num.toHex(newSession.nonceParams.validAfter),
591
+ starknet.num.toHex(newSession.nonceParams.validUntil),
592
+ starknet.num.toHex(newSession.nonceParams.renewalDeadline),
593
+ allowedContractsRoot,
594
+ starknet.num.toHex(maxCallsPerTx)
595
+ ]);
596
+ const oldSignature = starknet.ec.starkCurve.sign(message, oldSession.sessionPrivateKey);
597
+ const spendingCalldata = [];
598
+ if (policy?.spendingLimits?.length) {
599
+ spendingCalldata.push(starknet.num.toHex(policy.spendingLimits.length));
600
+ for (const limit of policy.spendingLimits) {
601
+ spendingCalldata.push(starknet.num.toHex(limit.token));
602
+ const limitBig = BigInt(limit.limit);
603
+ spendingCalldata.push(starknet.num.toHex(limitBig & (1n << 128n) - 1n));
604
+ spendingCalldata.push(starknet.num.toHex(limitBig >> 128n));
605
+ }
606
+ } else {
607
+ spendingCalldata.push(starknet.num.toHex(0));
608
+ }
609
+ const renewCall = {
610
+ contractAddress: oldSession.walletAddress,
611
+ entrypoint: "renew_session",
612
+ calldata: [
613
+ oldSession.sessionPubKey,
614
+ starknet.num.toHex(oldSignature.r),
615
+ starknet.num.toHex(oldSignature.s),
616
+ newSession.sessionPubKey,
617
+ newSession.nonce,
618
+ starknet.num.toHex(newSession.nonceParams.validAfter),
619
+ starknet.num.toHex(newSession.nonceParams.validUntil),
620
+ starknet.num.toHex(newSession.nonceParams.renewalDeadline),
621
+ allowedContractsRoot,
622
+ starknet.num.toHex(maxCallsPerTx),
623
+ ...spendingCalldata
624
+ ]
625
+ };
626
+ const baseUrl = network === "mainnet" ? "https://starknet.api.avnu.fi" : "https://sepolia.api.avnu.fi";
627
+ const buildResponse = await fetch(`${baseUrl}/paymaster/v1/build-typed-data`, {
628
+ method: "POST",
629
+ headers: {
630
+ "Content-Type": "application/json",
631
+ "api-key": paymasterApiKey
632
+ },
633
+ body: JSON.stringify({
634
+ userAddress: oldSession.walletAddress,
635
+ calls: [{
636
+ contractAddress: renewCall.contractAddress,
637
+ entrypoint: renewCall.entrypoint,
638
+ calldata: renewCall.calldata
639
+ }]
640
+ })
641
+ });
642
+ if (!buildResponse.ok) {
643
+ throw new Error(`Renew build-typed-data failed: ${await buildResponse.text()}`);
644
+ }
645
+ const paymasterTypedData = await buildResponse.json();
646
+ const messageHash = computeTypedDataHash(paymasterTypedData, oldSession.walletAddress);
647
+ const signature = buildSessionSignature(
648
+ messageHash,
649
+ oldSession.sessionPrivateKey,
650
+ oldSession.sessionPubKey
651
+ );
652
+ const executeResponse = await fetch(`${baseUrl}/paymaster/v1/execute`, {
653
+ method: "POST",
654
+ headers: {
655
+ "Content-Type": "application/json",
656
+ "api-key": paymasterApiKey
657
+ },
658
+ body: JSON.stringify({
659
+ userAddress: oldSession.walletAddress,
660
+ typedData: JSON.stringify(paymasterTypedData),
661
+ signature
662
+ })
663
+ });
664
+ if (!executeResponse.ok) {
665
+ const errorText = await executeResponse.text();
666
+ if (errorText.includes("Renewal period expired")) {
667
+ throw new Error("Grace period expired. Please login again.");
668
+ }
669
+ throw new Error(`Renew session failed: ${errorText}`);
670
+ }
671
+ const result = await executeResponse.json();
672
+ return result.transactionHash;
673
+ }
674
+ async function revokeSession(provider, session, sessionKeyToRevoke, salt, paymasterApiKey, network, backendUrl) {
675
+ const revokeCall = {
676
+ contractAddress: session.walletAddress,
677
+ entrypoint: "revoke_session",
678
+ calldata: [sessionKeyToRevoke]
679
+ };
680
+ return execute(provider, session, [revokeCall], salt, paymasterApiKey, network, backendUrl);
681
+ }
682
+ async function emergencyRevokeAllSessions(provider, session, salt, paymasterApiKey, network, backendUrl) {
683
+ const revokeCall = {
684
+ contractAddress: session.walletAddress,
685
+ entrypoint: "emergency_revoke",
686
+ calldata: []
687
+ };
688
+ return execute(provider, session, [revokeCall], salt, paymasterApiKey, network, backendUrl);
689
+ }
690
+ async function getBalance(provider, tokenAddress, walletAddress) {
691
+ const result = await provider.callContract({
692
+ contractAddress: tokenAddress,
693
+ entrypoint: "balanceOf",
694
+ calldata: [walletAddress]
695
+ });
696
+ const low = BigInt(result[0]);
697
+ const high = BigInt(result[1]);
698
+ return low + (high << 128n);
699
+ }
700
+ function computeTypedDataHash(paymasterTypedData, address) {
701
+ return starknet.typedData.getMessageHash(paymasterTypedData, address);
702
+ }
703
+
704
+ // src/auth/FirebaseAuth.ts
705
+ async function firebaseLogin(backendUrl, appId, email, password, nonce) {
706
+ const response = await fetch(`${backendUrl}/api/oauth/firebase/login`, {
707
+ method: "POST",
708
+ headers: { "Content-Type": "application/json" },
709
+ body: JSON.stringify({ email, password, nonce, app_id: appId })
710
+ });
711
+ if (!response.ok) {
712
+ const error = await response.json().catch(() => ({ error: "Login failed" }));
713
+ if (error.error === "email_not_verified") {
714
+ throw new Error("Email not verified. Please verify your email on agent.cavos.xyz first.");
715
+ }
716
+ throw new Error(error.error || "Login failed");
717
+ }
718
+ const { jwt } = await response.json();
719
+ const claims = parseJWT(jwt);
720
+ if (claims.nonce !== nonce) {
721
+ throw new Error("JWT nonce mismatch. Possible replay attack.");
722
+ }
723
+ return { jwt, claims };
724
+ }
725
+ async function validateApp(backendUrl, appId, network) {
726
+ try {
727
+ const response = await fetch(
728
+ `${backendUrl}/api/apps/${appId}/validate?network=${network}`
729
+ );
730
+ if (!response.ok) return { allowed: true };
731
+ const result = await response.json();
732
+ return { allowed: result.allowed !== false, appSalt: result.app_salt };
733
+ } catch {
734
+ return { allowed: true };
735
+ }
736
+ }
737
+ var CAVOS_DIR = path__namespace.join(os__namespace.homedir(), ".cavos");
738
+ var SESSIONS_DIR = path__namespace.join(CAVOS_DIR, "sessions");
739
+ var CONFIG_FILE = path__namespace.join(CAVOS_DIR, "config.json");
740
+ function ensureDir(dir) {
741
+ if (!fs__namespace.existsSync(dir)) {
742
+ fs__namespace.mkdirSync(dir, { recursive: true, mode: 448 });
743
+ }
744
+ }
745
+ function saveSession(appId, session) {
746
+ ensureDir(SESSIONS_DIR);
747
+ const stored = {
748
+ sessionPrivateKey: session.sessionPrivateKey,
749
+ sessionPubKey: session.sessionPubKey,
750
+ nonceParams: {
751
+ sessionPubKey: session.nonceParams.sessionPubKey,
752
+ validAfter: session.nonceParams.validAfter.toString(),
753
+ validUntil: session.nonceParams.validUntil.toString(),
754
+ renewalDeadline: session.nonceParams.renewalDeadline.toString(),
755
+ randomness: session.nonceParams.randomness.toString()
756
+ },
757
+ nonce: session.nonce,
758
+ jwt: session.jwt,
759
+ jwtClaims: session.jwtClaims,
760
+ walletAddress: session.walletAddress,
761
+ addressSeed: session.addressSeed,
762
+ sessionPolicy: session.sessionPolicy ? {
763
+ spendingLimits: session.sessionPolicy.spendingLimits.map((sl) => ({
764
+ token: sl.token,
765
+ limit: sl.limit.toString()
766
+ })),
767
+ allowedContracts: session.sessionPolicy.allowedContracts,
768
+ maxCallsPerTx: session.sessionPolicy.maxCallsPerTx
769
+ } : void 0,
770
+ appSalt: session.appSalt,
771
+ walletName: session.walletName
772
+ };
773
+ const filePath = path__namespace.join(SESSIONS_DIR, `${appId}.json`);
774
+ fs__namespace.writeFileSync(filePath, JSON.stringify(stored, null, 2), { mode: 384 });
775
+ }
776
+ function loadSession(appId) {
777
+ const filePath = path__namespace.join(SESSIONS_DIR, `${appId}.json`);
778
+ if (!fs__namespace.existsSync(filePath)) return null;
779
+ try {
780
+ const stored = JSON.parse(fs__namespace.readFileSync(filePath, "utf-8"));
781
+ return {
782
+ sessionPrivateKey: stored.sessionPrivateKey,
783
+ sessionPubKey: stored.sessionPubKey,
784
+ nonceParams: {
785
+ sessionPubKey: stored.nonceParams.sessionPubKey,
786
+ validAfter: BigInt(stored.nonceParams.validAfter),
787
+ validUntil: BigInt(stored.nonceParams.validUntil),
788
+ renewalDeadline: BigInt(stored.nonceParams.renewalDeadline),
789
+ randomness: BigInt(stored.nonceParams.randomness)
790
+ },
791
+ nonce: stored.nonce,
792
+ jwt: stored.jwt,
793
+ jwtClaims: stored.jwtClaims,
794
+ walletAddress: stored.walletAddress,
795
+ addressSeed: stored.addressSeed,
796
+ sessionPolicy: stored.sessionPolicy ? {
797
+ spendingLimits: stored.sessionPolicy.spendingLimits.map((sl) => ({
798
+ token: sl.token,
799
+ limit: BigInt(sl.limit)
800
+ })),
801
+ allowedContracts: stored.sessionPolicy.allowedContracts,
802
+ maxCallsPerTx: stored.sessionPolicy.maxCallsPerTx
803
+ } : void 0,
804
+ appSalt: stored.appSalt,
805
+ walletName: stored.walletName
806
+ };
807
+ } catch {
808
+ return null;
809
+ }
810
+ }
811
+ function deleteSession(appId) {
812
+ const filePath = path__namespace.join(SESSIONS_DIR, `${appId}.json`);
813
+ if (fs__namespace.existsSync(filePath)) {
814
+ fs__namespace.unlinkSync(filePath);
815
+ }
816
+ }
817
+ path__namespace.join(CAVOS_DIR, "policy.json");
818
+ function saveConfig(config) {
819
+ ensureDir(CAVOS_DIR);
820
+ const existing = loadConfig();
821
+ const merged = { ...existing, ...config };
822
+ fs__namespace.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 384 });
823
+ }
824
+ function loadConfig() {
825
+ if (!fs__namespace.existsSync(CONFIG_FILE)) return {};
826
+ try {
827
+ return JSON.parse(fs__namespace.readFileSync(CONFIG_FILE, "utf-8"));
828
+ } catch {
829
+ return {};
830
+ }
831
+ }
832
+
833
+ // src/CavosAgent.ts
834
+ var CavosAgent = class {
835
+ constructor(config) {
836
+ this.session = null;
837
+ this.appSalt = null;
838
+ const network = config.network ?? "sepolia";
839
+ const backendUrl = config.backendUrl ?? DEFAULT_BACKEND_URL;
840
+ const rpcUrl = config.starknetRpcUrl ?? DEFAULT_RPC[network];
841
+ this.config = { ...config, network, backendUrl };
842
+ this.provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
843
+ const envToken = process.env.CAVOS_TOKEN || process.env.CAVOS_SESSION_TOKEN;
844
+ if (envToken) {
845
+ try {
846
+ const sessionData = JSON.parse(Buffer.from(envToken, "base64").toString());
847
+ this.session = sessionData;
848
+ this.appSalt = sessionData.appSalt ?? null;
849
+ } catch (e) {
850
+ console.warn(`[CavosAgent] Failed to parse session token: ${e}`);
851
+ }
852
+ } else {
853
+ this.restoreSession();
854
+ }
855
+ }
856
+ // ============ Auth ============
857
+ /**
858
+ * Login with Firebase email/password.
859
+ * Generates session keys, authenticates, and persists the session.
860
+ */
861
+ async login(email, password, walletName) {
862
+ const { appId, network, backendUrl } = this.config;
863
+ const { allowed, appSalt } = await validateApp(backendUrl, appId, network);
864
+ if (!allowed) {
865
+ throw new Error("App not allowed or subscription limit reached.");
866
+ }
867
+ this.appSalt = appSalt ?? "0x0";
868
+ const { privateKey, publicKey } = generateSessionKeyPair();
869
+ const now = BigInt(Math.floor(Date.now() / 1e3));
870
+ const duration = BigInt(this.config.sessionDuration ?? 86400);
871
+ const grace = BigInt(this.config.renewalGracePeriod ?? 172800);
872
+ const nonceParams = generateNonceParams(publicKey, now, duration, grace);
873
+ const nonce = computeNonce(nonceParams);
874
+ const { jwt, claims } = await firebaseLogin(backendUrl, appId, email, password, nonce);
875
+ const oauthConfig = network === "mainnet" ? DEFAULT_OAUTH_CONFIG_MAINNET : DEFAULT_OAUTH_CONFIG_SEPOLIA;
876
+ const addressSeed = computeAddressSeed(claims.sub, this.appSalt, walletName);
877
+ const walletAddress = computeContractAddress(
878
+ claims.sub,
879
+ this.appSalt,
880
+ oauthConfig.cavosAccountClassHash,
881
+ oauthConfig.jwksRegistryAddress,
882
+ walletName
883
+ );
884
+ this.session = {
885
+ jwt,
886
+ sessionPrivateKey: privateKey,
887
+ sessionPubKey: publicKey,
888
+ nonce,
889
+ nonceParams,
890
+ jwtClaims: claims,
891
+ walletAddress,
892
+ addressSeed,
893
+ appSalt: this.appSalt,
894
+ sessionPolicy: this.config.policy,
895
+ walletName
896
+ };
897
+ saveSession(appId, this.session);
898
+ saveConfig({ defaultAppId: appId, network });
899
+ }
900
+ /**
901
+ * Login using a pre-existing JWT token.
902
+ * Useful for non-interactive agents or CI/CD.
903
+ */
904
+ async loginWithJWT(jwt, walletName) {
905
+ const { appId, network, backendUrl } = this.config;
906
+ const { allowed, appSalt } = await validateApp(backendUrl, appId, network);
907
+ if (!allowed) {
908
+ throw new Error("App not allowed or subscription limit reached.");
909
+ }
910
+ this.appSalt = appSalt ?? "0x0";
911
+ const claims = parseJWT(jwt);
912
+ const { privateKey, publicKey } = generateSessionKeyPair();
913
+ const now = BigInt(Math.floor(Date.now() / 1e3));
914
+ const duration = BigInt(this.config.sessionDuration ?? 86400);
915
+ const grace = BigInt(this.config.renewalGracePeriod ?? 172800);
916
+ const nonceParams = generateNonceParams(publicKey, now, duration, grace);
917
+ const nonce = computeNonce(nonceParams);
918
+ const oauthConfig = network === "mainnet" ? DEFAULT_OAUTH_CONFIG_MAINNET : DEFAULT_OAUTH_CONFIG_SEPOLIA;
919
+ const addressSeed = computeAddressSeed(claims.sub, this.appSalt, walletName);
920
+ const walletAddress = computeContractAddress(
921
+ claims.sub,
922
+ this.appSalt,
923
+ oauthConfig.cavosAccountClassHash,
924
+ oauthConfig.jwksRegistryAddress,
925
+ walletName
926
+ );
927
+ this.session = {
928
+ jwt,
929
+ sessionPrivateKey: privateKey,
930
+ sessionPubKey: publicKey,
931
+ nonce,
932
+ nonceParams,
933
+ jwtClaims: claims,
934
+ walletAddress,
935
+ addressSeed,
936
+ appSalt: this.appSalt,
937
+ sessionPolicy: this.config.policy,
938
+ walletName
939
+ };
940
+ saveSession(appId, this.session);
941
+ saveConfig({ defaultAppId: appId, network });
942
+ }
943
+ /**
944
+ * Check if the agent has a valid session.
945
+ */
946
+ isAuthenticated() {
947
+ return this.session !== null;
948
+ }
949
+ /**
950
+ * Get the wallet address.
951
+ */
952
+ getAddress() {
953
+ return this.session?.walletAddress ?? null;
954
+ }
955
+ /**
956
+ * Logout — clear the persisted session.
957
+ */
958
+ logout() {
959
+ deleteSession(this.config.appId);
960
+ this.session = null;
961
+ this.appSalt = null;
962
+ }
963
+ // ============ Transactions ============
964
+ /**
965
+ * Execute one or more calls via paymaster.
966
+ * Handles session registration automatically on first call.
967
+ */
968
+ async execute(calls) {
969
+ this.ensureSession();
970
+ return execute(
971
+ this.provider,
972
+ this.session,
973
+ calls,
974
+ this.getSalt(),
975
+ this.getPaymasterKey(),
976
+ this.config.network,
977
+ this.config.backendUrl
978
+ );
979
+ }
980
+ /**
981
+ * Transfer ERC-20 tokens.
982
+ */
983
+ async transfer(tokenAddress, to, amount) {
984
+ const low = starknet.num.toHex(amount & (1n << 128n) - 1n);
985
+ const high = starknet.num.toHex(amount >> 128n);
986
+ return this.execute({
987
+ contractAddress: tokenAddress,
988
+ entrypoint: "transfer",
989
+ calldata: [to, low, high]
990
+ });
991
+ }
992
+ /**
993
+ * Approve ERC-20 spending.
994
+ */
995
+ async approve(tokenAddress, spender, amount) {
996
+ const low = starknet.num.toHex(amount & (1n << 128n) - 1n);
997
+ const high = starknet.num.toHex(amount >> 128n);
998
+ return this.execute({
999
+ contractAddress: tokenAddress,
1000
+ entrypoint: "approve",
1001
+ calldata: [spender, low, high]
1002
+ });
1003
+ }
1004
+ /**
1005
+ * Deploy the account contract.
1006
+ */
1007
+ async deploy() {
1008
+ this.ensureSession();
1009
+ const oauthConfig = this.config.network === "mainnet" ? DEFAULT_OAUTH_CONFIG_MAINNET : DEFAULT_OAUTH_CONFIG_SEPOLIA;
1010
+ return deployAccount(
1011
+ this.provider,
1012
+ this.session,
1013
+ oauthConfig.cavosAccountClassHash,
1014
+ oauthConfig.jwksRegistryAddress,
1015
+ this.getSalt(),
1016
+ this.getPaymasterKey(),
1017
+ this.config.network,
1018
+ this.config.backendUrl
1019
+ );
1020
+ }
1021
+ // ============ Queries ============
1022
+ /**
1023
+ * Get ERC-20 balance.
1024
+ */
1025
+ async getBalance(tokenAddress) {
1026
+ this.ensureSession();
1027
+ const tokens = this.config.network === "mainnet" ? TOKENS_MAINNET : TOKENS_SEPOLIA;
1028
+ const token = tokenAddress ?? tokens.STRK;
1029
+ return getBalance(this.provider, token, this.session.walletAddress);
1030
+ }
1031
+ /**
1032
+ * Check if the account is deployed.
1033
+ */
1034
+ async isDeployed() {
1035
+ this.ensureSession();
1036
+ return isDeployed(this.provider, this.session.walletAddress);
1037
+ }
1038
+ /**
1039
+ * Get on-chain session status.
1040
+ */
1041
+ async getSessionStatus() {
1042
+ this.ensureSession();
1043
+ return getSessionStatus(this.provider, this.session.walletAddress, this.session.sessionPubKey);
1044
+ }
1045
+ // ============ Session Management ============
1046
+ /**
1047
+ * Renew the current session (if in grace period).
1048
+ */
1049
+ async renewSession() {
1050
+ this.ensureSession();
1051
+ const oldSession = this.session;
1052
+ const { privateKey, publicKey } = generateSessionKeyPair();
1053
+ const now = BigInt(Math.floor(Date.now() / 1e3));
1054
+ const duration = BigInt(this.config.sessionDuration ?? 86400);
1055
+ const grace = BigInt(this.config.renewalGracePeriod ?? 172800);
1056
+ const nonceParams = generateNonceParams(publicKey, now, duration, grace);
1057
+ const nonce = computeNonce(nonceParams);
1058
+ const txHash = await renewSession(
1059
+ this.provider,
1060
+ oldSession,
1061
+ { sessionPubKey: publicKey, nonce, nonceParams, sessionPolicy: this.config.policy },
1062
+ this.getPaymasterKey(),
1063
+ this.config.network
1064
+ );
1065
+ this.session = {
1066
+ ...oldSession,
1067
+ sessionPrivateKey: privateKey,
1068
+ sessionPubKey: publicKey,
1069
+ nonce,
1070
+ nonceParams,
1071
+ sessionPolicy: this.config.policy
1072
+ };
1073
+ saveSession(this.config.appId, this.session);
1074
+ return txHash;
1075
+ }
1076
+ /**
1077
+ * Revoke a specific session key (defaults to current).
1078
+ */
1079
+ async revokeSession(sessionKey) {
1080
+ this.ensureSession();
1081
+ const keyToRevoke = sessionKey ?? this.session.sessionPubKey;
1082
+ return revokeSession(
1083
+ this.provider,
1084
+ this.session,
1085
+ keyToRevoke,
1086
+ this.getSalt(),
1087
+ this.getPaymasterKey(),
1088
+ this.config.network,
1089
+ this.config.backendUrl
1090
+ );
1091
+ }
1092
+ /**
1093
+ * Emergency revoke all sessions.
1094
+ */
1095
+ async emergencyRevokeAll() {
1096
+ this.ensureSession();
1097
+ return emergencyRevokeAllSessions(
1098
+ this.provider,
1099
+ this.session,
1100
+ this.getSalt(),
1101
+ this.getPaymasterKey(),
1102
+ this.config.network,
1103
+ this.config.backendUrl
1104
+ );
1105
+ }
1106
+ // ============ Internals ============
1107
+ ensureSession() {
1108
+ if (!this.session) {
1109
+ throw new Error("Not authenticated. Call login() first.");
1110
+ }
1111
+ }
1112
+ getSalt() {
1113
+ return this.appSalt ?? "0x0";
1114
+ }
1115
+ getPaymasterKey() {
1116
+ return this.config.paymasterApiKey ?? DEFAULT_PAYMASTER_KEY;
1117
+ }
1118
+ restoreSession() {
1119
+ const stored = loadSession(this.config.appId);
1120
+ if (!stored) return;
1121
+ this.session = {
1122
+ jwt: stored.jwt ?? "",
1123
+ sessionPrivateKey: stored.sessionPrivateKey,
1124
+ sessionPubKey: stored.sessionPubKey,
1125
+ nonce: stored.nonce,
1126
+ nonceParams: stored.nonceParams,
1127
+ jwtClaims: stored.jwtClaims ?? { sub: "", nonce: "", exp: 0, iss: "", aud: "" },
1128
+ walletAddress: stored.walletAddress ?? "",
1129
+ addressSeed: stored.addressSeed ?? "",
1130
+ sessionPolicy: stored.sessionPolicy,
1131
+ appSalt: stored.appSalt,
1132
+ walletName: stored.walletName
1133
+ };
1134
+ this.appSalt = stored.appSalt ?? null;
1135
+ }
1136
+ };
1137
+
1138
+ exports.CavosAgent = CavosAgent;
1139
+ exports.TOKENS_MAINNET = TOKENS_MAINNET;
1140
+ exports.TOKENS_SEPOLIA = TOKENS_SEPOLIA;
1141
+ exports.buildJWTSignatureData = buildJWTSignatureData;
1142
+ exports.buildSessionSignature = buildSessionSignature;
1143
+ exports.computeAddressSeed = computeAddressSeed;
1144
+ exports.computeContractAddress = computeContractAddress;
1145
+ exports.computeMerkleProof = computeMerkleProof;
1146
+ exports.computeMerkleRoot = computeMerkleRoot;
1147
+ exports.computeNonce = computeNonce;
1148
+ exports.generateNonceParams = generateNonceParams;
1149
+ exports.generateSessionKeyPair = generateSessionKeyPair;
1150
+ //# sourceMappingURL=index.js.map
1151
+ //# sourceMappingURL=index.js.map