Build a Frontend (SDK)
The goal
By the end of this page you'll have a tiny single-page app that connects to a Valdium wallet, reads a token balance, and sends a transfer. The same skeleton scales up to any dapp; the rest is product work, not protocol work.
Project setup
From an empty directory:
npm init -y
npm i @valdium/sdk
npm i -D viteVite gives you a dev server and a build step; the SDK gives you the actual chain access. No framework choice yet — we'll do this in plain HTML+JS to keep the dependency tree minimal, then point at React/Svelte/Vue notes at the end.
Create index.html:
<!doctype html>
<html>
<body>
<button id="connect">Connect wallet</button>
<p id="status">Not connected</p>
<script type="module" src="/app.js"></script>
</body>
</html>The connect button
Create app.js:
import { ValdiumProvider } from '@valdium/sdk';
const $ = (s) => document.querySelector(s);
$('#connect').addEventListener('click', async () => {
if (!window.valdium) {
$('#status').textContent = 'Valdium wallet not detected — install the extension';
return;
}
const provider = new ValdiumProvider(window.valdium);
const [address] = await provider.request({
method: 'eth_requestAccounts',
});
$('#status').textContent = \`Connected: \${address.slice(0,6)}…\${address.slice(-4)}\`;
window.__provider = provider;
window.__address = address;
});Run npx vite and click the button. The Valdium Wallet popup should appear; approve, and your address should land in the status line.
Reading a balance
Add a balance display. First the markup — drop this into the body before the script tag:
<p>Balance: <span id="balance">—</span> VLD</p>Then in app.js, after the connect handler runs:
async function refreshBalance() {
const wei = await window.__provider.request({
method: 'eth_getBalance',
params: [window.__address, 'latest'],
});
const vld = Number(BigInt(wei)) / 1e18;
$('#balance').textContent = vld.toFixed(4);
}
// initial load
refreshBalance();
// poll every block (~2s on testnet)
setInterval(refreshBalance, 2000);If you don't have testnet VLD, hit the faucet and refresh. The balance should appear within a block.
Sending a transfer
Add a form:
<input id="to" placeholder="0x..." />
<input id="amount" placeholder="amount in VLD" />
<button id="send">Send</button>And the handler:
$('#send').addEventListener('click', async () => {
const to = $('#to').value.trim();
const amount = $('#amount').value.trim();
if (!/^0x[0-9a-fA-F]{40}$/.test(to)) return alert('bad address');
if (!/^[0-9.]+$/.test(amount)) return alert('bad amount');
const wei = (BigInt(Math.floor(parseFloat(amount) * 1e9)) * 10n**9n).toString();
try {
const hash = await window.__provider.request({
method: 'eth_sendTransaction',
params: [{ from: window.__address, to, value: wei }],
});
$('#status').textContent = \`Sent. Tx \${hash.slice(0,8)}…\`;
refreshBalance();
} catch (err) {
if (err.code === 4001) return; // user rejected — silent
$('#status').textContent = 'Error: ' + err.message;
}
});The wallet pops up, the user confirms, the chain mines a block within ~2 seconds, and the balance refreshes itself on the next poll. That's the full happy path.
Three things that bite first-time dapp authors
Base units vs VLD. The chain stores balances in wei (1 VLD = 1,000,000 wei). Reading eth_getBalance returns wei; sending in eth_sendTransaction wants wei. Convert at the UI edge, never internally — most rounding bugs come from converting too early.
User-rejected vs error. Code 4001 means the user clicked Reject on the wallet popup. Treat it as a silent no-op, never as a red error toast — the user already knows what they did.
Account changes. The user might switch accounts in the wallet without notifying your page. Listen for provider.on('accountsChanged', ...) and update your state, or refetch the address before every send.
Framework adapters
The SDK ships first-class hooks for React and Svelte. The React one looks like:
import { useValdium, useBalance } from '@valdium/react';
function App() {
const { connect, address, isConnecting } = useValdium();
const balance = useBalance(address);
if (!address) return <button onClick={connect}>Connect</button>;
return <p>{address}: {balance} VLD</p>;
}Same idea, smaller surface, more idiomatic if you're already in a React codebase.