SDK
Subscription gating via on-chain timelocks
The DevFridge SDK lets any site gate access behind a Fridge timelock. Instead of recurring payments, users lock your project’s token on devfridge.cool. As long as qualifying locks meet the required duration and amount, and remain above the renewal threshold, the subscription is valid.
When remaining days drop below the threshold (e.g. 29 days for a monthly plan), the user must create a new lock. Longer locks = fewer renewals, incentivizing long-term commitment and reducing sell pressure.
How it works
- Developer configures plans with
minLockDays,renewalThresholdDays, and optionallyminLockAmount. - User connects wallet and locks tokens on devfridge.cool.
- SDK fetches all on-chain locks via
scan.devfridge.cool/api/sdk/check, filters locks that individually meetminLockDays, sums their amounts, and checks againstminLockAmount. activeistrueonly when qualifying locks satisfy both duration and amount requirements.daysRemainingandneedsRenewalare computed from the best qualifying lock, not the global best lock.- When
daysRemaining <= renewalThresholdDays, the SDK flagsneedsRenewal: trueand provides a renewal URL. - Users who lock for longer periods skip multiple renewal cycles entirely.
Example: monthly plan
| Config | Value |
|---|---|
minLockDays | 60 |
renewalThresholdDays | 29 |
| Active subscription window | Day 60 → Day 30 (31 days of access) |
| Renewal triggered at | ≤ 29 days remaining |
| User locks for 180 days? | Active for 151 days before renewal prompt |
Quick start
1. Add the script
<script src="https://sdk.devfridge.cool/sdk/devfridge-sdk.js"></script>Or install via npm / import:
// ESM import from your bundle
import { DevFridgeSDK } from "https://sdk.devfridge.cool/sdk/devfridge-sdk.js";2. Configure plans
const fridge = new DevFridgeSDK({
tokenMint: "YOUR_TOKEN_MINT_ADDRESS",
plans: {
basic: { minLockDays: 14, renewalThresholdDays: 7 },
pro: { minLockDays: 60, renewalThresholdDays: 29, minLockAmount: 10_000_000_000_000 },
// ↑ 10M tokens × 10^6 decimals
}
});3. Check subscription
const status = await fridge.checkSubscription(walletAddress);
if (status.active && !status.needsRenewal) {
// Full access
showContent();
} else if (status.active && status.needsRenewal) {
// Access granted but prompt renewal
showContent();
showRenewalBanner(status.renewalUrl);
} else {
// No active lock — gate access
showPaywall(fridge.getLockUrl(60));
}Configuration
new DevFridgeSDK({
// Required
tokenMint: string, // Your token's SPL mint address
plans: { // At least one plan
[name: string]: {
minLockDays: number, // Minimum lock duration to qualify
renewalThresholdDays: number, // When to flag needsRenewal
minLockAmount?: number, // Minimum token amount to qualify (raw units)
}
},
// Optional
scannerUrl: string, // Default: "https://scan.devfridge.cool"
fridgeUrl: string, // Default: "https://devfridge.cool"
cacheTTL: number, // Response cache in ms (default: 60000)
})Plan guidelines
| Plan type | Suggested minLockDays | Suggested renewalThresholdDays | minLockAmount |
|---|---|---|---|
| Weekly | 14 | 7 | optional |
| Monthly | 60 | 29 | optional |
| Quarterly | 120 | 29 | optional |
| Annual | 400 | 29 | optional |
renewalThresholdDays must be strictly less than minLockDays. minLockAmount is optional — when set, the SDK sums amounts only from locks whose individual duration ≥ minLockDays, then checks the sum against minLockAmount (in raw token units, i.e. with decimals applied). Short-duration locks never contribute to the qualifying amount, preventing gaming via a small long lock combined with a large short lock.
API reference
checkSubscription(walletAddress)
Returns a Promise resolving to:
{
active: boolean, // true only if qualifying locks meet duration AND amount
plan: string | null, // Matched plan name, or null if none qualifies
daysRemaining: number, // Days until best *qualifying* lock expires
needsRenewal: boolean, // true when active AND daysRemaining <= threshold
renewalUrl: string | null,// URL to devfridge.cool (when renewal needed or not active)
totalLockAmount: number, // Sum of qualifying lock amounts (duration-filtered)
wallet: string, // The checked wallet
locks: Lock[], // All locks (active + expired)
activeLocks: Lock[], // Only active locks
bestLock: Lock | null, // Lock with furthest unlockAt (global, unfiltered)
}getLockUrl(days?)
Returns a URL to devfridge.cool pre-filled with the token mint. Pass days to suggest a lock duration.
getScanUrl()
Returns the scan page URL: scan.devfridge.cool/t/<mint>
getBadgeUrl(opts?)
Returns the badge image URL. Options: theme ("dark" | "light"), style ("full" | "compact").
getBadgeHtml(opts?)
Returns an <a><img> HTML string linking to the scan page with a live badge image. Embed directly into your page.
startPolling(walletAddress, callback, intervalMs?)
Polls subscription status every intervalMs (default 60s). Calls callback(status) only when the status changes. Returns a stop() function.
REST API
The SDK calls this endpoint. You can also call it directly for server-side checks.
GET https://scan.devfridge.cool/api/sdk/check?wallet=<WALLET>&mint=<MINT>Response:
{
"wallet": "...",
"mint": "...",
"locks": [...], // All locks for this wallet+mint
"activeLocks": [...], // Only locks with unlockAt > now
"bestLock": { ... }, // Lock with furthest unlockAt
"daysRemaining": 45, // Days until bestLock expires
"ts": 1724234567 // Server unix timestamp
}CORS is enabled (access-control-allow-origin: *). Rate limit: 100 requests/minute per IP.
Badge integration
Show real-time lock status on your site with the Fridge badge:
// Get embeddable HTML
const html = fridge.getBadgeHtml({ theme: "dark", style: "compact" });
document.getElementById("badge-container").innerHTML = html;
// Or use the URL directly
const url = fridge.getBadgeUrl({ theme: "light", style: "full" });
// → https://scan.devfridge.cool/api/badge?mint=...&theme=light&style=fullThe badge updates automatically (60s cache). It shows FRIDGED, EXPIRED, or OPEN status, the locked amount, and unlock time.
Full examples
Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<script src="https://sdk.devfridge.cool/sdk/devfridge-sdk.js"></script>
</head>
<body>
<div id="app">Connect wallet to continue</div>
<div id="badge"></div>
<script>
const fridge = new DevFridgeSDK({
tokenMint: "YOUR_MINT_HERE",
plans: {
monthly: {
minLockDays: 60,
renewalThresholdDays: 29,
minLockAmount: 10_000_000_000_000, // optional: 10M tokens × 10^6
}
}
});
// Show badge
document.getElementById("badge").innerHTML =
fridge.getBadgeHtml({ theme: "dark" });
async function onWalletConnect(walletAddress) {
const status = await fridge.checkSubscription(walletAddress);
if (!status.active) {
document.getElementById("app").innerHTML =
'<h2>Lock tokens to subscribe</h2>' +
'<p>Lock for at least 60 days to get monthly access.</p>' +
'<a href="' + fridge.getLockUrl(60) + '">Lock on DevFridge</a>';
return;
}
if (status.needsRenewal) {
document.getElementById("app").innerHTML =
'<h2>Welcome back!</h2>' +
'<p>Your ' + status.plan + ' plan has ' +
status.daysRemaining + ' days left.</p>' +
'<p>Please <a href="' + status.renewalUrl +
'">renew your lock</a> to maintain access.</p>' +
'<div id="content"><!-- premium content --></div>';
return;
}
document.getElementById("app").innerHTML =
'<h2>Welcome!</h2>' +
'<p>Plan: ' + status.plan + ' — ' +
status.daysRemaining + ' days remaining</p>' +
'<div id="content"><!-- premium content --></div>';
}
</script>
</body>
</html>React
import { useEffect, useState } from "react";
import { DevFridgeSDK } from "https://sdk.devfridge.cool/sdk/devfridge-sdk.js";
const fridge = new DevFridgeSDK({
tokenMint: "YOUR_MINT_HERE",
plans: {
weekly: { minLockDays: 14, renewalThresholdDays: 7 },
monthly: { minLockDays: 60, renewalThresholdDays: 29, minLockAmount: 10_000_000_000_000 },
}
});
function useSubscription(walletAddress) {
const [status, setStatus] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!walletAddress) return;
setLoading(true);
const stop = fridge.startPolling(walletAddress, (s) => {
setStatus(s);
setLoading(false);
});
return stop;
}, [walletAddress]);
return { status, loading };
}
function App({ walletAddress }) {
const { status, loading } = useSubscription(walletAddress);
if (loading) return <p>Checking subscription...</p>;
if (!status || !status.active) {
return (
<div>
<h2>Subscribe</h2>
<p>Lock tokens for at least 60 days to access this site.</p>
<a href={fridge.getLockUrl(60)}>Lock on DevFridge</a>
<div dangerouslySetInnerHTML={{
__html: fridge.getBadgeHtml()
}} />
</div>
);
}
return (
<div>
{status.needsRenewal && (
<div className="renewal-banner">
{status.daysRemaining} days left.
<a href={status.renewalUrl}>Renew lock</a>
</div>
)}
<h2>Premium content</h2>
<p>Plan: {status.plan} — {status.daysRemaining} days remaining</p>
{/* Your gated content here */}
</div>
);
}Server-side check (Node.js)
// Server-side subscription verification
async function verifySubscription(wallet, mint, {
minDays = 60,
threshold = 29,
minAmount = 0, // raw token units (e.g. 10_000_000_000_000 for 10M × 10^6)
} = {}) {
const res = await fetch(
`https://scan.devfridge.cool/api/sdk/check?wallet=${wallet}&mint=${mint}`
);
const data = await res.json();
const now = data.ts;
// Filter locks that individually meet the duration requirement
const qualifying = (data.activeLocks || []).filter((l) => {
const duration = Math.floor((l.unlockAt - l.createdAt) / 86400);
return duration >= minDays;
});
if (qualifying.length === 0) {
return { valid: false, reason: "no_qualifying_lock" };
}
// Sum amounts only from qualifying locks
const totalAmount = qualifying.reduce((s, l) => s + Number(l.amount), 0);
if (minAmount > 0 && totalAmount < minAmount) {
return { valid: false, reason: "amount_too_low", totalAmount };
}
// daysRemaining from the best qualifying lock
const bestUnlock = Math.max(...qualifying.map((l) => l.unlockAt));
const daysRemaining = Math.floor((bestUnlock - now) / 86400);
if (daysRemaining <= 0) {
return { valid: false, reason: "expired" };
}
return {
valid: true,
daysRemaining,
totalAmount,
needsRenewal: daysRemaining <= threshold,
};
}AI integration prompts
Copy-paste these prompts to your AI assistant (Claude, ChatGPT, etc.) to integrate DevFridge subscription gating into your project.
Prompt 1 — Basic setup
Integrate DevFridge SDK subscription gating into my site.
SDK script: https://sdk.devfridge.cool/sdk/devfridge-sdk.js
API docs: https://sdk.devfridge.cool
My token mint: [PASTE YOUR MINT HERE]
I want a monthly plan:
- minLockDays: 60 (user must lock for at least 60 days)
- renewalThresholdDays: 29 (prompt renewal at 29 days remaining)
- minLockAmount: 10_000_000_000_000 (optional: require at least 10M tokens × 10^6 decimals)
The SDK only counts locks whose individual duration >= minLockDays toward the
qualifying amount. Short locks never contribute.
Steps:
1. Add the SDK script tag to my HTML
2. After wallet connect, call fridge.checkSubscription(walletAddress)
3. If status.active && !status.needsRenewal → show content
4. If status.active && status.needsRenewal → show content + renewal banner with status.renewalUrl
5. If !status.active → show paywall with link to fridge.getLockUrl(60)
6. Add the Fridge badge with fridge.getBadgeHtml() so users can see lock statusPrompt 2 — React integration
Add DevFridge subscription gating to my React app.
SDK: https://sdk.devfridge.cool/sdk/devfridge-sdk.js
Token mint: [PASTE YOUR MINT HERE]
Create a useSubscription(walletAddress) hook that:
1. Instantiates DevFridgeSDK with my token mint and plans
2. Uses fridge.startPolling() in a useEffect to get real-time status
3. Returns { status, loading }
Create a <SubscriptionGate> component that:
- Shows a loading spinner while checking
- If not subscribed: shows a paywall with a link to fridge.getLockUrl()
- If subscribed but needs renewal: shows content + a renewal banner
- If subscribed: shows the children (gated content)
- Always shows the Fridge badge via fridge.getBadgeHtml()
Plans config:
weekly: { minLockDays: 14, renewalThresholdDays: 7 }
monthly: { minLockDays: 60, renewalThresholdDays: 29, minLockAmount: 10_000_000_000_000 }
Note: the SDK sums only locks whose individual duration >= minLockDays.
status.totalLockAmount reflects the qualifying amount, not the total.Prompt 3 — Server-side verification
Add server-side DevFridge subscription verification to my API.
Endpoint to call: GET https://scan.devfridge.cool/api/sdk/check?wallet=WALLET&mint=MINT
For each authenticated request:
1. Get the user's wallet address from their session
2. Call the SDK check endpoint with their wallet and my token mint: [PASTE MINT]
3. Filter activeLocks: keep only locks where (unlockAt - createdAt) / 86400 >= 60
4. Sum amounts of qualifying locks only
5. Verify: qualifying locks exist AND sum >= minLockAmount AND best qualifying unlockAt > now
6. If valid, proceed with the request
7. If not valid, return 403 with a message to lock tokens
IMPORTANT: do NOT use bestLock alone — it may not meet the duration requirement.
Always filter by individual lock duration before summing amounts.
Cache the result for 5 minutes per wallet to avoid excessive API calls.
Never trust client-side subscription checks alone for sensitive operations.Prompt 4 — Custom plan configuration
Configure DevFridge SDK subscription plans for my project.
SDK docs: https://sdk.devfridge.cool
Token mint: [PASTE YOUR MINT HERE]
I want these subscription tiers:
- [DESCRIBE YOUR TIERS, e.g.:
"Basic: 2 weeks access, Pro: 1 month access, VIP: 3 months access"]
Rules:
- minLockDays = how long the user must lock tokens (should be roughly
2x the access period so there's always a buffer before renewal)
- renewalThresholdDays = when to prompt for a new lock
(must be < minLockDays)
- minLockAmount = optional minimum token amount in raw units
(e.g. 10_000_000_000_000 for 10M tokens with 6 decimals)
- Only locks whose individual duration >= minLockDays count toward the
qualifying amount — short locks are excluded
- Longer locks mean the user doesn't need to renew as often
- A 180-day lock on a monthly plan (minLockDays: 60) gives ~151 days
of access before the renewal prompt at 29 days remaining
Set up the DevFridgeSDK with these plans and create the UI to show
which plan the user qualifies for based on their lock duration and amount.Key concepts
Why not auto-renew?
Traditional subscriptions require recurring on-chain transactions or off-chain billing infrastructure. With Fridge locks, the user makes one lock transaction. Longer locks automatically extend access without any renewal. A user who locks for 180 days on a monthly plan gets ~5 months of uninterrupted access from a single transaction.
Incentive alignment
Longer locks reduce sell pressure on the token, benefit the community, and give the user fewer renewal steps. The 2% claim fee on unlock also buys and burns $PASTA, adding deflationary pressure.
Security
Locks are enforced by the Fridge on-chain program (9RY54dNPYTzDyh3TfFqDdt2b2KMM56KW1tw9erRTGQo6). Nobody can withdraw before unlock_at, including the depositor. The SDK reads lock state via RPC — there is no centralized database that can be tampered with.
The SDK evaluates each lock individually: only locks whose original duration ≥ minLockDays contribute to the qualifying amount. This prevents gaming via short high-value locks combined with long low-value locks. For sensitive operations, always verify subscription server-side using the REST API rather than trusting client-side checks alone.