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 |
|---|---|
| Mainnet | 0x769928aBDfc949D0718d8766a1C2d7dBb63954Eb |
| Sepolia (testnet) | 0x0Cab0A9440643128540222acC6eF5028736675d3 |
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 = "0x769928aBDfc949D0718d8766a1C2d7dBb63954Eb"; // Ethereum Mainnet
const LOCK_ABI = [
"function isLocked(address user, uint256 minAmount) view returns (bool)",
"function lockedBalance(address user) view returns (uint256)",
];
const MIN_LOCK = ethers.parseUnits("5000", 9); // 5000 IFR minimum
const provider = new ethers.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.formatUnits(balance, 9),
minRequired: "5000",
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000);
Note: The resolver uses ethers v6 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 = "0x77e99917Eca8539c62F509ED1193ac36580A6e7B"; // Ethereum Mainnet
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.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.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 a result for at most 60 seconds and only for the same block; check again when the benefit is redeemed
- 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 keyed by block, kept for at most 60 seconds, with no personal identifiers (ifr-benefits-verify/1 §5).
9. Choosing Your Minimum Lock Amount
For customer benefits (discounts, perks, access) use the shared open tiers of
ifr-benefits-verify/1,
so every integrator shows the same scheme. They are published as data in
ifr-benefits-tiers.v1.json
and implemented by the MIT-licensed reference library
apps/benefits-verify.
No registration and no API are involved.
| Tier key | Lock Amount | Base units (9 decimals) |
|---|---|---|
| BRONZE | 1,000 IFR | 1000000000000 |
| SILVER | 2,500 IFR | 2500000000000 |
| GOLD | 5,000 IFR | 5000000000000 |
| PLATINUM | 10,000 IFR | 10000000000000 |
Offer benefits per tier key (for example “from SILVER”), not per IFR amount: a threshold change then ships as a new tier file with a later valid_from and applies without editing your offers. A lock check is a fact about one block — repeat it when the benefit is redeemed. For a single product feature that is not a tier benefit, any threshold greater than zero in your resolver remains possible.
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 (Mainnet) | 0x769928aBDfc949D0718d8766a1C2d7dBb63954Eb |
| InfernoToken (Mainnet) | 0x77e99917Eca8539c62F509ED1193ac36580A6e7B |
| Token Decimals | 9 |
| Ticker | IFR |
| Ethers.js version | v6 |
| Uniswap Router (Sepolia) | 0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008 |
11. Builder Benefits
Builders who integrate IFR Lock into their products gain access to a range of benefits designed to support growth and alignment with the Inferno ecosystem.
Live IFR Benefits app: shop.ifrunit.tech is the installable customer/seller PWA. Customers connect an external wallet, review balances and lock status, discover offers, and preferably present a private short-lived pass. Sellers manage profiles, products, discount rules and checkout operators; each rule can accept IFRLock, active TIME_ONLY CommitmentVault tranches, or the full threshold in either source without adding partial balances. Price-conditioned commitments do not qualify. Sellers may additionally require IFR to remain free in the customer wallet, bind the exact rule to that pass, and redeem an approved checkout once. The compatible seller-issued QR flow remains available. PartnerVault rewards remain governance-gated. Architecture: docs/ifrp-commerce-app/MASTER_ARCHITECTURE.md.
- Partner Rewards (planned, not active): IFR for verified checkout redemptions within a fixed per-partner budget, activated per pilot partner by Governance
- 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
Partner Rewards (planned, not active)
Partner rewards are not active: no partner is registered and no reward has been paid. Under the decided model, a pilot partner receives IFR from the PartnerVault only for verified checkout redemptions, valued in EUR, within a fixed budget. A lock alone never earns a reward. Details: reward policy.
| Parameter | Value | Description |
|---|---|---|
| Trigger | Verified checkout redemption | Only a real, seller-confirmed purchase at a registered pilot partner counts |
| Valuation | EUR per redemption, paid in IFR | Converted at the published 7-day TWAP of the IFR/WETH pool at settlement; no fixed rate |
| Per-Partner Budget | Hard cap per partner | The partner's PartnerVault allocation; the 40M pool is never refilled |
| Vesting | 6–12 months linear | Rewards vest linearly with optional cliff — no instant dumps |
How It Works
- Builder Registration: Governance creates a builder entry in PartnerVault with a beneficiary address, max allocation, and vesting schedule.
- Verified Redemptions: The Benefits backend records each seller-confirmed checkout redemption once (idempotent event id).
- Settlement: Per period, Governance records the exact IFR amount with
recordMilestone(partnerId, milestoneId, unlockAmount), capped by the partner allocation. The contract also hasrecordLockReward(bytes32 partnerId, uint256 lockAmount, address wallet)(lock amount ×rewardBps, one reward per wallet and partner); the project does not use it. - Vested Claiming: Builders call
claim(partnerId)to withdraw vested rewards. Linear vesting ensures long-term alignment.
Total Builder Pool: 40M IFR (4% of total supply), managed by the PartnerVault contract on-chain. The separate 6% Community & Grants allocation arrived as 57.9M IFR in the Community Safe; 50M funded BootstrapVaultV3 and 7.9M IFR remain as the operational reserve for community grants and ecosystem development.
PartnerVault Integration (ethers.js v6)
Query builder rewards and claimconst VAULT_ADDR = "0xc6eb7714bCb035ebc2D4d9ba7B3762ef7B9d4F7D"; // Ethereum Mainnet
const VAULT_ABI = [
"function claimable(bytes32 partnerId) view returns (uint256)",
"function partners(bytes32 partnerId) view returns (address beneficiary,uint256 maxAllocation,uint256 unlockedTotal,uint256 rewardAccrued,uint256 claimedTotal,uint32 vestingStart,uint32 vestingDuration,uint32 cliff,bool active,bool milestonesFinal,uint8 tier)",
"function claim(bytes32 partnerId)",
];
const vault = new ethers.Contract(VAULT_ADDR, VAULT_ABI, provider);
const partnerId = ethers.id("example-partner");
// Check claimable amount for the bytes32 partner key
const amount = await vault.claimable(partnerId);
console.log("Claimable:", ethers.formatUnits(amount, 9), "IFR");
// Claim vested rewards (requires signer)
const vaultSigner = vault.connect(signer);
await vaultSigner.claim(partnerId);
12. Full Integration Example
Below is a complete ethers.js v6 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 v6const { ethers } = require("ethers");
const LOCK_ADDR = "0x769928aBDfc949D0718d8766a1C2d7dBb63954Eb"; // Ethereum Mainnet
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.JsonRpcProvider(process.env.RPC_URL);
const lock = new ethers.Contract(LOCK_ADDR, LOCK_ABI, provider);
async function checkLicense(wallet) {
const minLock = ethers.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.formatUnits(balance, 9),
lockedSince: new Date(Number(lockedAt) * 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, same block only, 60 s max; re-check at redemption |
| Audit Trail | Log access decisions, not amounts | Log premium=true/false only |
| GDPR | No personal data processed | Wallet addresses are pseudonymous |
14. Builder Onboarding Flow
The builder 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 — Builders 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 | POST | Create a single-use 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
POST /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 swapData, // adapter-specific calldata
DiscountVoucher calldata voucher, // ignored when useVoucher=false
bytes calldata voucherSig, // ignored when useVoucher=false
bool useVoucher
) external payable;
// Voucher Struct (EIP-712)
struct DiscountVoucher {
address user;
uint16 discountBps;
uint32 maxUses;
uint64 expiry;
uint256 nonce;
}
// Off-chain voucher validation
function isVoucherValid(
DiscountVoucher calldata voucher,
bytes calldata voucherSig
) external view returns (bool valid, string memory reason);
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 |
Builder Registration Process
BuilderRegistry.sol
registerBuilder() · removeBuilder() · isBuilder(address) · getActiveBuilders()
Builder Categories
Already registered as a Builder? Verify your wallet to unlock Telegram Dev&Builder topic access.
🔐 Verify Wallet →