diff --git a/account-kit/signer/src/base.ts b/account-kit/signer/src/base.ts index 352f85145b..c582f0d207 100644 --- a/account-kit/signer/src/base.ts +++ b/account-kit/signer/src/base.ts @@ -36,6 +36,7 @@ import { } from "./types.js"; import { assertNever } from "./utils/typeAssertions.js"; import type { SessionManagerEvents } from "./session/types"; +import { hashAuthorization, type Authorization } from "viem/experimental"; export interface BaseAlchemySignerParams { client: TClient; @@ -51,6 +52,12 @@ type AlchemySignerStore = { isNewUser?: boolean; }; +type UnpackedSignature = { + r: `0x${string}`; + s: `0x${string}`; + v: bigint; +}; + type InternalStore = Mutate< StoreApi, [["zustand/subscribeWithSelector", never]] @@ -524,16 +531,64 @@ export abstract class BaseAlchemySigner keccak256(serializedTx) ); - const signature = { - r: takeBytes(signatureHex, { count: 32 }), - s: takeBytes(signatureHex, { count: 32, offset: 32 }), - v: BigInt(takeBytes(signatureHex, { count: 1, offset: 64 })), - }; + const signature = this.unpackSignRawMessageBytes(signatureHex); return serializeFn(tx, signature); } ); + /** + * Signs an EIP-7702 Authorization and then returns the authorization with the signature. + * + * @example + * ```ts + * import { AlchemyWebSigner } from "@account-kit/signer"; + * + * const signer = new AlchemyWebSigner({ + * client: { + * connection: { + * rpcUrl: "/api/rpc", + * }, + * iframeConfig: { + * iframeContainerId: "alchemy-signer-iframe-container", + * }, + * }, + * }); + * + * const tx = await signer.signAuthorization({ + * contractAddress: "0x1234", + * chainId: 1, + * nonce: 0, + * }); + * ``` + * + * @param {Authorization} unsignedAuthorization the authorization to be signed + * @returns {Promise> | undefined} a promise that resolves to the authorization with the signature + */ + signAuthorization: ( + unsignedAuthorization: Authorization + ) => Promise> | undefined = SignerLogger.profiled( + "BaseAlchemySigner.signAuthorization", + async (unsignedAuthorization) => { + const hashedAuthorization = hashAuthorization(unsignedAuthorization); + const signedAuthorizationHex = await this.inner.signRawMessage( + hashedAuthorization + ); + const signature = this.unpackSignRawMessageBytes(signedAuthorizationHex); + return { ...unsignedAuthorization, ...signature }; + } + ); + + private unpackSignRawMessageBytes = ( + hex: `0x${string}` + ): UnpackedSignature => { + return { + r: takeBytes(hex, { count: 32 }), + s: takeBytes(hex, { count: 32, offset: 32 }), + v: BigInt(takeBytes(hex, { count: 1, offset: 64 })), + }; + }; + /** * Unauthenticated call to look up a user's organizationId by email * diff --git a/site/pages/reference/account-kit/signer/classes/BaseAlchemySigner/signAuthorization.mdx b/site/pages/reference/account-kit/signer/classes/BaseAlchemySigner/signAuthorization.mdx new file mode 100644 index 0000000000..2547cfa279 --- /dev/null +++ b/site/pages/reference/account-kit/signer/classes/BaseAlchemySigner/signAuthorization.mdx @@ -0,0 +1,51 @@ +--- +# This file is autogenerated + +title: signAuthorization +description: Overview of the signAuthorization method +--- + +# signAuthorization + +Signs an EIP-7702 Authorization and then returns the authorization with the signature. + +## Import + +```ts +import { BaseAlchemySigner } from "@account-kit/signer"; +``` + +## Usage + +```ts +import { AlchemyWebSigner } from "@account-kit/signer"; + +const signer = new AlchemyWebSigner({ + client: { + connection: { + rpcUrl: "/api/rpc", + }, + iframeConfig: { + iframeContainerId: "alchemy-signer-iframe-container", + }, + }, +}); + +const tx = await signer.signAuthorization({ + contractAddress: "0x1234", + chainId: 1, + nonce: 0, +}); +``` + +## Parameters + +### unsignedAuthorization + +`Authorization` +the authorization to be signed + +## Returns + +`Promise> | undefined` +a promise that resolves to the authorization with the signature