akca-catalyst
v0.2.1
Published
Official SDK for Akca Catalyst — NFT subscriptions, sales, DAO memberships, decentralized gated access on Solana
Maintainers
Readme
akca-catalyst
Official SDK for Akca Catalyst — NFT infrastructure on Solana. Subscriptions, one-time sales, DAO memberships, and token-gated access.
Built by Akca Network OÜ.
Features
- Subscriptions — Recurring NFT billing (monthly/quarterly/yearly), auto-expiry, renewal
- NFT Sales — One-time purchases with supply limits and trait-based tiers
- DAO / Membership — Token-gated communities with role-based access via traits
- Verify — Ownership, trait, and rule-based access checks
- Mint — Full mint flow with wallet signing
- Webhooks — Signature-verified real-time event notifications
- React Hooks —
useAkcaGate,useAkcaMint,useAkcaSubscription - Middleware — Next.js and Express middleware for server-side gating
Install
npm install akca-catalystQuick Start
Server-side (Node.js)
import { AkcaCatalyst } from 'akca-catalyst';
const catalyst = new AkcaCatalyst({
apiKey: process.env.CATALYST_API_KEY!,
});
// Verify NFT ownership
const result = await catalyst.verifyOwnership({
wallet: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
collectionId: 'col_xxx',
});
if (result.hasAccess) {
// User owns an NFT from the collection
}React
import { AkcaProvider, useAkcaGate } from 'akca-catalyst/react';
function App() {
return (
<AkcaProvider config={{ apiKey: 'ak_test_xxx' }}>
<GatedContent />
</AkcaProvider>
);
}
function GatedContent() {
const { hasAccess, isLoading, nfts } = useAkcaGate({
collection: 'col_xxx',
wallet: connectedWallet,
});
if (isLoading) return <p>Checking access...</p>;
if (!hasAccess) return <p>NFT required for access.</p>;
return <p>Welcome! You own {nfts.length} NFT(s).</p>;
}Subscriptions
Recurring NFT-based billing. The NFT carries subscription state — expired subscriptions return hasAccess: false even though the wallet still owns the NFT.
// Check subscription status
const { subscription } = await catalyst.getSubscription({
nftMintAddress: 'NFT_MINT_ADDRESS',
});
console.log(subscription.status); // 'active' | 'expired'
console.log(subscription.expiresAt); // '2026-04-01T00:00:00Z'
console.log(subscription.interval); // 'monthly' | 'quarterly' | 'yearly'
// Renew subscription (after user sends SOL payment)
const renewed = await catalyst.renewSubscription({
nftMintAddress: 'NFT_MINT_ADDRESS',
txSignature: '5Gz...',
});React Hook
import { useAkcaSubscription } from 'akca-catalyst/react';
function SubscriptionStatus({ nftMint }: { nftMint: string }) {
const { subscription, isExpired, daysRemaining, renew } = useAkcaSubscription({
nftMintAddress: nftMint,
});
if (isExpired) {
return (
<button onClick={() => renew(signTransaction)}>
Renew for {subscription.price} SOL
</button>
);
}
return <p>Active — {daysRemaining} days remaining</p>;
}DAO / Membership
Token-gated communities with role-based access. Create membership tier items with Role traits, then use verifyTrait to gate features by role.
// Basic membership check (any NFT from collection)
const { hasAccess } = await catalyst.verifyOwnership({
wallet: userWallet,
collectionId: 'col_xxx',
});
// Role-based gating
const { hasAccess: canVote } = await catalyst.verifyTrait({
wallet: userWallet,
collectionId: 'col_xxx',
traitType: 'Role',
traitValues: ['Contributor', 'Core'],
});
// Core-only access
const { hasAccess: isCore } = await catalyst.verifyTrait({
wallet: userWallet,
collectionId: 'col_xxx',
traitType: 'Role',
traitValues: ['Core'],
});React: Role-Gated UI
import { useAkcaGate } from 'akca-catalyst/react';
function DAODashboard() {
const { hasAccess, nfts } = useAkcaGate({
collection: 'col_xxx',
wallet: connectedWallet,
});
if (!hasAccess) return <JoinPage />;
const roles = nfts.flatMap(n =>
n.attributes?.filter(a => a.trait_type === 'Role').map(a => a.value) || []
);
const isCore = roles.includes('Core');
const canVote = roles.includes('Contributor') || isCore;
return (
<div>
<PublicChannels />
{canVote && <VotingPanel />}
{isCore && <TreasuryPanel />}
</div>
);
}Mint Flow (React)
import { useAkcaMint } from 'akca-catalyst/react';
function MintButton() {
const { stage, mint, error, startMint, reset } = useAkcaMint({
collectionId: 'col_xxx',
wallet: connectedWallet,
});
const handleMint = () => startMint(async (transaction) => {
const signed = await wallet.signTransaction(deserialize(transaction));
return bs58.encode(signed.signature);
});
return (
<div>
<button onClick={handleMint} disabled={stage !== 'idle'}>
{stage === 'idle' ? 'Mint NFT' : stage}
</button>
{error && <p>{error} <button onClick={reset}>Retry</button></p>}
{mint?.nftMintAddress && <p>Minted: {mint.nftMintAddress}</p>}
</div>
);
}Next.js Middleware
import { createAkcaMiddleware } from 'akca-catalyst/nextjs';
import { NextResponse } from 'next/server';
export default createAkcaMiddleware({
apiKey: process.env.CATALYST_API_KEY!,
protectedPaths: ['/dashboard'],
verify: { mode: 'ownership', collectionId: 'col_xxx' },
getWallet: (req) => req.cookies.get('wallet')?.value || null,
onUnauthorized: (req, reason) =>
NextResponse.redirect(new URL('/login', req.url)),
});Express Middleware
import { akcaAuth } from 'akca-catalyst/express';
// Gate by ownership
app.use('/api/premium', akcaAuth({
apiKey: process.env.CATALYST_API_KEY!,
verify: { mode: 'ownership', collectionId: 'col_xxx' },
getWallet: (req) => req.headers['x-wallet'] || null,
}));
// Gate by role (DAO)
app.use('/api/dao/vote', akcaAuth({
apiKey: process.env.CATALYST_API_KEY!,
verify: {
mode: 'trait',
collectionId: 'col_xxx',
traitType: 'Role',
traitValues: ['Contributor', 'Core'],
},
getWallet: (req) => req.headers['x-wallet'] || null,
}));
app.get('/api/premium/data', (req, res) => {
const { hasAccess, nfts } = req.catalystGate!;
res.json({ hasAccess, nfts });
});Webhooks
import { verifyWebhookSignature, parseWebhookPayload } from 'akca-catalyst/webhook';
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-catalyst-signature'];
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET!)) {
return res.status(400).send('Invalid signature');
}
const payload = parseWebhookPayload(req.body);
switch (payload.event) {
case 'mint.success': // New NFT minted
case 'verify.success': // Ownership verified
case 'verify.fail': // Verification denied
case 'subscription.created': // Subscription started
case 'subscription.renewed': // Subscription extended
case 'subscription.expired': // Subscription expired
}
res.sendStatus(200);
});API Reference
| Method | Description |
|---|---|
| verifyOwnership({ wallet, collectionId }) | Check NFT ownership (expired subscriptions filtered) |
| verifyTrait({ wallet, collectionId, traitType, traitValues }) | Check trait-based access |
| verifyRules({ wallet, ruleId }) | Evaluate access rules |
| verifyToken({ token }) | Validate embed token |
| mint({ wallet, collectionId, itemId?, txSignature? }) | Initiate or confirm mint |
| getSubscription({ nftMintAddress }) | Get subscription status |
| renewSubscription({ nftMintAddress, txSignature }) | Extend subscription period |
Subpath Exports
| Path | Description |
|---|---|
| akca-catalyst | Core client + types |
| akca-catalyst/react | React hooks + provider (useAkcaGate, useAkcaMint, useAkcaSubscription) |
| akca-catalyst/nextjs | Next.js middleware |
| akca-catalyst/express | Express middleware |
| akca-catalyst/webhook | Webhook signature verification (server-only) |
Works With
- React 18+
- Next.js 14+
- Express 4+
- Node.js 18+
- TypeScript (full type definitions included)
- Solana (devnet & mainnet)
Links
License
MIT — Copyright (c) 2025-present Akca Network OÜ
See LICENSE for details.
