Deploy a Scrypto component to Stokenet and build a frontend that interacts with it.
Time: ~30 minutes | Prerequisites: Completed Scrypto in 10 Minutes + Radix Wallet on Stokenet | Network: Stokenet (testnet)
What You'll Build
A complete "Token Faucet" dApp:
Scrypto component that distributes free tokens (deployed to Stokenet)
Web frontend that connects to wallets and claims tokens
dApp definition that links everything together
This is the full flow: write → deploy → use.
Part 1: The Scrypto Component (10 minutes)
Create the package
scrypto new-package token_faucet
cd token_faucetWrite the blueprint
Replace src/lib.rs with:
use scrypto::prelude::*;
#[blueprint]
mod token_faucet {
struct TokenFaucet {
// Vault holding tokens to distribute
vault: Vault,
// How many tokens to give per claim
amount_per_claim: Decimal,
}
impl TokenFaucet {
/// Creates a new TokenFaucet with a custom token
pub fn instantiate(
token_name: String,
token_symbol: String,
initial_supply: Decimal,
amount_per_claim: Decimal,
) -> Global<TokenFaucet> {
// Create the faucet token
let tokens = ResourceBuilder::new_fungible(OwnerRole::None)
.divisibility(DIVISIBILITY_MAXIMUM)
.metadata(metadata! {
init {
"name" => token_name, locked;
"symbol" => token_symbol, locked;
"description" => "A token from the quickstart faucet", locked;
}
})
.mint_initial_supply(initial_supply)
.into();
// Create and globalize the component
Self {
vault: Vault::with_bucket(tokens),
amount_per_claim,
}
.instantiate()
.prepare_to_globalize(OwnerRole::None)
.globalize()
}
/// Claim free tokens from the faucet
pub fn claim(&mut self) -> Bucket {
assert!(
self.vault.amount() >= self.amount_per_claim,
"Faucet is empty!"
);
self.vault.take(self.amount_per_claim)
}
/// Check faucet status
pub fn get_status(&self) -> (Decimal, Decimal, ResourceAddress) {
(
self.vault.amount(),
self.amount_per_claim,
self.vault.resource_address(),
)
}
}
}Build it
scrypto buildPart 2: Deploy to Stokenet (8 minutes)
Get your Stokenet account ready
Open your Radix Wallet
Ensure you're on Stokenet (Settings → App Settings → Gateways)
Get test XRD: tap your account → "Get XRD Test Tokens"
You need at least 100 XRD for deployment fees.
Deploy using the Developer Console
Click Deploy Package
Connect your wallet
Upload
target/wasm32-unknown-unknown/release/token_faucet.wasmUpload
target/wasm32-unknown-unknown/release/token_faucet.rpdSubmit and sign the transaction
Where are these files?
Save the Package Address from the transaction receipt (starts with package_tdx_2_).
Instantiate the component
In the Developer Console, click Send Raw Transaction
Paste this manifest (replace the placeholder addresses):
CALL_FUNCTION
Address("YOUR_PACKAGE_ADDRESS")
"TokenFaucet"
"instantiate"
"Quickstart Token"
"QST"
Decimal("10000")
Decimal("10");
CALL_METHOD
Address("YOUR_ACCOUNT_ADDRESS")
"deposit_batch"
Expression("ENTIRE_WORKTOP");Submit and sign. Save the Component Address and Resource Address from the receipt.
Part 3: Create a dApp Definition (3 minutes)
A dApp definition tells wallets about your app. It's an account with special metadata.
In your Radix Wallet, create a new account named "Token Faucet dApp"
Go to the Stokenet Dashboard
Search for your new account address
Click Manage → Set as dApp Definition
Fill in: Name, Description, and add your component address to Claimed Entities
Save the dApp Definition Address (your account address).
Part 4: Build the Frontend (9 minutes)
Set up the project
mkdir token-faucet-frontend
cd token-faucet-frontend
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>Token Faucet</title>
<script type="module" src="main.js"></script>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 2rem 1rem;
background: #0a0a0a;
color: #fafafa;
}
h1 { margin-bottom: 0.5rem; }
.subtitle { color: #888; margin-bottom: 2rem; }
.card {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.stat { display: flex; justify-content: space-between; margin: 0.5rem 0; }
.stat-label { color: #888; }
.stat-value { font-weight: 600; font-family: monospace; }
button {
width: 100%;
padding: 1rem;
font-size: 1.1rem;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
background: #00d4aa;
color: #000;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
.success { background: #0f2a1f; border-color: #00d4aa; }
.error { background: #2a0f0f; border-color: #ff4444; }
radix-connect-button { margin-bottom: 1rem; }
#connection-status { display: none; }
</style>
</head>
<body>
<h1>🚰 Token Faucet</h1>
<p class="subtitle">Claim free QST tokens on Stokenet</p>
<radix-connect-button></radix-connect-button>
<div id="connection-status" class="card">
<div class="stat">
<span class="stat-label">Connected</span>
<span class="stat-value" id="account-short">-</span>
</div>
<div class="stat">
<span class="stat-label">Your QST Balance</span>
<span class="stat-value" id="user-balance">-</span>
</div>
</div>
<div class="card">
<div class="stat">
<span class="stat-label">Faucet Supply</span>
<span class="stat-value"><span id="faucet-supply">-</span> QST</span>
</div>
<div class="stat">
<span class="stat-label">Amount Per Claim</span>
<span class="stat-value"><span id="claim-amount">-</span> QST</span>
</div>
</div>
<button id="claim-btn" disabled>Connect Wallet to Claim</button>
<div id="result" class="card" style="display: none;">
<p id="result-message"></p>
</div>
</body>
</html>Create main.js
Replace the placeholder addresses with your actual addresses:
import { RadixDappToolkit, DataRequestBuilder } from '@radixdlt/radix-dapp-toolkit';
// ========================================
// 🔧 REPLACE THESE WITH YOUR ADDRESSES
// ========================================
const DAPP_DEFINITION = 'account_tdx_2_YOUR_DAPP_DEFINITION_ADDRESS';
const COMPONENT_ADDRESS = 'component_tdx_2_YOUR_COMPONENT_ADDRESS';
const TOKEN_ADDRESS = 'resource_tdx_2_YOUR_TOKEN_ADDRESS';
// ========================================
const GATEWAY_URL = 'https://stokenet.radixdlt.com';
// Initialize toolkit
const dappToolkit = RadixDappToolkit({
dAppDefinitionAddress: DAPP_DEFINITION,
networkId: 2,
applicationName: 'Token Faucet',
applicationVersion: '1.0.0',
});
let connectedAccount = null;
// Request accounts on connect
dappToolkit.walletApi.setRequestData(
DataRequestBuilder.accounts().atLeast(1)
);
// Handle wallet connection
dappToolkit.walletApi.walletData$.subscribe(async (walletData) => {
if (walletData.accounts.length > 0) {
connectedAccount = walletData.accounts[0].address;
document.getElementById('connection-status').style.display = 'block';
document.getElementById('account-short').textContent =
connectedAccount.slice(0, 15) + '...' + connectedAccount.slice(-6);
document.getElementById('claim-btn').textContent = 'Claim 10 QST';
document.getElementById('claim-btn').disabled = false;
await fetchUserBalance();
}
});
// Claim tokens
document.getElementById('claim-btn').addEventListener('click', async () => {
if (!connectedAccount) return;
const manifest = `
CALL_METHOD
Address("${COMPONENT_ADDRESS}")
"claim";
CALL_METHOD
Address("${connectedAccount}")
"deposit_batch"
Expression("ENTIRE_WORKTOP");
`;
const result = await dappToolkit.walletApi.sendTransaction({
transactionManifest: manifest,
version: 1,
});
if (result.isOk()) {
showResult('success', 'Claimed 10 QST!');
setTimeout(fetchUserBalance, 3000);
} else {
showResult('error', result.error.message);
}
});
// Helper functions...
fetchFaucetState();Run it
npx serve .Open http://localhost:3000, connect your wallet, and claim some tokens!
Part 5: Verify It Works
Connect your wallet in the dApp
Claim tokens — approve the transaction in your wallet
Check your balance updated in the dApp
Open your Radix Wallet — you should see QST tokens in your account
🎉 Congratulations! You just built and deployed a complete dApp on Radix.
What You Just Learned
Concept | What you did |
|---|---|
Blueprint | Defined a reusable smart contract template |
Component | Instantiated the blueprint with specific parameters |
Resource | Created a new token type (QST) |
dApp Definition | Registered your app's identity on-ledger |
Transaction Manifest | Wrote instructions for the claim transaction |
dApp Toolkit | Connected a frontend to wallets |
Gateway API | Read on-ledger state into your UI |
Extend Your dApp
Prevent users from claiming too often:
// Add to struct
claims: KeyValueStore<ComponentAddress, u64>,
// In claim method
let caller = Runtime::global_caller().unwrap();
let count = self.claims.get(&caller).unwrap_or(0);
assert!(count < 5, "Max claims reached");
self.claims.insert(caller, count + 1);Let users refill the faucet:
pub fn donate(&mut self, donation: Bucket) {
assert!(
donation.resource_address() == self.vault.resource_address(),
"Wrong token type"
);
self.vault.put(donation);
}Emit events for off-chain analytics:
#[derive(ScryptoSbor, ScryptoEvent)]
struct ClaimEvent {
claimer: ComponentAddress,
amount: Decimal,
}
// In claim method
Runtime::emit_event(ClaimEvent {
claimer: Runtime::global_caller().unwrap(),
amount: self.amount_per_claim,
});Next Steps
🎓 Scrypto Fundamentals
Deep dive: resources, authorization, testing, multi-blueprint packages
💻 Frontend Development
Deep dive: Gateway SDK, ROLA authentication, error handling
🔷 Design Patterns
Real-world patterns: badges, DEX, governance, NFTs
🚀 Deploy to Mainnet
Go live with your dApp
Troubleshooting
Transaction fails with 'insufficient balance'
'Faucet is empty' error
Wallet not connecting
Wrong network error
Connect your wallet to join the discussion.

