diff --git a/src/accounts/actions.ts b/src/accounts/actions.ts index c7926fa..12cd2e2 100644 --- a/src/accounts/actions.ts +++ b/src/accounts/actions.ts @@ -1,7 +1,7 @@ -import {GenLayerClient, TransactionHash, GenLayerChain, Address} from "../types"; +import {TransactionHash, GenLayerChain, Address, BaseActionsClient} from "../types"; import {localnet} from "../chains"; -export function accountActions(client: GenLayerClient) { +export function accountActions(client: BaseActionsClient) { return { fundAccount: async ({address, amount}: {address: Address; amount: number}): Promise => { if (client.chain?.id !== localnet.id) { diff --git a/src/chains/actions.ts b/src/chains/actions.ts index 7e5ddb1..dd255a6 100644 --- a/src/chains/actions.ts +++ b/src/chains/actions.ts @@ -1,7 +1,7 @@ -import {GenLayerClient, GenLayerChain} from "@/types"; +import {GenLayerChain, BaseActionsClient} from "@/types"; import {testnetAsimov} from "./testnetAsimov"; -export function chainActions(client: GenLayerClient) { +export function chainActions(client: BaseActionsClient) { return { initializeConsensusSmartContract: async (forceReset: boolean = false): Promise => { if (client.chain?.id === testnetAsimov.id) { diff --git a/src/client/client.ts b/src/client/client.ts index 4208aad..c74d796 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -14,10 +14,14 @@ import {accountActions} from "../accounts/actions"; import {contractActions} from "../contracts/actions"; import {receiptActions, transactionActions} from "../transactions/actions"; import {walletActions as genlayerWalletActions} from "../wallet/actions"; -import {GenLayerClient, GenLayerChain} from "@/types"; +import {BaseActionsClient, GenLayerClient, GenLayerChain} from "@/types"; import {chainActions} from "@/chains/actions"; import {localnet} from "@/chains"; +function mergeActions(base: TBase, ext: TExt): TBase & TExt { + return Object.assign({}, base, ext) as TBase & TExt; +} + // Define the configuration interface for the client interface ClientConfig { chain?: { @@ -84,7 +88,7 @@ const getCustomTransportConfig = (config: ClientConfig) => { }; export const createClient = (config: ClientConfig = {chain: localnet}): GenLayerClient => { - const chainConfig = config.chain || localnet; + const chainConfig = (config.chain || localnet) as GenLayerChain; if (config.endpoint) { chainConfig.rpcUrls.default.http = [config.endpoint]; } @@ -100,32 +104,21 @@ export const createClient = (config: ClientConfig = {chain: localnet}): GenLayer ...(config.account ? {account: config.account} : {}), }); - // First extend with basic actions - const clientWithBasicActions = baseClient - .extend(publicActions) - .extend(walletActions) - .extend(client => accountActions(client as unknown as GenLayerClient)); - - // Create a client with all actions except transaction actions - const clientWithAllActions = { - ...clientWithBasicActions, - ...contractActions(clientWithBasicActions as unknown as GenLayerClient, publicClient), - ...chainActions(clientWithBasicActions as unknown as GenLayerClient), - ...genlayerWalletActions(clientWithBasicActions as unknown as GenLayerClient), - ...transactionActions(clientWithBasicActions as unknown as GenLayerClient, publicClient), - } as unknown as GenLayerClient; - - // Add transaction actions last, after all other actions are in place - const finalClient = { - ...clientWithAllActions, - ...receiptActions(clientWithAllActions as unknown as GenLayerClient, publicClient), - } as unknown as GenLayerClient; + // Extend only with viem actions, then merge custom actions to avoid protected name conflicts + const baseWithViem = baseClient.extend(publicActions).extend(walletActions) as BaseActionsClient; + const withAccounts = mergeActions(baseWithViem, accountActions(baseWithViem)); + const withWallet = mergeActions(withAccounts, genlayerWalletActions(withAccounts)); + const withChain = mergeActions(withWallet, chainActions(withWallet)); + const withContracts = mergeActions(withChain, contractActions(withChain, publicClient)); + const withTx = mergeActions(withContracts, transactionActions(withContracts, publicClient)); + const finalClient = mergeActions(withTx, receiptActions(withTx, publicClient)); + const finalClientTyped: GenLayerClient = finalClient as GenLayerClient; // Initialize in the background - finalClient.initializeConsensusSmartContract().catch(error => { + finalClientTyped.initializeConsensusSmartContract().catch(error => { console.error("Failed to initialize consensus smart contract:", error); }); - return finalClient; + return finalClientTyped; }; export const createPublicClient = ( diff --git a/src/contracts/actions.ts b/src/contracts/actions.ts index da8f5dc..f4ea545 100644 --- a/src/contracts/actions.ts +++ b/src/contracts/actions.ts @@ -12,7 +12,19 @@ import { } from "@/types"; import {fromHex, toHex, zeroAddress, encodeFunctionData, PublicClient, parseEventLogs} from "viem"; -export const contractActions = (client: GenLayerClient, publicClient: PublicClient) => { +type ContractCapabilities = { + chain: GenLayerChain; + account?: Account; + request: GenLayerClient["request"]; + prepareTransactionRequest: GenLayerClient["prepareTransactionRequest"]; + sendRawTransaction: GenLayerClient["sendRawTransaction"]; + getCurrentNonce: GenLayerClient["getCurrentNonce"]; +}; + +export const contractActions = ( + client: ContractCapabilities, + publicClient: PublicClient, +) => { return { getContractSchema: async (address: Address): Promise => { if (client.chain.id !== localnet.id) { @@ -186,7 +198,7 @@ const _encodeAddTransactionData = ({ data, consensusMaxRotations = client.chain.defaultConsensusMaxRotations, }: { - client: GenLayerClient; + client: ContractCapabilities; senderAccount?: Account; recipient?: `0x${string}`; data?: `0x${string}`; @@ -210,7 +222,7 @@ const _encodeSubmitAppealData = ({ client, txId, }: { - client: GenLayerClient; + client: ContractCapabilities; txId: `0x${string}`; }): `0x${string}` => { return encodeFunctionData({ @@ -227,7 +239,7 @@ const _sendTransaction = async ({ senderAccount, value = 0n, }: { - client: GenLayerClient; + client: ContractCapabilities; publicClient: PublicClient; encodedData: `0x${string}`; senderAccount?: Account; diff --git a/src/transactions/actions.ts b/src/transactions/actions.ts index 49d6b0a..322c564 100644 --- a/src/transactions/actions.ts +++ b/src/transactions/actions.ts @@ -1,11 +1,5 @@ -import {GenLayerClient} from "../types/clients"; -import { - TransactionHash, - TransactionStatus, - GenLayerTransaction, - GenLayerRawTransaction, - transactionsStatusNameToNumber, -} from "../types/transactions"; +import {BaseActionsClient} from "../types/clients"; +import {TransactionHash, TransactionStatus, GenLayerTransaction, GenLayerRawTransaction, transactionsStatusNameToNumber} from "@/types"; import {transactionsConfig} from "../config/transactions"; import {sleep} from "../utils/async"; import {GenLayerChain} from "@/types"; @@ -15,7 +9,14 @@ import {decodeLocalnetTransaction, decodeTransaction, simplifyTransactionReceipt -export const receiptActions = (client: GenLayerClient, publicClient: PublicClient) => ({ +type ClientWithGetTransaction = BaseActionsClient & { + getTransaction: (args: {hash: TransactionHash}) => Promise; +}; + +export const receiptActions = ( + client: ClientWithGetTransaction, + publicClient: PublicClient, +) => ({ waitForTransactionReceipt: async ({ hash, status = TransactionStatus.ACCEPTED, @@ -68,16 +69,29 @@ export const receiptActions = (client: GenLayerClient, publicClie }, }); -export const transactionActions = (client: GenLayerClient, publicClient: PublicClient) => ({ +type TransactionCapabilities = BaseActionsClient & { + // using viem public client for remote branch +}; + +export const transactionActions = ( + client: TransactionCapabilities, + publicClient: PublicClient, +) => ({ getTransaction: async ({hash}: {hash: TransactionHash}): Promise => { if (client.chain.id === localnet.id) { - const transaction = await client.getTransaction({hash}); + // Not using viem's getTransaction here: its protected action signature and return type + // differ from our GenLayerTransaction (localnet adds consensus fields). Direct RPC avoids + // signature conflicts and preserves our expected types. + const transaction = (await client.request({ + method: "eth_getTransactionByHash", + params: [hash], + })) as GenLayerTransaction; const localnetStatus = (transaction.status as string) === "ACTIVATED" ? TransactionStatus.PENDING : transaction.status; transaction.status = Number(transactionsStatusNameToNumber[localnetStatus as TransactionStatus]); transaction.statusName = localnetStatus as TransactionStatus; - return decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction); + return decodeLocalnetTransaction(transaction); } const transaction = (await publicClient.readContract({ address: client.chain.consensusDataContract?.address as Address, diff --git a/src/types/clients.ts b/src/types/clients.ts index 0ce098c..af8e10a 100644 --- a/src/types/clients.ts +++ b/src/types/clients.ts @@ -84,3 +84,17 @@ export type GenLayerClient = Omit< txId: `0x${string}`; }) => Promise; }; + +// Base client shape after applying viem public and wallet actions, used by action factories +export type BaseActionsClient = Client< + Transport, + TGenLayerChain +> & + Omit, "getTransaction" | "readContract" | "waitForTransactionReceipt"> & + WalletActions & { + request: Client["request"] & { + ( + args: Extract, + ): Promise; + }; + }; diff --git a/src/wallet/actions.ts b/src/wallet/actions.ts index 8659eef..03f8fdc 100644 --- a/src/wallet/actions.ts +++ b/src/wallet/actions.ts @@ -1,8 +1,8 @@ import {connect} from "./connect"; -import {GenLayerClient, GenLayerChain, Network, SnapSource} from "@/types"; +import {GenLayerChain, Network, SnapSource, BaseActionsClient} from "@/types"; import {metamaskClient} from "@/wallet/metamaskClient"; -export function walletActions(client: GenLayerClient) { +export function walletActions(client: BaseActionsClient) { return { connect: (network: Network, snapSource: SnapSource) => connect(client, network, snapSource), metamaskClient: (snapSource: SnapSource = "npm") => metamaskClient(snapSource), diff --git a/src/wallet/connect.ts b/src/wallet/connect.ts index d4d4313..3981a7b 100644 --- a/src/wallet/connect.ts +++ b/src/wallet/connect.ts @@ -1,7 +1,7 @@ import {localnet} from "@/chains/localnet"; import {studionet} from "@/chains/studionet"; import {testnetAsimov} from "@/chains/testnetAsimov"; -import {GenLayerClient, GenLayerChain} from "@/types"; +import {GenLayerChain} from "@/types"; import {Network} from "@/types/network"; import {SnapSource} from "@/types/snapSource"; import {snapID} from "@/config/snapID"; @@ -13,7 +13,7 @@ const networks = { }; export const connect = async ( - client: GenLayerClient, + client: { chain?: GenLayerChain }, network: Network = "studionet", snapSource: SnapSource = "npm", ): Promise => {