Author: @NoahSaso
Adapter | Summary |
---|---|
DaoVotingCw20Staked | CW20 token staked balance voting. |
DaoVotingCw4 | CW4 group voting, multisig style. |
DaoVotingNativeStaked | Native token staked balance voting. |
Fallback | Fallback to allow for DAO page rendering even with an unsupported voting module. |
Location | Summary |
---|---|
adapters | Voting module adapters. |
components | Components shared between adapters. |
react | The external React interface used by apps and packages when using this voting module adapter system. This uses the core logic under the hood. |
core.ts | The core logic that matches and loads an adapter from the available adapters. |
This is a voting module adapter package. It creates a common interface for various components and pieces of data that apps need to access which change based on the voting module used by the DAO. For example, a DAO that uses a CW20 governance token for voting power will need to display staking interfaces and wallet token balances, whereas a CW4 multisig will just need to display member voting weights.
Before this adapter system was implemented, we had a VotingModuleType
enum
that was accessible everywhere via a Provider, and we had conditional statements
littering the codebase which contributed to poor readability and extensibility.
This made data fetching and error handling difficult and unclear: because we
can't conditionally call React hooks, we were attempting to load all data that
could potentially be needed by any voting module, using conditional statements
to check which data we expected to be defined. This led to, unsurprisingly,
confusing and unreadable code. And we only had two voting modules at the time of
writing this!
Add the VotingModuleAdapterProvider
to your app, likely at a high enough level
to encompass the entire app or entire pages. At this point, you must already
know the contract name of the voting module (from the info
query) so that the
correct adapter can be chosen and its interface passed down to descendant
components. You will also need to pass some options, like the contract address
of the DAO's core contract.
import { VotingModuleAdapterProvider } from '@dao-dao/stateful/voting-module-adapter'
const App = () => (
<VotingModuleAdapterProvider
contractName={votingModuleContractName}
options={{
votingModuleAddress,
coreAddress,
}}
>
{children}
</VotingModuleAdapterProvider>
)
In the @dao-dao/dapp
Next.js app, votingModuleAddress
,
votingModuleContractName
, and coreAddress
are fetched via getStaticProps
and passed to a common page wrapper component, on each page.
Code
const coreAddress = context.params.address as string
const cwClient = await cosmWasmClientRouter.connect(CHAIN_RPC_ENDPOINT)
const coreClient = new CwCoreV1QueryClient(cwClient, coreAddress)
const votingModuleAddress = await coreClient.votingModule()
const votingModuleContractName = (
await cwClient.queryContractSmart(votingModuleAddress, {
info: {},
})
).info.contract
Now that the library has been setup, we can use the hook anywhere as a descendant of the Provider to access the voting module adapter interface.
import { SuspenseLoader } from '@dao-dao/stateful'
import { Loader } from '@dao-dao/stateless'
import { useVotingModuleAdapter } from '@dao-dao/stateful/voting-module-adapter'
const DaoThinInfoDisplay = () => {
const {
ui: { DaoThinInfoContent },
} = useVotingModuleAdapter()
return (
<SuspenseLoader fallback={<Loader />}>
<DaoThinInfoContent />
</SuspenseLoader>
)
}
It's very easy to write an adapter, especially because TypeScript will tell you what fields and types you need based on the shared interface. You can also reference the existing adapters which follow the exact same pattern.
All you need to do is define an adapter object and register it using the registration function shown above.
import { VotingModuleAdapter } from '@dao-dao/types/voting-module-adapter'
const MyVotingModuleAdapter: VotingModuleAdapter = {
id: 'my_voting_module_adapter_id',
contractNames: ['my_voting_module'],
load: (options) => ({
hooks: {
...
},
components: {
...
},
}),
}