Skip to content

Commit

Permalink
feat(common,world): improvements for smart accounts (#2578)
Browse files Browse the repository at this point in the history
Co-authored-by: mous <[email protected]>
Co-authored-by: alvrs <[email protected]>
Co-authored-by: karooolis <[email protected]>
  • Loading branch information
4 people authored Apr 2, 2024
1 parent 66865c5 commit d2e4d0f
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 34 deletions.
9 changes: 9 additions & 0 deletions .changeset/smooth-ducks-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@latticexyz/common": patch
---

`transactionQueue` decorator now accepts an optional `publicClient` argument, which will be used in place of the extended viem client for making public action calls (`getChainId`, `getTransactionCount`, `simulateContract`, `call`). This helps in cases where the extended viem client is a smart account client, like in [permissionless.js](https://github.com/pimlicolabs/permissionless.js), where the transport is the bundler, not an RPC.

`writeObserver` decorator now accepts any `Client`, not just a `WalletClient`.

`createBurnerAccount` now returns a `PrivateKeyAccount`, the more specific `Account` type.
5 changes: 5 additions & 0 deletions .changeset/smooth-laws-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world": patch
---

`callFrom` decorator now accepts any `Client`, not just a `WalletClient`. It also no longer attempts to wrap/redirect calls to `call`, `callFrom`, and `registerDelegationWithSignature`.
18 changes: 12 additions & 6 deletions packages/common/src/actions/transactionQueue.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import type { Transport, Chain, Account, WalletActions, WalletClient } from "viem";
import type { Transport, Chain, Account, WalletActions, Client, PublicClient } from "viem";
import { writeContract as mud_writeContract } from "../writeContract";
import { sendTransaction as mud_sendTransaction } from "../sendTransaction";

export function transactionQueue<TChain extends Chain, TAccount extends Account>(): (
client: WalletClient<Transport, TChain, TAccount>,
) => Pick<WalletActions<TChain, TAccount>, "writeContract" | "sendTransaction"> {
export type TransactionQueueOptions<chain extends Chain> = {
publicClient?: PublicClient<Transport, chain>;
};

export function transactionQueue<chain extends Chain, account extends Account>({
publicClient,
}: TransactionQueueOptions<chain> = {}): (
client: Client<Transport, chain, account>,
) => Pick<WalletActions<chain, account>, "writeContract" | "sendTransaction"> {
return (client) => ({
// Applies to: `client.writeContract`, `getContract(client, ...).write`
writeContract: (args) => mud_writeContract(client, args),
writeContract: (args) => mud_writeContract(client, args, publicClient),
// Applies to: `client.sendTransaction`
sendTransaction: (args) => mud_sendTransaction(client, args),
sendTransaction: (args) => mud_sendTransaction(client, args, publicClient),
});
}
4 changes: 2 additions & 2 deletions packages/common/src/actions/writeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type {
Chain,
Account,
WalletActions,
WalletClient,
WriteContractReturnType,
Client,
} from "viem";
import { getAction } from "viem/utils";
import { writeContract } from "viem/actions";
Expand All @@ -16,7 +16,7 @@ type WriteObserverParameters = { onWrite: (write: ContractWrite) => void };
export function writeObserver<TChain extends Chain, TAccount extends Account>({
onWrite,
}: WriteObserverParameters): (
client: WalletClient<Transport, TChain, TAccount>,
client: Client<Transport, TChain, TAccount>,
) => Pick<WalletActions<TChain, TAccount>, "writeContract"> {
let nextWriteId = 0;

Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/createBurnerAccount.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Account, Hex } from "viem";
import { Hex, PrivateKeyAccount } from "viem";
import { privateKeyToAccount } from "viem/accounts";

export function createBurnerAccount(privateKey: Hex): Account {
export function createBurnerAccount(privateKey: Hex): PrivateKeyAccount {
const account = privateKeyToAccount(privateKey);
// We may override account features here
return {
Expand Down
8 changes: 6 additions & 2 deletions packages/common/src/getContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,14 @@ export function getContract<
TChain,
TAccount
>;
const result = writeContract(walletClient, request);
const result = writeContract(walletClient, request, publicClient);

const id = `${walletClient.chain.id}:${walletClient.account.address}:${nextWriteId++}`;
onWrite?.({ id, request: request as WriteContractParameters, result });
onWrite?.({
id,
request: request as WriteContractParameters,
result,
});

return result;
};
Expand Down
22 changes: 12 additions & 10 deletions packages/common/src/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SendTransactionParameters,
Transport,
SendTransactionReturnType,
PublicClient,
} from "viem";
import { call, sendTransaction as viem_sendTransaction } from "viem/actions";
import pRetry from "p-retry";
Expand All @@ -19,12 +20,13 @@ const debug = parentDebug.extend("sendTransaction");

/** @deprecated Use `walletClient.extend(transactionQueue())` instead. */
export async function sendTransaction<
TChain extends Chain | undefined,
TAccount extends Account | undefined,
TChainOverride extends Chain | undefined,
chain extends Chain | undefined,
account extends Account | undefined,
chainOverride extends Chain | undefined,
>(
client: Client<Transport, TChain, TAccount>,
request: SendTransactionParameters<TChain, TAccount, TChainOverride>,
client: Client<Transport, chain, account>,
request: SendTransactionParameters<chain, account, chainOverride>,
publicClient?: PublicClient<Transport, chain>,
): Promise<SendTransactionReturnType> {
const rawAccount = request.account ?? client.account;
if (!rawAccount) {
Expand All @@ -34,23 +36,23 @@ export async function sendTransaction<
const account = parseAccount(rawAccount);

const nonceManager = await getNonceManager({
client,
client: publicClient ?? client,
address: account.address,
blockTag: "pending",
});

async function prepare(): Promise<SendTransactionParameters<TChain, TAccount, TChainOverride>> {
async function prepare(): Promise<SendTransactionParameters<chain, account, chainOverride>> {
if (request.gas) {
debug("gas provided, skipping simulate", request.to);
return request;
}

debug("simulating tx to", request.to);
await call(client, {
await call(publicClient ?? client, {
...request,
blockTag: "pending",
account,
} as CallParameters<TChain>);
} as CallParameters<chain>);

return request;
}
Expand All @@ -67,7 +69,7 @@ export async function sendTransaction<

const nonce = nonceManager.nextNonce();
debug("sending tx with nonce", nonce, "to", preparedRequest.to);
const parameters: SendTransactionParameters<TChain, TAccount, TChainOverride> = { nonce, ...preparedRequest };
const parameters: SendTransactionParameters<chain, account, chainOverride> = { nonce, ...preparedRequest };
return await viem_sendTransaction(client, parameters);
},
{
Expand Down
22 changes: 15 additions & 7 deletions packages/common/src/writeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WriteContractReturnType,
ContractFunctionName,
ContractFunctionArgs,
PublicClient,
} from "viem";
import { simulateContract, writeContract as viem_writeContract } from "viem/actions";
import pRetry from "p-retry";
Expand All @@ -31,6 +32,7 @@ export async function writeContract<
>(
client: Client<Transport, chain, account>,
request: WriteContractParameters<abi, functionName, args, chain, account, chainOverride>,
publicClient?: PublicClient<Transport, chain>,
): Promise<WriteContractReturnType> {
const rawAccount = request.account ?? client.account;
if (!rawAccount) {
Expand All @@ -40,7 +42,7 @@ export async function writeContract<
const account = parseAccount(rawAccount);

const nonceManager = await getNonceManager({
client,
client: publicClient ?? client,
address: account.address,
blockTag: "pending",
});
Expand All @@ -54,11 +56,14 @@ export async function writeContract<
}

debug("simulating", request.functionName, "at", request.address);
const result = await simulateContract<chain, account, abi, functionName, args, chainOverride>(client, {
...request,
blockTag: "pending",
account,
} as unknown as SimulateContractParameters<abi, functionName, args, chain, chainOverride>);
const result = await simulateContract<chain, account | undefined, abi, functionName, args, chainOverride>(
publicClient ?? client,
{
...request,
blockTag: "pending",
account,
} as unknown as SimulateContractParameters<abi, functionName, args, chain, chainOverride>,
);

return result.request as unknown as WriteContractParameters<abi, functionName, args, chain, account, chainOverride>;
}
Expand All @@ -75,7 +80,10 @@ export async function writeContract<

const nonce = nonceManager.nextNonce();
debug("calling", preparedWrite.functionName, "with nonce", nonce, "at", preparedWrite.address);
return await viem_writeContract(client, { nonce, ...preparedWrite } as typeof preparedWrite);
return await viem_writeContract(client, {
nonce,
...preparedWrite,
} as typeof preparedWrite);
},
{
retries: 3,
Expand Down
5 changes: 4 additions & 1 deletion packages/dev-tools/src/actions/getTransactionResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export function getTransactionResult(
const transaction = getTransaction(publicClient, write);
const transactionReceipt = getTransactionReceipt(publicClient, write);
cache[write.id] = Promise.all([transaction, transactionReceipt]).then(([tx, receipt]) => {
const { functionName, args } = decodeFunctionData({ abi: worldAbi, data: tx.input });
const { functionName, args } = decodeFunctionData({
abi: worldAbi,
data: tx.input,
});
return publicClient.simulateContract({
account: tx.from,
address: tx.to!,
Expand Down
13 changes: 9 additions & 4 deletions packages/world/ts/actions/callFrom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
slice,
concat,
type WalletClient,
type Transport,
type Chain,
type Account,
Expand All @@ -10,6 +9,7 @@ import {
type WriteContractReturnType,
type EncodeFunctionDataParameters,
type PublicClient,
Client,
} from "viem";
import { getAction, encodeFunctionData } from "viem/utils";
import { writeContract } from "viem/actions";
Expand Down Expand Up @@ -52,12 +52,17 @@ type SystemFunction = { systemId: Hex; systemFunctionSelector: Hex };
// The function mapping is cached to avoid redundant retrievals for the same World function.
export function callFrom<TChain extends Chain, TAccount extends Account>(
params: CallFromParameters,
): (client: WalletClient<Transport, TChain, TAccount>) => Pick<WalletActions<TChain, TAccount>, "writeContract"> {
): (client: Client<Transport, TChain, TAccount>) => Pick<WalletActions<TChain, TAccount>, "writeContract"> {
return (client) => ({
// Applies to: `client.writeContract`, `getContract(client, ...).write`
writeContract: async (writeArgs): Promise<WriteContractReturnType> => {
// Skip if the contract isn't the World.
if (writeArgs.address !== params.worldAddress) {
// Skip if the contract isn't the World or the function called should not be redirected through `callFrom`.
if (
writeArgs.address !== params.worldAddress ||
writeArgs.functionName === "call" ||
writeArgs.functionName === "callFrom" ||
writeArgs.functionName === "registerDelegationWithSignature"
) {
return getAction(client, writeContract, "writeContract")(writeArgs);
}

Expand Down

0 comments on commit d2e4d0f

Please sign in to comment.