Token Claimer is an open-source protocol on LUKSO that lets anyone deploy token and NFT drop campaigns. All contracts are verified on-chain.
The system uses a factory pattern. Two factory contracts (one for LSP7 tokens, one for LSP8 NFTs) deploy individual drop contracts. Each drop is its own contract owned by the creator.
All contracts are verified and readable on the LUKSO block explorer.
Creator deploys a drop
Calls createTokenDrop() or createNFTDrop() on the factory. This deploys a fresh contract and transfers ownership to the creator.
Creator configures the drop
Enable claiming, set a time window, add token-gating requirements (hold LSP7/LSP8 tokens), require a minimum follower count via LSP26, or set a secret codeword.
Users claim tokens or NFTs
Eligible users call claim() or claimWithCode(). The contract validates all requirements, then mints tokens/NFTs directly to the claimer. Each address can only claim once.
// LSP7 (Fungible Tokens)
function createTokenDrop(
string name,
string symbol,
uint256 amountPerClaim,
uint256 maxSupply, // 0 = unlimited
bool isNonDivisible // true = 0 decimals, false = 18
) returns (address)
// LSP8 (NFTs)
function createNFTDrop(
string name,
string symbol,
uint256 maxSupply, // 0 = unlimited
uint256 amountPerClaim // max 50 per claim
) returns (address)// LSP7
function totalCampaigns() view returns (uint256)
function getCampaigns(uint256 offset, uint256 limit)
view returns (Campaign[] memory)
// LSP8
function totalNFTCampaigns() view returns (uint256)
function getNFTCampaigns(uint256 offset, uint256 limit)
view returns (Campaign[] memory)
// Campaign struct
struct Campaign {
address contractAddr;
address creator;
string name;
string symbol;
uint256 createdAt;
}function setClaimEnabled(bool enabled)
function setClaimWindow(uint256 startTime, uint256 endTime)
function setAmountPerClaim(uint256 amount)
function setRequirements(
address[] tokens,
uint256[] minBalances,
uint256 minFollowers // LSP26 follower count
)
function setCodeword(string word) // stored as keccak256 hash
function clearCodeword()function claim()
function claimWithCode(string codeword)function claimEnabled() view returns (bool)
function isClaimActive() view returns (bool)
function hasClaimed(address) view returns (bool)
function meetsRequirements(address)
view returns (bool tokenOk, bool followersOk)
function getTokenRequirements()
view returns (address[] tokens, uint256[] minBalances)
function requiredFollowers() view returns (uint256)
function maxSupply() view returns (uint256)
function amountPerClaim() view returns (uint256)
function codewordEnabled() view returns (bool)
function owner() view returns (address)
// LSP7 only
function totalClaimers() view returns (uint256)
function totalClaimedAmount() view returns (uint256)
function decimals() view returns (uint8)
// LSP8 only
function totalClaimed() view returns (uint256)ClaimNotActive()
AlreadyClaimed()
OutsideClaimWindow()
MaxSupplyReached()
InsufficientTokenBalance()
InsufficientFollowers()
WrongCodeword()
ReentrancyGuard()Digital Asset (Fungible Token)
LUKSO's standard for fungible tokens. Similar to ERC-20 but with force parameter on transfers and metadata stored in ERC725Y (no name()/symbol() view functions).
Identifiable Digital Asset (NFT)
LUKSO's NFT standard. Token IDs are bytes32 (not uint256). Drop contracts auto-increment IDs starting at 1.
Follower System
On-chain social graph. Drops can require claimers to have a minimum number of followers. Registry is at 0xf01103E5a9909Fc0DBe8166dA7085e0285daDDcA.
Digital Asset Metadata
Token metadata (name, description, icon) stored on-chain as VerifiableURI pointing to IPFS JSON. Creators can set this after deployment via the manage page.
Using ethers.js v6 to interact with the contracts.
import { JsonRpcProvider, Contract } from "ethers";
const rpc = new JsonRpcProvider("https://rpc.mainnet.lukso.network");
const factory = new Contract(
"0x9b132E764f92c6E6F5E91E276E310758C33dB08F",
[
"function totalCampaigns() view returns (uint256)",
"function getCampaigns(uint256,uint256) view returns " +
"(tuple(address contractAddr, address creator, " +
"string name, string symbol, uint256 createdAt)[])",
],
rpc
);
const total = await factory.totalCampaigns();
const campaigns = await factory.getCampaigns(0, total);
campaigns.forEach(c => {
console.log(c.name, c.contractAddr);
});const drop = new Contract(dropAddress, [
"function isClaimActive() view returns (bool)",
"function hasClaimed(address) view returns (bool)",
"function meetsRequirements(address) view returns " +
"(bool tokenOk, bool followersOk)",
], rpc);
const active = await drop.isClaimActive();
const claimed = await drop.hasClaimed(userAddress);
const { tokenOk, followersOk } = await drop.meetsRequirements(
userAddress
);
const canClaim = active && !claimed && tokenOk && followersOk;import { BrowserProvider, Contract } from "ethers";
// Get the UP browser extension provider
const provider = new BrowserProvider(window.lukso);
const signer = await provider.getSigner();
const drop = new Contract(dropAddress, [
"function claim() external",
"function claimWithCode(string) external",
], signer);
// Simple claim
const tx = await drop.claim();
await tx.wait();
// Or claim with codeword
const tx2 = await drop.claimWithCode("secret");
await tx2.wait();// LSP7 has decimals(), LSP8 does not
async function detectTokenType(contract) {
try {
await contract.decimals();
return "LSP7"; // Fungible token
} catch {
return "LSP8"; // NFT
}
}