@zerodev/permissions 5.2.1-alpha.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 (157) hide show
  1. package/_cjs/constants.js +17 -0
  2. package/_cjs/constants.js.map +1 -0
  3. package/_cjs/deserializePermissionAccount.js +94 -0
  4. package/_cjs/deserializePermissionAccount.js.map +1 -0
  5. package/_cjs/index.js +12 -0
  6. package/_cjs/index.js.map +1 -0
  7. package/_cjs/package.json +1 -0
  8. package/_cjs/policies/callPolicyUtils.js +87 -0
  9. package/_cjs/policies/callPolicyUtils.js.map +1 -0
  10. package/_cjs/policies/index.js +16 -0
  11. package/_cjs/policies/index.js.map +1 -0
  12. package/_cjs/policies/toCallPolicy.js +42 -0
  13. package/_cjs/policies/toCallPolicy.js.map +1 -0
  14. package/_cjs/policies/toGasPolicy.js +30 -0
  15. package/_cjs/policies/toGasPolicy.js.map +1 -0
  16. package/_cjs/policies/toRateLimitPolicy.js +30 -0
  17. package/_cjs/policies/toRateLimitPolicy.js.map +1 -0
  18. package/_cjs/policies/toSignatureCallerPolicy.js +24 -0
  19. package/_cjs/policies/toSignatureCallerPolicy.js.map +1 -0
  20. package/_cjs/policies/toSudoPolicy.js +22 -0
  21. package/_cjs/policies/toSudoPolicy.js.map +1 -0
  22. package/_cjs/policies/types.js +19 -0
  23. package/_cjs/policies/types.js.map +1 -0
  24. package/_cjs/policies/utils/toPolicyId.js +11 -0
  25. package/_cjs/policies/utils/toPolicyId.js.map +1 -0
  26. package/_cjs/serializePermissionAccount.js +27 -0
  27. package/_cjs/serializePermissionAccount.js.map +1 -0
  28. package/_cjs/signers/index.js +12 -0
  29. package/_cjs/signers/index.js.map +1 -0
  30. package/_cjs/signers/toECDSASigner.js +39 -0
  31. package/_cjs/signers/toECDSASigner.js.map +1 -0
  32. package/_cjs/signers/toWebAuthnPubKey.js +97 -0
  33. package/_cjs/signers/toWebAuthnPubKey.js.map +1 -0
  34. package/_cjs/signers/toWebAuthnSigner.js +154 -0
  35. package/_cjs/signers/toWebAuthnSigner.js.map +1 -0
  36. package/_cjs/signers/utils/toSignerId.js +9 -0
  37. package/_cjs/signers/utils/toSignerId.js.map +1 -0
  38. package/_cjs/signers/webAuthnUtils.js +45 -0
  39. package/_cjs/signers/webAuthnUtils.js.map +1 -0
  40. package/_cjs/toPermissionValidator.js +102 -0
  41. package/_cjs/toPermissionValidator.js.map +1 -0
  42. package/_cjs/types.js +3 -0
  43. package/_cjs/types.js.map +1 -0
  44. package/_cjs/utils.js +37 -0
  45. package/_cjs/utils.js.map +1 -0
  46. package/_esm/constants.js +14 -0
  47. package/_esm/constants.js.map +1 -0
  48. package/_esm/deserializePermissionAccount.js +89 -0
  49. package/_esm/deserializePermissionAccount.js.map +1 -0
  50. package/_esm/index.js +5 -0
  51. package/_esm/index.js.map +1 -0
  52. package/_esm/package.json +1 -0
  53. package/_esm/policies/callPolicyUtils.js +82 -0
  54. package/_esm/policies/callPolicyUtils.js.map +1 -0
  55. package/_esm/policies/index.js +7 -0
  56. package/_esm/policies/index.js.map +1 -0
  57. package/_esm/policies/toCallPolicy.js +38 -0
  58. package/_esm/policies/toCallPolicy.js.map +1 -0
  59. package/_esm/policies/toGasPolicy.js +26 -0
  60. package/_esm/policies/toGasPolicy.js.map +1 -0
  61. package/_esm/policies/toRateLimitPolicy.js +26 -0
  62. package/_esm/policies/toRateLimitPolicy.js.map +1 -0
  63. package/_esm/policies/toSignatureCallerPolicy.js +20 -0
  64. package/_esm/policies/toSignatureCallerPolicy.js.map +1 -0
  65. package/_esm/policies/toSudoPolicy.js +18 -0
  66. package/_esm/policies/toSudoPolicy.js.map +1 -0
  67. package/_esm/policies/types.js +17 -0
  68. package/_esm/policies/types.js.map +1 -0
  69. package/_esm/policies/utils/toPolicyId.js +7 -0
  70. package/_esm/policies/utils/toPolicyId.js.map +1 -0
  71. package/_esm/serializePermissionAccount.js +23 -0
  72. package/_esm/serializePermissionAccount.js.map +1 -0
  73. package/_esm/signers/index.js +5 -0
  74. package/_esm/signers/index.js.map +1 -0
  75. package/_esm/signers/toECDSASigner.js +36 -0
  76. package/_esm/signers/toECDSASigner.js.map +1 -0
  77. package/_esm/signers/toWebAuthnPubKey.js +107 -0
  78. package/_esm/signers/toWebAuthnPubKey.js.map +1 -0
  79. package/_esm/signers/toWebAuthnSigner.js +164 -0
  80. package/_esm/signers/toWebAuthnSigner.js.map +1 -0
  81. package/_esm/signers/utils/toSignerId.js +5 -0
  82. package/_esm/signers/utils/toSignerId.js.map +1 -0
  83. package/_esm/signers/webAuthnUtils.js +41 -0
  84. package/_esm/signers/webAuthnUtils.js.map +1 -0
  85. package/_esm/toPermissionValidator.js +98 -0
  86. package/_esm/toPermissionValidator.js.map +1 -0
  87. package/_esm/types.js +3 -0
  88. package/_esm/types.js.map +1 -0
  89. package/_esm/utils.js +32 -0
  90. package/_esm/utils.js.map +1 -0
  91. package/_types/constants.d.ts +13 -0
  92. package/_types/constants.d.ts.map +1 -0
  93. package/_types/deserializePermissionAccount.d.ts +13 -0
  94. package/_types/deserializePermissionAccount.d.ts.map +1 -0
  95. package/_types/index.d.ts +6 -0
  96. package/_types/index.d.ts.map +1 -0
  97. package/_types/policies/callPolicyUtils.d.ts +5 -0
  98. package/_types/policies/callPolicyUtils.d.ts.map +1 -0
  99. package/_types/policies/index.d.ts +7 -0
  100. package/_types/policies/index.d.ts.map +1 -0
  101. package/_types/policies/toCallPolicy.d.ts +9 -0
  102. package/_types/policies/toCallPolicy.d.ts.map +1 -0
  103. package/_types/policies/toGasPolicy.d.ts +10 -0
  104. package/_types/policies/toGasPolicy.d.ts.map +1 -0
  105. package/_types/policies/toRateLimitPolicy.d.ts +9 -0
  106. package/_types/policies/toRateLimitPolicy.d.ts.map +1 -0
  107. package/_types/policies/toSignatureCallerPolicy.d.ts +8 -0
  108. package/_types/policies/toSignatureCallerPolicy.d.ts.map +1 -0
  109. package/_types/policies/toSudoPolicy.d.ts +6 -0
  110. package/_types/policies/toSudoPolicy.d.ts.map +1 -0
  111. package/_types/policies/types.d.ts +56 -0
  112. package/_types/policies/types.d.ts.map +1 -0
  113. package/_types/policies/utils/toPolicyId.d.ts +4 -0
  114. package/_types/policies/utils/toPolicyId.d.ts.map +1 -0
  115. package/_types/serializePermissionAccount.d.ts +5 -0
  116. package/_types/serializePermissionAccount.d.ts.map +1 -0
  117. package/_types/signers/index.d.ts +5 -0
  118. package/_types/signers/index.d.ts.map +1 -0
  119. package/_types/signers/toECDSASigner.d.ts +8 -0
  120. package/_types/signers/toECDSASigner.d.ts.map +1 -0
  121. package/_types/signers/toWebAuthnPubKey.d.ts +11 -0
  122. package/_types/signers/toWebAuthnPubKey.d.ts.map +1 -0
  123. package/_types/signers/toWebAuthnSigner.d.ts +16 -0
  124. package/_types/signers/toWebAuthnSigner.d.ts.map +1 -0
  125. package/_types/signers/utils/toSignerId.d.ts +4 -0
  126. package/_types/signers/utils/toSignerId.d.ts.map +1 -0
  127. package/_types/signers/webAuthnUtils.d.ts +13 -0
  128. package/_types/signers/webAuthnUtils.d.ts.map +1 -0
  129. package/_types/toPermissionValidator.d.ts +5 -0
  130. package/_types/toPermissionValidator.d.ts.map +1 -0
  131. package/_types/types.d.ts +49 -0
  132. package/_types/types.d.ts.map +1 -0
  133. package/_types/utils.d.ts +8 -0
  134. package/_types/utils.d.ts.map +1 -0
  135. package/constants.ts +17 -0
  136. package/deserializePermissionAccount.ts +119 -0
  137. package/index.ts +5 -0
  138. package/package.json +63 -0
  139. package/policies/callPolicyUtils.ts +122 -0
  140. package/policies/index.ts +12 -0
  141. package/policies/toCallPolicy.ts +65 -0
  142. package/policies/toGasPolicy.ts +44 -0
  143. package/policies/toRateLimitPolicy.ts +43 -0
  144. package/policies/toSignatureCallerPolicy.ts +34 -0
  145. package/policies/toSudoPolicy.ts +27 -0
  146. package/policies/types.ts +118 -0
  147. package/policies/utils/toPolicyId.ts +13 -0
  148. package/serializePermissionAccount.ts +38 -0
  149. package/signers/index.ts +10 -0
  150. package/signers/toECDSASigner.ts +61 -0
  151. package/signers/toWebAuthnPubKey.ts +152 -0
  152. package/signers/toWebAuthnSigner.ts +245 -0
  153. package/signers/utils/toSignerId.ts +9 -0
  154. package/signers/webAuthnUtils.ts +54 -0
  155. package/toPermissionValidator.ts +152 -0
  156. package/types.ts +71 -0
  157. package/utils.ts +42 -0
@@ -0,0 +1,245 @@
1
+ import { startAuthentication } from "@simplewebauthn/browser"
2
+ import type { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/typescript-types"
3
+ import type { TypedData } from "abitype"
4
+ import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts"
5
+ import {
6
+ type Chain,
7
+ type Client,
8
+ type Hex,
9
+ type LocalAccount,
10
+ type SignTypedDataParameters,
11
+ type Transport,
12
+ type TypedDataDefinition,
13
+ getTypesForEIP712Domain,
14
+ hashTypedData,
15
+ validateTypedData
16
+ } from "viem"
17
+ import { type SignableMessage, encodeAbiParameters } from "viem"
18
+ import { toAccount } from "viem/accounts"
19
+ import { getChainId } from "viem/actions"
20
+ import { WEBAUTHN_SIGNER_CONTRACT } from "../constants.js"
21
+ import type { ModularSigner, ModularSignerParams } from "../types.js"
22
+ import { WebAuthnMode, toWebAuthnPubKey } from "./toWebAuthnPubKey.js"
23
+ import {
24
+ b64ToBytes,
25
+ findQuoteIndices,
26
+ isRIP7212SupportedNetwork,
27
+ parseAndNormalizeSig,
28
+ uint8ArrayToHexString
29
+ } from "./webAuthnUtils.js"
30
+
31
+ export type WebAuthnKey = {
32
+ pubX: bigint
33
+ pubY: bigint
34
+ authenticatorIdHash: Hex
35
+ }
36
+
37
+ export type WebAuthnModularSignerParams = ModularSignerParams & {
38
+ passkeyName: string
39
+ passkeyServerUrl: string
40
+ pubKey?: WebAuthnKey
41
+ mode?: WebAuthnMode
42
+ }
43
+
44
+ export const toWebAuthnSigner = async <
45
+ TTransport extends Transport = Transport,
46
+ TChain extends Chain | undefined = Chain | undefined
47
+ >(
48
+ client: Client<TTransport, TChain, undefined>,
49
+ {
50
+ signerContractAddress = WEBAUTHN_SIGNER_CONTRACT,
51
+ pubKey,
52
+ passkeyServerUrl,
53
+ passkeyName,
54
+ mode = WebAuthnMode.Register
55
+ }: WebAuthnModularSignerParams
56
+ ): Promise<ModularSigner> => {
57
+ pubKey =
58
+ pubKey ??
59
+ (await toWebAuthnPubKey({
60
+ passkeyName,
61
+ passkeyServerUrl,
62
+ mode
63
+ }))
64
+ if (!pubKey) {
65
+ throw new Error("WebAuthn public key not found")
66
+ }
67
+
68
+ const chainId = await getChainId(client)
69
+
70
+ const signMessageUsingWebAuthn = async (message: SignableMessage) => {
71
+ let messageContent: string
72
+ if (typeof message === "string") {
73
+ // message is a string
74
+ messageContent = message
75
+ } else if ("raw" in message && typeof message.raw === "string") {
76
+ // message.raw is a Hex string
77
+ messageContent = message.raw
78
+ } else if ("raw" in message && message.raw instanceof Uint8Array) {
79
+ // message.raw is a ByteArray
80
+ messageContent = message.raw.toString()
81
+ } else {
82
+ throw new Error("Unsupported message format")
83
+ }
84
+
85
+ // remove 0x prefix if present
86
+ const formattedMessage = messageContent.startsWith("0x")
87
+ ? messageContent.slice(2)
88
+ : messageContent
89
+
90
+ if (window.sessionStorage === undefined) {
91
+ throw new Error("sessionStorage is not available")
92
+ }
93
+ const userId = sessionStorage.getItem("userId")
94
+
95
+ // initiate signing
96
+ const signInitiateResponse = await fetch(
97
+ `${passkeyServerUrl}/sign-initiate`,
98
+ {
99
+ method: "POST",
100
+ headers: { "Content-Type": "application/json" },
101
+ body: JSON.stringify({ data: formattedMessage, userId }),
102
+ credentials: "include"
103
+ }
104
+ )
105
+ const signInitiateResult = await signInitiateResponse.json()
106
+
107
+ // prepare assertion options
108
+ const assertionOptions: PublicKeyCredentialRequestOptionsJSON = {
109
+ challenge: signInitiateResult.challenge,
110
+ allowCredentials: signInitiateResult.allowCredentials,
111
+ userVerification: "required"
112
+ }
113
+
114
+ // start authentication (signing)
115
+ const cred = await startAuthentication(assertionOptions)
116
+
117
+ // verify signature from server
118
+ const verifyResponse = await fetch(`${passkeyServerUrl}/sign-verify`, {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/json" },
121
+ body: JSON.stringify({ cred, userId }),
122
+ credentials: "include"
123
+ })
124
+
125
+ const verifyResult = await verifyResponse.json()
126
+
127
+ if (!verifyResult.success) {
128
+ throw new Error("Signature not verified")
129
+ }
130
+
131
+ // get authenticator data
132
+ const authenticatorData = verifyResult.authenticatorData
133
+ const authenticatorDataHex = uint8ArrayToHexString(
134
+ b64ToBytes(authenticatorData)
135
+ )
136
+
137
+ // get client data JSON
138
+ const clientDataJSON = atob(cred.response.clientDataJSON)
139
+
140
+ // get challenge and response type location
141
+ const { beforeType } = findQuoteIndices(clientDataJSON)
142
+
143
+ // get signature r,s
144
+ const signature = verifyResult.signature
145
+ const signatureHex = uint8ArrayToHexString(b64ToBytes(signature))
146
+ const { r, s } = parseAndNormalizeSig(signatureHex)
147
+
148
+ // encode signature
149
+ const encodedSignature = encodeAbiParameters(
150
+ [
151
+ { name: "authenticatorData", type: "bytes" },
152
+ { name: "clientDataJSON", type: "string" },
153
+ { name: "responseTypeLocation", type: "uint256" },
154
+ { name: "r", type: "uint256" },
155
+ { name: "s", type: "uint256" },
156
+ { name: "usePrecompiled", type: "bool" }
157
+ ],
158
+ [
159
+ authenticatorDataHex,
160
+ clientDataJSON,
161
+ beforeType,
162
+ BigInt(r),
163
+ BigInt(s),
164
+ isRIP7212SupportedNetwork(chainId)
165
+ ]
166
+ )
167
+ return encodedSignature
168
+ }
169
+ const account: LocalAccount = toAccount({
170
+ // note that this address will be overwritten by actual address
171
+ address: "0x0000000000000000000000000000000000000000",
172
+ async signMessage({ message }) {
173
+ return signMessageUsingWebAuthn(message)
174
+ },
175
+ async signTransaction(_, __) {
176
+ throw new SignTransactionNotSupportedBySmartAccount()
177
+ },
178
+ async signTypedData<
179
+ const TTypedData extends TypedData | Record<string, unknown>,
180
+ TPrimaryType extends
181
+ | keyof TTypedData
182
+ | "EIP712Domain" = keyof TTypedData
183
+ >(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) {
184
+ const { domain, message, primaryType } =
185
+ typedData as unknown as SignTypedDataParameters
186
+
187
+ const types = {
188
+ EIP712Domain: getTypesForEIP712Domain({ domain }),
189
+ ...typedData.types
190
+ }
191
+
192
+ validateTypedData({ domain, message, primaryType, types })
193
+
194
+ const hash = hashTypedData(typedData)
195
+ return signMessageUsingWebAuthn(hash)
196
+ }
197
+ })
198
+
199
+ return {
200
+ account,
201
+ signerContractAddress,
202
+ getSignerData: () => {
203
+ if (!pubKey) {
204
+ throw new Error("WebAuthn public key not found")
205
+ }
206
+ return encodeAbiParameters(
207
+ [
208
+ {
209
+ components: [
210
+ { name: "pubKeyX", type: "uint256" },
211
+ { name: "pubKeyY", type: "uint256" }
212
+ ],
213
+ name: "WebAuthnSignerData",
214
+ type: "tuple"
215
+ },
216
+ { name: "authenticatorIdHash", type: "bytes32" }
217
+ ],
218
+ [
219
+ { pubKeyX: pubKey.pubX, pubKeyY: pubKey.pubY },
220
+ pubKey.authenticatorIdHash
221
+ ]
222
+ )
223
+ },
224
+ getDummySignature: () => {
225
+ return encodeAbiParameters(
226
+ [
227
+ { name: "authenticatorData", type: "bytes" },
228
+ { name: "clientDataJSON", type: "string" },
229
+ { name: "responseTypeLocation", type: "uint256" },
230
+ { name: "r", type: "uint256" },
231
+ { name: "s", type: "uint256" },
232
+ { name: "usePrecompiled", type: "bool" }
233
+ ],
234
+ [
235
+ "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
236
+ '{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false}',
237
+ 1n,
238
+ 44941127272049826721201904734628716258498742255959991581049806490182030242267n,
239
+ 9910254599581058084911561569808925251374718953855182016200087235935345969636n,
240
+ false
241
+ ]
242
+ )
243
+ }
244
+ }
245
+ }
@@ -0,0 +1,9 @@
1
+ import { type Hex, concat, encodeAbiParameters } from "viem"
2
+ import type { ModularSigner } from "../../types.js"
3
+
4
+ export const toSignerId = (signer: ModularSigner): Hex => {
5
+ return encodeAbiParameters(
6
+ [{ name: "signerData", type: "bytes" }],
7
+ [concat([signer.signerContractAddress, signer.getSignerData()])]
8
+ )
9
+ }
@@ -0,0 +1,54 @@
1
+ import { p256 } from "@noble/curves/p256"
2
+ import { type Hex, bytesToBigInt, hexToBytes } from "viem"
3
+
4
+ const RIP7212_SUPPORTED_NETWORKS = [80001]
5
+
6
+ export const uint8ArrayToHexString = (array: Uint8Array): `0x${string}` => {
7
+ return `0x${Array.from(array, (byte) =>
8
+ byte.toString(16).padStart(2, "0")
9
+ ).join("")}` as `0x${string}`
10
+ }
11
+
12
+ export const b64ToBytes = (base64: string): Uint8Array => {
13
+ const paddedBase64 = base64
14
+ .replace(/-/g, "+")
15
+ .replace(/_/g, "/")
16
+ .padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=")
17
+ const binString = atob(paddedBase64)
18
+ return Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0)
19
+ }
20
+
21
+ export const findQuoteIndices = (
22
+ input: string
23
+ ): { beforeType: bigint; beforeChallenge: bigint } => {
24
+ const beforeTypeIndex = BigInt(input.lastIndexOf('"type":"webauthn.get"'))
25
+ const beforeChallengeIndex = BigInt(input.indexOf('"challenge'))
26
+ return {
27
+ beforeType: beforeTypeIndex,
28
+ beforeChallenge: beforeChallengeIndex
29
+ }
30
+ }
31
+
32
+ // Parse DER-encoded P256-SHA256 signature to contract-friendly signature
33
+ // and normalize it so the signature is not malleable.
34
+ export function parseAndNormalizeSig(derSig: Hex): { r: bigint; s: bigint } {
35
+ const parsedSignature = p256.Signature.fromDER(derSig.slice(2))
36
+ const bSig = hexToBytes(`0x${parsedSignature.toCompactHex()}`)
37
+ // assert(bSig.length === 64, "signature is not 64 bytes");
38
+ const bR = bSig.slice(0, 32)
39
+ const bS = bSig.slice(32)
40
+
41
+ // Avoid malleability. Ensure low S (<= N/2 where N is the curve order)
42
+ const r = bytesToBigInt(bR)
43
+ let s = bytesToBigInt(bS)
44
+ const n = BigInt(
45
+ "0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
46
+ )
47
+ if (s > n / 2n) {
48
+ s = n - s
49
+ }
50
+ return { r, s }
51
+ }
52
+
53
+ export const isRIP7212SupportedNetwork = (chainId: number): boolean =>
54
+ RIP7212_SUPPORTED_NETWORKS.includes(chainId)
@@ -0,0 +1,152 @@
1
+ import { KernelV3AccountAbi } from "@zerodev/sdk"
2
+ import { getAction, getUserOperationHash } from "permissionless"
3
+ import type { EntryPoint } from "permissionless/types/entrypoint"
4
+ import {
5
+ type Address,
6
+ type Chain,
7
+ type Client,
8
+ type Hex,
9
+ type Transport,
10
+ concat,
11
+ encodeAbiParameters,
12
+ keccak256,
13
+ pad,
14
+ slice,
15
+ zeroAddress
16
+ } from "viem"
17
+ import { getChainId, readContract } from "viem/actions"
18
+ import { PolicyFlags } from "./constants.js"
19
+ import { toPolicyId } from "./policies/index.js"
20
+ import { toSignerId } from "./signers/index.js"
21
+ import type {
22
+ PermissionData,
23
+ PermissionPlugin,
24
+ PermissionPluginParams
25
+ } from "./types.js"
26
+
27
+ export async function toPermissionValidator<
28
+ entryPoint extends EntryPoint,
29
+ TTransport extends Transport = Transport,
30
+ TChain extends Chain | undefined = Chain | undefined
31
+ >(
32
+ client: Client<TTransport, TChain, undefined>,
33
+ {
34
+ signer,
35
+ policies,
36
+ entryPoint: entryPointAddress,
37
+ flag = PolicyFlags.FOR_ALL_VALIDATION
38
+ }: PermissionPluginParams
39
+ ): Promise<PermissionPlugin<entryPoint>> {
40
+ const chainId = await getChainId(client)
41
+
42
+ const getEnableData = async (
43
+ _kernelAccountAddress?: Address
44
+ ): Promise<Hex> => {
45
+ const enableData = encodeAbiParameters(
46
+ [{ name: "policyAndSignerData", type: "bytes[]" }],
47
+ [
48
+ [
49
+ ...policies.map((policy) =>
50
+ concat([
51
+ policy.getPolicyInfoInBytes(),
52
+ policy.getPolicyData(getPermissionId())
53
+ ])
54
+ ),
55
+ concat([
56
+ flag,
57
+ signer.signerContractAddress,
58
+ signer.getSignerData(getPermissionId())
59
+ ])
60
+ ]
61
+ ]
62
+ )
63
+ return enableData
64
+ }
65
+
66
+ const getPermissionId = (): Hex => {
67
+ const pIdData = encodeAbiParameters(
68
+ [{ name: "policyAndSignerData", type: "bytes[]" }],
69
+ [[toPolicyId(policies), flag, toSignerId(signer)]]
70
+ )
71
+ return slice(keccak256(pIdData), 0, 2)
72
+ }
73
+
74
+ return {
75
+ ...signer.account,
76
+ validatorType: "PERMISSION",
77
+ address: zeroAddress,
78
+ source: "PermissionValidator",
79
+ getEnableData,
80
+ getIdentifier: () =>
81
+ pad(getPermissionId(), {
82
+ size: 20,
83
+ dir: "right"
84
+ }),
85
+
86
+ signMessage: async ({ message }) => {
87
+ return concat([
88
+ "0xff",
89
+ await signer.account.signMessage({ message })
90
+ ])
91
+ },
92
+ signTypedData: async (typedData) => {
93
+ return concat([
94
+ "0xff",
95
+ await signer.account.signTypedData(typedData)
96
+ ])
97
+ },
98
+
99
+ signUserOperation: async (userOperation): Promise<Hex> => {
100
+ const userOpHash = getUserOperationHash({
101
+ userOperation: { ...userOperation, signature: "0x" },
102
+ entryPoint: entryPointAddress,
103
+ chainId: chainId
104
+ })
105
+
106
+ const signature = await signer.account.signMessage({
107
+ message: { raw: userOpHash }
108
+ })
109
+ return concat(["0xff", signature])
110
+ },
111
+
112
+ getNonceKey: async () => {
113
+ return 0n
114
+ },
115
+
116
+ async getDummySignature(_userOperation) {
117
+ return concat(["0xff", signer.getDummySignature()])
118
+ },
119
+ getPluginSerializationParams: (): PermissionData => {
120
+ return {
121
+ policies
122
+ }
123
+ },
124
+ isEnabled: async (
125
+ kernelAccountAddress: Address,
126
+ _selector: Hex
127
+ ): Promise<boolean> => {
128
+ try {
129
+ const permissionConfig = await getAction(
130
+ client,
131
+ readContract
132
+ )({
133
+ abi: KernelV3AccountAbi,
134
+ address: kernelAccountAddress,
135
+ functionName: "permissionConfig",
136
+ args: [
137
+ concat([
138
+ "0x00",
139
+ pad(getPermissionId(), {
140
+ size: 20,
141
+ dir: "right"
142
+ })
143
+ ])
144
+ ]
145
+ })
146
+ return permissionConfig.signer === signer.signerContractAddress
147
+ } catch (error) {
148
+ return false
149
+ }
150
+ }
151
+ }
152
+ }
package/types.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { type KernelValidator } from "@zerodev/sdk"
2
+ import type { Action, PluginValidityData } from "@zerodev/sdk/types"
3
+ import type { EntryPoint } from "permissionless/types/entrypoint"
4
+ import type { Abi, Address, Hex, LocalAccount } from "viem"
5
+ import { PolicyFlags } from "./constants.js"
6
+ import type {
7
+ CallPolicyParams,
8
+ GasPolicyParams,
9
+ RateLimitPolicyParams,
10
+ SignatureCallerPolicyParams,
11
+ SudoPolicyParams
12
+ } from "./policies/index.js"
13
+
14
+ export type PermissionPlugin<entryPoint extends EntryPoint> = KernelValidator<
15
+ entryPoint,
16
+ "PermissionValidator"
17
+ > & {
18
+ getPluginSerializationParams: () => PermissionData
19
+ }
20
+
21
+ export type ModularSignerParams = {
22
+ signerContractAddress?: Address
23
+ }
24
+ export type PolicyParams = {
25
+ policyAddress?: Address
26
+ policyFlag?: PolicyFlags
27
+ }
28
+
29
+ export type ModularSigner = {
30
+ account: LocalAccount
31
+ signerContractAddress: Address
32
+ getSignerData: (permissionId?: Hex) => Hex
33
+ getDummySignature: () => Hex
34
+ }
35
+
36
+ export type Policy = {
37
+ getPolicyData: (permissionId?: Hex) => Hex
38
+ getPolicyInfoInBytes: () => Hex
39
+ // return params directly to serialize/deserialize Policy
40
+ policyParams:
41
+ | CallPolicyParams<Abi | readonly unknown[], string>
42
+ | GasPolicyParams
43
+ | RateLimitPolicyParams
44
+ | SignatureCallerPolicyParams
45
+ | SudoPolicyParams
46
+ }
47
+
48
+ export type PermissionPluginParams = {
49
+ signer: ModularSigner
50
+ policies: Policy[]
51
+ entryPoint: EntryPoint
52
+ flag?: PolicyFlags
53
+ }
54
+
55
+ export interface PermissionData {
56
+ policies?: Policy[]
57
+ }
58
+
59
+ export type ExportPermissionAccountParams = {
60
+ initCode: Hex
61
+ accountAddress: Address
62
+ }
63
+
64
+ export type PermissionAccountParams = {
65
+ permissionParams: PermissionData
66
+ action: Action
67
+ validityData: PluginValidityData
68
+ accountParams: ExportPermissionAccountParams
69
+ enableSignature?: Hex
70
+ privateKey?: Hex
71
+ }
package/utils.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { EntryPoint } from "permissionless/types"
2
+ import type { PermissionAccountParams, PermissionPlugin } from "./types.js"
3
+
4
+ export function base64ToBytes(base64: string) {
5
+ const binString = atob(base64)
6
+ return Uint8Array.from(binString, (m) => m.codePointAt(0) as number)
7
+ }
8
+
9
+ export function bytesToBase64(bytes: Uint8Array) {
10
+ const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join("")
11
+ return btoa(binString)
12
+ }
13
+
14
+ export function isPermissionValidatorPlugin<entryPoint extends EntryPoint>(
15
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
16
+ plugin: any
17
+ ): plugin is PermissionPlugin<entryPoint> {
18
+ return plugin?.getPluginSerializationParams !== undefined
19
+ }
20
+
21
+ export const serializePermissionAccountParams = (
22
+ params: PermissionAccountParams
23
+ ) => {
24
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
25
+ const replacer = (_: string, value: any) => {
26
+ if (typeof value === "bigint") {
27
+ return value.toString()
28
+ }
29
+ return value
30
+ }
31
+
32
+ const jsonString = JSON.stringify(params, replacer)
33
+ const uint8Array = new TextEncoder().encode(jsonString)
34
+ const base64String = bytesToBase64(uint8Array)
35
+ return base64String
36
+ }
37
+
38
+ export const deserializePermissionAccountParams = (params: string) => {
39
+ const uint8Array = base64ToBytes(params)
40
+ const jsonString = new TextDecoder().decode(uint8Array)
41
+ return JSON.parse(jsonString) as PermissionAccountParams
42
+ }