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