Last active
August 11, 2024 18:49
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}, | |
}; | |
}, | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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