Developer Integration Guide
How to integrate IFR Lock into your product — permissionless, on-chain license gating for any application.
1. Overview: How to Integrate IFR Lock Into Your Product
Any product can use IFR Lock to offer premium tiers to its users. The contract is fully permissionless — no approval, API key, or partnership is required. Your product simply queries the on-chain lock status of a wallet and gates features accordingly.
Your product decides the minimum lock amount for premium access. The user locks IFR tokens on-chain, and your application checks whether the lock meets your threshold.
Integration in 3 steps: (1) Set up a resolver that reads from the IFRLock contract, (2) query isLocked() for each user wallet, (3) gate features based on the boolean result.
2. Architecture Overview
The integration follows a simple three-layer architecture. Your application talks to a lightweight resolver (your backend), which queries the IFRLock contract on Ethereum.
The resolver maps wallet addresses to license status. It is stateless — it queries on demand and does not persist user data. You own and operate the resolver, giving you full control over caching, rate limiting, and minimum lock thresholds.
3. Step 1: ABI Reference
You only need the read functions from the IFRLock contract. Here is the minimal ABI for integration:
Minimal ABI (read-only)const IFR_LOCK_ABI = [
"function isLocked(address user, uint256 minAmount) view returns (bool)",
"function lockedBalance(address user) view returns (uint256)",
"function lockInfo(address user) view returns (uint256 amount, uint256 lockedAt)",
"function totalLocked() view returns (uint256)"
];
| Network | Contract Address |
|---|---|
| Sepolia (testnet) | 0x0Cab0A9440643128540222acC6eF5028736675d3 |
| Mainnet | TBD |
4. Step 2: Set Up Your Resolver
The resolver is a simple Express server that queries the IFRLock contract and returns a JSON response. Deploy this on your own infrastructure.
Node.js / Express — resolver.jsconst { ethers } = require("ethers");
const express = require("express");
const LOCK_ADDRESS = "0x0Cab0A9440643128540222acC6eF5028736675d3";
const LOCK_ABI = [
"function isLocked(address user, uint256 minAmount) view returns (bool)",
"function lockedBalance(address user) view returns (uint256)",
];
const MIN_LOCK = ethers.utils.parseUnits("5000", 9); // 5000 IFR minimum
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const lock = new ethers.Contract(LOCK_ADDRESS, LOCK_ABI, provider);
const app = express();
app.get("/api/license/:wallet", async (req, res) => {
try {
const wallet = req.params.wallet;
const isPremium = await lock.isLocked(wallet, MIN_LOCK);
const balance = await lock.lockedBalance(wallet);
res.json({
wallet,
premium: isPremium,
lockedAmount: ethers.utils.formatUnits(balance, 9),
minRequired: "5000",
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000);
Note: The resolver uses ethers v5 syntax. IFR uses 9 decimals (not 18), so always use parseUnits(value, 9) and formatUnits(value, 9).
5. Step 3: Gate Features in Your App
On the frontend, call your resolver endpoint and use the response to enable or disable premium features.
Frontend — feature gating// Frontend example
async function checkPremium(walletAddress) {
const res = await fetch(`/api/license/${walletAddress}`);
const data = await res.json();
if (data.premium) {
// Enable premium features
showPremiumUI();
} else {
// Show standard plan + prompt to lock IFR
showUpgradePrompt(data.minRequired);
}
}
The resolver returns a simple JSON object. The premium field is a boolean — true if the user has at least the minimum amount locked, false otherwise. Your frontend logic is entirely up to you.
6. Step 4: Lock/Unlock UI (Optional)
If you want to let users lock IFR directly from your application (instead of directing them to the Inferno dashboard), you can integrate the lock flow with a connected wallet such as MetaMask.
Lock flow — approve + lock// Requires user's connected wallet (e.g., MetaMask)
const TOKEN_ADDRESS = "0x3Bd71947F288d1dd8B21129B1bE4FF16EDd5d1F4";
const TOKEN_ABI = ["function approve(address spender, uint256 amount) returns (bool)"];
async function lockIFR(signer, amount) {
const token = new ethers.Contract(TOKEN_ADDRESS, TOKEN_ABI, signer);
const lock = new ethers.Contract(LOCK_ADDRESS, LOCK_ABI_FULL, signer);
const parsedAmount = ethers.utils.parseUnits(amount, 9);
// Step 1: Approve
const approveTx = await token.approve(LOCK_ADDRESS, parsedAmount);
await approveTx.wait();
// Step 2: Lock
const lockTx = await lock.lock(parsedAmount);
await lockTx.wait();
return true;
}
Important: The lock flow requires two transactions: (1) approve the IFRLock contract to spend tokens, then (2) call lock(). Both must be signed by the user's wallet.
7. Using lockType for Multi-App
If you want to tag locks with your application's identifier, use lockWithType(). This is useful for analytics and distinguishing which app prompted the lock.
// Lock with your app's specific tag
const lockType = ethers.utils.id("myapp_premium"); // bytes32 hash
await lock.lockWithType(amount, lockType);
Note: lockType is metadata only — it does not affect isLocked() queries. One user has one lock balance, shared across all apps. The tag is for your own tracking purposes.
8. Privacy Checklist
Integrating with on-chain data requires care around user privacy. Follow these guidelines:
- DO Query only
isLocked()(boolean) — minimum data exposure - DO Use your own RPC endpoint (not a public one)
- DO Cache results with a short TTL (e.g., 5 minutes)
- DON'T Store wallet addresses linked to user identities
- DON'T Log lock amounts or transaction details
- DON'T Track lock/unlock patterns across users
The resolver should be stateless — query on demand, don't persist. If you need caching, use an in-memory store with a short TTL and no personal identifiers.
9. Choosing Your Minimum Lock Amount
The minimum lock amount is entirely your decision. It lives in your resolver, not on-chain, so you can change it at any time without a contract interaction.
| Tier | Lock Amount | Use Case |
|---|---|---|
| Basic | 1,000 IFR | Low barrier, broad access, community features |
| Standard | 5,000 IFR | Premium features, moderate commitment |
| Enterprise | 25,000 IFR | High-value access, strong holder commitment |
Consider the value of your premium features when setting the threshold. A higher minimum means fewer premium users but stronger commitment. A lower minimum increases accessibility. You can also implement multiple tiers by checking against different thresholds.
10. Testing on Sepolia
Before going live, test your integration on the Sepolia testnet. You will need Sepolia ETH (from a faucet) and test IFR tokens (contact the team or swap on Uniswap Sepolia).
Foundry (cast) — query lock status# Get Sepolia ETH from a faucet
# Get test IFR tokens (contact the team or use Uniswap Sepolia)
# Query lock status directly
cast call 0x0Cab0A9440643128540222acC6eF5028736675d3 \
"isLocked(address,uint256)(bool)" \
YOUR_WALLET_ADDRESS \
5000000000000 # 5000 * 1e9
Tip: The cast command is part of the Foundry toolkit. It lets you call read functions directly from the command line without writing any code. Replace YOUR_WALLET_ADDRESS with an actual Sepolia address.
Quick Reference
| Resource | Link / Value |
|---|---|
| IFRLock (Sepolia) | 0x0Cab0A9440643128540222acC6eF5028736675d3 |
| InfernoToken (Sepolia) | 0x3Bd71947F288d1dd8B21129B1bE4FF16EDd5d1F4 |
| Token Decimals | 9 |
| Ticker | $IFR |
| Ethers.js version | v5 (CommonJS) |
| Uniswap Router (Sepolia) | 0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008 |
11. Partner Benefits
Partners who integrate IFR Lock into their products gain access to a range of benefits designed to support growth and alignment with the Inferno ecosystem.
- Creator Rewards: Earn IFR when users lock tokens for your product — managed by the PartnerVault contract
- Revenue Share: Earn a percentage of lock-related protocol fees
- Co-Marketing: Joint announcements, featured on the Inferno website
- Technical Support: Direct integration support from the Inferno team
- Early Access: Priority access to new features and protocol upgrades
Lock-triggered Creator Rewards
Partner rewards are driven by real user engagement through the PartnerVault smart contract. When a user locks IFR for a creator's product, the creator earns a percentage of the locked amount from the Partner Ecosystem Pool.
| Parameter | Value | Description |
|---|---|---|
| RewardBps | 10–25% (governance-controlled) | Percentage of each lock amount credited to the creator |
| Annual Emission Cap | 4M IFR / year | Hard cap on total yearly rewards to prevent pool exhaustion |
| Per-Partner Cap | Configurable per partner | Maximum allocation per partner (governance-controlled) |
| Vesting | 6–12 months linear | Rewards vest linearly with optional cliff — no instant dumps |
How It Works
- Partner Registration: Governance creates a partner entry in PartnerVault with a beneficiary address, max allocation, and vesting schedule.
- User Locks IFR: When a user locks IFR for the partner's product via IFRLock, the lock event is recorded.
- Reward Accrual:
recordLockReward(partnerId, lockAmount)credits the partner withlockAmount × rewardBps / 10000IFR from the pool. - Vested Claiming: Partners call
claim(partnerId)to withdraw vested rewards. Linear vesting ensures long-term alignment.
Total Partner Pool: 40M IFR (4% of total supply), managed by the PartnerVault contract on-chain. The remaining 60M IFR (6%) is reserved for community grants, bug bounties, and ecosystem development.
PartnerVault Integration (ethers.js v5)
Query partner rewards and claimconst VAULT_ADDR = "0x5F12C0bC616e9Ca347D48C33266aA8fe98490A39";
const VAULT_ABI = [
"function claimable(uint256 partnerId) view returns (uint256)",
"function partners(uint256 partnerId) view returns (address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bool,bool,string)",
"function claim(uint256 partnerId)",
];
const vault = new ethers.Contract(VAULT_ADDR, VAULT_ABI, provider);
// Check claimable amount for partner #0
const amount = await vault.claimable(0);
console.log("Claimable:", ethers.utils.formatUnits(amount, 9), "IFR");
// Claim vested rewards (requires signer)
const vaultSigner = vault.connect(signer);
await vaultSigner.claim(0);
12. Full Integration Example
Below is a complete ethers.js v5 example showing the full resolver flow — from contract setup to querying lock status, balance, and lock details for a given wallet.
Full resolver flow — ethers.js v5const { ethers } = require("ethers");
const LOCK_ADDR = "0x0Cab0A9440643128540222acC6eF5028736675d3";
const LOCK_ABI = [
"function isLocked(address user, uint256 minAmount) view returns (bool)",
"function lockedBalance(address user) view returns (uint256)",
"function lockInfo(address user) view returns (uint256 amount, uint256 lockedAt)",
];
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const lock = new ethers.Contract(LOCK_ADDR, LOCK_ABI, provider);
async function checkLicense(wallet) {
const minLock = ethers.utils.parseUnits("5000", 9); // 5000 IFR (9 decimals)
const premium = await lock.isLocked(wallet, minLock);
const balance = await lock.lockedBalance(wallet);
const [amount, lockedAt] = await lock.lockInfo(wallet);
return {
premium,
lockedAmount: ethers.utils.formatUnits(balance, 9),
lockedSince: new Date(lockedAt.toNumber() * 1000).toISOString(),
};
}
Tip: This example combines all three read functions into a single helper. In production, consider batching calls with Promise.all() or a multicall contract for better RPC efficiency.
13. Privacy & Compliance
When integrating on-chain data into your product, ensure your implementation meets privacy and compliance requirements. Use the following checklist as a guide.
| Category | Requirement | Implementation |
|---|---|---|
| Data Minimization | Only query boolean lock status | Use isLocked() not lockedBalance() |
| No PII Storage | Don't link wallets to identities | Stateless resolver, no database |
| RPC Security | Use private RPC endpoint | Alchemy/Infura with API key |
| Caching | Short TTL, no persistent store | In-memory cache, 5min TTL max |
| Audit Trail | Log access decisions, not amounts | Log premium=true/false only |
| GDPR | No personal data processed | Wallet addresses are pseudonymous |
14. Partner Onboarding Flow
The partner onboarding process is designed to get you integrated quickly and smoothly. Follow these steps to go from application to live integration.
- Application — Contact the Inferno team with your product description and user base size
- Technical Review — We assess integration feasibility and determine the minimum lock amount
- Token Allocation — Partners receive an IFR allocation for initial user onboarding
- Integration — Implement the resolver using the code examples above (typically 1-2 days)
- Testing — Verify on Sepolia testnet before going live
- Launch — Go live on mainnet, joint announcement, featured on the Inferno ecosystem page
Ready to integrate? The entire process from application to live integration typically takes less than a week. Most of the time is spent on your side implementing and testing the resolver.
15. Creator Gateway Integration
The Creator Gateway enables YouTube creators and content platforms to gate premium content behind an IFR Lock — as an alternative (or addition) to traditional memberships.
- Hybrid Model B: Users access premium content via YouTube Membership OR IFR Lock (OR logic)
- Docker Setup: Self-hosted gateway with Google OAuth + IFRLock bridge
- Entitlement Engine: Configurable rules (JSON) for access control
// Entitlement Config Example (config/entitlements.json)
{
"premium": {
"logic": "OR",
"conditions": [
{ "type": "youtube_member", "tier": "any" },
{ "type": "ifr_lock", "minIFR": 5000 }
]
}
}
16. Points Backend Integration
The IFR Points Backend provides off-chain points tracking and EIP-712 discount voucher issuance. Points reduce the protocol fee on swaps via FeeRouterV1.
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/auth/siwe/nonce | GET | Get SIWE nonce |
/auth/siwe/verify | POST | Verify SIWE signature → JWT |
/points/balance | GET | Get wallet points balance |
/points/event | POST | Record points event |
/voucher/issue | POST | Issue EIP-712 discount voucher |
SIWE Authentication Flow
- Frontend calls
GET /auth/siwe/nonce - User signs SIWE message with wallet
- Frontend sends signature to
POST /auth/siwe/verify - Backend returns JWT (24h expiry)
- All subsequent calls use
Authorization: Bearer {jwt}
Voucher → FeeRouter Flow
- User accumulates points (guide completion, lock events, referrals)
- Frontend calls
POST /voucher/issuewith JWT - Backend signs EIP-712
DiscountVoucherstruct - Frontend passes voucher to
FeeRouterV1.swapWithFee() - On-chain: discount applied, nonce consumed (replay-protected)
17. FeeRouter Integration
FeeRouterV1 routes swaps through whitelisted adapters with an optional protocol fee, discountable via EIP-712 signed vouchers.
Key Functions
// Execute swap with protocol fee (+ optional discount voucher)
function swapWithFee(
address adapter, // whitelisted swap adapter
bytes calldata data, // adapter-specific swap calldata
address tokenIn, // input token
uint256 amountIn, // input amount
DiscountVoucher calldata voucher // EIP-712 voucher (or empty)
) external payable;
// Voucher Struct (EIP-712)
struct DiscountVoucher {
address user; // beneficiary
uint256 discountBps; // discount in basis points
uint256 nonce; // replay protection
uint256 deadline; // expiry timestamp
bytes signature; // EIP-712 signature from voucherSigner
}
// Off-chain voucher validation
function isVoucherValid(DiscountVoucher calldata voucher)
external view returns (bool);
Sepolia Deployment
| Parameter | Value |
|---|---|
| Address | 0x499289C8Ef49769F4FcFF3ca86D4BD7b55B49aa4 |
| protocolFeeBps | 5 (0.05%) |
| FEE_CAP_BPS | 25 (0.25%) — hard cap |
| EIP-712 Domain | name="InfernoFeeRouter", version="1", chainId=11155111 |