Skip to content

Instantly share code, notes, and snippets.

@shirotech
Last active August 11, 2024 18:49
Show Gist options
  • Save shirotech/5d934f0d827b79fe9ab74f993d0825a3 to your computer and use it in GitHub Desktop.
Save shirotech/5d934f0d827b79fe9ab74f993d0825a3 to your computer and use it in GitHub Desktop.
This function is used to extend any `viem` client that will add a `getContract` function, and supports calling `read`, `write`, `simulate` with `inputs` and `outputs` objects mapping contract ABI rather than tuples.
import type { AbiParameterKind, ExtractAbiFunction } from "abitype";
import type {
Abi,
AbiParameterToPrimitiveType,
Account,
Address,
Chain,
Client,
ContractFunctionArgs,
ContractFunctionName,
ReadContractParameters,
SimulateContractParameters,
Transport,
WriteContractParameters,
WriteContractReturnType,
} from "viem";
import { readContract, simulateContract, writeContract } from "viem/actions";
type FunctionArgs<
abi extends Abi,
functionName extends ContractFunctionName<abi>,
abiFunction extends ExtractAbiFunction<abi, functionName>,
abiKind extends AbiParameterKind,
> = {
[K in keyof abiFunction[abiKind] as K extends `${number}`
? abiFunction[abiKind][K] extends { name: string }
? abiFunction[abiKind][K]["name"]
: K
: never]: AbiParameterToPrimitiveType<abiFunction[abiKind][K & `${number}`], abiKind>;
};
type ReadableState = "pure" | "view";
type WritableState = "nonpayable" | "payable";
type CoreContractParams = "address" | "abi" | "functionName" | "args";
type FunctionOutput<
abi extends Abi,
functionName extends ContractFunctionName<abi>,
abiFunction extends ExtractAbiFunction<abi, functionName>,
abiKind extends AbiParameterKind = "outputs",
> = abiFunction[abiKind]["length"] extends 1
? AbiParameterToPrimitiveType<abiFunction[abiKind][0], abiKind>
: FunctionArgs<abi, functionName, abiFunction, abiKind>;
export function contractActions<
transport extends Transport,
chain extends Chain,
account extends Account,
>(client: Client<transport, chain, account>) {
return {
getContract<const abi extends Abi>(address: Address, abi: abi) {
return {
async read<
functionName extends ContractFunctionName<abi, ReadableState>,
abiFunction extends ExtractAbiFunction<abi, functionName>,
const args extends FunctionArgs<abi, functionName, abiFunction, "inputs">,
>(
functionName: functionName,
args: args,
params?: Omit<
ReadContractParameters<
abi,
functionName,
ContractFunctionArgs<abi, ReadableState, functionName>
>,
CoreContractParams
>,
): Promise<FunctionOutput<abi, functionName, abiFunction>> {
const result = await readContract(client, {
address,
abi,
functionName,
args: abi
.find(
(item): item is abiFunction =>
item.type === "function" && item.name === functionName,
)!
.inputs.map((input) => args[input.name!]),
...params,
});
const outputs = abi.find(
(item): item is abiFunction => item.type === "function" && item.name === functionName,
)!.outputs;
if (outputs.length === 1) {
return result as any;
}
return outputs.reduce((acc, output, index) => {
acc[output.name || index] = result[index];
return acc;
}, {} as any);
},
async write<
functionName extends ContractFunctionName<abi, WritableState>,
abiFunction extends ExtractAbiFunction<abi, functionName>,
const args extends FunctionArgs<abi, functionName, abiFunction, "inputs">,
>(
functionName: functionName,
args: args,
params?: Omit<
WriteContractParameters<
abi,
functionName,
ContractFunctionArgs<abi, WritableState, functionName>,
chain,
account
>,
CoreContractParams
>,
): Promise<WriteContractReturnType> {
return writeContract(client, {
address,
abi,
functionName,
args: abi
.find(
(item): item is abiFunction =>
item.type === "function" && item.name === functionName,
)!
.inputs.map((input) => args[input.name!]),
...params,
} as any);
},
async simulate<
functionName extends ContractFunctionName<abi, WritableState>,
abiFunction extends ExtractAbiFunction<abi, functionName>,
const args extends FunctionArgs<abi, functionName, abiFunction, "inputs">,
>(
functionName: functionName,
args: args,
params?: Omit<
SimulateContractParameters<
abi,
functionName,
ContractFunctionArgs<abi, WritableState, functionName>,
chain
>,
CoreContractParams
>,
): Promise<FunctionOutput<abi, functionName, abiFunction>> {
const { result } = await simulateContract(client, {
address,
abi,
functionName,
args: abi
.find(
(item): item is abiFunction =>
item.type === "function" && item.name === functionName,
)!
.inputs.map((input) => args[input.name!]),
...params,
} as any);
const outputs = abi.find(
(item): item is abiFunction => item.type === "function" && item.name === functionName,
)!.outputs;
if (outputs.length === 1) {
return result as any;
}
return outputs.reduce((acc, output, index) => {
acc[output.name || index] = result[index];
return acc;
}, {} as any);
},
};
},
};
}
import { createWalletClient, erc20Abi, http } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { bsc } from "viem/chains";
import { contractActions } from "./contractActions";
const client = createWalletClient({
account: privateKeyToAccount(generatePrivateKey()),
chain: bsc,
transport: http(),
}).extend(contractActions);
const contract = client.getContract("0x55d398326f99059ff775485246999027b3197955", erc20Abi);
const result = await contract.simulate("approve", {
spender: "0x10ed43c718714eb63d5aa57b78b54704e256024e",
amount: 100n,
});
console.log(result);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment