Skip to main content
Glama
turnkey-stacks-integration-guide.md16.8 kB
# TurnkeyHQ + Stacks Integration: Complete Implementation Guide ## Overview This guide provides a step-by-step implementation of a seamless purchase experience using TurnkeyHQ's secure enclave technology with your SIP-compliant Stacks smart contract. This eliminates the need for browser extensions like Leather or Xverse. ## Architecture ``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Frontend │ │ Backend API │ │ TurnkeyHQ │ │ (React/Next) │◄──►│ (Node.js) │◄──►│ Secure Enclave │ └─────────────────┘ └──────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ Stacks Network │ │ (SIP-010 FT) │ └─────────────────┘ ``` ## Step 1: TurnkeyHQ Setup ### 1.1 Create TurnkeyHQ Organization ```bash # Install TurnkeyHQ SDK npm install @turnkey/sdk-server @turnkey/sdk-client # Create organization curl -X POST https://api.turnkey.com/v1/activities/create-sub-organization \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "subOrganizationName": "stacks-wallet-app", "rootQuorumThreshold": 1, "rootUsers": [ { "userName": "admin", "userEmail": "admin@yourdomain.com" } ] }' ``` ### 1.2 Configure Policies ```typescript // policies/stacks-policy.json { "policyName": "Stacks Wallet Policy", "policyBody": { "statements": [ { "effect": "ALLOW", "actions": [ "CreatePrivateKey", "SignTransaction", "SignRawPayload" ], "resources": [ "wallet:stacks:*" ], "conditions": { "maxAmountPerTransaction": "1000000000", // 1 STX in microSTX "allowedRecipients": [ "SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE", "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9" ], "requireConfirmation": true } } ] } } ``` ## Step 2: Backend API Implementation ### 2.1 Initialize TurnkeyHQ Client ```typescript // backend/src/turnkey-client.ts import { TurnkeySDK } from '@turnkey/sdk-server'; import { TurnkeyClient } from '@turnkey/sdk-client'; export class TurnkeyStacksClient { private sdk: TurnkeySDK; private client: TurnkeyClient; constructor() { this.sdk = new TurnkeySDK({ apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY!, apiPublicKey: process.env.TURNKEY_PUBLIC_KEY!, baseUrl: 'https://api.turnkey.com', }); this.client = new TurnkeyClient({ apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY!, apiPublicKey: process.env.TURNKEY_PUBLIC_KEY!, baseUrl: 'https://api.turnkey.com', }); } async createUserWallet(userId: string, userEmail: string) { // Create sub-organization for user const subOrg = await this.sdk.createSubOrganization({ subOrganizationName: `user-${userId}`, rootQuorumThreshold: 1, rootUsers: [ { userName: userId, userEmail: userEmail, }, ], }); // Create Stacks wallet const wallet = await this.sdk.createWallet({ organizationId: subOrg.subOrganizationId, walletName: 'stacks-main', }); // Create Stacks private key const privateKey = await this.sdk.createPrivateKey({ organizationId: subOrg.subOrganizationId, privateKeyName: 'stacks-key', curve: 'CURVE_SECP256K1', addressFormats: ['ADDRESS_FORMAT_STACKS_P2PKH'], privateKeyTags: ['stacks', 'main'], }); return { subOrganizationId: subOrg.subOrganizationId, walletId: wallet.walletId, privateKeyId: privateKey.privateKeyId, address: privateKey.addresses[0].address, }; } } ``` ### 2.2 Stacks Transaction Builder ```typescript // backend/src/stacks-transaction.ts import { makeSTXTokenTransfer, broadcastTransaction, StacksNetwork, StacksTestnet, StacksMainnet } from '@stacks/transactions'; export class StacksTransactionBuilder { private network: StacksNetwork; constructor(isMainnet: boolean = false) { this.network = isMainnet ? new StacksMainnet() : new StacksTestnet(); } async buildTransferTransaction( recipient: string, amount: number, // in microSTX memo?: string ) { return makeSTXTokenTransfer({ recipient, amount, memo, network: this.network, }); } async buildContractCall( contractAddress: string, contractName: string, functionName: string, functionArgs: any[], senderKey: string ) { return makeContractCall({ contractAddress, contractName, functionName, functionArgs, network: this.network, senderKey, }); } } ``` ### 2.3 API Routes ```typescript // backend/src/routes/wallet.ts import express from 'express'; import { TurnkeyStacksClient } from '../turnkey-client'; import { StacksTransactionBuilder } from '../stacks-transaction'; const router = express.Router(); const turnkeyClient = new TurnkeyStacksClient(); const stacksBuilder = new StacksTransactionBuilder(); // Register user and create wallet router.post('/register', async (req, res) => { try { const { userId, userEmail } = req.body; // Create TurnkeyHQ wallet const wallet = await turnkeyClient.createUserWallet(userId, userEmail); // Register user on Stacks contract const registerTx = await stacksBuilder.buildContractCall( process.env.STACKS_CONTRACT_ADDRESS!, 'embedded-wallet-v4', 'register-user-secure', [ // Add your contract arguments here ], wallet.privateKeyId ); res.json({ success: true, wallet: { address: wallet.address, subOrganizationId: wallet.subOrganizationId, walletId: wallet.walletId, }, }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Execute purchase transaction router.post('/purchase', async (req, res) => { try { const { userId, recipient, amount, productId, signature } = req.body; // Verify signature (implement your verification logic) const isValid = await verifyPurchaseSignature(req.body, signature); if (!isValid) { return res.status(400).json({ error: 'Invalid signature' }); } // Get user's wallet info const wallet = await getUserWallet(userId); // Build Stacks transaction const tx = await stacksBuilder.buildTransferTransaction( recipient, amount, `Purchase: ${productId}` ); // Sign with TurnkeyHQ const signedTx = await turnkeyClient.signTransaction( wallet.subOrganizationId, wallet.privateKeyId, tx ); // Broadcast to Stacks network const result = await broadcastTransaction(signedTx, this.network); res.json({ success: true, txId: result.txid, explorerUrl: `https://explorer.stacks.co/txid/${result.txid}`, }); } catch (error) { res.status(500).json({ error: error.message }); } }); export default router; ``` ## Step 3: Frontend Implementation ### 3.1 React Hook for TurnkeyHQ Integration ```typescript // frontend/src/hooks/useTurnkeyWallet.ts import { useState, useEffect } from 'react'; import { TurnkeyClient } from '@turnkey/sdk-client'; export const useTurnkeyWallet = () => { const [wallet, setWallet] = useState(null); const [loading, setLoading] = useState(false); const createWallet = async (userEmail: string) => { setLoading(true); try { const response = await fetch('/api/wallet/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: generateUserId(), userEmail, }), }); const data = await response.json(); if (data.success) { setWallet(data.wallet); localStorage.setItem('turnkey-wallet', JSON.stringify(data.wallet)); } } catch (error) { console.error('Failed to create wallet:', error); } finally { setLoading(false); } }; const purchase = async (recipient: string, amount: number, productId: string) => { if (!wallet) throw new Error('Wallet not initialized'); try { const response = await fetch('/api/wallet/purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: wallet.subOrganizationId, recipient, amount, productId, signature: await signPurchaseData({ recipient, amount, productId }), }), }); const data = await response.json(); return data; } catch (error) { console.error('Purchase failed:', error); throw error; } }; return { wallet, loading, createWallet, purchase, }; }; ``` ### 3.2 Purchase Component ```tsx // frontend/src/components/PurchaseFlow.tsx import React, { useState } from 'react'; import { useTurnkeyWallet } from '../hooks/useTurnkeyWallet'; interface Product { id: string; name: string; price: number; // in STX description: string; } const PurchaseFlow: React.FC = () => { const { wallet, loading, createWallet, purchase } = useTurnkeyWallet(); const [userEmail, setUserEmail] = useState(''); const [selectedProduct, setSelectedProduct] = useState<Product | null>(null); const [purchasing, setPurchasing] = useState(false); const products: Product[] = [ { id: '1', name: 'Premium NFT', price: 0.5, // 0.5 STX description: 'Exclusive digital artwork', }, { id: '2', name: 'VIP Access', price: 1.0, // 1.0 STX description: 'Premium platform access', }, ]; const handleCreateWallet = async () => { if (!userEmail) return; await createWallet(userEmail); }; const handlePurchase = async (product: Product) => { setPurchasing(true); try { const result = await purchase( process.env.NEXT_PUBLIC_CONTRACT_ADDRESS!, // Your Stacks contract address product.price * 1000000, // Convert to microSTX product.id ); alert(`Purchase successful! Transaction: ${result.txId}`); } catch (error) { alert(`Purchase failed: ${error.message}`); } finally { setPurchasing(false); } }; if (!wallet) { return ( <div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md"> <h2 className="text-2xl font-bold mb-4">Create Your Wallet</h2> <p className="text-gray-600 mb-4"> No browser extension needed! Create a secure wallet with just your email. </p> <input type="email" placeholder="Enter your email" value={userEmail} onChange={(e) => setUserEmail(e.target.value)} className="w-full p-3 border rounded-md mb-4" /> <button onClick={handleCreateWallet} disabled={loading || !userEmail} className="w-full bg-blue-600 text-white p-3 rounded-md hover:bg-blue-700 disabled:opacity-50" > {loading ? 'Creating...' : 'Create Secure Wallet'} </button> </div> ); } return ( <div className="max-w-4xl mx-auto mt-8 p-6"> <div className="mb-6 p-4 bg-green-50 rounded-lg"> <h3 className="text-lg font-semibold text-green-800"> ✅ Wallet Connected </h3> <p className="text-green-600"> Address: {wallet.address} </p> </div> <h2 className="text-2xl font-bold mb-6">Available Products</h2> <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> {products.map((product) => ( <div key={product.id} className="border rounded-lg p-6"> <h3 className="text-xl font-semibold mb-2">{product.name}</h3> <p className="text-gray-600 mb-4">{product.description}</p> <div className="flex justify-between items-center"> <span className="text-2xl font-bold">{product.price} STX</span> <button onClick={() => handlePurchase(product)} disabled={purchasing} className="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50" > {purchasing ? 'Processing...' : 'Purchase'} </button> </div> </div> ))} </div> </div> ); }; export default PurchaseFlow; ``` ## Step 4: Environment Configuration ### 4.1 Backend Environment Variables ```bash # .env TURNKEY_PRIVATE_KEY=your_turnkey_private_key TURNKEY_PUBLIC_KEY=your_turnkey_public_key STACKS_CONTRACT_ADDRESS=SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE STACKS_NETWORK=testnet DATABASE_URL=your_database_url JWT_SECRET=your_jwt_secret ``` ### 4.2 Frontend Environment Variables ```bash # .env.local NEXT_PUBLIC_API_URL=http://localhost:3001/api NEXT_PUBLIC_CONTRACT_ADDRESS=SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE NEXT_PUBLIC_STACKS_NETWORK=testnet ``` ## Step 5: Deployment ### 5.1 Backend Deployment (Vercel/Heroku) ```json // package.json { "name": "turnkey-stacks-backend", "scripts": { "start": "node dist/index.js", "build": "tsc", "dev": "ts-node-dev src/index.ts" }, "dependencies": { "@turnkey/sdk-server": "^0.1.0", "@turnkey/sdk-client": "^0.1.0", "@stacks/transactions": "^4.0.0", "express": "^4.18.0", "cors": "^2.8.5" } } ``` ### 5.2 Frontend Deployment (Vercel) ```json // package.json { "name": "turnkey-stacks-frontend", "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "dependencies": { "@turnkey/sdk-client": "^0.1.0", "next": "^14.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" } } ``` ## Step 6: Security Considerations ### 6.1 TurnkeyHQ Security Features - **Secure Enclaves**: Private keys never leave hardware security modules - **Policy Enforcement**: All transactions validated against security policies - **Audit Logging**: Complete transaction history and access logs - **Multi-Factor Authentication**: Optional additional security layers ### 6.2 Stacks Contract Security - **SIP-010 Compliance**: Native fungible token with post-conditions - **Rate Limiting**: DoS protection built into contract - **Nonce Protection**: Replay attack prevention - **Policy Enforcement**: On-chain validation of all transactions ## Step 7: Testing ### 7.1 Unit Tests ```typescript // tests/turnkey-client.test.ts import { TurnkeyStacksClient } from '../src/turnkey-client'; describe('TurnkeyStacksClient', () => { let client: TurnkeyStacksClient; beforeEach(() => { client = new TurnkeyStacksClient(); }); it('should create user wallet', async () => { const wallet = await client.createUserWallet('test-user', 'test@example.com'); expect(wallet).toHaveProperty('address'); expect(wallet).toHaveProperty('subOrganizationId'); }); }); ``` ### 7.2 Integration Tests ```typescript // tests/integration.test.ts import request from 'supertest'; import app from '../src/app'; describe('Wallet API', () => { it('should register user and create wallet', async () => { const response = await request(app) .post('/api/wallet/register') .send({ userId: 'test-user', userEmail: 'test@example.com', }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); }); }); ``` ## Benefits of This Implementation 1. **No Browser Extensions**: Users don't need to install Leather, Xverse, or any wallet extensions 2. **Secure Key Management**: Private keys stored in TurnkeyHQ's secure enclaves 3. **SIP Compliance**: Full compliance with Stacks Improvement Proposals 4. **Seamless UX**: Email-based authentication with familiar web patterns 5. **Production Ready**: Includes rate limiting, error handling, and security measures 6. **Scalable**: Supports multiple users and high transaction volumes This implementation provides a complete, production-ready solution that combines the security of TurnkeyHQ with the power of Stacks and Clarity smart contracts, creating a seamless purchase experience without browser extensions.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/exponentlabshq/stacks-clarity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server