Two payment systems. One credit model. Stripe for humans, Base/USDC for AI agents. Full backend implementation for the developer.
npm install stripe
// routes/credits.js const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const CREDIT_PACKS = { 99: { credits: 100, price_id: 'price_stripe_id_99' }, 299: { credits: 350, price_id: 'price_stripe_id_299' }, 599: { credits: 800, price_id: 'price_stripe_id_599' }, 999: { credits: 1500, price_id: 'price_stripe_id_999' }, }; // POST /credits/purchase app.post('/credits/purchase', authenticate, async (req, res) => { const { pack } = req.body; // 99 | 299 | 599 | 999 const packInfo = CREDIT_PACKS[pack]; if (!packInfo) return res.status(400).json({ error: 'Invalid pack' }); const session = await stripe.checkout.sessions.create({ mode: 'payment', line_items: [{ price: packInfo.price_id, quantity: 1 }], customer_email: req.user.email, metadata: { user_id: req.user.id, credits: packInfo.credits, pack_amount: pack, }, success_url: `https://sloiai.com/credits/success?session={CHECKOUT_SESSION_ID}`, cancel_url: 'https://sloiai.com/credits', }); res.json({ url: session.url }); // redirect buyer to Stripe });
// POST /webhooks/stripe // Stripe signs every webhook — always verify signature app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), // raw body required async (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { return res.status(400).send(`Webhook error: ${err.message}`); } if (event.type === 'checkout.session.completed') { const session = event.data.object; const { user_id, credits, pack_amount } = session.metadata; // Add credits to Supabase await addCredits({ user_id, credits: parseInt(credits), source: 'stripe', amount_usd: parseInt(pack_amount), stripe_session_id: session.id, }); // Notify buyer via Telegram await sendTelegram(user_id, `⚡ ${credits} credits added to your SLOI AI account.\nBalance: ${await getBalance(user_id)} credits.` ); } res.json({ received: true }); });
// lib/credits.js const { createClient } = require('@supabase/supabase-js'); const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY // service role — bypasses RLS ); async function addCredits({ user_id, credits, source, amount_usd, stripe_session_id, tx_hash }) { // 1. Update balance await supabase.rpc('increment_credits', { p_user_id: user_id, p_amount: credits }); // 2. Log transaction await supabase.from('credit_transactions').insert({ user_id, credits, source, amount_usd: amount_usd || null, stripe_session_id: stripe_session_id || null, tx_hash: tx_hash || null, // for Base/USDC payments created_at: new Date().toISOString(), }); } async function getBalance(user_id) { const { data } = await supabase .from('credits') .select('balance') .eq('user_id', user_id) .single(); return data?.balance || 0; } async function deductCredits(user_id, amount, action) { const balance = await getBalance(user_id); if (balance < amount) throw new Error('insufficient_credits'); await supabase.rpc('decrement_credits', { p_user_id: user_id, p_amount: amount }); await supabase.from('credit_transactions').insert({ user_id, credits: -amount, source: 'usage', action }); } module.exports = { addCredits, getBalance, deductCredits };
-- Credits balance per user CREATE TABLE credits ( user_id UUID PRIMARY KEY REFERENCES users(id), balance INT NOT NULL DEFAULT 0, total_purchased INT DEFAULT 0, total_spent INT DEFAULT 0, updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Transaction log CREATE TABLE credit_transactions ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, user_id UUID REFERENCES users(id), credits INT NOT NULL, -- positive=purchase, negative=usage source TEXT, -- stripe | usdc | usage action TEXT, -- deal_hunter | loi | autopilot amount_usd NUMERIC(10,2), stripe_session_id TEXT, tx_hash TEXT, -- Base blockchain tx wallet_address TEXT, -- agent wallet created_at TIMESTAMPTZ DEFAULT NOW() ); -- Atomic increment (thread-safe) CREATE OR REPLACE FUNCTION increment_credits(p_user_id UUID, p_amount INT) RETURNS VOID AS $$ INSERT INTO credits (user_id, balance, total_purchased) VALUES (p_user_id, p_amount, p_amount) ON CONFLICT (user_id) DO UPDATE SET balance = credits.balance + p_amount, total_purchased = credits.total_purchased + p_amount, updated_at = NOW(); $$ LANGUAGE sql; -- Atomic decrement CREATE OR REPLACE FUNCTION decrement_credits(p_user_id UUID, p_amount INT) RETURNS VOID AS $$ UPDATE credits SET balance = balance - p_amount, total_spent = total_spent + p_amount, updated_at = NOW() WHERE user_id = p_user_id AND balance >= p_amount; $$ LANGUAGE sql;
// routes/webhooks/base.js // Alchemy sends this webhook on every USDC transfer to SLOI_AI_WALLET const SLOI_AI_WALLET = process.env.SLOI_AI_WALLET_ADDRESS; const USDC_CONTRACT = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base const USDC_PACKS = { '99': 100, '299': 350, '599': 800, '999': 1500, }; // POST /webhooks/base app.post('/webhooks/base', async (req, res) => { const { event } = req.body; for (const activity of event.activity) { const { fromAddress, toAddress, value, asset, hash } = activity; // Only USDC transfers to our wallet if (toAddress.toLowerCase() !== SLOI_AI_WALLET.toLowerCase()) continue; if (asset !== 'USDC') continue; const amountUsdc = Math.round(value).toString(); const credits = USDC_PACKS[amountUsdc]; if (!credits) { // Non-standard amount — handle as streaming payment await handleStreamingPayment(fromAddress, value, hash); continue; } // Find agent by wallet address const agent = await getAgentByWallet(fromAddress); if (!agent) { console.warn('Unknown wallet:', fromAddress); continue; } // Add credits await addCredits({ user_id: agent.user_id, credits, source: 'usdc', amount_usd: parseInt(amountUsdc), tx_hash: hash, wallet_address: fromAddress, }); console.log(`✓ ${credits} credits → ${agent.name} (${amountUsdc} USDC, tx: ${hash})`); } res.json({ ok: true }); });
// Streaming: agent sends exact USDC for one action const STREAMING_PRICES = { watcher: 1.00, // $1.00 USDC / day deal_hunter: 10.00, // $10.00 USDC / session autopilot: 20.00, // $20.00 USDC / session loi: 5.00, // $5.00 USDC / LOI }; async function handleStreamingPayment(fromAddress, value, txHash) { const amount = Math.round(value * 100) / 100; // 2 decimal precision const agent = await getAgentByWallet(fromAddress); if (!agent) return; // Find matching action const action = Object.entries(STREAMING_PRICES) .find(([_, price]) => Math.abs(price - amount) < 0.01)?.[0]; if (!action) return; // unknown amount // Issue one-time authorization token const token = await createAuthToken({ agent_id: agent.id, action, tx_hash: txHash, expires_in: 3600, // 1 hour to use }); // Notify agent via webhook await notifyAgent(agent.id, { type: 'payment_confirmed', action, auth_token: token, amount_usdc: amount, tx_hash: txHash, }); }
// POST /v1/agents/register app.post('/v1/agents/register', async (req, res) => { const { name, email, framework, wallet_address } = req.body; // Create agent account const { data: agent } = await supabase .from('agents') .insert({ name, email, framework, wallet_address, tier: 'read' }) .select().single(); // Generate API key const api_key = 'sk-sloi-' + generateSecureKey(32); await supabase.from('api_keys').insert({ agent_id: agent.id, key_hash: hash(api_key) }); res.json({ agent_id: agent.id, api_key, tier: 'read', sloi_wallet: process.env.SLOI_AI_WALLET_ADDRESS, // agent sends USDC here payment_methods: { usdc_on_base: { address: process.env.SLOI_AI_WALLET_ADDRESS, network: 'base-mainnet', token: 'USDC', pack_amounts: [99, 299, 599, 999], streaming_prices: STREAMING_PRICES, } } }); });
// POST /v1/credits/purchase // Works for both humans (Stripe) and agents (USDC) app.post('/v1/credits/purchase', authenticate, async (req, res) => { const { pack } = req.body; // 99 | 299 | 599 | 999 const caller = req.caller; // set by authenticate middleware if (caller.type === 'agent' && caller.wallet_address) { // Agent → instruct to send USDC on Base return res.json({ method: 'usdc_on_base', instruction: `Send ${pack} USDC to ${process.env.SLOI_AI_WALLET_ADDRESS} on Base mainnet`, wallet: process.env.SLOI_AI_WALLET_ADDRESS, amount_usdc: pack, credits_to_receive: CREDIT_PACKS[pack].credits, confirmation: 'Credits added within 1 block (~2 seconds)', }); } // Human buyer → Stripe checkout const session = await stripe.checkout.sessions.create({ /* ... */ }); res.json({ method: 'stripe', url: session.url }); }); // Authenticate middleware — detects human vs agent async function authenticate(req, res, next) { const key = req.headers['x-api-key']; const token = req.headers['authorization']?.replace('Bearer ', ''); if (key?.startsWith('sk-sloi-')) { // API key → agent const agent = await getAgentByKey(key); req.caller = { type: 'agent', ...agent }; } else if (token) { // JWT → human (Supabase auth) const user = await supabase.auth.getUser(token); req.caller = { type: 'human', ...user.data.user }; } else { return res.status(401).json({ error: 'unauthorized' }); } next(); }
// Agent calls this before every negotiation async function ensureCredits(minCredits = 15) { const { data } = await sloi.get('/credits/balance'); if (data.balance >= minCredits) return; // enough // Choose pack — buy smallest that covers need const pack = data.balance < 15 ? 99 : 299; // Check USDC balance on Base const usdcBalance = await agentkit.getBalance({ token: 'usdc' }); if (usdcBalance >= pack) { // Pay with USDC on Base const tx = await agentkit.transfer({ to: SLOI_AI_WALLET, amount: pack.toString(), token: 'usdc', network: 'base-mainnet', }); await tx.wait(); // ~2 seconds console.log(`Paid ${pack} USDC on Base. Credits incoming.`); } else { // Fallback: request human top-up via Telegram Bot await notifyBoss({ type: 'low_credits', message: `Agent needs top-up. Balance: ${data.balance}. Please add USDC to agent wallet or buy credits at sloiai.com/credits` }); throw new Error('insufficient_funds'); } }
| Variable | Value | Where to get it |
|---|---|---|
| STRIPE_SECRET_KEY | sk_live_... | dashboard.stripe.com → API keys |
| STRIPE_WEBHOOK_SECRET | whsec_... | Stripe → Webhooks → signing secret |
| STRIPE_PRICE_ID_99 | price_... | Stripe → Products → $99 pack |
| STRIPE_PRICE_ID_299 | price_... | Stripe → Products → $299 pack |
| STRIPE_PRICE_ID_599 | price_... | Stripe → Products → $599 pack |
| STRIPE_PRICE_ID_999 | price_... | Stripe → Products → $999 pack |
| SLOI_AI_WALLET_ADDRESS | 0x... | Coinbase CDP → create wallet on Base |
| SLOI_AI_WALLET_PRIVATE_KEY | 0x... | Coinbase CDP → export key (keep secret) |
| ALCHEMY_API_KEY | alchemy_... | alchemy.com → Base mainnet app |
| ALCHEMY_WEBHOOK_AUTH | ... | Alchemy → Notify → webhook auth token |
| SUPABASE_URL | https://xxx.supabase.co | Supabase → Project Settings → API |
| SUPABASE_SERVICE_KEY | eyJh... | Supabase → service_role key |
https://api.sloiai.com/webhooks/stripecheckout.session.completedstripe trigger checkout.session.completedSLOI_AI_WALLET_ADDRESShttps://api.sloiai.com/webhooks/baseStripe webhook: https://api.sloiai.com/webhooks/stripe Base webhook: https://api.sloiai.com/webhooks/base Telegram Bot events: https://api.sloiai.com/webhooks/telegram-bot