zksync-sso 0.2.0 → 0.3.0

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.
Files changed (50) hide show
  1. package/README.md +11 -2
  2. package/dist/_cjs/client/session/actions/session.js +53 -1
  3. package/dist/_cjs/client/session/actions/session.js.map +1 -1
  4. package/dist/_cjs/client/session/client.js +19 -0
  5. package/dist/_cjs/client/session/client.js.map +1 -1
  6. package/dist/_cjs/client/session/decorators/wallet.js +39 -0
  7. package/dist/_cjs/client/session/decorators/wallet.js.map +1 -1
  8. package/dist/_cjs/client-auth-server/Signer.js +25 -1
  9. package/dist/_cjs/client-auth-server/Signer.js.map +1 -1
  10. package/dist/_cjs/client-auth-server/WalletProvider.js +3 -1
  11. package/dist/_cjs/client-auth-server/WalletProvider.js.map +1 -1
  12. package/dist/_cjs/utils/helpers.js +26 -0
  13. package/dist/_cjs/utils/helpers.js.map +1 -1
  14. package/dist/_cjs/utils/session.js +283 -1
  15. package/dist/_cjs/utils/session.js.map +1 -1
  16. package/dist/_esm/client/session/actions/session.js +59 -1
  17. package/dist/_esm/client/session/actions/session.js.map +1 -1
  18. package/dist/_esm/client/session/client.js +20 -0
  19. package/dist/_esm/client/session/client.js.map +1 -1
  20. package/dist/_esm/client/session/decorators/wallet.js +47 -0
  21. package/dist/_esm/client/session/decorators/wallet.js.map +1 -1
  22. package/dist/_esm/client-auth-server/Signer.js +25 -1
  23. package/dist/_esm/client-auth-server/Signer.js.map +1 -1
  24. package/dist/_esm/client-auth-server/WalletProvider.js +3 -1
  25. package/dist/_esm/client-auth-server/WalletProvider.js.map +1 -1
  26. package/dist/_esm/utils/helpers.js +24 -0
  27. package/dist/_esm/utils/helpers.js.map +1 -1
  28. package/dist/_esm/utils/session.js +296 -1
  29. package/dist/_esm/utils/session.js.map +1 -1
  30. package/dist/_types/client/session/actions/session.d.ts +26 -1
  31. package/dist/_types/client/session/actions/session.d.ts.map +1 -1
  32. package/dist/_types/client/session/client.d.ts +6 -1
  33. package/dist/_types/client/session/client.d.ts.map +1 -1
  34. package/dist/_types/client/session/decorators/wallet.d.ts.map +1 -1
  35. package/dist/_types/client-auth-server/Signer.d.ts +10 -1
  36. package/dist/_types/client-auth-server/Signer.d.ts.map +1 -1
  37. package/dist/_types/client-auth-server/WalletProvider.d.ts +8 -1
  38. package/dist/_types/client-auth-server/WalletProvider.d.ts.map +1 -1
  39. package/dist/_types/utils/helpers.d.ts +7 -0
  40. package/dist/_types/utils/helpers.d.ts.map +1 -1
  41. package/dist/_types/utils/session.d.ts +51 -0
  42. package/dist/_types/utils/session.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/client/session/actions/session.ts +85 -2
  45. package/src/client/session/client.ts +28 -1
  46. package/src/client/session/decorators/wallet.ts +59 -3
  47. package/src/client-auth-server/Signer.ts +17 -1
  48. package/src/client-auth-server/WalletProvider.ts +6 -1
  49. package/src/utils/helpers.ts +28 -0
  50. package/src/utils/session.ts +356 -1
@@ -1,4 +1,6 @@
1
- import { type Address, getAddress, type Hash, type Hex } from "viem";
1
+ import { type Address, bytesToHex, getAddress, type Hash, type Hex, hexToBigInt, hexToBytes } from "viem";
2
+
3
+ import { calculateMaxFee, findSmallestBigInt } from "./helpers.js";
2
4
 
3
5
  export enum LimitType {
4
6
  Unlimited = 0,
@@ -167,3 +169,356 @@ export const getPeriodIdsForTransaction = (args: {
167
169
  ];
168
170
  return periodIds;
169
171
  };
172
+
173
+ export enum SessionErrorType {
174
+ SessionInactive = "session_inactive",
175
+ SessionExpired = "session_expired",
176
+ FeeLimitExceeded = "fee_limit_exceeded",
177
+ NoCallPolicy = "no_call_policy",
178
+ NoTransferPolicy = "no_transfer_policy",
179
+ MaxValuePerUseExceeded = "max_value_per_use_exceeded",
180
+ ValueLimitExceeded = "value_limit_exceeded",
181
+ ConstraintIndexOutOfBounds = "constraint_index_out_of_bounds",
182
+ NoLimitStateFound = "no_limit_state_found",
183
+ ParameterLimitExceeded = "parameter_limit_exceeded",
184
+ ConstraintEqualViolated = "constraint_equal_violated",
185
+ ConstraintGreaterViolated = "constraint_greater_violated",
186
+ ConstraintLessViolated = "constraint_less_violated",
187
+ ConstraintGreaterEqualViolated = "constraint_greater_equal_violated",
188
+ ConstraintLessEqualViolated = "constraint_less_equal_violated",
189
+ ConstraintNotEqualViolated = "constraint_not_equal_violated",
190
+ }
191
+
192
+ export enum SessionEventType {
193
+ Expired = "session_expired",
194
+ Revoked = "session_revoked",
195
+ Inactive = "session_inactive",
196
+ }
197
+
198
+ export type SessionStateEvent = {
199
+ type: SessionEventType;
200
+ message: string;
201
+ };
202
+
203
+ export type SessionStateEventCallback = (event: SessionStateEvent) => void;
204
+
205
+ export type ValidationResult = {
206
+ valid: boolean;
207
+ error: null | {
208
+ type: SessionErrorType;
209
+ message: string;
210
+ };
211
+ };
212
+
213
+ export type TransactionValidationArgs = {
214
+ sessionState: SessionState;
215
+ sessionConfig: SessionConfig;
216
+ transaction: {
217
+ to: Address;
218
+ value?: bigint;
219
+ data?: Hex;
220
+ gas?: bigint;
221
+ gasPrice?: bigint;
222
+ maxFeePerGas?: bigint;
223
+ maxPriorityFeePerGas?: bigint;
224
+ paymaster?: Address;
225
+ };
226
+ currentTimestamp?: bigint;
227
+ };
228
+
229
+ export function validateSessionTransaction(args: TransactionValidationArgs): ValidationResult {
230
+ const { sessionState, sessionConfig, transaction, currentTimestamp } = args;
231
+ const timestamp = currentTimestamp || BigInt(Math.floor(Date.now() / 1000));
232
+
233
+ // 1. Check if session is active
234
+ if (sessionState.status !== SessionStatus.Active) {
235
+ return {
236
+ valid: false,
237
+ error: {
238
+ type: SessionErrorType.SessionInactive,
239
+ message: "Session is not active",
240
+ },
241
+ };
242
+ }
243
+
244
+ // 2. Check if session hasn't expired
245
+ if (sessionConfig.expiresAt <= timestamp) {
246
+ return {
247
+ valid: false,
248
+ error: {
249
+ type: SessionErrorType.SessionExpired,
250
+ message: "Session has expired",
251
+ },
252
+ };
253
+ }
254
+
255
+ // 3. Extract transaction data
256
+ const to = getAddress(transaction.to.toLowerCase());
257
+ const value = transaction.value || 0n;
258
+ const data = transaction.data || "0x";
259
+ const gas = transaction.gas || 0n;
260
+ const selector = data.length >= 10 ? data.slice(0, 10) as Hash : undefined;
261
+
262
+ if (!transaction.paymaster) {
263
+ const maxFee = calculateMaxFee({
264
+ gas,
265
+ gasPrice: transaction.gasPrice,
266
+ maxFeePerGas: transaction.maxFeePerGas,
267
+ maxPriorityFeePerGas: transaction.maxPriorityFeePerGas,
268
+ });
269
+
270
+ if (maxFee > sessionState.feesRemaining) {
271
+ return {
272
+ valid: false,
273
+ error: {
274
+ type: SessionErrorType.FeeLimitExceeded,
275
+ message: `Transaction max fee ${maxFee} exceeds remaining fee limit ${sessionState.feesRemaining}`,
276
+ },
277
+ };
278
+ }
279
+ }
280
+
281
+ const isContractCall = !!selector;
282
+ if (isContractCall) {
283
+ const policies = sessionConfig.callPolicies.filter(
284
+ (policy) => policy.target === to && policy.selector === selector,
285
+ );
286
+
287
+ if (!policies.length) {
288
+ return {
289
+ valid: false,
290
+ error: {
291
+ type: SessionErrorType.NoCallPolicy,
292
+ message: `No call policy found for target ${to} with selector ${selector}`,
293
+ },
294
+ };
295
+ }
296
+
297
+ // Verify max value per use
298
+ const lowestMaxValuePerUse = findSmallestBigInt(policies.map((policy) => policy.maxValuePerUse));
299
+ if (value > lowestMaxValuePerUse) {
300
+ return {
301
+ valid: false,
302
+ error: {
303
+ type: SessionErrorType.MaxValuePerUseExceeded,
304
+ message: `Transaction value ${value} exceeds max value per use ${lowestMaxValuePerUse}`,
305
+ },
306
+ };
307
+ }
308
+
309
+ // Verify remaining value limit
310
+ const remainingValue = findLowestRemainingValue(sessionState.callValue, to, selector);
311
+ if (value > remainingValue) {
312
+ return {
313
+ valid: false,
314
+ error: {
315
+ type: SessionErrorType.ValueLimitExceeded,
316
+ message: `Transaction value ${value} exceeds remaining value limit ${remainingValue || 0n}`,
317
+ },
318
+ };
319
+ }
320
+
321
+ for (const policy of policies) {
322
+ const constraintResult = validateConstraints(data, policy.constraints, sessionState.callParams);
323
+ if (!constraintResult.valid) return constraintResult;
324
+ }
325
+ } else {
326
+ // This is a simple transfer
327
+ const policies = sessionConfig.transferPolicies.filter((policy) => policy.target === to);
328
+ if (!policies.length) {
329
+ return {
330
+ valid: false,
331
+ error: {
332
+ type: SessionErrorType.NoTransferPolicy,
333
+ message: `No transfer policy found for target ${to}`,
334
+ },
335
+ };
336
+ }
337
+
338
+ // Verify max value per use
339
+ const lowestMaxValuePerUse = findSmallestBigInt(policies.map((policy) => policy.maxValuePerUse));
340
+ if (value > lowestMaxValuePerUse) {
341
+ return {
342
+ valid: false,
343
+ error: {
344
+ type: SessionErrorType.MaxValuePerUseExceeded,
345
+ message: `Transaction value ${value} exceeds max value per use ${lowestMaxValuePerUse}`,
346
+ },
347
+ };
348
+ }
349
+
350
+ // Verify remaining value limit
351
+ const remainingValue = findLowestRemainingValue(sessionState.transferValue, to);
352
+ if (value > remainingValue) {
353
+ return {
354
+ valid: false,
355
+ error: {
356
+ type: SessionErrorType.ValueLimitExceeded,
357
+ message: `Transaction value ${value} exceeds remaining value limit ${remainingValue || 0n}`,
358
+ },
359
+ };
360
+ }
361
+ }
362
+
363
+ return {
364
+ valid: true,
365
+ error: null,
366
+ };
367
+ }
368
+
369
+ function findLowestRemainingValue(
370
+ valueArray: SessionState["transferValue"] | SessionState["callValue"],
371
+ target: Address,
372
+ selector?: Hash,
373
+ ): bigint {
374
+ if (selector) {
375
+ const filtered = valueArray
376
+ .filter((item) => item.target === target && item.selector === selector)
377
+ .map((item) => item.remaining);
378
+ if (!filtered.length) return 0n;
379
+ return findSmallestBigInt(filtered);
380
+ } else {
381
+ const filtered = valueArray
382
+ .filter((item) => item.target === target)
383
+ .map((item) => item.remaining);
384
+ if (!filtered.length) return 0n;
385
+ return findSmallestBigInt(filtered);
386
+ }
387
+ }
388
+
389
+ function validateConstraints(
390
+ data: Hex,
391
+ constraints: Constraint[],
392
+ callParams: SessionState["callParams"],
393
+ ): ValidationResult {
394
+ const dataBytes = hexToBytes(data);
395
+
396
+ for (const constraint of constraints) {
397
+ // Find proper index and extract data
398
+ const index = Number(constraint.index);
399
+ if (index + 32 > dataBytes.length) {
400
+ return {
401
+ valid: false,
402
+ error: {
403
+ type: SessionErrorType.ConstraintIndexOutOfBounds,
404
+ message: `Constraint index ${index} out of bounds for data length ${dataBytes.length}`,
405
+ },
406
+ };
407
+ }
408
+
409
+ const parameterBytes = dataBytes.slice(index, index + 32);
410
+ const parameterValue = bytesToHex(parameterBytes);
411
+ const refValue = constraint.refValue;
412
+
413
+ // Find remaining limit value if applicable
414
+ let remaining: bigint | undefined;
415
+ if (constraint.limit.limitType !== LimitType.Unlimited) {
416
+ const paramItem = callParams.find((item) => Number(item.index) === index);
417
+ remaining = paramItem?.remaining;
418
+
419
+ // If this is a limited constraint, but we don't have state for it, something is wrong
420
+ if (remaining === undefined) {
421
+ return {
422
+ valid: false,
423
+ error: {
424
+ type: SessionErrorType.NoLimitStateFound,
425
+ message: `No remaining limit state found for constraint at index ${index}`,
426
+ },
427
+ };
428
+ }
429
+
430
+ // Check if parameter value exceeds remaining limit
431
+ const parameterValueBigInt = hexToBigInt(parameterValue);
432
+ if (parameterValueBigInt > remaining) {
433
+ return {
434
+ valid: false,
435
+ error: {
436
+ type: SessionErrorType.ParameterLimitExceeded,
437
+ message: `Parameter value ${parameterValueBigInt} at index ${index} exceeds remaining limit ${remaining}`,
438
+ },
439
+ };
440
+ }
441
+ }
442
+
443
+ // Check condition
444
+ switch (constraint.condition) {
445
+ case ConstraintCondition.Equal:
446
+ if (parameterValue !== refValue) {
447
+ return {
448
+ valid: false,
449
+ error: {
450
+ type: SessionErrorType.ConstraintEqualViolated,
451
+ message: `Parameter value ${parameterValue} must equal ${refValue}`,
452
+ },
453
+ };
454
+ }
455
+ break;
456
+ case ConstraintCondition.Greater:
457
+ if (parameterValue <= refValue) {
458
+ return {
459
+ valid: false,
460
+ error: {
461
+ type: SessionErrorType.ConstraintGreaterViolated,
462
+ message: `Parameter value ${parameterValue} must be greater than ${refValue}`,
463
+ },
464
+ };
465
+ }
466
+ break;
467
+ case ConstraintCondition.Less:
468
+ if (parameterValue >= refValue) {
469
+ return {
470
+ valid: false,
471
+ error: {
472
+ type: SessionErrorType.ConstraintLessViolated,
473
+ message: `Parameter value ${parameterValue} must be less than ${refValue}`,
474
+ },
475
+ };
476
+ }
477
+ break;
478
+ case ConstraintCondition.GreaterEqual:
479
+ if (parameterValue < refValue) {
480
+ return {
481
+ valid: false,
482
+ error: {
483
+ type: SessionErrorType.ConstraintGreaterEqualViolated,
484
+ message: `Parameter value ${parameterValue} must be greater than or equal to ${refValue}`,
485
+ },
486
+ };
487
+ }
488
+ break;
489
+ case ConstraintCondition.LessEqual:
490
+ if (parameterValue > refValue) {
491
+ return {
492
+ valid: false,
493
+ error: {
494
+ type: SessionErrorType.ConstraintLessEqualViolated,
495
+ message: `Parameter value ${parameterValue} must be less than or equal to ${refValue}`,
496
+ },
497
+ };
498
+ }
499
+ break;
500
+ case ConstraintCondition.NotEqual:
501
+ if (parameterValue === refValue) {
502
+ return {
503
+ valid: false,
504
+ error: {
505
+ type: SessionErrorType.ConstraintNotEqualViolated,
506
+ message: `Parameter value ${parameterValue} must not equal ${refValue}`,
507
+ },
508
+ };
509
+ }
510
+ break;
511
+ case ConstraintCondition.Unconstrained:
512
+ // Unconstrained means no checks, so we skip
513
+ break;
514
+ default:
515
+ console.warn(`Unhandled constraint condition: ${constraint.condition}`);
516
+ break;
517
+ }
518
+ }
519
+
520
+ return {
521
+ valid: true,
522
+ error: null,
523
+ };
524
+ }