Tutorial: Building a Kaspa Block Scanner with the WASM SDK
Send a transaction with a payload and then retrieve it in real-time
Full demo with all the files can be downloaded from the minKasWasm repo on github. If you missed prior guides, here’s A Beginner’s Guide to Mastering Kaspa WASM SDK in the Browser and Unlocking Secure Communication and Mastering Encryption with Kaspa WASM SDK
Introduction
Kaspa’s DAG architecture makes block scanning a bit different from traditional blockchain systems. Instead of a single chain of blocks, Kaspa allows multiple blocks to coexist and reference each other. This tutorial shows how to build a block scanner in the browser using the Kaspa WASM SDK, with a simple wallet integration for sending transactions.
What the Demo Does
Connects to a Kaspa node (mainnet or testnet).
Subscribes to WASM SDK’s block-added events.
Scans each block’s transactions for payloads containing a search string.
Displays matching blocks separately.
Provides a wallet interface to create an address, check balance, and send transactions with optional payloads.
Important Notes
Payloads are not encrypted.
Transaction payloads are hex‑encoded strings. Anyone scanning the network can decode and read them. This makes them suitable for simple metadata or signaling, but not for confidential information.Kaspa’s DAG means duplicates are normal.
Because Kaspa allows parallel block creation, the same transaction may appear in multiple blocks. This is expected behavior and not an error.Public nodes may filter blocks.
If you connect through a public resolver, the node operator may limit how many blocks are relayed. You could miss some transactions. For full visibility, run your own private node and connect directly.
Step‑by‑Step Walkthrough
a. Connect to a Node
Enter an IP:port or leave blank to use the public resolver.
Choose a network (
mainnet,testnet-10, ortestnet-11).Click Connect.
The SDK establishes a WebSocket connection to the Kaspa node.
b. Start the Scanner
Enter a search term (e.g.,
"test").Click Start.
The app subscribes to
block-addedevents.Each new block is checked for transactions containing payloads.
Matches are added in a separate container.
c. Wallet Integration
Click Create Wallet to generate a new wallet with a mnemonic and receiving address.
Copy the receiving address for testing.
Enter a destination address, amount, and optional payload.
Click Send to broadcast a transaction.
The payload will appear in the scanner if it matches your search term.
Code Highlights
Block subscription:
First we tell the client “I want to know about any new blocks”, and then we tell the client “when you do tell me about these new blocks, go ahead and run this function named blockHandler”
await client.subscribeBlockAdded();
client.addEventListener("block-added", blockHandler);Which is done inside the demo repo’s scanner.html:
async function startScanner() {
if (!client) return;
scanning = true;
startStopBtn.textContent = "Stop";
statusDiv.textContent = "Scanning...";
const iframeDoc = blocksIframe.contentDocument || blocksIframe.contentWindow.document;
if (iframeDoc && iframeDoc.body) {
iframeDoc.body.innerHTML = "";
}
if (blockSubscription) {
client.removeEventListener("block-added", blockSubscription);
blockSubscription = null;
}
await client.subscribeBlockAdded();
blockSubscription = blockHandler;
client.addEventListener("block-added", blockSubscription);
}Then the blockHandler function receives the new block event with a payload containing the new block and all of its data. Which we can check if each block has any transactions, and if so check if they have any payloads. If they do, then we can decode them with utilities.hexToString(tx.payload). Now we can check if the user supplied search text is contained within the payload. If it is then we show it to the user.
function blockHandler(event) {
const block = event.data.block;
if (!block) return;
const searchText = searchInput.value.trim().toLowerCase();
let foundMatch = false;
let matchedPayload = "";
// Check transactions for payloads
if (Array.isArray(block.transactions)) {
for (const tx of block.transactions) {
if (tx.payload) {
const decoded = utilities.hexToString(tx.payload);
if (searchText && decoded.toLowerCase().includes(searchText)) {
console.log("Match found in payload:", decoded);
foundMatch = true;
matchedPayload = decoded;
//break; // Stop at first match
}
}
}
}
// Show the first matched payload if found, otherwise empty string
addBlockToUI(block, foundMatch ? matchedPayload : "", foundMatch);
}Unsubscribing:
To prevent old/stale events from firing, then we must tell the client “I no longer want to receive new block notifications” by running:
client.removeEventListener(”block-added”, blockSubscription);Which in the demo is tracked with a variable: let blockSubscription = null:
function stopScanner() {
scanning = false;
startStopBtn.textContent = "Start";
statusDiv.textContent = "Waiting to scan...";
if (blockSubscription) {
client.removeEventListener("block-added", blockSubscription);
blockSubscription = null;
}
}Wallet creation:
Using the demo’s wallet_service.js, a wallet can easily be created by first using the demo’s kaspa_client.js to connect to a Kaspa node. Then initialize the wallet with await init(client, networkId);
async function connectToNode() {
statusDiv.textContent = "Connecting...";
const url = nodeInput.value.trim();
const networkId = networkInput.value.trim() || "mainnet";
const usePublicResolver = document.getElementById("publicResolverCheckbox").checked;
try {
if (usePublicResolver) {
client = await connect(null, networkId);
} else {
client = await connect(url, networkId);
}
statusDiv.textContent = "Connected";
if (walletInitialized) {
await init(client, networkId);
}
} catch (err) {
statusDiv.textContent = "Connection failed";
}
}
Next, when the user is ready, to create the wallet all that is needed is await createWallet({ password: “1234” }); and if you set a variable to catch the result then you can show the address, or mnemonic if you want:
const { address, mnemonic } = await createWallet({ password: "1234" });In the demo it is done in the on click event for the create wallet button:
createWalletBtn.onclick = () => {
document.getElementById("walletLoading").style.display = "inline-block";
// Defer the wallet creation logic so the loading icon is rendered first
setTimeout(async () => {
if (!client) {
document.getElementById("walletLoading").style.display = "none";
return alert("Connect to a node first!");
}
const networkId = networkInput.value.trim();
if(networkId === "public") {
await init(client, "mainnet");
} else {
await init(client, networkId);
}
const { address } = await createWallet({ password: "1234" });
walletInitialized = true;
currentAddress = address;
receiveAddressLabel.textContent = address;
toAddressInput.value = address;
document.getElementById("walletLoading").style.display = "none";
}, 0);
};Transaction sending:
First, be sure to use the proper testnet-10/testnet-11 faucet to send the wallet some funds. Then by using wallet_service.js we can easily send by running:
await send({ amount, toAddress, payload });Which is done when the user clicks the send button in the demo:
sendBtn.onclick = async () => {
if (!walletInitialized) return alert("Create a wallet first!");
if (!scanning) await startScanner();
const toAddress = toAddressInput.value.trim();
const amount = amountInput.value.trim();
let payload = payloadInput.value.trim();
try {
await send({ amount, toAddress, payload });
sendResultLabel.textContent = "Transaction sent!";
} catch (err) {
sendResultLabel.textContent = "Send error: " + err.message;
}
};What Can You Build With This?
Once you’ve mastered block scanning and wallet integration, you can start creating projects that showcase Kaspa’s unique DAG and payload features:
On‑chain Messaging Board
Use transaction payloads as public posts.
The scanner displays messages in real time.
Great demo of Kaspa’s throughput and DAG structure.
Metadata‑Driven Explorer
Build a custom explorer that highlights transactions containing specific payload tags (e.g., “NFT”, “vote”, “chat”).
Useful for visualizing how apps embed data on Kaspa.
Proof‑of‑Action Logger
Store hashes of off‑chain events (like documents or sensor readings) in payloads.
The scanner verifies and displays them, creating an immutable audit trail.
Real‑Time Game or App Signals
Players or apps broadcast lightweight signals in payloads.
The scanner listens for matches and triggers in‑game or app events.
Voting / Polling Demo
Payloads represent votes.
Scanner tallies results live.
Shows how transparency and immutability can be combined with simple metadata.
Wallet‑Linked Notifications
Scan for transactions to your address with specific payloads.
Trigger alerts or automate workflows when they appear.
Closing Thought
Kaspa’s WASM SDK enables real‑time, browser‑based blockchain apps. By combining block scanning with wallet operations, you can build everything from playful demos (chat boards, games) to serious tools (auditing, proof‑of‑ownership, metadata explorers). In this tutorial, payloads are visible metadata and not encrypted secrets.
While encryption is possible, it’s not future‑proof; advances like quantum computing could eventually break today’s ciphers. Never store sensitive information on Kaspa, even if encrypted. Instead, embrace its transparency to design creative, verifiable projects and use Kaspa as an immutable anchor.


