Connect a web page to the Radix Wallet and interact with on-ledger components.
Time: ~15 minutes | Prerequisites: Node.js 18+, modern browser | Network: Stokenet (testnet)
What You'll Build
A single HTML page that:
Shows a "Connect Wallet" button
Displays the user's account address when connected
Reads state from an on-ledger component (Gumball Club)
Sends a transaction to buy a gumball
No frameworks required—just HTML and JavaScript. You can adapt this to React, Vue, or any framework afterward.
1. Set Up Your Environment (3 minutes)
Install the Radix Wallet
During setup:
Create a new wallet
Switch to Stokenet (Settings → App Settings → Gateways → Stokenet)
Create an account
Get test XRD from the faucet (tap the account → "Get XRD Test Tokens")
Stokenet only: Make sure your wallet is set to Stokenet, not Mainnet. Test XRD has no real value.
Install the Radix Connector
Install the browser extension:
Link it to your mobile wallet by scanning the QR code in the extension.
2. Create the Project (2 minutes)
mkdir radix-frontend-quickstart
cd radix-frontend-quickstart
npm init -y
npm install @radixdlt/radix-dapp-toolkitCreate index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Radix Frontend Quickstart</title>
<script type="module" src="main.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
.card { background: #f5f5f5; padding: 1rem; border-radius: 8px; margin: 1rem 0; }
button { padding: 0.5rem 1rem; font-size: 1rem; cursor: pointer; }
#account-info { display: none; }
radix-connect-button { display: block; margin: 1rem 0; }
</style>
</head>
<body>
<h1>🍬 Gumball Buyer</h1>
<!-- The Radix Connect Button -->
<radix-connect-button></radix-connect-button>
<div id="account-info" class="card">
<p><strong>Connected:</strong> <span id="account-address">-</span></p>
<p><strong>XRD Balance:</strong> <span id="xrd-balance">-</span></p>
</div>
<div class="card">
<h3>Gumball Machine</h3>
<p><strong>Price:</strong> <span id="gumball-price">Loading...</span> XRD</p>
<p><strong>Supply:</strong> <span id="gumball-supply">Loading...</span></p>
<button id="buy-btn" disabled>Buy Gumball (1 XRD)</button>
</div>
<div id="result" class="card" style="display:none;">
<h3>Transaction Result</h3>
<p id="result-text"></p>
</div>
</body>
</html>3. Initialize the dApp Toolkit (3 minutes)
Create main.js:
import { RadixDappToolkit, DataRequestBuilder } from '@radixdlt/radix-dapp-toolkit';
// Stokenet Gumball Machine addresses
const GUMBALL_COMPONENT = 'component_tdx_2_1czg8zvl2q2mcawjqavzyq2ls2az5zak2rr2ntkvfv7x4d3eflvsxg7';
const XRD_ADDRESS = 'resource_tdx_2_1tknxxxxxxxxxradaborxnf7ta7nev3jqxwvq8tkvhla';
// Initialize the toolkit
const dappToolkit = RadixDappToolkit({
dAppDefinitionAddress: 'account_tdx_2_12y6vz59fmzgfsrn7w3plqk8wnlwdtd2e76hrp32ax7axe7drr7lxk9',
networkId: 2, // Stokenet
applicationName: 'Gumball Quickstart',
applicationVersion: '1.0.0',
});
// Get DOM elements
const accountInfo = document.getElementById('account-info');
const accountAddress = document.getElementById('account-address');
const xrdBalance = document.getElementById('xrd-balance');
const gumballPrice = document.getElementById('gumball-price');
const gumballSupply = document.getElementById('gumball-supply');
const buyBtn = document.getElementById('buy-btn');
const result = document.getElementById('result');
const resultText = document.getElementById('result-text');
// Store connected account
let connectedAccount = null;This sets up the Radix dApp Toolkit with:
A dApp definition address (identifies your app to the wallet)
Network ID 2 for Stokenet (use 1 for Mainnet)
App name/version shown in the wallet
4. Handle Wallet Connection (3 minutes)
Add this to main.js:
// Subscribe to wallet connection state
dappToolkit.walletApi.setRequestData(
DataRequestBuilder.accounts().atLeast(1)
);
dappToolkit.walletApi.walletData$.subscribe((walletData) => {
if (walletData.accounts.length > 0) {
// User connected
connectedAccount = walletData.accounts[0].address;
accountInfo.style.display = 'block';
accountAddress.textContent = shortenAddress(connectedAccount);
buyBtn.disabled = false;
// Fetch their XRD balance
fetchAccountBalance(connectedAccount);
} else {
// User disconnected
connectedAccount = null;
accountInfo.style.display = 'none';
buyBtn.disabled = true;
}
});
// Helper to shorten addresses for display
function shortenAddress(address) {
return address.slice(0, 20) + '...' + address.slice(-8);
}The toolkit automatically handles:
Detecting the Radix Connector extension
Communication with the mobile wallet
Connection state persistence
5. Read On-Ledger State (2 minutes)
Add these functions to main.js:
// Fetch account balance using Gateway API
async function fetchAccountBalance(address) {
const response = await fetch('https://stokenet.radixdlt.com/state/entity/details', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
addresses: [address],
aggregation_level: 'Vault',
}),
});
const data = await response.json();
const fungibles = data.items[0]?.fungible_resources?.items || [];
const xrd = fungibles.find(r => r.resource_address === XRD_ADDRESS);
xrdBalance.textContent = xrd ? parseFloat(xrd.vaults.items[0].amount).toFixed(2) : '0';
}
// Fetch gumball machine state
async function fetchGumballState() {
const response = await fetch('https://stokenet.radixdlt.com/state/entity/details', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
addresses: [GUMBALL_COMPONENT],
aggregation_level: 'Vault',
}),
});
const data = await response.json();
gumballPrice.textContent = '1'; // The Gumball Club uses 1 XRD
const fungibles = data.items[0]?.fungible_resources?.items || [];
const gumballs = fungibles.find(r => r.resource_address !== XRD_ADDRESS);
gumballSupply.textContent = gumballs ? gumballs.vaults.items[0].amount : 'Unknown';
}
// Load initial state
fetchGumballState();This uses the Gateway API to query ledger state—the same API the Radix Dashboard uses.
6. Send a Transaction (2 minutes)
Add the transaction logic:
// Buy a gumball
buyBtn.addEventListener('click', async () => {
if (!connectedAccount) return;
buyBtn.disabled = true;
buyBtn.textContent = 'Sending...';
// Build the transaction manifest
const manifest = `
CALL_METHOD
Address("${connectedAccount}")
"withdraw"
Address("${XRD_ADDRESS}")
Decimal("1");
TAKE_FROM_WORKTOP
Address("${XRD_ADDRESS}")
Decimal("1")
Bucket("xrd_bucket");
CALL_METHOD
Address("${GUMBALL_COMPONENT}")
"buy_gumball"
Bucket("xrd_bucket");
CALL_METHOD
Address("${connectedAccount}")
"deposit_batch"
Expression("ENTIRE_WORKTOP");
`;
try {
const txResult = await dappToolkit.walletApi.sendTransaction({
transactionManifest: manifest,
version: 1,
});
if (txResult.isOk()) {
result.style.display = 'block';
resultText.textContent = `Success! Transaction ID: ${txResult.value.transactionIntentHash}`;
// Refresh balances
setTimeout(() => fetchAccountBalance(connectedAccount), 3000);
} else {
result.style.display = 'block';
resultText.textContent = `Error: ${txResult.error.message}`;
}
} catch (err) {
result.style.display = 'block';
resultText.textContent = `Error: ${err.message}`;
}
buyBtn.disabled = false;
buyBtn.textContent = 'Buy Gumball (1 XRD)';
});The transaction manifest describes exactly what happens:
Withdraw 1 XRD from your account
Put it in a temporary bucket
Call
buy_gumballwith that bucketDeposit whatever comes back (gumball + any change)
7. Run It
npx serve .Open http://localhost:3000 in your browser.
Click the Connect button
Approve the connection in your Radix Wallet
Click Buy Gumball
Review and approve the transaction in your wallet
You should receive a gumball NFT!
What Just Happened?
You built a complete dApp frontend that:
Step | What happened |
|---|---|
Connect | Wallet shared your account address (with your permission) |
Read state | Gateway API queried the ledger for component/account data |
Build transaction | Manifest described the asset movements |
Sign & submit | Wallet showed you exactly what would happen, you approved |
Confirm | Transaction was processed by the network |
The wallet never shared your private keys. It only signed the transaction locally.
Key Concepts
Transaction Manifests
The Worktop
Gateway API vs dApp Toolkit
Next Steps
🚀 Add Your Own Component
Deploy a Scrypto component and connect your frontend to it
💻 Frontend Development Guide
Deep dive into the dApp Toolkit, Gateway SDK, and ROLA authentication
Quick Reference
// Initialize toolkit
const dappToolkit = RadixDappToolkit({
dAppDefinitionAddress: 'account_...',
networkId: 2, // 1 = Mainnet, 2 = Stokenet
applicationName: 'My App',
});
// Request account access
dappToolkit.walletApi.setRequestData(
DataRequestBuilder.accounts().atLeast(1)
);
// Subscribe to connection state
dappToolkit.walletApi.walletData$.subscribe((data) => {
console.log('Accounts:', data.accounts);
});
// Send a transaction
const result = await dappToolkit.walletApi.sendTransaction({
transactionManifest: manifestString,
version: 1,
});
// Gateway API base URLs
// Mainnet: https://mainnet.radixdlt.com
// Stokenet: https://stokenet.radixdlt.comConnect your wallet to join the discussion.

