Token Threshold Verification
Token Threshold Verification allows you to create tiered access levels based on how many NFTs users hold from a collection, enabling loyalty programs, governance systems, and VIP tiers.
Threshold verification works with the same /api/wallet/verify-ownership endpoint as NFT verification, using the minCount parameter.
How It Works
The threshold verification system:
- User authenticates with their Bitcoin identity
- User connects wallet addresses (using bitcoin-auth)
- Your app defines threshold tiers (e.g., Bronze: 10, Gold: 50, Platinum: 100)
- Sigma Auth checks collection ownership with pagination
- Returns whether user meets the threshold, along with actual count
Use Cases
Tiered VIP Programs
Create multiple access tiers based on NFT holdings:
const tiers = {
bronze: 10, // 10+ NFTs
silver: 50, // 50+ NFTs
gold: 100, // 100+ NFTs
platinum: 250 // 250+ NFTs
};
async function getUserTier(sessionToken, collectionId) {
// Check user's total NFT count
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({ origin: collectionId })
});
const { count } = await response.json();
// Determine tier
if (count >= tiers.platinum) return 'platinum';
if (count >= tiers.gold) return 'gold';
if (count >= tiers.silver) return 'silver';
if (count >= tiers.bronze) return 'bronze';
return 'free';
}Governance Voting
Weight voting power by NFT holdings:
// Check if user has minimum NFTs to vote
const minVotingNFTs = 1;
const maxVotingPower = 100; // Cap voting power
async function getVotingPower(sessionToken, collectionId) {
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({
origin: collectionId,
minCount: minVotingNFTs
})
});
const { owns, count } = await response.json();
if (!owns) return 0; // Below minimum threshold
// Weight by holdings but cap at max
return Math.min(count, maxVotingPower);
}Loyalty Rewards
Unlock benefits based on collection size:
const rewards = {
member: 5, // 5+ NFTs: 5% discount
vip: 20, // 20+ NFTs: 10% discount + free shipping
elite: 100 // 100+ NFTs: 20% discount + early access
};
async function getDiscount(sessionToken, collectionId) {
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({ origin: collectionId })
});
const { count } = await response.json();
if (count >= rewards.elite) return { tier: 'elite', discount: 0.20, freeShipping: true, earlyAccess: true };
if (count >= rewards.vip) return { tier: 'vip', discount: 0.10, freeShipping: true };
if (count >= rewards.member) return { tier: 'member', discount: 0.05 };
return { tier: 'free', discount: 0 };
}API Usage
Basic Threshold Check
// Example: Check if user owns at least 50 NFTs from a collection
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({
origin: '1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0', // Collection ID
minCount: 50 // Threshold
})
});
const { owns, count } = await response.json();
if (owns) {
console.log(`Threshold met! User has ${count} NFTs (>= 50)`);
} else {
console.log(`Threshold not met. User has ${count} NFTs (< 50)`);
}Response Format
// User has 268 NFTs, threshold is 50
{
"owns": true, // Meets threshold (268 >= 50)
"count": 268, // Total NFTs from collection
"nfts": [ /* ... first 100 NFTs ... */ ]
}
// User has 25 NFTs, threshold is 50
{
"owns": false, // Below threshold (25 < 50)
"count": 25 // Total NFTs from collection
}
// User has exactly 100 NFTs, threshold is 100
{
"owns": true, // Meets threshold (100 >= 100)
"count": 100
}Multi-Tier Verification
Check against multiple thresholds efficiently:
const collectionId = '1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0';
const thresholds = [10, 50, 100, 250];
async function checkAllTiers(sessionToken) {
// Single API call to get total count
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({ origin: collectionId })
});
const { count } = await response.json();
// Check all thresholds locally
const results = thresholds.map(threshold => ({
threshold,
meets: count >= threshold
}));
return { count, results };
}
// Example output:
// {
// count: 75,
// results: [
// { threshold: 10, meets: true },
// { threshold: 50, meets: true },
// { threshold: 100, meets: false },
// { threshold: 250, meets: false }
// ]
// }Dynamic Tier Calculation
Calculate the highest tier a user qualifies for:
function calculateTier(count, thresholds) {
// Sort thresholds descending
const sorted = Object.entries(thresholds).sort((a, b) => b[1] - a[1]);
// Find highest tier user qualifies for
for (const [tierName, minCount] of sorted) {
if (count >= minCount) {
return tierName;
}
}
return null; // No tier
}
// Example
const count = 75;
const thresholds = {
bronze: 10,
silver: 50,
gold: 100,
platinum: 250
};
const tier = calculateTier(count, thresholds);
// Returns: "silver" (highest tier where 75 >= threshold)Best Practices
Cache Aggressively
NFT counts change infrequently. Cache longer than basic ownership checks:
// Cache for 15 minutes (NFTs don't transfer that often)
const cacheKey = `nft-count-${userId}-${collectionId}`;
let result = cache.get(cacheKey);
if (!result) {
result = await verifyOwnership({ origin: collectionId });
cache.set(cacheKey, result, 900); // 15 minutes
}Progressive Disclosure
Show users their current tier and next milestone:
async function getTierProgress(sessionToken, collectionId, thresholds) {
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({ origin: collectionId })
});
const { count } = await response.json();
// Find current tier
const tiers = Object.entries(thresholds).sort((a, b) => a[1] - b[1]);
let currentTier = null;
let currentIndex = -1;
for (let i = tiers.length - 1; i >= 0; i--) {
if (count >= tiers[i][1]) {
currentTier = tiers[i][0];
currentIndex = i;
break;
}
}
// Find next tier
let nextTier = null;
if (currentIndex < tiers.length - 1) {
const [name, threshold] = tiers[currentIndex + 1];
nextTier = {
name,
threshold,
needed: threshold - count
};
}
return {
count,
currentTier,
nextTier
};
}
// Example usage
const progress = await getTierProgress(sessionToken, collectionId, {
bronze: 10,
silver: 50,
gold: 100
});
console.log(`You have ${progress.count} NFTs`);
console.log(`Current tier: ${progress.currentTier || 'None'}`);
if (progress.nextTier) {
console.log(`${progress.nextTier.needed} more NFTs to reach ${progress.nextTier.name}`);
}Threshold Optimization
Choose thresholds that create meaningful tiers:
// ❌ Bad: Tiers too close together
const badTiers = {
bronze: 10,
silver: 15, // Only 5 NFTs difference
gold: 20
};
// ✅ Good: Clear progression
const goodTiers = {
bronze: 10,
silver: 50, // 5x increase
gold: 100, // 2x increase
platinum: 250 // 2.5x increase
};Handle Edge Cases
Always account for zero holdings and exact matches:
async function checkThreshold(sessionToken, collectionId, minCount) {
try {
const response = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `better-auth.session_token=${sessionToken}`
},
body: JSON.stringify({ origin: collectionId, minCount })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const { owns, count } = await response.json();
return {
meetsThreshold: owns,
actualCount: count,
shortage: owns ? 0 : minCount - count
};
} catch (error) {
console.error('Threshold check failed:', error);
return { meetsThreshold: false, actualCount: 0, shortage: minCount };
}
}Technical Details
How Thresholds Work
The API uses a simple >= comparison:
const owns = matchingNFTs.length >= minCount;This means:
count: 100, minCount: 100→owns: true✅ (exact match passes)count: 100, minCount: 99→owns: true✅count: 100, minCount: 101→owns: false❌
Pagination Impact
The API paginates through ALL NFTs before checking the threshold:
- Fetches 100 NFTs per page
- Continues until no more results
- Counts total across all pages
- Then compares against threshold
This ensures accurate threshold checks even for large collections.
Default Threshold
If you omit minCount, it defaults to 1:
// These are equivalent:
{ origin: collectionId }
{ origin: collectionId, minCount: 1 }Supported Collections
BSV 1Sat Ordinals
- Full pagination support
- Real-time blockchain verification
- Collection metadata-based identification
Future Support (Planned)
- ERC-721/ERC-1155 (Ethereum, Polygon, Base)
- SPL tokens (Solana)
- Other NFT standards
Performance Considerations
API Call Optimization
Fetch once, check multiple thresholds locally:
// ✅ Good: Single API call
const { count } = await verifyOwnership({ origin: collectionId });
const bronze = count >= 10;
const silver = count >= 50;
const gold = count >= 100;
// ❌ Bad: Multiple API calls
const bronze = await verifyOwnership({ origin: collectionId, minCount: 10 });
const silver = await verifyOwnership({ origin: collectionId, minCount: 50 });
const gold = await verifyOwnership({ origin: collectionId, minCount: 100 });Caching Strategy
// Cache the count, not the threshold result
const cacheKey = `nft-count-${userId}-${collectionId}`;
let count = cache.get(cacheKey);
if (count === undefined) {
const response = await verifyOwnership({ origin: collectionId });
count = response.count;
cache.set(cacheKey, count, 900); // 15 minutes
}
// Check thresholds from cached count
const meetsSilver = count >= 50;
const meetsGold = count >= 100;Next Steps
- NFT Verification - Basic ownership verification
- API Reference - Complete API docs
- Examples - Real-world examples