Skip to main content

ContractAppContext

How to use ContractAppContext

🚩 1. Generating types for your contract

First, you'll have to generate your contract types for Hardhat and external contracts.

To do this, you can use eth-sdk or typechain with hardhat to generate your contracts. Then add them to a folder, such as generated/contract-types.

tip

You can see how this is done in the scaffold-eth-typescript repo

How to use eth-sdk for external contracts

Scaffold-eth-typescript uses eth-sdk to generate types and an ABI for use by external contracts.

📝 Note: This would be a dev dependency on your project.

tip

You can learn more at the eth-sdk Github

How to use Hardhat with typechain

Check out the excellent typechain docs. You can also find an example in scaffold-eth-typescript hardhat.config.ts.

🚩 2. Creating the context with contractsContextFactory

You need to create a config that returns a config of your contracts. This would be heterogenous key-value pair. Each value is generated by the helper functions in eth-hooks.

For example:

// a function that generates the config. Note that your types have to exist already!
export const contractConnectorConfig = () => {
try {
const result = {
// 🙋🏽‍♂️ Add your hadrdhat contracts here
YourContract: createConnectorForHardhatContract(
'YourContract',
hardhatContracts.YourContract__factory,
hardhatContractsJson
),

// 🙋🏽‍♂️ Add your external contracts here, make sure to define the address in `externalContractsConfig.ts`
DAI: createConnectorForExternalContract('DAI', externalContracts.DAI__factory, externalContractsAddressMap),
UNI: createConnectorForExternalContract('UNI', externalContracts.UNI__factory, externalContractsAddressMap),

// 🙋🏽‍♂️ Add your external abi here (unverified contracts)`
// DAI: createConnectorForExternalAbi('DAI', { 1: {address: 'xxxx'}}, abi),
} as const;

return result;
} catch (e) {
console.error(
'❌ contractConnectorConfig: ERROR with loading contracts please run `yarn contracts:build or yarn contracts:rebuild`. Then run `yarn deploy`!',
e
);
}

return undefined;
};

// create a type from the return value of the function above
export type TAppConnectorList = NonNullable<ReturnType<typeof contractConnectorConfig>>;

Use contractContextFactory to create your hooks and context in your app from the above configuration.

You can copy the code below and use it to get started fast:

// you're passing in function `contractConnectorConfig` from above into the factory.  You then have to use the type we defined to type the factory outputs.
export const {
ContractsAppContext,
useAppContractsActions,
useAppContracts,
useLoadAppContracts,
useConnectAppContracts,
} = contractsContextFactory<
/* the contractNames (keys) in config output */
keyof TAppConnectorList,
/* the type of the config output */
TAppConnectorList,
/* A type that infers the value of each contractName: contract pair*/
TTypedContract<keyof TAppConnectorList, TAppConnectorList>
>(contractConnectorConfig);
tip

See scaffold-eth-typescript contractContext.tsx and contractConnectorConfig.ts for full examples of how to do this

🚩 3. Using hooks to get your contracts

Now that you've created the context, you can use hooks in your app.

The first step is to load your contracts using the hooks you've created with the factory:

// 🛻 load contracts
useLoadAppContracts();

Next you'll want to connect the contracts:

// 🏭 connect to  contracts for current network & signer
useConnectAppContracts(asEthersAdaptor(ethersContext));

// 🏭 connect to contracts for mainnet network & signer
const [mainnetAdaptor] = useEthersAdaptorFromProviderOrSigners(mainnetProvider);
useConnectAppContracts(mainnetAdaptor);

Now you can get typed contracts anywhere in your app:

const yourContract = useAppContracts('YourContract', ethersContext.chainId);
const mainnetDai = useAppContracts('DAI', NETWORKS.mainnet.chainId);

And you can do cool stuff like read values from your contracts using the useContractReader hook:

// keep track of a variable from the contract in the local React state:
const [purpose, update] = useContractReader(
/* the contract */
yourContract,
/* the contract variable or function to read */
yourContract?.purpose,
/* the arguments, they are typed tuple */
[],
/* optional: if you want your contracts to only update on event */
yourContract?.filters.SetPurpose()
);

Or like this:

// keep track of a variable from the contract in the local React state:
const [purpose, update] = useContractReader(
/* the contract */
yourContract,
/* the contract variable or function to read */
yourContract?.purpose,
/* the arguments, they are typed tuple */
[],
undefined,
/* optional: update every 10 blocks */
{ blockNumberInterval: 10 }
);