import {
type AuthorizationRequest,
type SmartAccountSigner,
unpackSignRawMessageBytes,
} from "@aa-sdk/core";
import {
ServerSignerClient,
type ServerSignerClientParams,
} from "./client/server.js";
import {
hashMessage,
hashTypedData,
type Address,
type Hex,
type SignableMessage,
type SignedAuthorization,
type TypedData,
type TypedDataDefinition,
} from "viem";
import { hashAuthorization } from "viem/utils";
import type { AccessKeyAuthParams } from "./signer.js";
import { SolanaSigner } from "./solanaSigner.js";
import { p256 } from "@noble/curves/p256";
import { bytesToHex } from "@noble/curves/utils";
/**
* AlchemyServerSigner is a signer that can sign messages and typed data using an access key.
* It extends the SmartAccountSigner interface and uses the ServerSignerClient to sign requests.
* Primarily intended to be used server-side.
*/
export class AlchemyServerSigner implements SmartAccountSigner {
inner: ServerSignerClient;
signerType = "alchemy-server-signer";
/**
* Creates an instance of AlchemyServerSigner.
*
* @param {ServerSignerClient} client The underlying signer client
*/
constructor(client: ServerSignerClient) {
this.inner = client;
}
/**
* Gets the address of the user from the signer client.
*
* @returns {Promise
} The address of the user
* @throws {Error} If the user cannot be retrieved from the signer client
*/
async getAddress(): Promise {
const { address } = await this.inner.whoami();
return address;
}
/**
* Signs a message using the inner client.
*
* @param {SignableMessage} msg The message to sign
* @returns {Promise} The signed message
*/
async signMessage(msg: SignableMessage): Promise {
const messageHash = hashMessage(msg);
return this.inner.signRawMessage(messageHash);
}
/**
* Signs typed data using the inner client.
*
* @param {TypedDataDefinition} params The typed data to sign
* @returns {Promise} The signed typed data
*/
async signTypedData<
const TTypedData extends TypedData | Record,
TPrimaryType extends keyof TTypedData | "EIP712Domain" = keyof TTypedData,
>(params: TypedDataDefinition): Promise {
const messageHash = hashTypedData(params);
return this.inner.signRawMessage(messageHash);
}
/**
* Signs an authorization using the inner client.
*
* @param {Authorization} unsignedAuthorization The unsigned authorization to sign
* @returns {Promise>} The signed authorization
*/
async signAuthorization(
unsignedAuthorization: AuthorizationRequest,
): Promise> {
const hashedAuthorization = hashAuthorization(unsignedAuthorization);
const signedAuthorizationHex =
await this.inner.signRawMessage(hashedAuthorization);
const signature = unpackSignRawMessageBytes(signedAuthorizationHex);
const { address, contractAddress, ...unsignedAuthorizationRest } =
unsignedAuthorization;
return {
...unsignedAuthorizationRest,
...signature,
address: address ?? contractAddress,
};
}
/**
* Creates a new instance of `SolanaSigner` using the inner client.
*
* @example
* ```ts
* import { AlchemyServerSigner } from "@account-kit/signer";
*
* const signer = await createServerSigner({
* auth: { accessKey },
* connection: {
* apiKey: "alchemy-api-key",
* },
* });
*
* const solanaSigner = signer.toSolanaSigner();
* ```
*
* @returns {SolanaSigner} A new instance of `SolanaSigner`
*/
toSolanaSigner(): SolanaSigner {
return new SolanaSigner(this.inner);
}
}
type CreateServerSignerParams = ServerSignerClientParams & {
auth: AccessKeyAuthParams;
};
/**
* Creates a new server signer.
*
* @example
* ```ts
* const signer = await createServerSigner({
* auth: { accessKey },
* connection: {
* apiKey: "alchemy-api-key",
* }
* });
*
* console.log("Signer address:", await signer.getAddress());
* ```
*
* @param {CreateServerSignerParams} params Parameters
* @param {AccessKeyAuthParams} params.auth Authentication config for the server signer
* @param {ConnectionConfig} params.connection Connection config for the server signer
* @returns {Promise} A promise that resolves to a server signer
*/
export const createServerSigner = async (
params: CreateServerSignerParams,
): Promise => {
const client = new ServerSignerClient(params);
const signer = new AlchemyServerSigner(client);
await signer.inner.authenticateWithAccessKey({
...params.auth,
type: "accessKey",
});
return signer;
};
/**
* Generates a new access key for use in the server signer
*
* @returns {Hex} A randomly generated access key
*/
export const generateAccessKey = () =>
bytesToHex(p256.utils.randomPrivateKey());