Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/accounts/actions.ts
Original file line number Diff line number Diff line change
@@ -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<GenLayerChain>) {
export function accountActions<TChain extends GenLayerChain>(client: BaseActionsClient<TChain>) {
return {
fundAccount: async ({address, amount}: {address: Address; amount: number}): Promise<TransactionHash> => {
if (client.chain?.id !== localnet.id) {
Expand Down
4 changes: 2 additions & 2 deletions src/chains/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {GenLayerClient, GenLayerChain} from "@/types";
import {GenLayerClient, GenLayerChain, BaseActionsClient} from "@/types";

Check failure on line 1 in src/chains/actions.ts

View workflow job for this annotation

GitHub Actions / test

'GenLayerClient' is defined but never used. Allowed unused vars must match /^_/u
import {testnetAsimov} from "./testnetAsimov";

export function chainActions(client: GenLayerClient<GenLayerChain>) {
export function chainActions<TChain extends GenLayerChain>(client: BaseActionsClient<TChain>) {
return {
initializeConsensusSmartContract: async (forceReset: boolean = false): Promise<void> => {
if (client.chain?.id === testnetAsimov.id) {
Expand Down
41 changes: 17 additions & 24 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TBase extends object, TExt extends object>(base: TBase, ext: TExt): TBase & TExt {
return Object.assign({}, base, ext) as TBase & TExt;
}

// Define the configuration interface for the client
interface ClientConfig {
chain?: {
Expand Down Expand Up @@ -84,7 +88,7 @@ const getCustomTransportConfig = (config: ClientConfig) => {
};

export const createClient = (config: ClientConfig = {chain: localnet}): GenLayerClient<GenLayerChain> => {
const chainConfig = config.chain || localnet;
const chainConfig = (config.chain || localnet) as GenLayerChain;
if (config.endpoint) {
chainConfig.rpcUrls.default.http = [config.endpoint];
}
Expand All @@ -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<GenLayerChain>));

// Create a client with all actions except transaction actions
const clientWithAllActions = {
...clientWithBasicActions,
...contractActions(clientWithBasicActions as unknown as GenLayerClient<GenLayerChain>, publicClient),
...chainActions(clientWithBasicActions as unknown as GenLayerClient<GenLayerChain>),
...genlayerWalletActions(clientWithBasicActions as unknown as GenLayerClient<GenLayerChain>),
...transactionActions(clientWithBasicActions as unknown as GenLayerClient<GenLayerChain>, publicClient),
} as unknown as GenLayerClient<GenLayerChain>;

// Add transaction actions last, after all other actions are in place
const finalClient = {
...clientWithAllActions,
...receiptActions(clientWithAllActions as unknown as GenLayerClient<GenLayerChain>, publicClient),
} as unknown as GenLayerClient<GenLayerChain>;
// Extend only with viem actions, then merge custom actions to avoid protected name conflicts
const baseWithViem = baseClient.extend(publicActions).extend(walletActions) as BaseActionsClient<GenLayerChain>;
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<GenLayerChain> = finalClient as GenLayerClient<GenLayerChain>;

// 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 = (
Expand Down
20 changes: 16 additions & 4 deletions src/contracts/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ import {
} from "@/types";
import {fromHex, toHex, zeroAddress, encodeFunctionData, PublicClient, parseEventLogs} from "viem";

export const contractActions = (client: GenLayerClient<GenLayerChain>, publicClient: PublicClient) => {
type ContractCapabilities = {
chain: GenLayerChain;
account?: Account;
request: GenLayerClient<GenLayerChain>["request"];
prepareTransactionRequest: GenLayerClient<GenLayerChain>["prepareTransactionRequest"];
sendRawTransaction: GenLayerClient<GenLayerChain>["sendRawTransaction"];
getCurrentNonce: GenLayerClient<GenLayerChain>["getCurrentNonce"];
};

export const contractActions = (
client: ContractCapabilities,
publicClient: PublicClient,
) => {
return {
getContractSchema: async (address: Address): Promise<ContractSchema> => {
if (client.chain.id !== localnet.id) {
Expand Down Expand Up @@ -186,7 +198,7 @@ const _encodeAddTransactionData = ({
data,
consensusMaxRotations = client.chain.defaultConsensusMaxRotations,
}: {
client: GenLayerClient<GenLayerChain>;
client: ContractCapabilities;
senderAccount?: Account;
recipient?: `0x${string}`;
data?: `0x${string}`;
Expand All @@ -210,7 +222,7 @@ const _encodeSubmitAppealData = ({
client,
txId,
}: {
client: GenLayerClient<GenLayerChain>;
client: ContractCapabilities;
txId: `0x${string}`;
}): `0x${string}` => {
return encodeFunctionData({
Expand All @@ -227,7 +239,7 @@ const _sendTransaction = async ({
senderAccount,
value = 0n,
}: {
client: GenLayerClient<GenLayerChain>;
client: ContractCapabilities;
publicClient: PublicClient;
encodedData: `0x${string}`;
senderAccount?: Account;
Expand Down
38 changes: 26 additions & 12 deletions src/transactions/actions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import {GenLayerClient} from "../types/clients";
import {
TransactionHash,
TransactionStatus,
GenLayerTransaction,
GenLayerRawTransaction,
transactionsStatusNameToNumber,
} from "../types/transactions";
import {GenLayerClient, BaseActionsClient} from "../types/clients";

Check failure on line 1 in src/transactions/actions.ts

View workflow job for this annotation

GitHub Actions / test

'GenLayerClient' is defined but never used. Allowed unused vars must match /^_/u
import {TransactionHash, TransactionStatus, GenLayerTransaction, GenLayerRawTransaction, transactionsStatusNameToNumber} from "@/types";
import {transactionsConfig} from "../config/transactions";
import {sleep} from "../utils/async";
import {GenLayerChain} from "@/types";
Expand All @@ -15,7 +9,14 @@



export const receiptActions = (client: GenLayerClient<GenLayerChain>, publicClient: PublicClient) => ({
type ClientWithGetTransaction<TChain extends GenLayerChain> = BaseActionsClient<TChain> & {
getTransaction: (args: {hash: TransactionHash}) => Promise<GenLayerTransaction>;
};

export const receiptActions = <TChain extends GenLayerChain>(
client: ClientWithGetTransaction<TChain>,
publicClient: PublicClient,
) => ({
waitForTransactionReceipt: async ({
hash,
status = TransactionStatus.ACCEPTED,
Expand Down Expand Up @@ -68,16 +69,29 @@
},
});

export const transactionActions = (client: GenLayerClient<GenLayerChain>, publicClient: PublicClient) => ({
type TransactionCapabilities<TChain extends GenLayerChain> = BaseActionsClient<TChain> & {
// using viem public client for remote branch
};

export const transactionActions = <TChain extends GenLayerChain>(
client: TransactionCapabilities<TChain>,
publicClient: PublicClient,
) => ({
getTransaction: async ({hash}: {hash: TransactionHash}): Promise<GenLayerTransaction> => {
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,
Expand Down
14 changes: 14 additions & 0 deletions src/types/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,17 @@ export type GenLayerClient<TGenLayerChain extends GenLayerChain> = Omit<
txId: `0x${string}`;
}) => Promise<any>;
};

// Base client shape after applying viem public and wallet actions, used by action factories
export type BaseActionsClient<TGenLayerChain extends GenLayerChain> = Client<
Transport,
TGenLayerChain
> &
Omit<PublicActions<Transport, TGenLayerChain>, "getTransaction" | "readContract" | "waitForTransactionReceipt"> &
WalletActions<TGenLayerChain> & {
request: Client<Transport, TGenLayerChain>["request"] & {
<TMethod extends GenLayerMethod>(
args: Extract<GenLayerMethod, {method: TMethod["method"]}>,
): Promise<unknown>;
};
};
4 changes: 2 additions & 2 deletions src/wallet/actions.ts
Original file line number Diff line number Diff line change
@@ -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<GenLayerChain>) {
export function walletActions<TChain extends GenLayerChain>(client: BaseActionsClient<TChain>) {
return {
connect: (network: Network, snapSource: SnapSource) => connect(client, network, snapSource),
metamaskClient: (snapSource: SnapSource = "npm") => metamaskClient(snapSource),
Expand Down
4 changes: 2 additions & 2 deletions src/wallet/connect.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -13,7 +13,7 @@ const networks = {
};

export const connect = async (
client: GenLayerClient<GenLayerChain>,
client: { chain: GenLayerChain },
network: Network = "studionet",
snapSource: SnapSource = "npm",
): Promise<void> => {
Expand Down
Loading