Frontend Web3 Shared repo
The purpose of this repo is to have shared solutions for typical web3 related problems.
Transactions, signing, provider etc
Limitations
This is not a 1 size fit all library and more like a set of recipes to be used across multiple BGD projects. All solutions heavily rely on BGD tech stack, such as viem.sh, zustand, wagmi.sh. Outside this stack using BGD solutions will be a problem and repo is provided as is. Feel free to use it as example
Although it is possible to use TransactionsSlice
separately from WalletSlice
, but it is unrealistic scenario.
Requirements
Each solution should provide a complete flow with clear boundaries and entry point for custom logic
Installation
npm
npm i @bgd-labs/frontend-web3-utils
yarn
yarn add @bgd-labs/frontend-web3-utils
TransactionsSlice
Is used as a “black box” to work with transactions life cycle in a DAPP.
It will add, wait, save them to localstorage
and do all the necessary logic to check for a network status and updates
Transaction observer flow
First we need to define callbackObserver - the component which will be called after tx got broadcast into a blockchain, like so:
...createTransactionsSlice<TransactionsUnion>({
txStatusChangedCallback: (tx) => {
switch (tx.type) {
case "somethingNotVeryImportantHappened":
console.log(tx.payload.buzz);
return;
case "somethingImportantHappened":
console.log(tx.payload.fuzz);
return;
}
},
})(set, get)
TransactionUnion
will be different for each application and is used to associate payload type by transaction type
and clients: Record<number, PublicClient>;
Clients will be used to watch tx on multiple chains if needed.
To make it all work, each tx should go through .executeTx
callback. It’s fire and forget flow at the end callbackObserver
will fire tx with type ‘wear’, custom payload and all the data from transaction.
const tx = await get().executeTx({
body: () => {
return get().boredNFTService.wear(tokenID, {
location: collectionAddress,
id: svgId,
});
},
params: {
type: 'wear',
payload: { tokenID, collectionAddress },
},
});
WalletSlice
WalletSlice is a set of ready solutions to work with wagmi.sh
It will do appropriate logic to handle different connectors type and save the required states to zustand store
Since we don’t use wagmi.sh hooks to work with wagmi connectors, but write data directly to our store, we needed our custom Wagmi provider. Custom <WagmiProvider />
is required to make WalletSlice
work.
Example of how to use <WagmiProvider />
in your own app
yourapp/WagmiProvider.tsx
→
import { WagmiProvider as BaseWagmiProvider } from '@bgd-labs/frontend-web3-utils';
import { useStore } from '../../store';
import { CHAINS } from '../../utils/chains';
export default function WagmiProvider() {
return (
<BaseWagmiProvider
connectorsInitProps={{
appName: 'YourAppName',
chains: CHAINS,
defaultChainId: 1, // optional
wcParams: { // optional need for wallet connector work
projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID || '',
metadata: {
name: 'wagmi',
description: 'my wagmi app',
url: 'https://wagmi.sh',
icons: ['https://wagmi.sh/icon.png'],
},
},
}}
useStore={useStore}
/>
);
}
yourapp/App.tsx
→
import WagmiProvider from '../src/web3/components/WagmiProvider';
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<WagmiProvider />
<Component {...pageProps} />
</>
);
}
export default MyApp;
Once the setup is done you can finally initialize web3Slice
export const createWeb3Slice: StoreSlice<IWeb3Slice> = (
set,
get,
) => ({
...createWalletSlice({
walletConnected: () => {
get().connectSigner();
},
})(set, get),
});
walletConnected
is a callback which will be executed once wallet is connected, meaning get().activeWallet is set.
All the logic is going through store and NOT through wagmi.sh hooks
After preparing the slices, you need to initialize the app zustand store
import { create, StoreApi } from 'zustand';
import { devtools } from 'zustand/middleware';
import {
createTransactionsSlice,
TransactionsSlice,
} from '../transactions/store/transactionsSlice';
import { createWeb3Slice, IWeb3Slice } from '../web3/store/web3Slice';
type RootState = IWeb3Slice & TransactionsSlice;
const createRootSlice = (
set: StoreApi<RootState>['setState'],
get: StoreApi<RootState>['getState'],
) => ({
...createWeb3Slice(set, get),
...createTransactionsSlice(set, get),
});
export const useStore = create(devtools(createRootSlice, { serialize: true }));
After all the init steps are done, you can finally use everything you need to interact with web3
Wallet connection example:
import { WalletType } from '@bgd-labs/frontend-web3-utils';
import { useStore } from '../../store';
export function WalletItem({ walletType }: { walletType: WalletType }) {
const { activeWallet, connectWallet, disconnectActiveWallet } = useStore();
const isActive = useMemo(() => {
return activeWallet?.walletType === walletType;
}, [walletType, activeWallet]);
const handleWalletClick = async () => {
if (isActive) {
await disconnectActiveWallet();
} else {
await connectWallet(walletType);
}
};
return (
<button onClick={handleWalletClick}>
{isActive ? 'Disconnect' : 'Connect'} wallet {walletType}
</button>
);
}
You can find an example of what a React application using this library should look like, here: example repo