← Back to list

clerk-alchemy-supabase-integration-flow
by 2Rds
⭐ 0🍴 0📅 Jan 18, 2026
SKILL.md
name: Clerk-Alchemy-Supabase Integration Flow description: This skill should be used when the user asks about "BlockDrive auth flow", "wallet sync to database", "Clerk Alchemy Supabase integration", "embedded wallet sync", "user profile with wallet", "sync-alchemy-wallet", "wallet database storage", or needs to understand or implement the complete authentication and wallet synchronization flow used in BlockDrive. This is the core integration pattern connecting user identity to blockchain wallets. version: 0.1.0
Clerk-Alchemy-Supabase Integration Flow
Overview
BlockDrive uses a three-service integration pattern: Clerk for identity, Alchemy for embedded wallets, and Supabase for data persistence. This creates a seamless experience where users authenticate once and automatically receive a blockchain wallet linked to their account.
When to Use
Activate this skill when:
- Understanding the complete BlockDrive auth architecture
- Implementing wallet-to-user linking
- Syncing wallet addresses to the database
- Troubleshooting the auth/wallet flow
- Extending the integration pattern
Complete Architecture
┌─────────────────────────────────────────────────────────────────┐
│ BLOCKDRIVE AUTH ARCHITECTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CLERK │────▶│ ALCHEMY │────▶│ SUPABASE │ │
│ │ Identity │ │ Wallet │ │ Database │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ │ │ │ │
│ 1. User signs in 2. JWT creates 3. Wallet address │
│ via Clerk MPC wallet stored in profiles │
│ │
│ ──────────────────────────────────────────────────────────── │
│ │
│ User Experience: Sign in once → Wallet automatically created │
│ No seed phrases, no wallet extensions, gas-sponsored txns │
│ │
└─────────────────────────────────────────────────────────────────┘
Step-by-Step Flow
Step 1: User Authentication (Clerk)
// User signs in via Clerk UI component
// Clerk manages the entire auth flow
import { useAuth, useSession } from '@clerk/clerk-react';
const { isSignedIn, getToken } = useAuth();
const { session } = useSession();
// Wait for successful sign-in
if (isSignedIn && session) {
// Proceed to wallet initialization
}
Step 2: JWT Token Retrieval
// Get the Clerk session token for Alchemy auth
const clerkToken = await getToken();
// This token contains:
// - User ID (sub claim)
// - Session ID
// - Expiration time
// - Clerk issuer URL
// Token is OIDC-compliant for Alchemy validation
Step 3: Wallet Initialization (Alchemy)
import { AlchemySignerWebClient, SolanaSigner } from '@account-kit/signer';
// Initialize Alchemy Web Signer with Clerk JWT
const signerClient = new AlchemySignerWebClient({
connection: { jwt: clerkToken },
iframeConfig: { iframeContainerId: 'alchemy-signer-iframe-container' },
});
// Authenticate with Alchemy
const authResponse = await signerClient.submitJwt({
jwt: clerkToken,
authProvider: 'clerk',
});
// Complete authentication
await signerClient.completeAuthWithBundle({
bundle: authResponse.bundle,
orgId: authResponse.orgId,
connectedEventName: 'connected',
authenticatingType: 'jwt',
});
// Create Solana signer
const solanaSigner = new SolanaSigner(signerClient);
const walletAddress = solanaSigner.address;
Step 4: Wallet Sync to Supabase
// Sync the wallet address to user's profile in Supabase
const syncWalletToDatabase = async (address: string, token: string) => {
const response = await fetch(
`${SUPABASE_URL}/functions/v1/sync-alchemy-wallet`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
solanaAddress: address,
walletProvider: 'alchemy_embedded_mpc',
network: 'devnet', // or 'mainnet'
}),
}
);
return response.json();
};
Supabase Edge Function
The sync-alchemy-wallet edge function handles wallet persistence:
// supabase/functions/sync-alchemy-wallet/index.ts
Deno.serve(async (req) => {
// 1. Validate Authorization header (Clerk JWT)
const authHeader = req.headers.get('Authorization');
const token = authHeader?.replace('Bearer ', '');
// 2. Extract Clerk user ID from JWT
const payload = JSON.parse(atob(token.split('.')[1]));
const clerkUserId = payload.sub;
// 3. Parse request body
const { solanaAddress, walletProvider } = await req.json();
// 4. Validate Solana address format
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
if (!base58Regex.test(solanaAddress)) {
return new Response(JSON.stringify({ error: 'Invalid address' }), {
status: 400
});
}
// 5. Upsert to profiles table
const { error } = await supabase
.from('profiles')
.upsert({
clerk_user_id: clerkUserId,
solana_wallet_address: solanaAddress,
wallet_provider: walletProvider,
wallet_created_at: new Date().toISOString(),
}, {
onConflict: 'clerk_user_id'
});
return new Response(JSON.stringify({ success: true }));
});
Database Schema
Profiles Table
CREATE TABLE profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clerk_user_id TEXT UNIQUE NOT NULL,
solana_wallet_address TEXT,
wallet_provider TEXT DEFAULT 'alchemy_embedded_mpc',
wallet_created_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for fast lookups
CREATE INDEX idx_profiles_clerk_user_id ON profiles(clerk_user_id);
CREATE INDEX idx_profiles_wallet ON profiles(solana_wallet_address);
Complete Provider Implementation
// Combining all three services in one provider
export function BlockDriveAuthProvider({ children }) {
const { isSignedIn, getToken } = useAuth();
const { session } = useSession();
const [walletAddress, setWalletAddress] = useState<string | null>(null);
const [isWalletReady, setIsWalletReady] = useState(false);
const signerRef = useRef<SolanaSigner | null>(null);
useEffect(() => {
if (!isSignedIn || !session || isWalletReady) return;
const initializeWallet = async () => {
try {
// Step 1: Get Clerk token
const token = await getToken();
if (!token) throw new Error('No token');
// Step 2: Initialize Alchemy
const signerClient = new AlchemySignerWebClient({
connection: { jwt: token },
iframeConfig: { iframeContainerId: 'alchemy-signer-iframe' },
});
// Step 3: Authenticate
const authResponse = await signerClient.submitJwt({
jwt: token,
authProvider: 'clerk',
});
await signerClient.completeAuthWithBundle({
bundle: authResponse.bundle,
orgId: authResponse.orgId,
connectedEventName: 'connected',
authenticatingType: 'jwt',
});
// Step 4: Get wallet
const signer = new SolanaSigner(signerClient);
signerRef.current = signer;
setWalletAddress(signer.address);
// Step 5: Sync to Supabase
await syncWalletToDatabase(signer.address, token);
setIsWalletReady(true);
} catch (error) {
console.error('Wallet init failed:', error);
}
};
initializeWallet();
}, [isSignedIn, session, isWalletReady, getToken]);
return (
<AuthContext.Provider value={{ walletAddress, isWalletReady, signer: signerRef.current }}>
{children}
</AuthContext.Provider>
);
}
Query Wallet from Database
Retrieve wallet for any user operation:
// Get wallet address from Supabase for a user
const getUserWallet = async (clerkUserId: string) => {
const { data, error } = await supabase
.from('profiles')
.select('solana_wallet_address')
.eq('clerk_user_id', clerkUserId)
.single();
return data?.solana_wallet_address;
};
// Verify wallet ownership
const verifyWalletOwnership = async (clerkUserId: string, walletAddress: string) => {
const storedWallet = await getUserWallet(clerkUserId);
return storedWallet === walletAddress;
};
Error Handling
Common Integration Errors
const handleIntegrationError = (error: Error, step: string) => {
switch (step) {
case 'clerk':
// User not signed in or session expired
// Redirect to sign-in
break;
case 'alchemy':
// JWT invalid or Alchemy service issue
// Retry with fresh token
break;
case 'supabase':
// Database sync failed
// Retry or queue for later
break;
}
};
Testing the Flow
Verify Complete Integration
async function testIntegration() {
// 1. Check Clerk auth
const { isSignedIn, getToken } = useAuth();
console.log('Clerk signed in:', isSignedIn);
// 2. Check wallet initialized
const { walletAddress } = useAlchemyWallet();
console.log('Wallet address:', walletAddress);
// 3. Check database sync
const token = await getToken();
const response = await fetch(
`${SUPABASE_URL}/rest/v1/profiles?select=solana_wallet_address`,
{
headers: { Authorization: `Bearer ${token}` }
}
);
const data = await response.json();
console.log('Database wallet:', data[0]?.solana_wallet_address);
// 4. Verify match
console.log('Wallets match:', walletAddress === data[0]?.solana_wallet_address);
}
Additional Resources
Reference Files
references/database-schema.md- Complete Supabase schemareferences/edge-functions.md- Edge function patterns
Examples
examples/complete-provider.tsx- Full implementationexamples/sync-function.ts- Edge function code
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon