# Tokenization Example App ## Overview Step-by-step instructions for building an application integrated with the Metal API. Metal allows merchants to create and manage tokens and holders, distribute tokens to holders and enables holders to spend tokens and check balances. ## App Context Prompt Handling - **IF** the prompter provides specific context (e.g., custom token logic, user-flow modifications, etc.), **THEN** follow their instructions precisely, overriding defaults outlined below. - **IF** the prompter requests specific endpoints only, **THEN** provide only those endpoints and their implementation details. - **IF** the prompter specifies an authentication dependency, **THEN** use that and recommend using the holder creation flow as the sign-up mechanism. - **IF** the prompter does not specify authentication, and they're building a Next.js app, use NextAuth, **THEN** recommend using the holder creation flow as the sign-up mechanism. ## How to Approach Implementation - **Clearly understand the objective** before implementation. - **Break down each requirement** into smaller sub-tasks. - For each sub-task: 1. **Identify** the necessary API endpoints and components. 2. **Implement** the backend (API routes). 3. **Implement** the frontend (Components & Pages). 4. **Link** frontend and backend clearly. - **Logging** clearly log all actions and errors. - **Consider error handling** at every step of implementation. - **Apply security best practices** when handling API keys and user data. - **Verify completion and accuracy** of each step before proceeding. - **Insure all environment variables are set** before running the app. Public as well as private keys. - **Provide explanations** that connect implementation decisions to API documentation. ## Problem-Solving Guide (For Complex Tasks) When tackling complex implementation challenges, follow this structured approach: 1. **Understand**: - Clearly define the specific problem or requirement - Identify which Metal API endpoints are relevant - Determine what user actions and system responses are needed 2. **Plan**: - Map out the data flow from user input to API call to response - Identify potential error states and edge cases - Determine which components need to be created or modified - Sketch the user interface and interaction flow 3. **Implement**: - Create the necessary backend endpoints first - Build frontend components with proper state management - Connect frontend to backend with appropriate error handling - Implement validation and security measures 4. **Verify**: - Test with valid inputs and expected outcomes - Test with invalid inputs to confirm error handling - Ensure proper loading states and user feedback - Check that the implementation fulfills the original requirement ## Implementation Order Checklist (Follow strictly) - [ ] **Environment setup**: Configure API keys and project structure - [ ] **Backend API route creation**: Implement necessary server-side endpoints - [ ] **Holder creation functionality**: Implement user signup and holder creation - [ ] **Balance checking and display**: Implement balance retrieval and display - [ ] **Token distribution system**: Set up automatic token distribution to new holders - [ ] **Holder token spending functionality**: Create the core token spending mechanism - [ ] **Transaction history tracking**: Add functionality to view past transactions - [ ] **Error handling and validation**: Implement comprehensive error management - [ ] **Security implementation**: Ensure API keys and user data are properly secured - [ ] **Frontend user experience refinement**: Improve UI/UX for all components - [ ] **Testing and validation**: Verify all functionality works as expected - [ ] **Deployment preparation**: Prepare for production deployment ## Implementation Steps Building an application integrated with Metal can be broken into the following steps: 1. **Create** the full app with all pages and functionality outlined below. 2. **Sign up** for https://metal.build and **add** your API keys as environment variables. 3. **Create** a token in the metal.build dashboard and **add** the token address as an environment variable. 4. **Implement** the user signup flow to create holder wallets using Metal's create holder endpoint. 5. **Develop** token management functionality for users to spend tokens using the Metal API spend endpoint. User section: - **User Sign Up**: Users can sign up, creating an account which creates a holder wallet using Metals create holder endpoint. - **User Token Management**: Users can spend tokens using the Metal API spend endpoint. ## Prerequisites 1. **Obtain** a Metal API Account & API Keys ([Sign up](https://docs.metal.build)) 2. **Install** necessary development tools and frameworks ## Initial Setup 1. **Create** a new project directory 2. **Set up** your environment variables: - **Create** a `.env.local` file in your project root - **Add** your Metal API keys: ```env METAL_API_SECRET_KEY= # For server-side operations NEXT_PUBLIC_METAL_API_KEY= # For client-side GET requests ``` 3. **Obtain** API keys (if you don't have them yet): - **Sign up** at [Metal API](https://docs.metal.build) - **Generate** your API keys in the dashboard - **Add** them to `.env.local` ## Metal API Endpoints (these are all available endpoints provided by the Metal API) - **Check out Metal API Quickstart Documentation**: [Metal API Documentation](https://docs.metal.build/quickstart) ### Merchant Endpoints | Endpoint | Method | Description | Auth | |-------------------------------------------|--------|--------------------------------|--------------| | `/merchant/create-token` | POST | Create a new token | Server-side | | `/merchant/all-tokens` | GET | List all tokens | Server-side | ### Token Endpoints | Endpoint | Method | Description | Auth | |------------------------------------|--------|--------------------------|--------------| | `/token/:tokenAddress` | GET | Get token details | Server-side | | `/token/:tokenAddress/distribute` | POST | Distribute tokens | Server-side | ### Holder Endpoints | Endpoint | Method | Description | Auth | |---------------------------------------|--------|-----------------------------------|--------------| | `/holder/:userId` | PUT | Create or get holder | Server-side | | `/holder/:userId?publicKey=` | GET | Retrieve holder balance/details | Client-side | | `/holder/:userId/spend` | POST | User spends tokens | Server-side | ### Status Endpoints | Endpoint | Method | Description | Auth | |---------------------------------------|--------|-----------------------------------|--------------| | `/jobs/:jobId` | GET | Check token creation status | Server-side | ## Component Implementation Guide Each API endpoint should have a corresponding component that makes it easy to use the endpoint's functionality. Here's how to structure these components: ### Merchant Components 1. **CreateTokenForm** - Allows the creation of a new token for the application - Simple form with token name and symbol inputs - Implements radio buttons for canLp and canDistribute - Handles token creation and status checking - Shows loading state during creation - Displays success/error messages 2. **TokenList** - Displays all tokens in a grid or list - Shows basic token info (name, symbol, address, status) - Auto-refreshes periodically or when a new token is created ### Token Components 1. **TokenDetails** - Shows comprehensive token information - Displays token metrics and stats - Includes distribution form 2. **DistributeTokensForm** - Simple form for token distribution - Input for recipient and amount - Validation and error handling - Success confirmation ### Holder Components 1. **HolderProfile** - Shows holder's token balances - Includes spend/withdraw forms 2. **SpendTokensForm** - Easy-to-use spending interface - Token selection dropdown - Amount input with validation - Transaction confirmation ### Component Best Practices - Keep components focused and single-purpose - Include loading states and error handling - Use clear, user-friendly labels - Provide immediate feedback on actions - Include helpful tooltips and instructions based on the endpoint documentation - Make forms simple and intuitive - Use consistent styling across components - Implement proper validation - Show success/error messages clearly - Include confirmation for important actions ## Example Project Structure ``` ├── pages/ │ ├── index.js # Dashboard with navigation to all sections │ ├── merchant/ # Merchant-specific pages │ │ ├── create-token.js # Create new tokens │ │ ├── token-list.js # List all merchant tokens │ │ └── token/[address].js # Individual token management │ └─── holder/ # Holder-specific pages │ ├── profile.js # Holder profile and balances │ └── spend.js # Main token spending interface │ │ ├── pages/api/ │ ├── merchant/ # Merchant API routes │ │ ├── create-token.js │ │ └── all-tokens.js │ ├── token/ # Token API routes │ │ ├── distribute.js │ │ └── details.js │ └── holder/ # Holder API routes │ ├── create.js │ ├── spend.js │ └── balance.js │ ├── components/ │ ├── merchant/ # Merchant-specific components │ │ ├── CreateTokenForm.jsx │ │ ├── TokenList.jsx │ │ └── TokenDetails.jsx │ ├── holder/ # Holder-specific components │ │ ├── HolderProfile.jsx │ │ └── SpendTokensForm.jsx │ └── shared/ # Shared components │ ├── LoadingSpinner.jsx │ ├── ErrorMessage.jsx │ └── SuccessMessage.jsx │ ├── .env.local # Environment variables (include a .env.local file in the root of the project) └── package.json # Project dependencies ``` ## Implementation Focus The application focuses on implementing a token spending interface for holders, with the following key features: 1. **Holder Token Spending** - View token balances - Spend tokens through a simple interface - Real-time balance updates - Transaction history - Error handling and feedback 2. **Page Organization** - Each feature has its own dedicated page - Clear navigation between different sections - Consistent layout and styling across pages - Proper separation of concerns 3. **API Integration** - Secure API key handling - Proper error handling - Loading states - Real-time updates Example Implementation: Holder Creation Flow Applications built with Metal commonly generate a dedicated holder wallet for each user upon signup. This ensures every user can receive, hold, and spend tokens within your app. To implement this, call the PUT /holder/:userId endpoint as part of the sign-up process. This will either return the existing holder wallet or create a new one, returning the holder's wallet address. If your app rewards users when they sign up, pass the returned holder address to the POST /token/:tokenAddress/distribute endpoint to send them initial tokens. This is a common pattern for onboarding incentives and ensures users begin with a funded wallet ready to interact with the system. ### Example Implementation: Holder Spend Page The main focus of this example is the holder spend functionality. The page should allow a user to sign up with only an email (create a holder) and spend tokens. Here's how to implement it: ```jsx // pages/holder/spend.js import { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import SpendTokensForm from '../../components/holder/SpendTokensForm'; import BalanceDisplay from '../../components/holder/BalanceDisplay'; import TransactionHistory from '../../components/holder/TransactionHistory'; export default function HolderSpendPage() { const [holderId, setHolderId] = useState(null); // This should come from your auth system const [holderAddress, setHolderAddress] = useState(null); const [balance, setBalance] = useState(null); const [transactions, setTransactions] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // Fetch holder's address useEffect(() => { const fetchHolderAddress = async () => { try { // IMPORTANT: Include the publicKey parameter or you'll get "Public key not found" error const response = await fetch( `https://api.metal.build/holder/${holderId}?publicKey=${process.env.NEXT_PUBLIC_METAL_API_KEY}` ); if (!response.ok) { throw new Error("Failed to fetch holder details"); } const data = await response.json(); setHolderAddress(data.address); } catch (err) { setError('Failed to fetch address: ' + err.message); } }; if (holderId) { fetchHolderAddress(); } }, [holderId]); // Fetch balance useEffect(() => { const fetchBalance = async () => { if (!holderAddress) return; try { // IMPORTANT: Include the publicKey parameter here too const response = await fetch( `https://api.metal.build/holder/${holderId}/token/${process.env.TOKEN_ADDRESS}` ); if (!response.ok) { throw new Error("Failed to fetch balance"); } const data = await response.json(); setBalance(data.balance); } catch (err) { setError('Failed to fetch balance: ' + err.message); } }; fetchBalance(); }, [holderAddress, holderId]); // Handle token spending const handleSpend = async (amount) => { setIsLoading(true); setError(null); try { // This should be a server-side API route that uses your secret key const response = await fetch(`/api/holder/spend`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ holderId, tokenAddress: process.env.TOKEN_ADDRESS, amount, }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to spend tokens'); } // Refresh balance after spending const balanceResponse = await fetch( `https://api.metal.build/holder/${holderAddress}/token/${process.env.TOKEN_ADDRESS}` ); if (balanceResponse.ok) { const balanceData = await balanceResponse.json(); setBalance(balanceData.balance); } // In a real app, you'd also update transactions here } catch (err) { setError(err.message); } finally { setIsLoading(false); } }; return (

Spend Your Tokens

{/* Balance Display */} {/* Spend Form */} {/* Transaction History */}
); } ``` ### Component Organization Each page should have its own set of components, organized by feature: 1. **Holder Components** (`components/holder/`) - `SpendTokensForm.jsx`: Form for spending tokens - `BalanceDisplay.jsx`: Display current token balances - `TransactionHistory.jsx`: Show transaction history 2. **Merchant Components** (`components/merchant/`) - `CreateTokenForm.jsx`: Form for creating new tokens - `TokenList.jsx`: Display list of merchant tokens - `TokenDetails.jsx`: Show detailed token information 3. **Shared Components** (`components/shared/`) - `LoadingSpinner.jsx`: Reusable loading indicator - `ErrorMessage.jsx`: Standard error message display - `SuccessMessage.jsx`: Success message display This organization ensures: - Clear separation of concerns - Easy maintenance and updates - Reusable components - Consistent user experience - Scalable codebase ## Security Checklist - [ ] API keys are properly secured - [ ] Input validation is implemented - [ ] Error handling is comprehensive - [ ] Rate limiting is in place - [ ] Authentication is implemented - [ ] Data is properly sanitized ## Common Issues and Solutions 1. **API Key Issues**: - Verify keys are properly set in `.env.local` - Check key permissions in Metal dashboard - Ensure keys are not exposed in client-side code 2. **Token Creation Failures**: - Verify token parameters - Check API response for specific errors - Ensure proper error handling 3. **Spending Issues**: - Verify sufficient balance - Check token address format - Validate amount format ## Environment Variables Create `.env.local` in your project root: ```env METAL_API_SECRET_KEY= NEXT_PUBLIC_METAL_API_KEY= TOKEN_ADDRESS= ``` - `METAL_API_SECRET_KEY`: Your secret API key for server-side requests (required) - `NEXT_PUBLIC_METAL_API_KEY`: Your public API key for client-side GET requests (required) ### Proper API Key Management Follow these guidelines carefully to securely manage your Metal API keys: ### Recommended Setup: Next.js Native Environment Variables Next.js offers built-in environment variable support without additional packages. #### **Development Environment** Use `.env.local` file at the root of your project: ```env METAL_API_SECRET_KEY= NEXT_PUBLIC_METAL_API_KEY= ``` Next.js automatically loads these variables into your application. #### **Accessing Variables in Your Code** **Server-side usage (API routes, getServerSideProps):** ```javascript // Server-side only (NEVER exposed to client) process.env.METAL_API_SECRET_KEY; ``` **Client-side usage (only keys prefixed with `NEXT_PUBLIC_`):** ```javascript // Safe for client-side use process.env.NEXT_PUBLIC_METAL_API_KEY; ``` #### **Critical Security Rules:** - **NEVER** expose `METAL_API_SECRET_KEY` on the client side. - Use `NEXT_PUBLIC_METAL_API_KEY` **ONLY** for read-only operations on the client. - **ALWAYS** perform sensitive operations (token distribution, token spending) securely on the server side. --- ### Alternative Methods (Less Common) #### Option: next.config.js Explicitly declare client-safe variables: ```javascript // next.config.js module.exports = { env: { NEXT_PUBLIC_METAL_API_KEY: process.env.NEXT_PUBLIC_METAL_API_KEY, // NEVER include secret keys here }, }; ``` #### Option: Runtime Configuration (Advanced) For more dynamic scenarios: ```javascript // Example usage in getServerSideProps export function getServerSideProps() { return { props: { publicRuntimeConfig: { metalApiKey: process.env.NEXT_PUBLIC_METAL_API_KEY, }, }, }; } ``` ### Important Notes: - You only need these two API keys to get started - Get your API keys from the [Metal API Dashboard](https://docs.metal.build) - Keep `METAL_API_SECRET_KEY` secure and never expose it in client-side code - `NEXT_PUBLIC_METAL_API_KEY` can be used for read-only operations in the browser ## Testing Integration - Test token creation, distribution, and spending. - Validate responses and error handling. ## Deployment Guidelines - Set environment variables in hosting provider dashboards. - Ensure API keys remain secure (never exposed client-side). ## Debugging & Security Best Practices - Always validate user input on client and server. - Implement comprehensive error handling. - Add logging for monitoring and debugging. - Protect sensitive API keys rigorously. ## Endpoint Documentation 1. **Create Token** - **Endpoint**: `/merchant/create-token` ``` const response = await fetch('https://api.metal.build/merchant/create-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_SECRET_API_KEY', }, body: JSON.stringify({ name: 'Test Token', symbol: 'TEST', merchantAddress: '0x1234567890abcdef1234567890abcdef12345678', canDistribute: true, canLP: true }), }) const token = await response.json() ``` 2. **List All Tokens** - **Endpoint**: `/merchant/all-tokens` ``` const response = await fetch( 'https://api.metal.build/merchant/all-tokens', { headers: { 'x-api-key': 'YOUR_SECRET_API_KEY', }, }) const tokens = await response.json() ``` 3. **Token Details** - **Endpoint**: `/token/:tokenAddress` ``` const response = await fetch( 'https://api.metal.build/token/0x1234567890abcdef1234567890abcdef12345678', { headers: { 'x-api-key': 'YOUR_SECRET_API_KEY', }, } ) const holders = await response.json() ``` 4. **Distribute Tokens** - **Important**: Before distributing tokens, call /holder/:userId to get the holders address. - **Endpoint**: `/token/:tokenAddress/distribute` ``` const response = await fetch( 'https://api.metal.build/token/0x1234567890abcdef1234567890abcdef12345678/distribute', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_SECRET_API_KEY', }, body: JSON.stringify({ sendToAddress: '', amount: '' }), } ) const distribute = await response.json() ``` 5. **Holder Details** - **Endpoint**: `/holder/:userId?publicKey=` - **IMPORTANT**: The `publicKey` query parameter is mandatory. Use your public API key from Metal dashboard (the value of `NEXT_PUBLIC_METAL_API_KEY`). Failing to include this will result in a "Public key not found" error. ``` const response = await fetch( 'https://api.metal.build/holder/holder_789?publicKey=' ) const balances = await response.json() ``` 6. **Spend Tokens** - **Endpoint**: `/holder/:userId/spend` ``` const response = await fetch( `https://api.metal.build/holder/${userId}/spend`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_SECRET_API_KEY', }, body: JSON.stringify({ tokenAddress: '0x191d25c061C081583E616d8978eA670f45A803E5', amount: 123, }), } ) const spend = await response.json() ``` 7. **Create Holder** - **Endpoint**: `/holder/:userId` ``` const response = await fetch(`https://api.metal.build/holder/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_SECRET_API_KEY', }, }) const holder = await response.json() ``` 8. **Get Token Balance** - **Endpoint**: `/holder/:holderAddress/token/:tokenAddress` ``` const response = await fetch( 'https://api.metal.build/holder/0x1234567890abcdef1234567890abcdef12345678/token/0xabcdef1234567890abcdef1234567890abcdef12', { headers: { 'x-api-key': 'YOUR_SECRET_API_KEY', }, } ) const holder = await response.json() ``` 9. **Withdraw Tokens** - **Endpoint**: `/holder/:userId/withdraw` ``` const response = await fetch( `https://api.metal.build/holder/${userId}/withdraw`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_SECRET_API_KEY', }, body: JSON.stringify({ tokenAddress: '0x191d25c061C081583E616d8978eA670f45A803E5', amount: 123, toAddress: '0x191d25c061C081583E616d8978eA670f45A12345', }), } ) const withdraw = await response.json() ```