A reusable, modular wallet connect button for Apertum (EVM, chain 2786) apps. Originally extracted from the OpenPlaza NFT Marketplace UI and packaged so any platform on Apertum can drop it in and pick only the features it wants.
The full set of features OpenPlaza ships in its navbar button, exposed as independently togglable props or contracts:
| Feature | Prop / Config | Description |
|---|---|---|
| EIP-6963 + WalletConnect connect | always on | Discovers injected wallets (MetaMask, Rabby, Coinbase, Trust, Xova, …) and falls back to WalletConnect. |
| Wallet selection modal | always on | One modal instance shared across multiple button instances on a page. |
| ANS domain display | showSections.domain, contracts.ansRegistry |
Shows the user's .aptm domain in place of the raw 0x… address. |
| ANS-themed inline styles | contracts.ansRegistry |
If the domain has theme colors, the button uses inline gradient + accent color. |
| APTM balance | showSections.balance |
Auto-refreshes every 15s (configurable). |
| APTM send modal | enableSend |
Send to a 0x… address or to an *.aptm ANS name. |
| Music library + mini player | contracts.audioNft, <AudioPlayerProvider> |
Lazy-loads owned audio NFTs + artist albums; "Shuffle My Music" plays them in the shared AudioPlayerContext. |
| Pending offers badge / dropdown | contracts.marketplace |
Polls your marketplace listings every 60s, shows a red badge on the button + a dropdown list of active offers, and auto-opens a modal on connect. |
| Privacy toggle (hide balance/address) | always on | Eye / EyeOff icon in the dropdown header. |
| Custom actions | customActions |
Inject any extra label + icon + onClick(address) into the dropdown. |
npm install ethers@5.7.2 @walletconnect/ethereum-provider react-hot-toast lucide-react react-router-dom(Those are peer dependencies — they're what OpenPlaza already used.)
import { BrowserRouter } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import {
ANSWalletButton,
ANSWalletProvider,
AudioPlayerProvider,
WalletSelectionModal,
} from 'answallet-button'; // (or relative import while running locally)
export function AppShell() {
return (
<BrowserRouter>
<ANSWalletProvider>
<AudioPlayerProvider>
<YourApp />
<WalletSelectionModal />
<Toaster position="top-center" />
<ANSWalletButton
contracts={{
ansRegistry: '0xEE041dF9A7CF6435c049F80F6D7c052dC4580475',
publicResolver: '0xd98F3e67960a9671FF69baf265CbCC26c6E88263',
reverseRegistrar: '0x7b1dB4AAfd7531e267c0f4b68C69c97BF7d1D36f',
rpcUrl: 'https://rpc.apertum.io/ext/bc/YDJ1r9RMkewATmA7B35q1bdV18aywzmdiXwd9zGBq3uQjsCnn/rpc',
chainId: 2786,
chainName: 'Apertum',
// optional, only needed if you want offers / music:
marketplace: '0x9D6818758Bf0419C4C3f8500959569781aDD303c',
audioNft: '0x0000000000000000000000000000000000000000',
}}
enableSend
showBalanceInButton
onConnect={(address) => console.log('connected', address)}
onDisconnect={() => console.log('disconnected')}
/>
</AudioPlayerProvider>
</ANSWalletProvider>
</BrowserRouter>
);
}interface WalletButtonConfig {
contracts: {
ansRegistry: string; // required for ANS domain lookups
publicResolver: string;
reverseRegistrar: string;
rpcUrl: string; // public RPC for ANS reads
chainId: number;
chainName?: string;
marketplace?: string; // optional: enables pending offers
audioNft?: string; // optional: enables music library
};
showSections?: {
balance?: boolean; // default true
domain?: boolean; // default true
address?: boolean; // default true
textRecords?: boolean; // default false
network?: boolean; // default true
};
customActions?: Array<{ // appended to the dropdown
label: string;
icon?: React.ReactNode;
onClick: (walletAddress: string) => void;
className?: string;
}>;
theme?: { // override defaults
primaryColor?: string;
backgroundColor?: string;
textColor?: string;
borderRadius?: string;
buttonHoverColor?: string;
};
onConnect?: (address: string) => void;
onDisconnect?: () => void;
showBalanceInButton?: boolean; // default true
themeUpdateInterval?: number; // default 30000ms
enableSend?: boolean; // default false
balanceRefreshInterval?: number; // default 15000ms
refreshBalanceOnDropdownOpen?: boolean; // default true
}<ANSWalletButton
contracts={contracts}
customActions={[
{
label: 'My NFTs',
icon: <Gem size={14} />,
onClick: (addr) => navigate(`/profile/${addr}`),
},
{
label: 'View on Explorer',
icon: <ExternalLink size={14} />,
onClick: (addr) =>
window.open(`https://explorer.apertum.io/address/${addr}`, '_blank'),
},
]}
/>The button automatically prepends the built-in items (balance, address, copy, disconnect, send, shuffle, offers) so your custom items appear at the bottom of the dropdown.
The button is intentionally one file (src/components/wallet/ANSWalletButton.tsx).
The most common pattern to add a new section:
- Add a new optional field to
WalletButtonConfig(e.g.myFeature?: boolean). - In the dropdown JSX, conditionally render a new section that calls a feature hook you bring in.
- If the feature needs a contract, add it to
contracts.*and thread it through. - If the feature needs shared state across the app, wrap it in a new context
(see
AudioPlayerContext.tsxfor the pattern) and add it to the required providers in the README example.
That's it. No plugin loader, no overengineered API — the file is small enough that forking and adding a section is the supported path.
Two layers:
- Per-instance theme via the
themeprop (overrides the defaults for this one button render). - Domain theme: if the connected wallet has an ANS domain with theme
colors, the button uses those colors via inline styles. To opt out, pass a
themeprop and the component will prefer it.
Default theme is gold-on-dark to match OpenPlaza. Override in one prop to match your brand.
| File | What it does |
|---|---|
src/config/walletConfig.ts |
Chain definition (RPC, chainId, explorer), WalletConnect projectId, known-wallet RDNS map. |
src/config/ansConfig.ts |
ANS contract addresses + display defaults. |
src/config/audioContracts.ts |
Audio NFT contract addresses, IPFS gateway list. |
src/lib/themes.ts |
Built-in ColorTheme enum (for ANS domain themes). |
The repo also exports smaller, drop-in pieces you can use outside the main button:
<ANSDomainCard />— a Pokemon-style card for displaying a single ANS domain. Great for "browse my domains" pages.<WalletSelectionModal />— the wallet picker. Renders once for the whole app; the button uses a hook to claim it.useUnifiedWallet(),useWalletDiscovery(),useWalletModal()— the underlying hooks. Use them if you want to build your own custom button.usePendingOffers(address)— gives you the same offer data the button uses, so you can render your own UI.
npm install
npm run devThe dev page shows the button in the top-right, feature toggles to flip sections on/off, a sample ANS card render, and a code snippet showing the minimal integration.
vite.config.ts already includes the Web3 CSP entries you need. If you
deploy behind your own hosting, make sure the production CSP still allows:
connect-src→https://rpc.apertum.io(or your own RPC),wss://relay.walletconnect.com,https://*.walletconnect.com,https://explorer.apertum.ioimg-src→https://ipfs.io,https://gateway.pinata.cloud
MIT. Use it, fork it, ship it.