import { concatHex, sliceHex, type Hex } from "viem"; import type { KeySigner } from "../../schemas.ts"; import type { PermissionsCapability, PermissionsData } from "./index.ts"; import { assertNever } from "@alchemy/common"; export const SESSION_ID_LENGTH_BYTES = 16; export const ENTITY_ID_LENGTH_BYTES = 4; export const PermissionsContextVersion = { REMOTE_MODE_DEFERRED_ACTION: "0x00", LOCAL_MODE_DEFERRED_ACTION: "0x01", NON_DEFERRED_ACTION: "0x02", } as const; type ContextVersionHex = (typeof PermissionsContextVersion)[keyof typeof PermissionsContextVersion]; export type DecodedPermissionsContext = { contextVersion: keyof typeof PermissionsContextVersion; } & ( | { contextVersion: "REMOTE_MODE_DEFERRED_ACTION"; sessionId: Hex; signature: Hex; deferredAction?: never; } | { contextVersion: "LOCAL_MODE_DEFERRED_ACTION"; deferredAction: Hex; sessionId?: never; signature?: never; } | { contextVersion: "NON_DEFERRED_ACTION"; deferredAction?: never; sessionId?: never; signature?: never; entityId: Hex; isGlobalValidation: boolean; } ); export const encodePermissionsContext = ( context: DecodedPermissionsContext, ): Hex => { switch (context.contextVersion) { case "REMOTE_MODE_DEFERRED_ACTION": return concatHex([ PermissionsContextVersion.REMOTE_MODE_DEFERRED_ACTION, context.sessionId, context.signature, ]); case "LOCAL_MODE_DEFERRED_ACTION": return concatHex([ PermissionsContextVersion.LOCAL_MODE_DEFERRED_ACTION, context.deferredAction, ]); case "NON_DEFERRED_ACTION": return concatHex([ PermissionsContextVersion.NON_DEFERRED_ACTION, context.isGlobalValidation ? "0x01" : "0x00", context.entityId, ]); default: return assertNever(context, "Unexpected context version"); } }; export const decodePermissionsContext = ( capability: PermissionsCapability, ): DecodedPermissionsContext => { if (!("context" in capability)) { return { contextVersion: "REMOTE_MODE_DEFERRED_ACTION", sessionId: capability.sessionId, signature: capability.signature, }; } const input = capability.context; const contextVersion = sliceHex(input, 0, 1) as ContextVersionHex; switch (contextVersion) { case "0x00": { const sessionId = sliceHex(input, 1, SESSION_ID_LENGTH_BYTES + 1); const signature = sliceHex(input, SESSION_ID_LENGTH_BYTES + 1); return { contextVersion: "REMOTE_MODE_DEFERRED_ACTION", sessionId, signature, }; } case "0x01": { const deferredAction = sliceHex(input, 1); return { contextVersion: "LOCAL_MODE_DEFERRED_ACTION", deferredAction, }; } case "0x02": { const isGlobalValidation = sliceHex(input, 1, 2) === "0x01"; const entityId = sliceHex(input, 2, ENTITY_ID_LENGTH_BYTES + 2); return { contextVersion: "NON_DEFERRED_ACTION", isGlobalValidation, entityId, }; } default: return assertNever( contextVersion, `Unexpected context version: ${contextVersion}`, ); } }; export const prefixSignatureKeyType = ( sig: Hex, type: KeySigner["type"], ): Hex => { switch (type) { case "ecdsa": case "secp256k1": return concatHex(["0x00", sig]); case "contract": return concatHex(["0x01", sig]); default: return assertNever(type, "Unexpected session key type"); } }; export const isGlobalValidation = ( data: Pick, ): boolean => { return data.permissions.some((p) => p.type === "root"); };