Reputation Integration Guide
Karma is Status Network's soulbound reputation token. Every address earns Karma through genuine network participation such as staking SNT, bridging assets, providing liquidity, using apps, or paying premium gas fees. Karma cannot be bought, sold, or transferred.
This guide covers how to read Karma data and build apps that respond to a user's reputation tier.
Reading Karma On-Chainβ
Karma state is split across two contracts:
- Karma β the soulbound ERC-20 token.
balanceOfreturns the net balance after any slashing. Transfers and approvals always revert. - KarmaTiers β maps balances to tiers. Call
getTierIdByKarmaBalance(balance)thengetTierById(tierId)to resolve a user's tier and itstxPerEpochquota.
- Solidity
- ethers.js
- viem
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
/// @notice Minimal interface for the Karma soulbound token.
interface IKarma {
/// @notice Net Karma balance of `account` after slashing.
function balanceOf(address account) external view returns (uint256);
/// @notice Total amount slashed from `account` across all distributors.
function slashedAmountOf(address account) external view returns (uint256);
}
/// @notice Minimal interface for the KarmaTiers registry.
interface IKarmaTiers {
struct Tier {
uint256 minKarma;
uint256 maxKarma;
string name;
uint32 txPerEpoch; // gasless transactions allowed per epoch
}
/// @notice Returns the highest tier ID the given karma balance qualifies for.
function getTierIdByKarmaBalance(uint256 karmaBalance) external view returns (uint8);
/// @notice Returns the full Tier struct for a given tier ID.
function getTierById(uint8 tierId) external view returns (Tier memory);
/// @notice Returns the total number of configured tiers.
function getTierCount() external view returns (uint256);
}
contract KarmaGated {
IKarma public karma;
IKarmaTiers public karmaTiers;
constructor(address _karmaContract, address _karmaTiersContract) {
karma = IKarma(_karmaContract);
karmaTiers = IKarmaTiers(_karmaTiersContract);
}
/// @notice Resolves the tier ID for `user` from their current Karma balance.
function tierIdOf(address user) public view returns (uint8) {
return karmaTiers.getTierIdByKarmaBalance(karma.balanceOf(user));
}
modifier onlyTier(uint8 minTier) {
require(tierIdOf(msg.sender) >= minTier, "Karma tier too low");
_;
}
function premiumAction() external onlyTier(3) {
// Only users at tier 3 or above can call this
}
function getDiscount(address user) external view returns (uint256) {
uint8 tierId = tierIdOf(user);
// Higher tiers get bigger discounts
return uint256(tierId) * 5; // 0%, 5%, 10%, ...
}
}
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(
'https://public.sepolia.rpc.status.network'
);
const KARMA_ABI = [
'function balanceOf(address account) view returns (uint256)',
'function slashedAmountOf(address account) view returns (uint256)',
];
const KARMA_TIERS_ABI = [
'function getTierIdByKarmaBalance(uint256 karmaBalance) view returns (uint8)',
'function getTierById(uint8 tierId) view returns (tuple(uint256 minKarma, uint256 maxKarma, string name, uint32 txPerEpoch))',
'function getTierCount() view returns (uint256)',
];
const karma = new ethers.Contract(KARMA_ADDRESS, KARMA_ABI, provider);
const karmaTiers = new ethers.Contract(KARMA_TIERS_ADDRESS, KARMA_TIERS_ABI, provider);
// Read a user's Karma balance and resolve their tier
const balance = await karma.balanceOf(userAddress);
const tierId = await karmaTiers.getTierIdByKarmaBalance(balance);
const tier = await karmaTiers.getTierById(tierId);
console.log(`Karma balance: ${ethers.formatEther(balance)} KARMA`);
console.log(`Tier: ${tier.name} (ID ${tierId})`);
console.log(`Tx quota per epoch: ${tier.txPerEpoch}`);
import { createPublicClient, http } from 'viem';
const client = createPublicClient({
transport: http('https://public.sepolia.rpc.status.network'),
});
const karmaAbi = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
},
{
name: 'slashedAmountOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
},
] as const;
const karmaTiersAbi = [
{
name: 'getTierIdByKarmaBalance',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'karmaBalance', type: 'uint256' }],
outputs: [{ name: '', type: 'uint8' }],
},
{
name: 'getTierById',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'tierId', type: 'uint8' }],
outputs: [{
name: 'tier',
type: 'tuple',
components: [
{ name: 'minKarma', type: 'uint256' },
{ name: 'maxKarma', type: 'uint256' },
{ name: 'name', type: 'string' },
{ name: 'txPerEpoch', type: 'uint32' },
],
}],
},
] as const;
// Two sequential reads: balance β tier ID β tier details
const balance = await client.readContract({
address: KARMA_ADDRESS,
abi: karmaAbi,
functionName: 'balanceOf',
args: [userAddress],
});
const tierId = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierIdByKarmaBalance',
args: [balance],
});
const tier = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierById',
args: [tierId],
});
console.log(`Tier: ${tier.name} (ID ${tierId})`);
console.log(`Tx quota per epoch: ${tier.txPerEpoch}`);
Karma and KarmaTiers contract addresses for testnet are published on the Contract Addresses page.
Possible Integration Patternsβ
Feature Gatingβ
Unlock features based on Karma tier.
The onlyTier modifier you can check from the Reading Karma On-Chain section can restrict access at the contract level.
Alternatively, you can use tierId in your code to gate features:
Catalog idea: Karma-Gated Content Feed β a ready-to-scope app built entirely around this pattern.
- Solidity
- ethers.js
- viem
function accessPremiumContent() external onlyTier(3) {
// Only users at tier 3 or above can call this
}
// Resolve tier from Karma balance, then gate UI features
const balance = await karma.balanceOf(userAddress);
const tierId = await karmaTiers.getTierIdByKarmaBalance(balance);
if (tierId >= 3) {
showPremiumFeatures();
} else {
showUpgradePrompt(tierId);
}
const balance = await client.readContract({
address: KARMA_ADDRESS,
abi: karmaAbi,
functionName: 'balanceOf',
args: [userAddress],
});
const tierId = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierIdByKarmaBalance',
args: [balance],
});
if (tierId >= 3) {
showPremiumFeatures();
} else {
showUpgradePrompt(tierId);
}
Dynamic Pricingβ
Offer discounts or better rates to high-Karma users:
Catalog idea: Karma-Tiered NFT Marketplace β a marketplace where fees and access windows scale automatically with a buyer's Karma tier.
- Solidity
- ethers.js
- viem
function calculateFee(address user, uint256 baseAmount) public view returns (uint256) {
uint8 tierId = karmaTiers.getTierIdByKarmaBalance(karma.balanceOf(user));
// Each tier gives 5% discount
uint256 discount = uint256(tierId) * 5;
return baseAmount * (100 - discount) / 100;
}
// Compute the discounted fee on the client-side from the user's current tier
const balance = await karma.balanceOf(userAddress);
const tierId = await karmaTiers.getTierIdByKarmaBalance(balance);
const discount = BigInt(tierId) * 5n; // 5% per tier
const fee = baseAmount * (100n - discount) / 100n;
console.log(`Tier ${tierId}: ${discount}% discount β fee = ${fee}`);
const balance = await client.readContract({
address: KARMA_ADDRESS,
abi: karmaAbi,
functionName: 'balanceOf',
args: [userAddress],
});
const tierId = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierIdByKarmaBalance',
args: [balance],
});
const discount = BigInt(tierId) * 5n; // 5% per tier
const fee = baseAmount * (100n - discount) / 100n;
console.log(`Tier ${tierId}: ${discount}% discount β fee = ${fee}`);
Reputation Displayβ
Show Karma tier as a trust index in your UI by reading the tier name directly from the KarmaTiers contract:
Catalog idea: Gasless Micro-Blogging Platform β a social feed with Karma trust badges embedded in profiles and posts.
- ethers.js
- viem
// Fetch tier name on-chain β no hardcoded array needed
const balance = await karma.balanceOf(userAddress);
const tierId = await karmaTiers.getTierIdByKarmaBalance(balance);
const tier = await karmaTiers.getTierById(tierId);
function KarmaBadge({ tierName }) {
return <span className="karma-badge">{tierName}</span>;
}
// Usage
<KarmaBadge tierName={tier.name} />
const balance = await client.readContract({
address: KARMA_ADDRESS,
abi: karmaAbi,
functionName: 'balanceOf',
args: [userAddress],
});
const tierId = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierIdByKarmaBalance',
args: [balance],
});
const tier = await client.readContract({
address: KARMA_TIERS_ADDRESS,
abi: karmaTiersAbi,
functionName: 'getTierById',
args: [tierId],
});
function KarmaBadge({ tierName }: { tierName: string }) {
return <span className="karma-badge">{tierName}</span>;
}
// Usage
<KarmaBadge tierName={tier.name} />
Weighted Governanceβ
Use Karma for vote weighting in your app's governance:
Catalog idea: DAO Proposal & Karma-Weighted Voting App β polling and signal voting with optional Karma-weighted participation.
- Solidity
- ethers.js
- viem
function vote(uint256 proposalId, bool support) external {
uint256 weight = karma.balanceOf(msg.sender);
proposals[proposalId].votes += support ? int256(weight) : -int256(weight);
}
// Read the user's Karma balance as their vote weight, then submit
const weight = await karma.balanceOf(userAddress);
console.log(`Voting with weight: ${ethers.formatEther(weight)} KARMA`);
// Call your governance contract
const tx = await governanceContract.vote(proposalId, support);
await tx.wait();
import { formatEther, parseAbi } from 'viem';
// Read vote weight from Karma balance
const weight = await client.readContract({
address: KARMA_ADDRESS,
abi: karmaAbi,
functionName: 'balanceOf',
args: [userAddress],
});
console.log(`Voting with weight: ${formatEther(weight)} KARMA`);
// Submit vote via your governance contract
const { request } = await client.simulateContract({
address: GOVERNANCE_ADDRESS,
abi: parseAbi(['function vote(uint256 proposalId, bool support) external']),
functionName: 'vote',
args: [proposalId, support],
account: userAddress,
});
await walletClient.writeContract(request);
Next Stepsβ
- Refer to Gasless Integration to integrate Karma in making gasless transaction
- Check out Karmic Tokenomics to see a breakdown of how Karma is earned and used
- Go to Gasless Transactions to understand the technical details of the RLN-based gasless system