From bd95e3e7f68dd54000e9b5fc33f1f5e5e8cf6b0f Mon Sep 17 00:00:00 2001 From: KONFeature Date: Thu, 20 Mar 2025 21:02:21 +0100 Subject: [PATCH 1/4] chore: refacto + more generic support of solana tu support both version --- package.json | 3 +- src/actions/getCAB.ts | 26 +++--- src/actions/getIntent.ts | 24 ++--- src/actions/prepareUserIntent.ts | 87 +++++------------- src/actions/sendUserIntent.ts | 101 +++++++-------------- src/client/decorators/intent.ts | 39 +++----- src/client/intentClient.ts | 149 +++++++++++++++++++++++-------- 7 files changed, 207 insertions(+), 222 deletions(-) diff --git a/package.json b/package.json index abc78f3..095243d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "test:watch": "bun test --watch", "changeset": "changeset", "changeset:release": "bash remove-type.sh && bun run build && changeset publish && bash restore-type.sh && bun run format", - "changeset:version": "changeset version && bun install --lockfile-only" + "changeset:version": "changeset version && bun install --lockfile-only", + "typecheck": "tsc --noEmit" }, "exports": { ".": { diff --git a/src/actions/getCAB.ts b/src/actions/getCAB.ts index 883b43d..3d9e08a 100644 --- a/src/actions/getCAB.ts +++ b/src/actions/getCAB.ts @@ -1,14 +1,11 @@ -import type { Address as SolanaAddress, TransactionSigner } from "@solana/kit"; +import type { Address as SolanaAddress } from "@solana/kit"; import { AccountNotFoundError } from "@zerodev/sdk"; -import type { - Address as EvmAddress, - Chain, - Client, - Hex, - Transport, -} from "viem"; +import type { Address as EvmAddress, Chain, Hex, Transport } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; -import type { CombinedIntentRpcSchema } from "../client/intentClient.js"; +import type { + CombinedIntentRpcSchema, + IntentClient, +} from "../client/intentClient.js"; export type NetworkType = "mainnet" | "testnet"; @@ -97,13 +94,18 @@ export async function getCAB< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: Client, + client: IntentClient< + transport, + chain, + account, + undefined, + CombinedIntentRpcSchema + >, parameters: GetCABParameters, - solanaSigner: TransactionSigner | undefined, ): Promise { const { account: account_ = client.account } = parameters; const evmAddress = parameters.evmAddress ?? account_?.address; - const solanaAddress = parameters.solanaAddress ?? solanaSigner?.address; + const solanaAddress = parameters.solanaAddress ?? client?.solana?.address; if (!evmAddress && !solanaAddress) throw new AccountNotFoundError(); const result = await client.request({ diff --git a/src/actions/getIntent.ts b/src/actions/getIntent.ts index bae2bcd..fcc2ff1 100644 --- a/src/actions/getIntent.ts +++ b/src/actions/getIntent.ts @@ -1,12 +1,10 @@ -import type { - Address as SolanaAddress, - Rpc, - SolanaRpcApi, - TransactionSigner, -} from "@solana/kit"; -import type { Chain, Client, Hex, RpcErrorType, Transport } from "viem"; +import type { Address as SolanaAddress } from "@solana/kit"; +import type { Chain, Hex, RpcErrorType, Transport } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; -import type { CombinedIntentRpcSchema } from "../client/intentClient.js"; +import type { + CombinedIntentRpcSchema, + IntentClient, +} from "../client/intentClient.js"; import type { INTENT_VERSION_TYPE } from "../types/intent.js"; import { deepHexlify } from "../utils/deepHexlify.js"; @@ -87,11 +85,15 @@ export async function getIntent< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: Client, + client: IntentClient< + transport, + chain, + account, + undefined, + CombinedIntentRpcSchema + >, parameters: GetIntentParameters, version: INTENT_VERSION_TYPE, - _solanaSigner: TransactionSigner | undefined, - _solanaRpc: Rpc | undefined, ): Promise { const { gasToken, ...rest } = parameters; const parametersWithVersion = { diff --git a/src/actions/prepareUserIntent.ts b/src/actions/prepareUserIntent.ts index c3649ec..06b6771 100644 --- a/src/actions/prepareUserIntent.ts +++ b/src/actions/prepareUserIntent.ts @@ -1,19 +1,4 @@ -import type { - Address as SolanaAddress, - IInstruction, - Rpc, - SolanaRpcApi, - TransactionSigner, -} from "@solana/kit"; -import { - appendTransactionMessageInstructions, - compileTransactionMessage, - createTransactionMessage, - getCompiledTransactionMessageEncoder, - pipe, - setTransactionMessageFeePayer, - setTransactionMessageLifetimeUsingBlockhash, -} from "@solana/kit"; +import type { Address as SolanaAddress, ReadonlyUint8Array } from "@solana/kit"; import { AccountNotFoundError, type KernelSmartAccountImplementation, @@ -21,7 +6,6 @@ import { import type { Address, Chain, - Client, ContractFunctionParameters, Hex, Transport, @@ -33,7 +17,10 @@ import type { UserOperationCall, } from "viem/account-abstraction"; import { parseAccount } from "viem/utils"; -import type { CombinedIntentRpcSchema } from "../client/intentClient.js"; +import type { + CombinedIntentRpcSchema, + IntentClient, +} from "../client/intentClient.js"; import type { INTENT_VERSION_TYPE } from "../types/intent.js"; import { SOLANA_CHAIN_ID } from "../utils/constants.js"; import type { GetIntentReturnType } from "./getIntent.js"; @@ -55,12 +42,6 @@ export type PrepareUserIntentParameters< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = SmartAccount | undefined, calls extends readonly unknown[] = readonly unknown[], - solanaRpc extends Rpc | undefined = - | Rpc - | undefined, - solanaSigner extends TransactionSigner | undefined = - | TransactionSigner - | undefined, > = PrepareUserOperationParametersWithOptionalCalls< account, accountOverride, @@ -76,11 +57,13 @@ export type PrepareUserIntentParameters< amount: bigint; chainId: number; }>; - instructions?: IInstruction[]; + /** + * Prepared and encoded transactions for solana + * Built using : `getCompiledTransactionMessageEncoder().encode(compiledTransactionMessage)` + */ + solTransaction?: ReadonlyUint8Array; gasToken?: "SPONSORED" | "NATIVE"; chainId?: number; - solanaRpc?: solanaRpc; - solanaSigner?: solanaSigner; }; export type PrepareUserIntentResult = GetIntentReturnType; @@ -140,24 +123,16 @@ export async function prepareUserIntent< chain extends Chain | undefined = Chain | undefined, accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], - SolanaRpc extends Rpc | undefined = - | Rpc - | undefined, - SolanaSigner extends TransactionSigner | undefined = - | TransactionSigner - | undefined, >( - client: Client, - parameters: PrepareUserIntentParameters< + client: IntentClient< + Transport, + chain, account, - accountOverride, - calls, - SolanaRpc, - SolanaSigner + undefined, + CombinedIntentRpcSchema >, + parameters: PrepareUserIntentParameters, version: INTENT_VERSION_TYPE, - solanaSigner: TransactionSigner | undefined, - solanaRpc: Rpc | undefined, ): Promise { const { account: account_ = client.account } = parameters; if (!account_) throw new AccountNotFoundError(); @@ -192,29 +167,9 @@ export async function prepareUserIntent< // get instructionData const instructionData = await (async () => { - const instructions = parameters.instructions; - if (instructions) { - if (!solanaSigner || !solanaRpc) - throw new Error("please provide solanaSigner and solanaRpc"); - const { value: latestBlockhash } = await solanaRpc - .getLatestBlockhash() - .send(); - const transactionMessage = pipe( - createTransactionMessage({ version: 0 }), - (message) => - setTransactionMessageFeePayer(solanaSigner.address, message), - (message) => - setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message), - (message) => - appendTransactionMessageInstructions(instructions, message), - ); - const compiledTransactionMessage = - compileTransactionMessage(transactionMessage); - const encodedTransactionMessage = - getCompiledTransactionMessageEncoder().encode( - compiledTransactionMessage, - ); - return toHex(new Uint8Array(encodedTransactionMessage)); + // If we got a provided solana transaction, return it + if (parameters.solTransaction) { + return toHex(new Uint8Array(parameters.solTransaction)); } return "0x"; })(); @@ -231,7 +186,7 @@ export async function prepareUserIntent< } const recipient = BigInt(desinationChainId) === SOLANA_CHAIN_ID - ? solanaSigner?.address + ? client.solana?.address : account.address; if (!recipient) throw new Error("please provide solanaSigner"); @@ -250,7 +205,5 @@ export async function prepareUserIntent< initData, }, version, - solanaSigner, - solanaRpc, ); } diff --git a/src/actions/sendUserIntent.ts b/src/actions/sendUserIntent.ts index 9c03029..94973b8 100644 --- a/src/actions/sendUserIntent.ts +++ b/src/actions/sendUserIntent.ts @@ -1,10 +1,4 @@ -import { - type Transaction, - type TransactionPartialSigner, - getBase64EncodedWireTransaction, - getTransactionDecoder, -} from "@solana/kit"; -import { type Rpc, type SolanaRpcApi } from "@solana/kit"; +import { type Base64EncodedWireTransaction } from "@solana/kit"; import { MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS } from "@zerodev/multi-chain-ecdsa-validator"; import { AccountNotFoundError, @@ -18,7 +12,6 @@ import { import { MerkleTree } from "merkletreejs"; import { type Chain, - type Client, type Hex, type Transport, concatHex, @@ -36,7 +29,10 @@ import { } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; import { parseAccount } from "viem/utils"; -import type { CombinedIntentRpcSchema } from "../client/intentClient.js"; +import type { + CombinedIntentRpcSchema, + IntentClient, +} from "../client/intentClient.js"; import { V2_SAME_CHAIN_ORDER_DATA_TYPE } from "../config/constants.js"; import type { INTENT_VERSION_TYPE, UserIntentHash } from "../types/intent.js"; import { SOLANA_CHAIN_ID } from "../utils/constants.js"; @@ -51,24 +47,8 @@ export type SendUserIntentParameters< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = SmartAccount | undefined, calls extends readonly unknown[] = readonly unknown[], - SolanaRpc extends Rpc | undefined = - | Rpc - | undefined, - SolanaSigner extends TransactionPartialSigner | undefined = - | TransactionPartialSigner - | undefined, -> = Partial< - PrepareUserIntentParameters< - account, - accountOverride, - calls, - SolanaRpc, - SolanaSigner - > -> & { +> = Partial> & { intent?: GetIntentReturnType; - solanaSigner?: SolanaSigner; - solanaRpc?: SolanaRpc; }; export type RelayerSendUserIntentResult = { @@ -198,15 +178,17 @@ export async function sendOrders< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, - SolanaRpc extends Rpc | undefined = - | Rpc - | undefined, >( - client: Client, + client: IntentClient< + transport, + chain, + account, + undefined, + CombinedIntentRpcSchema + >, ordersWithSig: { order: GaslessCrossChainOrder; signature: Hex }[], version: INTENT_VERSION_TYPE, - solanaRpc: SolanaRpc, - solanaTx: Transaction | undefined, + solanaTx: Base64EncodedWireTransaction | undefined, ) { // send orders if (ordersWithSig.length > 0) { @@ -219,10 +201,7 @@ export async function sendOrders< order: order, signature, version, - solanaTransaction: - solanaTx && index === 0 - ? getBase64EncodedWireTransaction(solanaTx) - : undefined, + solanaTransaction: solanaTx && index === 0 ? solanaTx : undefined, }, ], }); @@ -240,9 +219,10 @@ export async function sendOrders< // send solana transaction if (!solanaTx) throw new Error("Solana transaction is missing"); + const solanaRpc = client.solana?.rpc; if (!solanaRpc) throw new Error("Solana RPC is required"); const transactionSignature = await solanaRpc - .sendTransaction(getBase64EncodedWireTransaction(solanaTx), { + .sendTransaction(solanaTx, { preflightCommitment: "confirmed", encoding: "base64", }) @@ -266,24 +246,16 @@ export async function sendUserIntent< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], - SolanaRpc extends Rpc | undefined = - | Rpc - | undefined, - SolanaSigner extends TransactionPartialSigner | undefined = - | TransactionPartialSigner - | undefined, >( - client: Client, - parameters: SendUserIntentParameters< + client: IntentClient< + transport, + chain, account, - accountOverride, - calls, - SolanaRpc, - SolanaSigner + undefined, + CombinedIntentRpcSchema >, + parameters: SendUserIntentParameters, version: INTENT_VERSION_TYPE, - solanaSigner: TransactionPartialSigner | undefined, - solanaRpc: Rpc | undefined, ): Promise { const { account: account_ = client.account, @@ -294,9 +266,9 @@ export async function sendUserIntent< if (!account_) throw new AccountNotFoundError(); // only one of calls or instructions should be provided - if (prepareParams.calls && prepareParams.instructions) { + if (prepareParams.calls && prepareParams.solTransaction) { throw new Error( - "Only one of calls or instructions should be provided in sendUserIntent", + "Only one of calls or solTransaction should be provided in sendUserIntent", ); } @@ -312,13 +284,9 @@ export async function sendUserIntent< prepareParams as PrepareUserIntentParameters< account, accountOverride, - calls, - SolanaRpc, - SolanaSigner + calls >, version, - solanaSigner, - solanaRpc, )); // Get the order hash @@ -335,22 +303,14 @@ export async function sendUserIntent< })); // solana signature if executionTransaction is provided - let solanaTx: Transaction | undefined; + let solanaTx: Base64EncodedWireTransaction | undefined; if (intent.executionTransaction && isHex(intent.executionTransaction)) { + const solanaSigner = client.solana?.signTransaction; + if (!solanaSigner) throw new Error("Solana signer is required"); const bytesTransaction = toBytes(intent.executionTransaction); - const transaction = getTransactionDecoder().decode(bytesTransaction); - const [transactionSignatures] = await solanaSigner.signTransactions([ - transaction, - ]); - solanaTx = { - ...transaction, - signatures: Object.freeze({ - ...transaction.signatures, - ...transactionSignatures, - }), - }; + solanaTx = await solanaSigner(bytesTransaction); } // Send the signed orders to the relayer @@ -358,7 +318,6 @@ export async function sendUserIntent< client, ordersWithSig, version, - solanaRpc, solanaTx, ); diff --git a/src/client/decorators/intent.ts b/src/client/decorators/intent.ts index f2de571..6620cf3 100644 --- a/src/client/decorators/intent.ts +++ b/src/client/decorators/intent.ts @@ -1,5 +1,4 @@ -import type { Rpc, SolanaRpcApi, TransactionPartialSigner } from "@solana/kit"; -import type { Chain, Client, Transport } from "viem"; +import type { Chain, Transport } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; import { type EnableIntentResult, @@ -52,7 +51,7 @@ import { waitForUserIntentOpenReceipt, } from "../../actions/waitForUserIntentOpenReceipt.js"; import type { INTENT_VERSION_TYPE } from "../../types/intent.js"; -import type { CombinedIntentRpcSchema } from "../intentClient.js"; +import type { CombinedIntentRpcSchema, IntentClient } from "../intentClient.js"; export type IntentClientActions< chain extends Chain | undefined = Chain | undefined, @@ -69,20 +68,8 @@ export type IntentClientActions< sendUserIntent: < accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], - solanaRpc extends Rpc | undefined = - | Rpc - | undefined, - solanaSigner extends TransactionPartialSigner | undefined = - | TransactionPartialSigner - | undefined, >( - parameters: SendUserIntentParameters< - account, - accountOverride, - calls, - solanaRpc, - solanaSigner - >, + parameters: SendUserIntentParameters, ) => Promise; estimateUserIntentFees: < accountOverride extends SmartAccount | undefined = undefined, @@ -112,23 +99,25 @@ export type IntentClientActions< export function intentClientActions( version: INTENT_VERSION_TYPE, - solanaSigner: TransactionPartialSigner | undefined, - solanaRpc: Rpc | undefined, ): < transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: Client, + client: IntentClient< + transport, + chain, + account, + undefined, + CombinedIntentRpcSchema + >, ) => IntentClientActions { return (client) => ({ - getIntent: (parameters) => - getIntent(client, parameters, version, solanaSigner, solanaRpc), - getCAB: (parameters) => getCAB(client, parameters, solanaSigner), + getIntent: (parameters) => getIntent(client, parameters, version), + getCAB: (parameters) => getCAB(client, parameters), prepareUserIntent: (parameters) => - prepareUserIntent(client, parameters, version, solanaSigner, solanaRpc), - sendUserIntent: (parameters) => - sendUserIntent(client, parameters, version, solanaSigner, solanaRpc), + prepareUserIntent(client, parameters, version), + sendUserIntent: (parameters) => sendUserIntent(client, parameters, version), estimateUserIntentFees: (parameters) => estimateUserIntentFees(client, parameters, version), getUserIntentStatus: (parameters) => diff --git a/src/client/intentClient.ts b/src/client/intentClient.ts index 035404a..324e04a 100644 --- a/src/client/intentClient.ts +++ b/src/client/intentClient.ts @@ -1,7 +1,7 @@ import { - type Rpc, - type SolanaRpcApi, - type TransactionPartialSigner, + type Address as SolanaAddress, + type Base64EncodedWireTransaction, + createSolanaRpc, } from "@solana/kit"; import { type KernelAccountClientActions, @@ -11,6 +11,7 @@ import { import { http, type Address, + type ByteArray, type Chain, type Client, type Hex, @@ -114,12 +115,6 @@ export type IntentClient< account extends SmartAccount | undefined = SmartAccount | undefined, client extends Client | undefined = Client | undefined, rpcSchema extends RpcSchema | undefined = undefined, - solanaRpc extends Rpc | undefined = - | Rpc - | undefined, - solanaSigner extends TransactionPartialSigner | undefined = - | TransactionPartialSigner - | undefined, > = Prettify< Client< transport, @@ -142,8 +137,18 @@ export type IntentClient< paymaster: BundlerClientConfig["paymaster"] | undefined; paymasterContext: BundlerClientConfig["paymasterContext"] | undefined; userOperation: BundlerClientConfig["userOperation"] | undefined; - solanaRpc: solanaRpc; - solanaSigner: solanaSigner; + + // Solana specific stuff + solana?: { + rpc: ReturnType; + address: SolanaAddress; + signTransaction: ( + transaction: ByteArray, + ) => Promise; + sendTransaction: ( + transaction: Base64EncodedWireTransaction, + ) => Promise; + }; }; export type CreateIntentClientConfig< @@ -159,10 +164,79 @@ export type CreateIntentClientConfig< projectId?: string; version: INTENT_VERSION_TYPE; - solanaSigner?: TransactionPartialSigner; - solanaRpc?: Rpc; + // Solana specific parameters + solana?: { + address: SolanaAddress; + rpcUrl: string; + signTransaction: ( + transaction: ByteArray, + ) => Promise; + }; }; +/** + * Creates an ZeroDev Intent client + * @param parameters - The configuration for the Intent client + * @returns An Intent client + * + * @example + * ```ts + * // Simple intent client than can send intent across any EVM chains + * const client = createIntentClient({ + * chain: mainnet, + * account: kernelAccount, + * version: INTENT_V0_3, + * bundlerTransport: http(bundlerRpc), + * }); + * + * // Intent client that can send intents across any EVM / Solana chain using the @solana/kit SDK + * const client = createIntentClient({ + * account: kernelAccount, + * bundlerTransport: http(bundlerRpc), + * version: INTENT_V0_3, + * solana: { + * rpcUrl: 'https://api.mainnet-beta.solana.com', + * address: solanaSigner.address, + * signTransaction: async (transaction) => { + * const decoded = getTransactionDecoder().decode(transaction) + * const [signatures] = await solanaSigner.signTransactions([decoded]) + * const newTransaction = { + * ...decoded, + * signatures: Object.freeze({ + * ...decoded.signatures, + * ...signatures + * }), + * } + * return getBase64EncodedWireTransaction(newTransaction); + * }, + * }, + * }); + * + * // Intent client that can send intents across any EVM / Solana chain using the @solana/web3js SDK + * // todo: Add guide around this + * const client = createIntentClient({ + * account: kernelAccount, + * bundlerTransport: http(bundlerRpc), + * version: INTENT_V0_3, + * solana: { + * rpcUrl: 'https://api.mainnet-beta.solana.com', + * address: solanaSigner.address, + * signTransaction: async (transaction) => { + * const decoded = getTransactionDecoder().decode(transaction) + * const [signatures] = await solanaSigner.signTransactions([decoded]) + * const newTransaction = { + * ...decoded, + * signatures: Object.freeze({ + * ...decoded.signatures, + * ...signatures + * }), + * } + * return getBase64EncodedWireTransaction(newTransaction); + * }, + * }, + * }); + * ``` + */ export function createIntentClient< transport extends Transport, chain extends Chain | undefined = undefined, @@ -195,8 +269,7 @@ export function createIntentClient( userOperation, version, projectId, - solanaRpc, - solanaSigner, + solana, } = parameters; const intentTransport = rawIntentTransport ? rawIntentTransport @@ -249,31 +322,37 @@ export function createIntentClient( paymaster, paymasterContext, userOperation, - solanaRpc, - solanaSigner, + solana: solana + ? { + ...solana, + rpc: createSolanaRpc(solana.rpcUrl), + } + : undefined, }, - ); + ) as unknown as IntentClient; if (parameters.userOperation?.prepareUserOperation) { const customPrepareUserOp = parameters.userOperation.prepareUserOperation; - return client - .extend(bundlerActions) - .extend(kernelAccountClientActions()) - .extend((client) => ({ - prepareUserOperation: (args: PrepareUserOperationParameters) => { - return customPrepareUserOp(client, args); - }, - })) - .extend( - intentClientActions(version, solanaSigner, solanaRpc), - ) as IntentClient; + return ( + client + .extend(bundlerActions) + .extend(kernelAccountClientActions()) + .extend((client) => ({ + prepareUserOperation: (args: PrepareUserOperationParameters) => { + return customPrepareUserOp(client, args); + }, + })) + // @ts-ignore : we know that the intentClientActions will return the correct type + .extend(intentClientActions(version)) as IntentClient + ); } - return client - .extend(bundlerActions) - .extend(kernelAccountClientActions()) - .extend( - intentClientActions(version, solanaSigner, solanaRpc), - ) as IntentClient; + return ( + client + .extend(bundlerActions) + .extend(kernelAccountClientActions()) + // @ts-ignore : we know that the intentClientActions will return the correct type + .extend(intentClientActions(version)) as IntentClient + ); } From b3a550745538a5d37732a5adad0479db41df1502 Mon Sep 17 00:00:00 2001 From: KONFeature Date: Thu, 20 Mar 2025 22:41:29 +0100 Subject: [PATCH 2/4] refactor: simplify IntentClient type by using viem extension --- src/actions/getCAB.ts | 8 +---- src/actions/getIntent.ts | 8 +---- src/actions/prepareUserIntent.ts | 8 +---- src/actions/sendUserIntent.ts | 16 ++------- src/client/decorators/intent.ts | 8 +---- src/client/intentClient.ts | 60 +++++++++++++++++--------------- 6 files changed, 38 insertions(+), 70 deletions(-) diff --git a/src/actions/getCAB.ts b/src/actions/getCAB.ts index 3d9e08a..a26fd52 100644 --- a/src/actions/getCAB.ts +++ b/src/actions/getCAB.ts @@ -94,13 +94,7 @@ export async function getCAB< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient< - transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, parameters: GetCABParameters, ): Promise { const { account: account_ = client.account } = parameters; diff --git a/src/actions/getIntent.ts b/src/actions/getIntent.ts index fcc2ff1..baee058 100644 --- a/src/actions/getIntent.ts +++ b/src/actions/getIntent.ts @@ -85,13 +85,7 @@ export async function getIntent< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient< - transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, parameters: GetIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/actions/prepareUserIntent.ts b/src/actions/prepareUserIntent.ts index 06b6771..bc3118a 100644 --- a/src/actions/prepareUserIntent.ts +++ b/src/actions/prepareUserIntent.ts @@ -124,13 +124,7 @@ export async function prepareUserIntent< accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], >( - client: IntentClient< - Transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, parameters: PrepareUserIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/actions/sendUserIntent.ts b/src/actions/sendUserIntent.ts index 94973b8..27508de 100644 --- a/src/actions/sendUserIntent.ts +++ b/src/actions/sendUserIntent.ts @@ -179,13 +179,7 @@ export async function sendOrders< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient< - transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, ordersWithSig: { order: GaslessCrossChainOrder; signature: Hex }[], version: INTENT_VERSION_TYPE, solanaTx: Base64EncodedWireTransaction | undefined, @@ -247,13 +241,7 @@ export async function sendUserIntent< accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], >( - client: IntentClient< - transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, parameters: SendUserIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/client/decorators/intent.ts b/src/client/decorators/intent.ts index 6620cf3..94e19fa 100644 --- a/src/client/decorators/intent.ts +++ b/src/client/decorators/intent.ts @@ -104,13 +104,7 @@ export function intentClientActions( chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient< - transport, - chain, - account, - undefined, - CombinedIntentRpcSchema - >, + client: IntentClient, ) => IntentClientActions { return (client) => ({ getIntent: (parameters) => getIntent(client, parameters, version), diff --git a/src/client/intentClient.ts b/src/client/intentClient.ts index 324e04a..acef211 100644 --- a/src/client/intentClient.ts +++ b/src/client/intentClient.ts @@ -109,31 +109,8 @@ export type RelayerRpcSchema = [ // Combined schema for the Intent client export type CombinedIntentRpcSchema = [...IntentRpcSchema, ...RelayerRpcSchema]; -export type IntentClient< - transport extends Transport = Transport, - chain extends Chain | undefined = Chain | undefined, - account extends SmartAccount | undefined = SmartAccount | undefined, - client extends Client | undefined = Client | undefined, - rpcSchema extends RpcSchema | undefined = undefined, -> = Prettify< - Client< - transport, - chain extends Chain - ? chain - : // biome-ignore lint/suspicious/noExplicitAny: - client extends Client - ? chain - : undefined, - account, - rpcSchema extends RpcSchema - ? [...rpcSchema, ...CombinedIntentRpcSchema] - : CombinedIntentRpcSchema, - BundlerActions & - KernelAccountClientActions & - IntentClientActions - > -> & { - client: client; +// The extension around a client to support zerodev intent +type IntentClientExtension = { paymaster: BundlerClientConfig["paymaster"] | undefined; paymasterContext: BundlerClientConfig["paymasterContext"] | undefined; userOperation: BundlerClientConfig["userOperation"] | undefined; @@ -151,6 +128,32 @@ export type IntentClient< }; }; +/** + * A fully built intent client with all the zerodev extensions applied + */ +export type IntentClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined, + rpcSchema extends RpcSchema | undefined = undefined, +> = Prettify< + Client< + transport, + chain extends Chain ? chain : undefined, + account, + rpcSchema extends RpcSchema + ? [...rpcSchema, ...CombinedIntentRpcSchema] + : CombinedIntentRpcSchema, + IntentClientExtension & + BundlerActions & + KernelAccountClientActions & + IntentClientActions + > +>; + +/** + * The configuration for the Intent client + */ export type CreateIntentClientConfig< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, @@ -251,7 +254,7 @@ export function createIntentClient< client, rpcSchema >, -): IntentClient; +): IntentClient; export function createIntentClient( parameters: CreateIntentClientConfig, @@ -308,6 +311,7 @@ export function createIntentClient( }, }); + // Create our base client (a viem client with the `IntentClientExtension` basically) const client = Object.assign( createClient({ ...parameters, @@ -318,7 +322,6 @@ export function createIntentClient( type: "intentClient", }), { - client: client_, paymaster, paymasterContext, userOperation, @@ -329,8 +332,9 @@ export function createIntentClient( } : undefined, }, - ) as unknown as IntentClient; + ); + // If the user has a custom prepareUserOperation function, use it if (parameters.userOperation?.prepareUserOperation) { const customPrepareUserOp = parameters.userOperation.prepareUserOperation; From 1524925f03ec058d8aaaaa7e6f27333cc87f280b Mon Sep 17 00:00:00 2001 From: KONFeature Date: Thu, 20 Mar 2025 22:48:07 +0100 Subject: [PATCH 3/4] fix: prevent type recursion of `IntentClient` by introducing `BaseIntentClient` --- src/actions/getCAB.ts | 4 +- src/actions/getIntent.ts | 4 +- src/actions/prepareUserIntent.ts | 4 +- src/actions/sendUserIntent.ts | 6 +-- src/client/decorators/intent.ts | 7 ++- src/client/intentClient.ts | 74 ++++++++++++++++++++++---------- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/actions/getCAB.ts b/src/actions/getCAB.ts index a26fd52..46a5a82 100644 --- a/src/actions/getCAB.ts +++ b/src/actions/getCAB.ts @@ -3,8 +3,8 @@ import { AccountNotFoundError } from "@zerodev/sdk"; import type { Address as EvmAddress, Chain, Hex, Transport } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; import type { + BaseIntentClient, CombinedIntentRpcSchema, - IntentClient, } from "../client/intentClient.js"; export type NetworkType = "mainnet" | "testnet"; @@ -94,7 +94,7 @@ export async function getCAB< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient, + client: BaseIntentClient, parameters: GetCABParameters, ): Promise { const { account: account_ = client.account } = parameters; diff --git a/src/actions/getIntent.ts b/src/actions/getIntent.ts index baee058..ff60018 100644 --- a/src/actions/getIntent.ts +++ b/src/actions/getIntent.ts @@ -2,8 +2,8 @@ import type { Address as SolanaAddress } from "@solana/kit"; import type { Chain, Hex, RpcErrorType, Transport } from "viem"; import type { SmartAccount } from "viem/account-abstraction"; import type { + BaseIntentClient, CombinedIntentRpcSchema, - IntentClient, } from "../client/intentClient.js"; import type { INTENT_VERSION_TYPE } from "../types/intent.js"; import { deepHexlify } from "../utils/deepHexlify.js"; @@ -85,7 +85,7 @@ export async function getIntent< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient, + client: BaseIntentClient, parameters: GetIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/actions/prepareUserIntent.ts b/src/actions/prepareUserIntent.ts index bc3118a..caf6dc8 100644 --- a/src/actions/prepareUserIntent.ts +++ b/src/actions/prepareUserIntent.ts @@ -18,8 +18,8 @@ import type { } from "viem/account-abstraction"; import { parseAccount } from "viem/utils"; import type { + BaseIntentClient, CombinedIntentRpcSchema, - IntentClient, } from "../client/intentClient.js"; import type { INTENT_VERSION_TYPE } from "../types/intent.js"; import { SOLANA_CHAIN_ID } from "../utils/constants.js"; @@ -124,7 +124,7 @@ export async function prepareUserIntent< accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], >( - client: IntentClient, + client: BaseIntentClient, parameters: PrepareUserIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/actions/sendUserIntent.ts b/src/actions/sendUserIntent.ts index 27508de..ac96b91 100644 --- a/src/actions/sendUserIntent.ts +++ b/src/actions/sendUserIntent.ts @@ -30,8 +30,8 @@ import { import type { SmartAccount } from "viem/account-abstraction"; import { parseAccount } from "viem/utils"; import type { + BaseIntentClient, CombinedIntentRpcSchema, - IntentClient, } from "../client/intentClient.js"; import { V2_SAME_CHAIN_ORDER_DATA_TYPE } from "../config/constants.js"; import type { INTENT_VERSION_TYPE, UserIntentHash } from "../types/intent.js"; @@ -179,7 +179,7 @@ export async function sendOrders< chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient, + client: BaseIntentClient, ordersWithSig: { order: GaslessCrossChainOrder; signature: Hex }[], version: INTENT_VERSION_TYPE, solanaTx: Base64EncodedWireTransaction | undefined, @@ -241,7 +241,7 @@ export async function sendUserIntent< accountOverride extends SmartAccount | undefined = undefined, calls extends readonly unknown[] = readonly unknown[], >( - client: IntentClient, + client: BaseIntentClient, parameters: SendUserIntentParameters, version: INTENT_VERSION_TYPE, ): Promise { diff --git a/src/client/decorators/intent.ts b/src/client/decorators/intent.ts index 94e19fa..457898a 100644 --- a/src/client/decorators/intent.ts +++ b/src/client/decorators/intent.ts @@ -51,7 +51,10 @@ import { waitForUserIntentOpenReceipt, } from "../../actions/waitForUserIntentOpenReceipt.js"; import type { INTENT_VERSION_TYPE } from "../../types/intent.js"; -import type { CombinedIntentRpcSchema, IntentClient } from "../intentClient.js"; +import type { + BaseIntentClient, + CombinedIntentRpcSchema, +} from "../intentClient.js"; export type IntentClientActions< chain extends Chain | undefined = Chain | undefined, @@ -104,7 +107,7 @@ export function intentClientActions( chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, >( - client: IntentClient, + client: BaseIntentClient, ) => IntentClientActions { return (client) => ({ getIntent: (parameters) => getIntent(client, parameters, version), diff --git a/src/client/intentClient.ts b/src/client/intentClient.ts index acef211..c1259b8 100644 --- a/src/client/intentClient.ts +++ b/src/client/intentClient.ts @@ -128,6 +128,26 @@ type IntentClientExtension = { }; }; +/** + * A base intent client, only supporting the intent client extensions + */ +export type BaseIntentClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined, + rpcSchema extends RpcSchema | undefined = undefined, +> = Prettify< + Client< + transport, + chain extends Chain ? chain : undefined, + account, + rpcSchema extends RpcSchema + ? [...rpcSchema, ...CombinedIntentRpcSchema] + : CombinedIntentRpcSchema, + IntentClientExtension + > +>; + /** * A fully built intent client with all the zerodev extensions applied */ @@ -256,9 +276,23 @@ export function createIntentClient< >, ): IntentClient; -export function createIntentClient( - parameters: CreateIntentClientConfig, -): IntentClient { +export function createIntentClient< + transport extends Transport, + chain extends Chain | undefined = undefined, + account extends SmartAccount | undefined = undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined, +>( + parameters: CreateIntentClientConfig< + transport, + chain, + account, + client, + rpcSchema + >, +): IntentClient { + type OutputType = IntentClient; + const { client: client_, key = "Account", @@ -332,31 +366,25 @@ export function createIntentClient( } : undefined, }, - ); + ) as unknown as BaseIntentClient; // If the user has a custom prepareUserOperation function, use it if (parameters.userOperation?.prepareUserOperation) { const customPrepareUserOp = parameters.userOperation.prepareUserOperation; - return ( - client - .extend(bundlerActions) - .extend(kernelAccountClientActions()) - .extend((client) => ({ - prepareUserOperation: (args: PrepareUserOperationParameters) => { - return customPrepareUserOp(client, args); - }, - })) - // @ts-ignore : we know that the intentClientActions will return the correct type - .extend(intentClientActions(version)) as IntentClient - ); - } - - return ( - client + return client .extend(bundlerActions) .extend(kernelAccountClientActions()) - // @ts-ignore : we know that the intentClientActions will return the correct type - .extend(intentClientActions(version)) as IntentClient - ); + .extend((client) => ({ + prepareUserOperation: (args: PrepareUserOperationParameters) => { + return customPrepareUserOp(client, args); + }, + })) + .extend(intentClientActions(version)) as OutputType; + } + + return client + .extend(bundlerActions) + .extend(kernelAccountClientActions()) + .extend(intentClientActions(version)) as OutputType; } From d82e43d06ab016034e052eb249bf7020ad38fd21 Mon Sep 17 00:00:00 2001 From: KONFeature Date: Fri, 21 Mar 2025 11:18:11 +0100 Subject: [PATCH 4/4] chore: add a bit of typedoc --- src/actions/prepareUserIntent.ts | 18 ++++++++++++++++++ src/client/intentClient.ts | 7 ++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/actions/prepareUserIntent.ts b/src/actions/prepareUserIntent.ts index caf6dc8..9e0d094 100644 --- a/src/actions/prepareUserIntent.ts +++ b/src/actions/prepareUserIntent.ts @@ -117,6 +117,24 @@ export type PrepareUserIntentResult = GetIntentReturnType; * chainId: 10n * }] * }) + * + * // Using solana transaction + * const result = await intentClient.sendUserIntent({ + * inputTokens: [ + * { + * chainId: 1n, + * address: '0x...', + * } + * ], + * outputTokens: [ + * { + * chainId: 10n, + * address: '0x...', + * amount: 900000n, + * } + * ], + * solTransaction: encodedTransactionMessage, + * }) */ export async function prepareUserIntent< account extends SmartAccount | undefined = SmartAccount | undefined, diff --git a/src/client/intentClient.ts b/src/client/intentClient.ts index c1259b8..9aa0b75 100644 --- a/src/client/intentClient.ts +++ b/src/client/intentClient.ts @@ -122,9 +122,6 @@ type IntentClientExtension = { signTransaction: ( transaction: ByteArray, ) => Promise; - sendTransaction: ( - transaction: Base64EncodedWireTransaction, - ) => Promise; }; }; @@ -212,7 +209,7 @@ export type CreateIntentClientConfig< * bundlerTransport: http(bundlerRpc), * }); * - * // Intent client that can send intents across any EVM / Solana chain using the @solana/kit SDK + * // Intent client that can send intents across any EVM / Solana chain using the `@solana/kit` SDK * const client = createIntentClient({ * account: kernelAccount, * bundlerTransport: http(bundlerRpc), @@ -235,7 +232,7 @@ export type CreateIntentClientConfig< * }, * }); * - * // Intent client that can send intents across any EVM / Solana chain using the @solana/web3js SDK + * // Intent client that can send intents across any EVM / Solana chain using the `@solana/web3js` SDK * // todo: Add guide around this * const client = createIntentClient({ * account: kernelAccount,