|
| 1 | +// Guards for issue #500: createSmartAccountClient's precise return type must |
| 2 | +// stay assignable to the bare SmartAccountClient alias cheaply (variance fast |
| 3 | +// path) WITHOUT the fix widening the return type. The Equal assertions fail if |
| 4 | +// the return type ever collapses to the loose defaults (or any) — which is |
| 5 | +// what the rejected fix in PR #511 would have done — and the bare-alias |
| 6 | +// assignments fail if the variance restructure ever rejects something the old |
| 7 | +// structural check accepted. |
| 8 | +import { |
| 9 | + type SmartAccountClient, |
| 10 | + createSmartAccountClient |
| 11 | +} from "permissionless" |
| 12 | +import { |
| 13 | + type ToSimpleSmartAccountReturnType, |
| 14 | + toSimpleSmartAccount |
| 15 | +} from "permissionless/accounts" |
| 16 | +import { |
| 17 | + type PasskeyServerClient, |
| 18 | + createPasskeyServerClient |
| 19 | +} from "permissionless/clients/passkeyServer" |
| 20 | +import { |
| 21 | + type PimlicoClient, |
| 22 | + createPimlicoClient |
| 23 | +} from "permissionless/clients/pimlico" |
| 24 | +import { |
| 25 | + http, |
| 26 | + type Transport, |
| 27 | + type createClient, |
| 28 | + createPublicClient |
| 29 | +} from "viem" |
| 30 | +import { entryPoint07Address } from "viem/account-abstraction" |
| 31 | +import { privateKeyToAccount } from "viem/accounts" |
| 32 | +import { sepolia } from "viem/chains" |
| 33 | + |
| 34 | +type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y |
| 35 | + ? 1 |
| 36 | + : 2 |
| 37 | + ? true |
| 38 | + : false |
| 39 | +type Expect<T extends true> = T |
| 40 | + |
| 41 | +const publicClient = createPublicClient({ |
| 42 | + chain: sepolia, |
| 43 | + transport: http("https://rpc.invalid") |
| 44 | +}) |
| 45 | + |
| 46 | +const pimlicoClient = createPimlicoClient({ |
| 47 | + transport: http("https://bundler.invalid"), |
| 48 | + entryPoint: { address: entryPoint07Address, version: "0.7" } |
| 49 | +}) |
| 50 | + |
| 51 | +export async function makeClient() { |
| 52 | + const account = await toSimpleSmartAccount({ |
| 53 | + client: publicClient, |
| 54 | + owner: privateKeyToAccount(`0x${"11".repeat(32)}` as `0x${string}`), |
| 55 | + entryPoint: { address: entryPoint07Address, version: "0.7" } |
| 56 | + }) |
| 57 | + |
| 58 | + return createSmartAccountClient({ |
| 59 | + account, |
| 60 | + chain: sepolia, |
| 61 | + bundlerTransport: http("https://bundler.invalid"), |
| 62 | + paymaster: pimlicoClient, |
| 63 | + userOperation: { |
| 64 | + estimateFeesPerGas: async () => |
| 65 | + (await pimlicoClient.getUserOperationGasPrice()).fast |
| 66 | + } |
| 67 | + }) |
| 68 | +} |
| 69 | + |
| 70 | +type PreciseClient = Awaited<ReturnType<typeof makeClient>> |
| 71 | +declare const precise: PreciseClient |
| 72 | + |
| 73 | +// The #500 hot path: precise instantiation → bare alias. |
| 74 | +export const bare: SmartAccountClient = precise |
| 75 | + |
| 76 | +// Return-type precision is unchanged — everything PR #511 would have widened. |
| 77 | +export type AccountIsExact = Expect< |
| 78 | + Equal<PreciseClient["account"], ToSimpleSmartAccountReturnType<"0.7">> |
| 79 | +> |
| 80 | +export type ChainIsExact = Expect<Equal<PreciseClient["chain"], typeof sepolia>> |
| 81 | +export type ClientSlotIsExact = Expect< |
| 82 | + Equal<PreciseClient["client"], undefined> |
| 83 | +> |
| 84 | + |
| 85 | +// A custom rpcSchema must still assign to the bare alias. Its type argument |
| 86 | +// differs from the bare default (`undefined`), so the variance fast path |
| 87 | +// cannot decide this pairwise — it must fall back to the structural check. |
| 88 | +// This line breaks loudly if `rpcSchema` ever gets a variance annotation (or |
| 89 | +// a future TypeScript starts measuring it) in a way that hard-rejects first. |
| 90 | +type CustomRpcSchema = [ |
| 91 | + { Method: "custom_method"; Parameters: [value: string]; ReturnType: string } |
| 92 | +] |
| 93 | +declare const withCustomSchema: SmartAccountClient< |
| 94 | + Transport, |
| 95 | + typeof sepolia, |
| 96 | + ToSimpleSmartAccountReturnType<"0.7">, |
| 97 | + undefined, |
| 98 | + CustomRpcSchema |
| 99 | +> |
| 100 | +export const bareFromCustomSchema: SmartAccountClient = withCustomSchema |
| 101 | + |
| 102 | +// Same guarantees for PimlicoClient (fixed alongside, identical pathology). |
| 103 | +export const barePimlico: PimlicoClient = pimlicoClient |
| 104 | +export type PimlicoChainIsExact = Expect< |
| 105 | + Equal<(typeof pimlicoClient)["chain"], undefined> |
| 106 | +> |
| 107 | + |
| 108 | +// Same guarantees for PasskeyServerClient (same inline-mapped restructure). |
| 109 | +const passkeyClient = createPasskeyServerClient({ |
| 110 | + transport: http("https://passkeys.invalid") |
| 111 | +}) |
| 112 | +export const barePasskey: PasskeyServerClient = passkeyClient |
| 113 | +declare const passkeyWithCustomSchema: PasskeyServerClient<CustomRpcSchema> |
| 114 | +export const barePasskeyFromCustomSchema: PasskeyServerClient = |
| 115 | + passkeyWithCustomSchema |
| 116 | + |
| 117 | +// The precise client still satisfies structural consumers that were never |
| 118 | +// spelled as the alias (no aliasSymbol on either side → structural path). |
| 119 | +declare const plainViemClient: ReturnType<typeof createClient> |
| 120 | +export const clientSlotAccepts: SmartAccountClient["client"] = plainViemClient |
0 commit comments