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 }
);