--- url: /guides/advanced-transactions.md --- # Advanced Transactions The Movement SDK supports multiple transaction types beyond basic transfers. These advanced patterns enable complex on-chain interactions. *** ## Multi-Agent Transactions Multi-agent transactions allow multiple accounts to participate in a single transaction. This is useful for: * Multi-signature wallets * Escrow services * Collaborative actions ### How It Works 1. **Primary sender** initiates the transaction 2. **Secondary signers** provide additional signatures 3. All signatures are verified on-chain 4. Transaction executes with all signers' permissions ### Example: Multi-Sig Approval ```typescript import { useMovementSDK } from '@movement-labs/miniapp-sdk'; const { sdk } = useMovementSDK(); // Execute a multi-sig transaction > Note: `sendMultiAgentTransaction()` is not implemented in the current host. const result = await sdk.sendMultiAgentTransaction({ function: '0x1::multisig::approve_transaction', arguments: [ multisigAddress, transactionId, ], type_arguments: [], secondarySigners: [ '0xabc...', // Second approver '0xdef...', // Third approver ], }); console.log('Multi-sig approved:', result.hash); ``` ### Security Notes * All secondary signers must be valid addresses * The Move function must accept the correct number of signers * Rate limiting applies per sender account *** ## Fee Payer (Sponsored) Transactions Fee payer transactions allow a different account to cover gas fees. Perfect for: * Onboarding new users (sponsor their first transactions) * App-funded operations * Gasless user experiences ### How It Works 1. **User** signs the transaction payload 2. **Fee payer** (sponsor) signs as the gas payer 3. Fee payer's account is charged for gas 4. Transaction executes on behalf of the user ### Example: Sponsored NFT Mint ```typescript const { sdk, address } = useMovementSDK(); // User mints NFT, sponsor pays gas > Note: `sendFeePayerTransaction()` is not implemented in the current host. const result = await sdk.sendFeePayerTransaction({ function: '0x1::nft::mint', arguments: [ 'Cool NFT', 'An awesome NFT', 'https://example.com/nft.png', ], type_arguments: [], feePayer: '0x_sponsor_address_...', // This account pays gas }); console.log('NFT minted (gas-free for user):', result.hash); ``` ### Use Cases * **Free trials**: Let users try your app without MOVE tokens * **Rewards**: Sponsor claim transactions for users * **Onboarding**: Remove gas fee friction for new users *** ## Batch Transactions Submit multiple transactions in a single call. Useful for: * Bulk token transfers * Sequential operations * Reducing transaction overhead ### How It Works 1. Provide an array of transaction payloads 2. SDK submits them sequentially 3. Returns results for all transactions 4. Partial failures are tracked ### Example: Bulk Airdrops ```typescript const { sdk } = useMovementSDK(); // Airdrop tokens to multiple users const recipients = [ { address: '0xabc...', amount: '1000000' }, { address: '0xdef...', amount: '2000000' }, { address: '0x123...', amount: '1500000' }, ]; > Note: `sendBatchTransactions()` is not implemented in the current host. const result = await sdk.sendBatchTransactions({ transactions: recipients.map(r => ({ function: '0x1::aptos_account::transfer', arguments: [r.address, r.amount], type_arguments: [], })), }); console.log(`Success: ${result.successCount}/${recipients.length}`); console.log('Failed:', result.failureCount); ``` ### Best Practices * **Error handling**: Check individual results for failures * **Gas estimation**: Ensure sufficient balance for all transactions * **Rate limits**: Batch size affects rate limiting *** ## Script Transactions Execute complex Move scripts with multiple function calls. For advanced use cases: * Custom logic composition * Multi-step operations * Dynamic contract interactions ### How It Works 1. Compile a Move script to bytecode 2. Pass bytecode and arguments to SDK 3. Script executes on-chain with full composability ### Example: Complex Swap & Stake ```typescript const { sdk } = useMovementSDK(); // Pre-compiled Move script that swaps tokens and stakes them const swapAndStakeScript = '0x...'; // Compiled bytecode const result = await sdk.sendScriptTransaction({ script: swapAndStakeScript, arguments: [ '1000000', // Amount to swap '0xabc...', // Pool address ], type_arguments: [ '0x1::aptos_coin::AptosCoin', '0x1::usdc::USDC', ], }); console.log('Swap & stake completed:', result.hash); ``` ### When to Use Scripts * **Complex workflows**: Multiple contract calls in one transaction * **Gas optimization**: Reduce multiple transactions to one * **Atomic operations**: All-or-nothing execution *** ## Transaction Options All transaction types support additional options: ```typescript // Coming soon: Gas configuration { maxGasAmount: '100000', gasUnitPrice: '100', expirationTimestamp: Date.now() + 60000, // 1 minute } ``` *** ## Error Handling Always handle errors appropriately: ```typescript try { const result = await sdk.sendMultiAgentTransaction(payload); console.log('Success:', result.hash); } catch (error) { if (error.message.includes('rate limit')) { // Rate limit exceeded alert('Too many requests. Please wait.'); } else if (error.message.includes('Invalid')) { // Validation error console.error('Invalid payload:', error); } else { // Other errors console.error('Transaction failed:', error); } } ``` *** ## Security Considerations All advanced transaction types include: * **Rate limiting** - Prevents spam and abuse * **Payload validation** - Ensures correct format * **Address verification** - Validates all addresses * **Amount limits** - Configurable transaction caps See [Security Guide](/guides/security) for more details. *** ## Next Steps * **[API Reference](/reference/sdk-api)** - Complete method documentation * **[Security Guide](/guides/security)** - Best practices * **[SDK Overview](/sdk/overview)** - Core features --- --- url: /ai-builder.md --- # AI Mini App Builder Build Movement mini apps with the help of AI. Describe your idea and get instant code generation with best practices built in. ## How It Works The AI Builder uses Claude (Anthropic's AI model) with deep knowledge of the Movement Mini App SDK to help you: 1. **Design your mini app** - Discuss features and architecture 2. **Generate code** - Get complete React/Next.js components with Movement SDK integration 3. **Create smart contracts** - Generate Move contracts when needed 4. **Write tests** - Get comprehensive test coverage 5. **Deploy** - Get deployment instructions and best practices ### LLM-Optimized Documentation Our documentation includes an [/llms.txt](/llms.txt) file that provides structured information to AI tools. This helps ensure AI-generated code follows best practices and uses the latest SDK patterns. **AI tools can access:** * Complete SDK API reference with `isInstalled()` and `ready()` patterns * Transaction and error handling patterns * TypeScript type definitions * Security best practices * Design guidelines ## What You Can Build * **DeFi Apps**: Token swaps, staking dashboards, yield farms * **NFT Apps**: Galleries, marketplaces, minting platforms * **Gaming**: On-chain games, leaderboards, achievements * **Social**: Chat apps, social feeds, reputation systems * **Payments**: P2P payments, invoicing, tipping * **Utilities**: Multi-sig wallets, batch senders, portfolio trackers ## Tips for Best Results 1. **Be specific**: Instead of "token app", try "token swap app with slippage protection" 2. **Mention features**: List the specific features you need (QR scanning, haptic feedback, notifications) 3. **Request patterns**: Ask for proper SDK initialization with `isInstalled()` and `ready()` checks 4. **Ask questions**: The AI can explain concepts and suggest improvements 5. **Iterate**: Start simple and add features incrementally 6. **Review code**: Always review generated code for your use case 7. **Use TypeScript**: Request TypeScript code with full type definitions for best results ## Example Prompts ### Simple Apps * "Create a token transfer app with QR code scanner for addresses, balance display, and haptic feedback" * "Build an NFT gallery that shows my owned NFTs with proper SDK initialization using isInstalled() and ready()" * "Make a simple staking dashboard with transaction notifications" ### SDK Patterns * "Show me how to properly initialize the SDK with isInstalled() and ready() checks in a Next.js app" * "Create a connect wallet button that uses the useMovementSDK hook with loading and error states" * "Implement a transaction flow with waitForTransaction() and success notifications" ### Complex Apps * "Create a P2P marketplace with escrow functionality and dispute resolution. Use a Move contract for escrow logic." * "Build a decentralized social feed where users can post messages to the blockchain and follow other users" * "Make a gaming leaderboard with on-chain score verification and rewards distribution" ### With Smart Contracts * "Create a token vesting app with a Move contract that handles time-locked releases" * "Build a DAO voting system with proposal creation, voting, and execution via Move contracts" * "Make a lottery app where users buy tickets and winners are selected randomly on-chain" ## Features * **Real-time code generation** - See code as it's being generated * **Complete project structure** - Get package.json, components, contracts, and more * **Best practices** - Security, error handling, and UX built in * **TypeScript support** - Fully typed code generation * **Smart contract generation** - Move contracts when blockchain logic is needed * **Test generation** - Unit and integration tests ## Prerequisites Before using the generated code: 1. Install Node.js 18+ 2. Set up Movement wallet (for testing) 3. Get testnet MOVE tokens from faucet 4. Familiarity with React/Next.js (helpful but not required) ### For Local Development If you're running the docs locally and want to use the AI Builder: 1. Get an Anthropic API key from [console.anthropic.com](https://console.anthropic.com/) 2. Set the environment variable: ```bash export ANTHROPIC_API_KEY=your-key-here npm run dev ``` The AI Builder requires the API server to be running (started automatically with `npm run dev`) and the `ANTHROPIC_API_KEY` environment variable to be set. ## Next Steps After generating your mini app: 1. **Download the code** - Copy the generated files to your project 2. **Install dependencies** - Run `npm install` 3. **Test locally** - Run `npm run dev` 4. **Deploy** - Follow our [Publishing Guide](/guides/publishing) 5. **Submit to Movement** - Get your app listed in the Movement Everything wallet ## Support Need help? Check out: * [SDK Documentation](/sdk/overview) * [API Reference](/reference/sdk-api) * [Security Guide](/guides/security) * [Discord Community](https://discord.gg/movementlabs) *** **Ready to build?** Start chatting with the AI Builder above! 🚀 --- --- url: /publishing/publisher.md --- # App Publisher --- --- url: /commands.md --- # Commands Commands are the primary way your mini app interacts with the Movement SDK. They enable blockchain transactions, device features, and platform capabilities. ## Available Commands ### Blockchain **[View Function](/commands/view)** Call read-only Move view functions to fetch on-chain data **[Send Transaction](/commands/send-transaction)** Execute blockchain transactions with user approval **Sign Message** *(Coming Soon)* Sign messages for authentication and verification ### Device Features **[Scan QR Code](/commands/scan-qr)** Scan wallet addresses and QR codes **[Haptic Feedback](/commands/haptic)** Provide tactile feedback for user actions **Camera** *(Coming Soon)* Capture photos and access media library **Biometrics** *(Coming Soon)* Authenticate with Face ID / Touch ID **Location** *(Coming Soon)* Access device GPS location ### Platform **[Notifications](/commands/notifications)** Send push notifications to users **Share** *(Coming Soon)* Open native share sheet **Storage** *(Coming Soon)* Save data locally on device ### Analytics **[Analytics](/reference/sdk-api#analytics)** Track events, screens, and user properties ## Usage Patterns All commands are available through the SDK instance: ```typescript // Get SDK instance const sdk = window.movementSDK; // Or use React hook const { sdk } = useMovementSDK(); // Call command const result = await sdk.sendTransaction({...}); ``` ## Response Handling Commands return promises that resolve with results or reject with errors: ```typescript try { const result = await sdk.sendTransaction({...}); console.log('Success:', result); } catch (error) { console.error('Error:', error.message); } ``` See [Responses](/quick-start/responses) for detailed error handling patterns. ## Rate Limits Commands are rate-limited to prevent abuse: * **Transactions**: 300 per day per user * **Notifications**: 50 per day per user * **Other commands**: 1000 per hour Rate limit errors return status code `429`. --- --- url: /quick-start/commands.md --- # Commands Commands are methods you call on the SDK to interact with the blockchain, device features, and platform capabilities. ## How Commands Work All commands are available through the SDK instance: ```typescript const sdk = window.movementSDK; // Call a command const result = await sdk.sendTransaction({...}); ``` Commands return promises that resolve with results or reject with errors. ## Command Categories ### Blockchain Commands Execute on-chain operations: ```typescript // Send transaction const tx = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [] }); // Sign message const signature = await sdk.signMessage({ message: 'Hello Movement', nonce: '12345' }); // Get balance const balance = await sdk.getBalance(); > Note: `sdk.getAccount()` is not exposed on `window.movementSDK` in the current host. Use `window.aptos.account()` instead. // Get account info const account = await sdk.getAccount(); ``` ### Device Commands Access device features: ```typescript // Scan QR code const address = await sdk.scanQRCode(); // Trigger haptic feedback await sdk.haptic({ type: 'impact', style: 'medium' }); > Note: `sdk.camera.*` is not implemented in the current host. // Take photo const photo = await sdk.camera.takePicture(); > Note: `sdk.biometric.*` is not implemented in the current host. // Authenticate with biometrics const auth = await sdk.biometric.authenticate({ promptMessage: 'Confirm transaction' }); > Note: `sdk.location.*` is not implemented in the current host. // Get location const location = await sdk.location.getCurrentPosition(); ``` ### Platform Commands Use platform features: ```typescript // Send notification await sdk.notify({ title: 'Transaction Complete', body: 'Your transfer was successful' }); // Share content await sdk.share({ message: 'Check out my mini app!', url: 'https://moveeverything.app/apps/my-app' }); > Note: Use `sdk.CloudStorage.setItem/getItem` instead of `sdk.storage.*` in the current host. // Save to storage await sdk.storage.set('user_prefs', JSON.stringify(prefs)); // Get from storage const data = await sdk.storage.get('user_prefs'); ``` ## Async Pattern All commands are asynchronous and return promises: ```typescript // Using async/await async function sendTokens() { try { const result = await sdk.sendTransaction({...}); console.log('Success:', result.hash); } catch (error) { console.error('Error:', error.message); } } // Using .then/.catch sdk.sendTransaction({...}) .then(result => { console.log('Success:', result.hash); }) .catch(error => { console.error('Error:', error.message); }); ``` ## Event Pattern (Advanced) Subscribe to real-time updates: ```typescript > Note: `sdk.onTransactionUpdate` is not implemented in the current host. // Listen for transaction updates sdk.onTransactionUpdate(hash, (status) => { if (status.status === 'success') { console.log('Transaction confirmed!'); } else if (status.status === 'failed') { console.error('Transaction failed:', status.error); } }); // Listen for balance changes sdk.on('balance:updated', (balance) => { updateBalanceUI(balance); }); // Cleanup when done sdk.off('balance:updated', handler); ``` ## Command Options Most commands accept optional parameters: ```typescript // Basic usage await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [] }); // With options await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [], // Display metadata title: 'Send MOVE', description: 'Transfer 1 MOVE to Alice', toAddress: recipient, amount: '1', // Advanced options maxGasAmount: '1000', gasUnitPrice: '100' }); ``` ## Chaining Commands Chain multiple commands together: ```typescript async function claimAndSend() { // Claim reward const claimTx = await sdk.sendTransaction({ function: '0xGame::rewards::claim', arguments: [playerId] }); // Wait for confirmation await sdk.waitForTransaction(claimTx.hash); // Get new balance const balance = await sdk.getBalance(); // Send to another address const sendTx = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [friendAddress, amount] }); return sendTx.hash; } ``` ## Error Handling Always handle errors gracefully: ```typescript try { const result = await sdk.sendTransaction({...}); // Check result if (result.success) { await sdk.notify({ title: 'Success!', body: 'Transaction confirmed' }); } } catch (error) { // Handle specific errors switch (error.code) { case 'USER_REJECTED': console.log('User cancelled transaction'); break; case 'INSUFFICIENT_BALANCE': showError('Not enough balance'); break; case 'RATE_LIMIT_EXCEEDED': showError('Too many requests, try again later'); break; default: showError('Transaction failed: ' + error.message); } } ``` ## Rate Limiting Commands are rate-limited to prevent abuse: | Command Type | Limit | Window | |--------------|-------|--------| | Transactions | 300 | Per day | | Notifications | 50 | Per day | | Storage operations | 1000 | Per hour | | Other commands | 1000 | Per hour | Check remaining quota: ```typescript const limits = await sdk.getRateLimitStatus(); console.log('Remaining transactions:', limits.transactions.remaining); console.log('Resets at:', new Date(limits.transactions.reset)); ``` ## Best Practices ::: tip USER FEEDBACK Always provide loading states and feedback: ```typescript setLoading(true); try { const result = await sdk.sendTransaction({...}); showSuccess('Transaction sent!'); } catch (error) { showError(error.message); } finally { setLoading(false); } ``` ::: ::: tip ERROR HANDLING Handle all error cases explicitly: ```typescript try { await sdk.sendTransaction({...}); } catch (error) { if (error.code === 'USER_REJECTED') { // User cancelled - don't show error } else if (error.code === 'INSUFFICIENT_BALANCE') { showError('Insufficient balance'); } else { // Log unexpected errors console.error('Unexpected error:', error); showError('Something went wrong'); } } ``` ::: ::: warning VALIDATION Validate inputs before calling commands: ```typescript // ❌ Bad await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [userInput, amount] // Not validated! }); // ✅ Good if (!isValidAddress(recipient)) { throw new Error('Invalid address'); } if (amount <= 0) { throw new Error('Amount must be positive'); } await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount] }); ``` ::: ## Next Steps * **[Responses →](/quick-start/responses)** - Handle command results * **[Testing →](/quick-start/testing)** - Test your commands * **[Commands Reference →](/commands/)** - See all available commands --- --- url: /guidelines/design.md --- # Design Guidelines Build beautiful, intuitive mini apps that feel native to Movement wallet using the Movement Design System. ## Design Principles ### 1. Mobile-First Design for mobile screens from the start. Most users will never see your app on desktop. ```css /* ✅ Good - Mobile-first approach */ .card { padding: 1rem; font-size: 16px; } @media (min-width: 768px) { .card { padding: 2rem; font-size: 18px; } } /* ❌ Bad - Desktop-first */ .card { padding: 2rem; font-size: 18px; } @media (max-width: 768px) { .card { padding: 1rem; /* Often forgotten */ } } ``` ### 2. Thumb-Friendly Design for one-handed use. Place important actions within thumb reach. **Guidelines:** * Primary actions in bottom third * Minimum touch target: 44x44px * Spacing between targets: 8px minimum ### 3. Fast & Responsive Users expect instant feedback. Optimize for performance. * **First load:** < 3 seconds * **Subsequent loads:** < 1 second (cached) * **Animations:** 60 FPS * **Touch response:** < 100ms ### 4. Consistent with Movement Match Movement wallet's design language for familiar UX. Use the Movement Design System components for consistency. ## Movement Design System The Movement Design System provides a complete set of components, tokens, and patterns for building consistent mini apps. ### Getting Started Install the design system: ```bash npm install movement-design-system # or pnpm add movement-design-system ``` Import styles in your app: ```css @import "movement-design-system/component-styles"; @import "movement-design-system/theme"; ``` ### Documentation ::: tip Design System Docs Browse the complete [Movement Design System documentation](https://movement-design-system-docs-git-shadcn-movement-labs.vercel.app/) for: * All available components (Card, Button, Badge, etc.) * Component variants and props * Interactive examples * Theme customization ::: **Note:** The design system docs require login to view. ### Available Components The design system includes: * **Layout**: Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription * **Buttons**: Button (with variants: default, outline, ghost, link) * **Forms**: Input, Textarea, Select, Checkbox, Radio * **Feedback**: Badge, Alert, Toast * **Navigation**: Tabs, Accordion * **Display**: Avatar, Separator * **Icons**: Comprehensive icon library See the [design system docs](https://movement-design-system-docs-git-shadcn-movement-labs.vercel.app/) for complete component documentation. ## Color Palette The Movement Design System provides all color tokens. Import the theme to access them: ```css @import "movement-design-system/theme"; /* Primary brand color */ --movement-primary: #00D4AA; /* Semantic colors */ --success: #00D4AA; --warning: #FFB020; --error: #FF4444; --info: #3B82F6; ``` For complete color documentation, see the [design system theme docs](https://movement-design-system-docs-git-shadcn-movement-labs.vercel.app/?path=/story/movement-design-system-theme--colors). ## Typography ### Font Stack ```css body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif; } ``` ### Font Sizes ```css /* Scale */ --text-xs: 0.75rem; /* 12px */ --text-sm: 0.875rem; /* 14px */ --text-base: 1rem; /* 16px */ --text-lg: 1.125rem; /* 18px */ --text-xl: 1.25rem; /* 20px */ --text-2xl: 1.5rem; /* 24px */ --text-3xl: 1.875rem; /* 30px */ ``` ### Usage ```css /* Body text */ body { font-size: var(--text-base); line-height: 1.5; } /* Headings */ h1 { font-size: var(--text-3xl); font-weight: 700; } h2 { font-size: var(--text-2xl); font-weight: 600; } h3 { font-size: var(--text-xl); font-weight: 600; } /* Labels */ .label { font-size: var(--text-sm); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } /* Captions */ .caption { font-size: var(--text-xs); color: var(--text-secondary); } ``` ## Spacing Use consistent spacing based on 4px grid: ```css --space-1: 0.25rem; /* 4px */ --space-2: 0.5rem; /* 8px */ --space-3: 0.75rem; /* 12px */ --space-4: 1rem; /* 16px */ --space-5: 1.25rem; /* 20px */ --space-6: 1.5rem; /* 24px */ --space-8: 2rem; /* 32px */ --space-10: 2.5rem; /* 40px */ --space-12: 3rem; /* 48px */ ``` ### Layout Example ```tsx

Send MOVE

``` ## Components Use the Movement Design System components instead of building custom ones. This ensures consistency and reduces maintenance. ### Using Design System Components ```tsx import { Button, Card, CardContent, CardHeader, CardTitle, Badge } from 'movement-design-system'; // Button with variants // Card components Card Title Card content goes here // Badge Success Warning ``` ### Component Documentation For complete component documentation, including: * All available props and variants * Usage examples * Interactive playgrounds * Accessibility guidelines See the [Movement Design System component docs](https://movement-design-system-docs-git-shadcn-movement-labs.vercel.app/?path=/story/movement-design-system-card--grid-layout-dots). ## Layout Patterns ### Full-Screen Card ```tsx

Send MOVE

{/* Content */}
``` ### List Items ```tsx
{items.map(item => (

{item.title}

{item.subtitle}

{item.value}
))}
``` ### Bottom Sheet ```tsx

Select Token

{/* Content */}
``` ## Icons & Images ### Icon Size ```css /* Icons */ .icon-sm { width: 16px; height: 16px; } .icon-md { width: 24px; height: 24px; } .icon-lg { width: 32px; height: 32px; } .icon-xl { width: 48px; height: 48px; } /* Token logos */ .token-icon { width: 40px; height: 40px; border-radius: 50%; } ``` ### Image Optimization ```tsx // Next.js Image import Image from 'next/image'; MOVE // Regular img MOVE ``` ## Animations ### Micro-interactions ```css /* Hover lift */ .lift:hover { transform: translateY(-2px); transition: transform 0.2s; } /* Press down */ .press:active { transform: scale(0.98); } /* Fade in */ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .fade-in { animation: fadeIn 0.3s ease-out; } /* Skeleton loading */ @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } .skeleton { background: linear-gradient( 90deg, var(--bg-secondary) 0%, var(--bg-tertiary) 50%, var(--bg-secondary) 100% ); background-size: 200% 100%; animation: shimmer 1.5s infinite; } ``` ## Dark Mode Movement wallet uses dark mode. Design for it from the start: ```css /* Always use semantic colors */ :root { color-scheme: dark; --bg: #0A0F1E; --text: #FFFFFF; } /* Don't hardcode black/white */ .card { background: var(--bg-secondary); /* ✅ Good */ background: #1A1F2E; /* ❌ Bad */ } ``` ## Accessibility ### Touch Targets Minimum 44x44px for interactive elements: ```css button, a, input[type="checkbox"] { min-width: 44px; min-height: 44px; padding: 12px; } ``` ### Contrast Ensure sufficient contrast (WCAG AA): ```css /* ✅ Good contrast */ .text-on-dark { color: #FFFFFF; /* 21:1 contrast on #0A0F1E */ } /* ❌ Poor contrast */ .text-on-dark { color: #666666; /* 2.5:1 - fails WCAG */ } ``` ### Focus States Always show focus indicators: ```css button:focus-visible { outline: 2px solid var(--movement-primary); outline-offset: 2px; } ``` ## Loading States ### Skeleton Screens ```tsx function TokenSkeleton() { return (
); } ``` ### Spinners ```tsx function Spinner() { return (
); } ``` ## Error States Show clear, actionable error messages: ```tsx function ErrorMessage({ message, onRetry }) { return (
⚠️

Transaction Failed

{message}

{onRetry && ( )}
); } ``` ## Best Practices ::: tip CONSISTENCY Use consistent spacing, colors, and patterns throughout your app. ::: ::: tip FEEDBACK Provide immediate feedback for all user actions (haptics, animations, messages). ::: ::: tip PERFORMANCE Optimize images, lazy-load content, minimize bundle size. ::: ::: warning AVOID * Don't use pure black (#000) - use dark gray * Don't animate expensive properties (use `transform` and `opacity`) * Don't hide errors - show helpful messages ::: ## Resources * **[Movement Design System Docs](https://movement-design-system-docs-git-shadcn-movement-labs.vercel.app/)** - Complete component library and documentation * **[Design System GitHub](https://github.com/movementlabsxyz/movement-design-system)** - Source code and issues ## Examples Check out these real mini apps that use the Movement Design System: * **[Scaffold App](/examples/scaffold)** - Complete SDK reference implementation with design system components * **[Social App](/examples/social)** - Social features with on-chain interactions These examples demonstrate: * Proper use of design system components (Card, Button, Badge) * Consistent styling and theming * Mobile-first responsive layouts * Best practices for mini app design --- --- url: /commands/haptic.md --- # Haptic Feedback Provide tactile feedback to enhance user experience with physical sensations. ## Basic Usage ```typescript await sdk.haptic({ type: 'impact', style: 'medium' }); ``` ## Parameters ### `type` Type of haptic feedback: * `'impact'` - Physical collision (button press, tap) * `'notification'` - Alerts and results (success, warning, error) * `'selection'` - UI selection changed (picker, toggle) ### `style` Intensity of feedback (for `impact` and `selection` types): * `'light'` - Subtle feedback * `'medium'` - Standard feedback (default) * `'heavy'` - Strong feedback For `notification` type: * `'success'` - Positive outcome * `'warning'` - Cautionary * `'error'` - Negative outcome ## Examples ### Button Press ```typescript function SubmitButton({ onClick }) { async function handleClick() { await sdk.haptic({ type: 'impact', style: 'medium' }); onClick(); } return ; } ``` ### Transaction Result ```typescript try { const result = await sdk.sendTransaction({...}); // Success haptic await sdk.haptic({ type: 'notification', style: 'success' }); } catch (error) { // Error haptic await sdk.haptic({ type: 'notification', style: 'error' }); } ``` ### Toggle Switch ```typescript function Toggle({ checked, onChange }) { async function handleToggle() { await sdk.haptic({ type: 'selection' }); onChange(!checked); } return ( ); } ``` ### Picker Selection ```typescript function TokenPicker({ tokens, onSelect }) { async function handleSelect(token) { await sdk.haptic({ type: 'selection' }); onSelect(token); } return (
{tokens.map(token => ( ))}
); } ``` ### Long Press ```typescript function DeleteButton({ onDelete }) { async function handleLongPress() { await sdk.haptic({ type: 'impact', style: 'heavy' }); const confirmed = await sdk.showConfirm( 'Are you sure you want to delete this?' ); if (confirmed) { onDelete(); await sdk.haptic({ type: 'notification', style: 'success' }); } } return ( ); } ``` ## Haptic Patterns ### Common Patterns ```typescript // Button tap sdk.haptic({ type: 'impact', style: 'light' }); // Important action sdk.haptic({ type: 'impact', style: 'medium' }); // Delete/destructive action sdk.haptic({ type: 'impact', style: 'heavy' }); // Success sdk.haptic({ type: 'notification', style: 'success' }); // Warning sdk.haptic({ type: 'notification', style: 'warning' }); // Error sdk.haptic({ type: 'notification', style: 'error' }); // Picker/dropdown selection sdk.haptic({ type: 'selection' }); ``` ### Transaction Flow ```typescript async function sendTokens() { // Button press await sdk.haptic({ type: 'impact', style: 'medium' }); try { // Sending... const result = await sdk.sendTransaction({...}); // Success await sdk.haptic({ type: 'notification', style: 'success' }); await sdk.notify({ title: 'Success!', body: 'Transaction confirmed' }); } catch (error) { // Error await sdk.haptic({ type: 'notification', style: 'error' }); } } ``` ## Platform Support | Platform | Support | Notes | |----------|---------|-------| | iOS | ✅ Full | Taptic Engine | | Android | ✅ Full | Vibration API | ## Best Practices ::: tip USE SPARINGLY Don't overuse haptics - they should enhance, not distract. ```typescript // ✅ Good - meaningful actions button.onClick = () => { sdk.haptic({ type: 'impact', style: 'light' }); navigate(); }; // ❌ Bad - every UI change div.onMouseEnter = () => { sdk.haptic({ type: 'impact' }); // Too much! }; ``` ::: ::: tip MATCH INTENSITY Match haptic intensity to action importance: ```typescript // Light - minor actions sdk.haptic({ type: 'impact', style: 'light' }); // Tap button // Medium - standard actions sdk.haptic({ type: 'impact', style: 'medium' }); // Submit form // Heavy - important/destructive actions sdk.haptic({ type: 'impact', style: 'heavy' }); // Delete item ``` ::: ::: tip COMBINE WITH VISUAL FEEDBACK Haptics should complement, not replace, visual feedback: ```typescript async function handleSubmit() { setLoading(true); // Visual feedback await sdk.haptic({ type: 'impact' }); // Haptic feedback const result = await submit(); if (result.success) { setSuccess(true); // Visual await sdk.haptic({ type: 'notification', style: 'success' }); // Haptic } } ``` ::: ::: warning ACCESSIBILITY Some users may have haptics disabled. Never rely solely on haptics for critical feedback. ::: ## Error Handling Haptics may fail silently if unsupported: ```typescript try { await sdk.haptic({ type: 'impact' }); } catch (error) { // Haptic failed - continue anyway console.log('Haptic unavailable'); } ``` ## Related * [Notifications](/commands/notifications) - Alert users * [Scan QR Code](/commands/scan-qr) - Scan with haptic feedback --- --- url: /quick-start/installing.md --- # Installing Get the Movement SDK integrated into your project. ## Quick Start Create a new mini app with our starter template: ```bash npx create-movement-app@latest my-app cd my-app npm run dev ``` This creates a fully configured Next.js app with the SDK pre-installed. ## Manual Installation ### For Next.js / React Install the SDK package: ```bash npm install @movement-labs/miniapp-sdk ``` Use the React hook in your components: ```tsx // app/page.tsx or any component 'use client'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; export default function MyComponent() { const { sdk, isConnected, address, isLoading } = useMovementSDK(); // Loading state while SDK initializes if (isLoading) { return

Loading...

; } // Check if app is running in Movement wallet if (!sdk?.isInstalled()) { return (

Not in Movement App

Please open this app inside Movement wallet

); } // SDK is ready return (

Connected: {isConnected ? 'Yes' : 'No'}

Address: {address || 'Not connected'}

); } ``` The `useMovementSDK()` hook automatically: * Checks if SDK is installed with `isInstalled()` * Waits for SDK to be ready with `ready()` * Returns connection state and user address * Handles loading states ### For Vanilla JavaScript The SDK is automatically injected by Movement wallet: ```html My Mini App
Loading...
``` ### For Unity 1. Download the Movement Unity SDK package: ```bash # Coming soon ``` 2. Import into Unity: * Assets → Import Package → Custom Package * Select `MovementSDK.unitypackage` 3. Add to your scene: ```csharp using MovementSDK; public class GameManager : MonoBehaviour { void Start() { if (MovementSDK.Instance.IsInstalled()) { string address = MovementSDK.Instance.GetAddress(); Debug.Log("Connected: " + address); } } } ``` ## SDK Detection & Ready State Movement SDK provides two key methods for reliable initialization: ### `isInstalled()` Returns `true` if your app is running inside Movement wallet: ```typescript const isInMovementApp = window.movementSDK?.isInstalled(); if (!isInMovementApp) { // Show error: "Please open in Movement wallet" } ``` ### `ready()` Waits for the SDK to be fully initialized before allowing method calls: ```typescript await window.movementSDK.ready(); // Now safe to call any SDK method const account = await window.movementSDK.getAccount(); ``` ### Complete Pattern Always use both methods together. The `useMovementSDK()` hook automatically calls `ready()` for you: ::: code-group ```typescript [React] import { useMovementSDK } from '@movement-labs/miniapp-sdk'; function App() { const { sdk, isLoading, error } = useMovementSDK(); // useMovementSDK() automatically calls sdk.ready() internally if (isLoading) { return
Loading SDK...
; } if (error || !sdk?.isInstalled()) { return (

Not in Movement App

Please open this app inside Movement wallet

Download Movement
); } // SDK is ready and installed (ready() was called by the hook) return ; } ``` ```javascript [Vanilla JS] async function initApp() { // Step 1: Check if installed if (!window.movementSDK?.isInstalled()) { console.error('Not running in Movement app'); showError('Please open this app inside Movement wallet'); return; } const sdk = window.movementSDK; // Step 2: Wait for ready try { await sdk.ready(); console.log('SDK is ready!'); } catch (error) { console.error('SDK initialization failed:', error); return; } // Step 3: SDK is ready - safe to use const account = await sdk.getAccount(); console.log('Account:', account); } // Call on page load initApp(); ``` ```csharp [Unity] using MovementSDK; public class GameManager : MonoBehaviour { async void Start() { // Check if installed if (!MovementSDK.Instance.IsInstalled()) { ShowError("Please open in Movement app"); return; } // Wait for ready bool isReady = await MovementSDK.Instance.Ready(); if (!isReady) { ShowError("SDK initialization failed"); return; } // SDK is ready InitializeGame(); } } ``` ::: ::: tip WHY USE `ready()`? The SDK may need time to initialize after injection. Calling `ready()` ensures all methods are available and prevents race conditions. ::: ::: warning DON'T SKIP THESE CHECKS Always check `isInstalled()` and wait for `ready()` before using SDK methods. Skipping these checks may cause runtime errors. ::: ## Verification Verify the SDK is working: ```typescript const sdk = window.movementSDK; console.log('Installed:', sdk.isInstalled()); console.log('Ready:', await sdk.ready()); console.log('Address:', sdk.address); console.log('Network:', sdk.network); ``` Expected output: ``` Installed: true Ready: true Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb Network: mainnet ``` ## TypeScript Support The SDK includes full TypeScript definitions: ```typescript import type { MovementSDK, TransactionPayload, TransactionResult, SignMessagePayload } from '@movement-labs/miniapp-sdk'; // Full type safety const payload: TransactionPayload = { function: '0x1::aptos_account::transfer', arguments: [recipientAddress, amount], type_arguments: [] }; const result: TransactionResult = await sdk.sendTransaction(payload); ``` ## Environment Setup ### Development Create a `.env.local` file: ```bash # Optional: Override default settings NEXT_PUBLIC_MOVEMENT_NETWORK=testnet NEXT_PUBLIC_DEBUG=true ``` ### Production Ensure your build is optimized: ```json // package.json { "scripts": { "build": "next build", "start": "next start" } } ``` ## Next Steps * **[Commands →](/quick-start/commands)** - Learn how to call SDK methods * **[Testing →](/quick-start/testing)** - Test your app in Movement wallet * **[Examples →](/examples/scaffold)** - See full examples ## Troubleshooting **SDK is undefined?** * Ensure you're testing inside the Movement app * Check Developer Mode is enabled in Settings **TypeScript errors?** * Update to latest SDK: `npm install @movement-labs/miniapp-sdk@latest` * Restart your TypeScript server **Build errors?** * Check Node.js version: `node --version` (requires 18+) * Clear cache: `rm -rf .next node_modules && npm install` --- --- url: /examples/nextjs.md --- # Next.js / React Build your Movement Mini App with Next.js and React. ## Prerequisites * Node.js 18+ installed * Basic knowledge of React and Next.js ## Installation ### 1. Create a new Next.js project ```bash npx create-next-app@latest my-miniapp cd my-miniapp ``` ### 2. Install the Movement SDK ```bash npm install @movement-labs/miniapp-sdk ``` ## Basic Setup ### 1. Create your mini app component Create `app/page.tsx`: ```tsx 'use client'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; import { useEffect } from 'react'; export default function Home() { const { sdk, isConnected, address, connect, sendTransaction } = useMovementSDK(); useEffect(() => { if (sdk && !isConnected) { connect(); } }, [sdk, isConnected, connect]); const handleTransaction = async () => { try { const result = await sendTransaction({ function: '0x1::aptos_account::transfer', type_arguments: [], arguments: ['0x123...', '1000000'], // receiver address, amount }); console.log('Transaction sent:', result); } catch (error) { console.error('Transaction failed:', error); } }; return (

My Mini App

{isConnected ? (

Connected: {address}

) : (

Connecting to wallet...

)}
); } ``` ## SDK Features ### Check Connection Status ```tsx const { sdk, isConnected, address } = useMovementSDK(); if (isConnected) { console.log('Wallet address:', address); } ``` ### Get Account Balance ```tsx const { sdk } = useMovementSDK(); const balance = await sdk.getAccountBalance(address); console.log('Balance:', balance); ``` ### Sign and Submit Transaction ```tsx const { sendTransaction } = useMovementSDK(); const result = await sendTransaction({ function: '0x1::coin::transfer', type_arguments: ['0x1::aptos_coin::MovementCoin'], arguments: [receiverAddress, amount], }); ``` ### Sign Message ```tsx const { sdk } = useMovementSDK(); const signature = await sdk.signMessage({ message: 'Hello Movement!', }); ``` ## Development ### Run locally ```bash npm run dev ``` Your app will be available at `http://localhost:3000` ### Test in Movement Everything 1. Open Movement Everything app 2. Go to Settings → Enable Developer Mode 3. Scroll to "Mini App Testing" 4. Enter `http://localhost:3000` 5. Click "Test App" ## Deployment ### Build for production ```bash npm run build ``` ### Deploy Deploy to any static hosting: * **Vercel**: `vercel deploy` * **Netlify**: `netlify deploy` * **GitHub Pages**: Push to gh-pages branch ## Example: Token Transfer App ```tsx 'use client'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; import { useState } from 'react'; export default function TransferApp() { const { isConnected, address, sendTransaction } = useMovementSDK(); const [recipient, setRecipient] = useState(''); const [amount, setAmount] = useState(''); const [status, setStatus] = useState(''); const handleTransfer = async () => { try { setStatus('Sending...'); const result = await sendTransaction({ function: '0x1::aptos_account::transfer', type_arguments: [], arguments: [recipient, (parseFloat(amount) * 100000000).toString()], }); setStatus(`Success! Hash: ${result.hash}`); } catch (error) { setStatus(`Error: ${error.message}`); } }; if (!isConnected) return
Connecting...
; return (

Transfer MOVE

From: {address}

setRecipient(e.target.value)} className="w-full p-2 border rounded mb-4" /> setAmount(e.target.value)} className="w-full p-2 border rounded mb-4" /> {status && (
{status}
)}
); } ``` ## Next Steps * Explore the [SDK API Reference](/reference/sdk-api) * Check out [example mini apps](https://github.com/movementlabsxyz/miniapp-examples) * Join the [Movement Discord](https://discord.gg/movement) for help --- --- url: /commands/notifications.md --- # Notifications Send push notifications to users from your mini app. ## Basic Usage ```typescript await sdk.notify({ title: 'Transaction Complete', body: 'Your transfer was successful!' }); ``` ## Parameters ### `title` Notification title (max 50 characters). ```typescript title: 'Achievement Unlocked!' ``` ### `body` Notification message (max 200 characters). ```typescript body: 'You reached level 10 and earned 100 points!' ``` ### `data` Custom data payload for deep linking and context. ```typescript data: { deepLink: '/apps/my-game/level/10', achievementId: 'level_10', reward: '100' } ``` ## Examples ### Transaction Confirmation ```typescript try { const result = await sdk.sendTransaction({...}); await sdk.waitForTransaction(result.hash); await sdk.notify({ title: 'Transaction Confirmed', body: `Sent ${amount} MOVE successfully`, data: { deepLink: '/apps/wallet/transactions', txHash: result.hash } }); await sdk.haptic({ type: 'notification', style: 'success' }); } catch (error) { await sdk.notify({ title: 'Transaction Failed', body: error.message, data: { deepLink: '/apps/wallet/help' } }); } ``` ### Game Achievement ```typescript async function unlockAchievement(achievement) { await sdk.notify({ title: `Achievement Unlocked! 🏆`, body: achievement.name, data: { deepLink: `/apps/my-game/achievements/${achievement.id}`, achievementId: achievement.id, points: achievement.points } }); await sdk.haptic({ type: 'notification', style: 'success' }); } ``` ### Daily Reward ```typescript async function sendDailyReward() { await sdk.notify({ title: 'Daily Reward Available! 🎁', body: 'Claim your 50 tokens now', data: { deepLink: '/apps/my-game/rewards', rewardType: 'daily', amount: '50' } }); } ``` ### Time-Sensitive Alert ```typescript async function sendEventReminder() { await sdk.notify({ title: 'Event Starting Soon! ⏰', body: 'Tournament begins in 5 minutes', data: { deepLink: '/apps/my-game/tournament', eventId: 'tournament_123', startsAt: Date.now() + 5 * 60 * 1000 } }); } ``` ### Social Interaction ```typescript async function notifyNewMessage(from, message) { await sdk.notify({ title: `Message from ${from}`, body: message.preview, data: { deepLink: `/apps/chat/conversation/${from}`, messageId: message.id, senderId: from } }); } ``` ## Deep Linking When a user taps a notification, they navigate to the `deepLink` URL: ```typescript // Send notification with deep link await sdk.notify({ title: 'Quest Complete!', body: 'Claim your reward', data: { deepLink: '/apps/my-game/quests/claim?id=123' } }); // Handle deep link in your app useEffect(() => { const params = new URLSearchParams(window.location.search); const questId = params.get('id'); if (questId) { showQuestReward(questId); } }, []); ``` ## Rate Limits * **50 notifications per day** per user * Exceeding limit returns error code `429` * Resets at midnight UTC Check remaining quota: ```typescript const limits = await sdk.getRateLimitStatus(); console.log('Notifications remaining:', limits.notifications.remaining); ``` ## Best Practices ::: tip RELEVANT & TIMELY Send notifications for meaningful events only: ```typescript // ✅ Good - important events sdk.notify({ title: 'Transaction Confirmed', ... }); sdk.notify({ title: 'Achievement Unlocked', ... }); // ❌ Bad - trivial events sdk.notify({ title: 'You opened the app', ... }); sdk.notify({ title: 'Welcome back!', ... }); // Every time ``` ::: ::: tip CLEAR & ACTIONABLE Make notifications clear and actionable: ```typescript // ✅ Good - clear action { title: 'Daily Reward Ready', body: 'Tap to claim 50 tokens', data: { deepLink: '/apps/game/rewards' } } // ❌ Bad - vague { title: 'Something happened', body: 'Check the app' } ``` ::: ::: tip RESPECT USER PREFERENCES Don't spam. Let users control notification frequency in settings. ::: ::: warning PERMISSIONS Users must grant notification permissions first. Handle permission denial gracefully. ::: ## Error Handling ```typescript try { await sdk.notify({ title: 'Transaction Complete', body: 'Your transfer was successful' }); } catch (error) { switch (error.code) { case 'PERMISSION_DENIED': console.log('User has disabled notifications'); break; case 'RATE_LIMIT_EXCEEDED': console.log('Too many notifications sent'); break; default: console.error('Notification failed:', error); } } ``` ## Notification Patterns ### Progress Updates ```typescript // Transaction submitted await sdk.notify({ title: 'Transaction Submitted', body: 'Confirming on blockchain...' }); // Wait for confirmation await sdk.waitForTransaction(hash); // Confirmed await sdk.notify({ title: 'Transaction Confirmed!', body: 'Transfer completed successfully' }); ``` ### Achievement System ```typescript const achievements = { first_transaction: { title: 'First Transaction! 🎉', body: 'You sent your first MOVE', points: 10 }, big_spender: { title: 'Big Spender! 💰', body: 'You sent over 100 MOVE', points: 50 } }; async function checkAchievements(txAmount) { if (txAmount >= 100) { await sdk.notify({ title: achievements.big_spender.title, body: achievements.big_spender.body, data: { deepLink: '/apps/game/achievements', achievementId: 'big_spender', points: achievements.big_spender.points } }); } } ``` ### Scheduled Reminders ```typescript // Schedule notification for later (using backend) async function scheduleDailyReward() { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0, 0); // 9 AM await fetch('/api/schedule-notification', { method: 'POST', body: JSON.stringify({ userId: sdk.address, scheduledAt: tomorrow.toISOString(), notification: { title: 'Daily Reward Available! 🎁', body: 'Claim your tokens now', data: { deepLink: '/apps/game/rewards', rewardType: 'daily' } } }) }); } ``` ## Testing Test notifications in development: ```typescript // Add test button in dev mode {process.env.NODE_ENV === 'development' && ( )} ``` ## Platform Support | Platform | Support | Notes | |----------|---------|-------| | iOS | ✅ Full | Native push notifications | | Android | ✅ Full | Native push notifications | ## Related * [Haptic Feedback](/commands/haptic) - Combine with tactile feedback * Share *(Coming Soon)* - Share content --- --- url: /guides/publishing.md --- # Publishing Your Mini App Once your mini app is ready, you can publish it to the Move Everything app directory. *** ## Prerequisites Before publishing: * ✅ App is deployed and accessible via HTTPS URL * ✅ All features work correctly inside Move Everything * ✅ Icon/logo is ready (512x512px recommended) * ✅ App has been tested on both iOS and Android *** ## Deployment Options ### Option 1: Vercel (Recommended) ```bash # Install Vercel CLI npm install -g vercel # Deploy vercel deploy --prod ``` ### Option 2: Netlify ```bash # Install Netlify CLI npm install -g netlify-cli # Deploy netlify deploy --prod ``` ### Option 3: GitHub Pages ```bash # Build your app npm run build # Push to gh-pages branch git subtree push --prefix dist origin gh-pages ``` ### Option 4: Custom Server Upload your built files to any web server with HTTPS support. *** ## App Submission To get your app listed in Move Everything: ### 1. Prepare Your Metadata Create a JSON file with your app details: ```json { "id": "my-awesome-app", "name": "My Awesome App", "description": "A brief description of what your app does", "icon": "https://yourdomain.com/icon.png", "url": "https://yourdomain.com", "category": "games", "author": { "name": "Your Name", "wallet": "0x..." }, "permissions": ["camera", "haptic", "storage"], "version": "1.0.0", "screenshots": [ "https://yourdomain.com/screenshot1.png", "https://yourdomain.com/screenshot2.png" ] } ``` ### 2. Submit for Review Email your app metadata to: **apps@movementlabs.xyz** Include: * App metadata JSON * Brief description of your app * Your contact information * Any special instructions ### 3. Review Process 1. **Automated checks** - URL accessibility, icon validation 2. **Manual review** - App functionality, security, user experience 3. **Approval** - Added to the app registry 4. **Launch** - Your app goes live in Move Everything! Review typically takes **1-3 business days**. *** ## App Categories Choose the best category for your app: * **games** - Games and entertainment * **earn** - Earning opportunities and rewards * **social** - Social and communication apps * **collect** - Collectibles and digital assets * **swap** - Token swaps and exchanges * **utility** - Tools and utilities * **other** - Other (specify your own category, max 24 characters) *** ## Permissions Declare which SDK features your app uses: | Permission | Description | |------------|-------------| | `camera` | Camera and photo access | | `haptic` | Haptic feedback | | `storage` | Local storage | | `location` | GPS location | | `biometric` | FaceID/TouchID | | `notifications` | Push notifications | *** ## Requirements ### Security * ✅ HTTPS only (no HTTP) * ✅ Content Security Policy headers * ✅ No external authentication required * ✅ Respect rate limits ### Performance * ✅ Load time < 3 seconds * ✅ Mobile-optimized design * ✅ Works on both iOS and Android ### User Experience * ✅ Clear onboarding * ✅ Responsive design * ✅ Error handling * ✅ Loading states *** ## Testing Checklist Before submitting, test your app: * \[ ] Opens correctly in Move Everything * \[ ] Wallet connects automatically * \[ ] Transactions work as expected * \[ ] All features work on iOS * \[ ] All features work on Android * \[ ] Error messages are clear * \[ ] App handles network errors * \[ ] Rate limits are respected *** ## Updating Your App After your app is published: 1. **Make changes** to your code 2. **Deploy updates** to your hosting provider 3. **No re-submission needed** - Updates go live immediately 4. **Version updates** - Email us if you change the app ID or major features *** ## Best Practices ### App Icon * 512x512px minimum * PNG or JPG format * Clear, recognizable logo * Works on light and dark backgrounds ### Screenshots * Show key features * Mobile-sized (1080x1920 or similar) * Clear, high-quality images ### Description * Clear and concise (max 280 characters) * Highlight key features * Include blockchain benefits *** ## Removal Policy Apps may be removed if they: * Violate security guidelines * Contain malicious code * Mislead users * Are abandoned/broken *** ## Support Need help publishing? * **Discord**: [discord.gg/movementlabs](https://discord.gg/movementlabs) * **Email**: apps@movementlabs.xyz * **Docs**: [docs.movementlabs.xyz](https://docs.movementlabs.xyz) *** ## Next Steps * **[Advanced Transactions](/guides/advanced-transactions)** - Complex transaction patterns * **[Security Guide](/guides/security)** - Best practices * **[Examples](https://github.com/movementlabsxyz/miniapp-examples)** - Sample apps --- --- url: /publishing.md --- # Publishing Your Mini App Learn how to publish your mini app to the Movement App Store. ::: tip QUICK PUBLISH **Want to publish without code?** Use our [**App Publisher Dashboard →**](/publishing/publisher) Connect your wallet and submit your app through our easy-to-use web interface. No SDK or coding required! ::: ## Overview The Movement App Store uses an on-chain registry to manage mini apps. All submissions, approvals, and updates are recorded on the Movement blockchain, ensuring transparency and immutability. ## Publishing Flow ```mermaid graph LR A[Submit App] --> B[Pending Review] B --> C{Admin Review} C -->|Approve| D[Live in App Store] C -->|Reject| E[Rejected] D --> F[Request Update] F --> G[Pending Review] G --> C ``` ## Quick Start 1. **Prepare your app** - Test thoroughly in developer mode 2. **Submit to registry** - Call the smart contract submission function 3. **Wait for approval** - Admins review within 24-48 hours 4. **Go live** - Approved apps appear automatically in the app store ## App Metadata When submitting your app, you'll provide the following metadata: | Field | Type | Description | Example | |-------|------|-------------|---------| | `name` | String | App name (max 50 chars) | `"DeFi Dashboard"` | | `description` | String | Brief description (max 200 chars) | `"Track your DeFi portfolio"` | | `icon` | String | Icon URL (HTTPS .png or .jpg) | `"https://yourdomain.com/icon.png"` | | `url` | String | App URL (HTTPS required) | `"https://app.example.com"` | | `slug` | String | URL-friendly identifier (lowercase, numbers, hyphens) | `"my-rewards-app"` | | `developer_name` | String | Your name or organization | `"Acme Labs"` | | `category` | String | App category | `"earn"` | | `permissions` | Array\ | Requested permissions | `["wallet_read", "sign_transaction"]` | ### Categories Choose one category for your app: * `games` - Games and entertainment * `earn` - Earning opportunities and rewards * `social` - Social and communication apps * `collect` - Collectibles and digital assets * `swap` - Token swaps and exchanges * `utility` - Productivity and utility tools * `other` - Other (specify your own category, max 24 characters) ### Permissions Declare all permissions your app needs: | Permission | Description | Use Case | |------------|-------------|----------| | `wallet_read` | Read wallet address and balance | Display user's address | | `sign_transaction` | Request transaction signatures | Submit on-chain transactions | | `sign_message` | Request message signatures | Authentication | | `storage_read` | Read local storage | Load user preferences | | `storage_write` | Write to local storage | Save user settings | | `camera` | Access device camera | QR code scanning | | `location` | Access device location | Location-based features | ## Submission Requirements Before submitting, ensure your app meets these requirements: ### ✅ Technical Requirements * \[ ] **HTTPS Required** - App must be served over HTTPS * \[ ] **Mobile Responsive** - Works on all mobile screen sizes * \[ ] **Fast Loading** - Loads in < 3 seconds * \[ ] **SDK Integration** - Uses Movement SDK correctly * \[ ] **Error Handling** - Handles all error states gracefully * \[ ] **No External Auth** - Uses wallet authentication only ### ✅ Content Requirements * \[ ] **Clear Purpose** - App purpose is immediately clear * \[ ] **User-Friendly** - Intuitive interface and navigation * \[ ] **Complete** - All features are functional (no "coming soon") * \[ ] **Professional** - High-quality UI and branding * \[ ] **Original** - Not a copy of existing app ### ✅ Security Requirements * \[ ] **Permission Scoping** - Only request necessary permissions * \[ ] **No Malicious Code** - No phishing, scams, or exploits * \[ ] **Data Privacy** - Complies with data protection standards * \[ ] **Open Source** (Recommended) - Source code publicly available ### 💰 Submission Fee A **1 MOVE** submission fee is required to submit your app for review. This one-time fee: * Prevents spam submissions * Ensures serious developers * Helps maintain registry quality * Is non-refundable Make sure your wallet has at least **1 MOVE + gas fees** before submitting. ## Submission Process ### 1. Test Your App Test thoroughly in developer mode: ```typescript // Enable developer mode in Movement app // Settings → Developer Options → Developer Mode // Enter your app URL in Mini App Testing // Test all features and edge cases ``` ### 2. Deploy to Production Deploy your app to a production URL: ```bash # Build for production npm run build # Deploy to your hosting provider # Vercel, Netlify, Cloudflare Pages, etc. ``` Ensure your URL: * Uses HTTPS * Is publicly accessible * Has a valid SSL certificate * Has CORS configured (if using APIs) ### 3. Submit to Registry Submit your app using one of these methods: #### Option A: Web Dashboard (Recommended) Use our [**App Publisher Dashboard**](/publishing/publisher) for a simple, code-free submission: 1. Visit [Publisher Dashboard](/publishing/publisher) 2. Connect your wallet 3. Fill in your app details 4. Click "Submit App for Review" #### Option B: Smart Contract (Advanced) Submit your app by calling the smart contract directly: ::: code-group ```typescript [TypeScript/SDK] import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk'; // Testnet registry address const REGISTRY_ADDRESS = '0xba8a509e05730d3025d6d63e4974cf3296f7af78b6bb9c1e26d9e7d0fc1d8d63'; async function submitApp() { const aptos = new Aptos(new AptosConfig({ network: Network.CUSTOM, fullnode: 'https://testnet.movementnetwork.xyz/v1' })); const transaction = await aptos.transaction.build.simple({ sender: yourAddress, data: { function: `${REGISTRY_ADDRESS}::app_registry::submit_app`, functionArguments: [ "My Rewards App", // name "Track your rewards and earnings", // description "https://yourdomain.com/icon.png", // icon (.png or .jpg over HTTPS) "https://myapp.com", // url "my-rewards-app", // slug (URL-friendly identifier) "Acme Labs", // developer_name "earn", // category ["wallet_read", "sign_transaction"] // permissions ], }, }); const response = await aptos.signAndSubmitTransaction({ signer: account, transaction, }); await aptos.waitForTransaction({ transactionHash: response.hash }); console.log('App submitted! Transaction:', response.hash); } ``` ```bash [Aptos CLI] aptos move run \ --function-id '@app_registry::app_registry::submit_app' \ --args \ string:"My Rewards App" \ string:"Track your rewards and earnings" \ string:"https://yourdomain.com/icon.png" \ # .png or .jpg string:"https://myapp.com" \ string:"my-rewards-app" \ string:"Acme Labs" \ string:"earn" \ 'vector<string>:["wallet_read","sign_transaction"]' ``` ::: ### 4. Track Submission Status Check your submission status: ```typescript // Get app by app_index (returned when you submit) const app = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::get_app`, functionArguments: [appIndex], // u64 app_index }, }); console.log('Status:', app.status); // 0=Pending, 1=Approved, 2=Rejected // Or get app by slug (for approved apps only) const appBySlug = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::get_app_by_slug`, functionArguments: ["my-rewards-app"], // slug string }, }); ``` Status codes: * `0` - **Pending** - Awaiting admin review * `1` - **Approved** - Live in app store * `2` - **Rejected** - See rejection reason ### 5. Go Live Once approved: * ✅ App appears in Movement App Store automatically * ✅ Users can discover and launch your app * ✅ App is marked as "verified" with badge * ✅ You can submit updates ## Updating Your App ### Update Process 1. **Deploy new version** to your URL 2. **Request update approval** via smart contract 3. **Wait for admin approval** 4. **Update goes live** automatically ### Submit Update Request You can request updates via the [**Publisher Dashboard**](/publishing/publisher) or using the smart contract: ```typescript async function requestUpdate(appIndex: number) { const transaction = await aptos.transaction.build.simple({ sender: yourAddress, data: { function: `${REGISTRY_ADDRESS}::app_registry::request_update`, functionArguments: [ appIndex, // u64 app_index "My Rewards App v2", // updated name "New features and improvements", // updated description "https://yourdomain.com/icon.png", // icon (.png or .jpg over HTTPS) "https://myapp.com", // url "earn", // category ["wallet_read", "sign_transaction", "storage_write"] // updated permissions ], }, }); await aptos.signAndSubmitTransaction({ signer: account, transaction }); } ``` ::: warning PENDING UPDATES You can only have **one pending update** at a time. Wait for approval before submitting another update. ::: ### Check Pending Updates ```typescript const hasPending = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::has_pending_change`, functionArguments: [appIndex], // u64 app_index }, }); if (hasPending) { const pendingChange = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::get_pending_change`, functionArguments: [appIndex], // u64 app_index }, }); console.log('Pending update:', pendingChange); } ``` ## Review Process ### Timeline * **Initial Review**: 24-48 hours * **Update Reviews**: 12-24 hours * **Resubmissions**: 24-48 hours ### Review Criteria Admins evaluate apps based on: 1. **Functionality** - Does the app work as described? 2. **User Experience** - Is it intuitive and polished? 3. **Security** - Are permissions appropriate? Any security concerns? 4. **Quality** - Professional design and implementation? 5. **Value** - Does it provide value to users? 6. **Compliance** - Follows all guidelines and requirements? ### Common Rejection Reasons * **Broken functionality** - App crashes or doesn't work * **Poor UX** - Confusing navigation or broken layouts * **Excessive permissions** - Requests more permissions than needed * **Security concerns** - Potential malicious code or vulnerabilities * **Incomplete** - Missing features or "coming soon" placeholders * **Low quality** - Unprofessional design or bugs * **Duplicate** - Too similar to existing app * **Inappropriate content** - Violates content policy ### After Rejection If your app is rejected: 1. Read the rejection reason carefully 2. Fix the issues 3. Resubmit using the same process 4. Address feedback in submission notes ## Removing Your App You can remove your app at any time: ```typescript async function removeApp(appIndex: number) { const transaction = await aptos.transaction.build.simple({ sender: yourAddress, data: { function: `${REGISTRY_ADDRESS}::app_registry::remove_app`, functionArguments: [ appIndex, // u64 app_index ], }, }); await aptos.signAndSubmitTransaction({ signer: account, transaction }); } ``` ::: warning PERMANENT REMOVAL Removing your app is permanent. Users will no longer be able to access it from the app store. ::: ## Stats and Analytics ### View Your Stats ```typescript // Get app by app_index const app = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::get_app`, functionArguments: [appIndex], // u64 app_index }, }); console.log('Downloads:', app.downloads); console.log('Rating:', app.rating / 10); // Stored as rating * 10 // Or get all your apps as a developer const [appIndices, apps] = await aptos.view({ payload: { function: `${REGISTRY_ADDRESS}::app_registry::get_developer_apps`, functionArguments: [yourAddress], // developer address }, }); ``` Stats tracked: * **Downloads** - Number of users who launched your app * **Rating** - Average user rating (0-5 stars) * **Verified Badge** - Approved apps get verified badge ::: tip STATS UPDATES Stats are updated by admins periodically. Check back regularly to see your app's performance. ::: ## Smart Contract Reference ### Registry Address The app registry contract uses a named address `@app_registry` internally. When calling functions, you can use either the named address or the actual address: ::: code-group ```typescript [Mainnet] const REGISTRY_ADDRESS = '0x...'; // Coming soon - mainnet not deployed yet ``` ```typescript [Testnet] const REGISTRY_ADDRESS = '0xba8a509e05730d3025d6d63e4974cf3296f7af78b6bb9c1e26d9e7d0fc1d8d63'; // Or use the named address: '@app_registry' ``` ::: ::: tip NAMED ADDRESS The contract uses the named address `@app_registry` internally. You don't need to pass `registry_address` as a parameter to any function - the contract automatically uses `@app_registry`. ::: ### Entry Functions | Function | Description | Parameters | Who Can Call | |----------|-------------|------------|--------------| | `submit_app` | Submit new app | `name, description, icon, url, slug, developer_name, category, permissions` | Any developer | | `request_update` | Request app update | `app_index, name, description, icon, url, category, permissions` | App owner | | `remove_app` | Remove your app | `app_index` | App owner | ### View Functions | Function | Description | Parameters | Returns | |----------|-------------|------------|---------| | `get_app` | Get app metadata by index | `app_index: u64` | `AppMetadata` | | `get_app_by_slug` | Get approved app by slug | `slug: String` | `AppMetadata` | | `get_developer_apps` | Get all apps by developer | `developer_address: address` | `(vector, vector)` | | `get_all_active_apps` | Get all approved apps | None | `vector` | | `app_exists` | Check if app exists | `app_index: u64` | `bool` | | `get_stats` | Get registry stats | None | `(total, approved, pending)` | | `has_pending_change` | Check for pending update | `app_index: u64` | `bool` | | `get_pending_change` | Get pending update | `app_index: u64` | `PendingChange` | | `get_submit_fee` | Get submission fee in octas | None | `u64` | | `get_treasury_address` | Get treasury address | None | `address` | ### Admin Functions These functions are restricted to registry admins: * `approve_app` - Approve pending submission * `reject_app` - Reject pending submission * `approve_update` - Approve pending update * `update_stats` - Update app downloads/rating * `update_submit_fee` - Update submission fee * `update_treasury_address` - Update treasury address * `add_owner` - Add new admin * `remove_owner` - Remove admin ## Events The registry emits events for all state changes: ```typescript // Listen for events aptos.getEvents({ address: REGISTRY_ADDRESS, eventType: `${REGISTRY_ADDRESS}::app_registry::AppSubmittedEvent`, }); ``` Event types: * `AppSubmittedEvent` - New app submitted * `AppApprovedEvent` - App approved * `AppRejectedEvent` - App rejected * `AppUpdateRequestedEvent` - Update requested * `AppRemovedEvent` - App removed ## Best Practices ### Development * ✅ **Test thoroughly** - Test all features before submitting * ✅ **Handle errors** - Show user-friendly error messages * ✅ **Loading states** - Show loading indicators * ✅ **Mobile-first** - Design for mobile screens * ✅ **Performance** - Optimize images and bundle size ### Submission * ✅ **Clear description** - Explain what your app does * ✅ **Appropriate category** - Choose the right category * ✅ **Minimal permissions** - Only request what you need * ✅ **Professional icon** - Use a clear, recognizable icon URL over HTTPS (.png or .jpg) * ✅ **Stable URL** - Don't change your app URL after approval ### Updates * ✅ **Version changes carefully** - Test before requesting update * ✅ **Communicate changes** - Update description with new features * ✅ **Maintain compatibility** - Don't break existing functionality * ✅ **Monitor stats** - Track downloads and ratings ## Support Need help publishing your app? * **Documentation** - Read the full [Mini App Docs](/quick-start/) * **Discord** - Join the Movement Discord * **GitHub** - Report issues on GitHub * **Admin Contact** - Email support@movement.xyz ## Next Steps * **[Testing →](/quick-start/testing)** - Test your app in developer mode * **[Design Guidelines →](/guidelines/design)** - Polish your UI * **Smart Contract** - View the [registry contract source](https://github.com/movement-labs/...) --- --- url: /quick-start.md --- # Quick Start Build your first mini app in 5 minutes. ## What You'll Build A simple app that connects to the user's wallet and sends a transaction. ## Prerequisites * Node.js 18+ * Basic HTML/JavaScript knowledge *** ## Step 1: Create Your Project ```bash mkdir my-mini-app cd my-mini-app npm init -y ``` ## Step 2: Create Your HTML Create `index.html`: ```html My Mini App

My Mini App

``` ## Step 3: Test Locally ```bash npx serve . ``` Open `http://localhost:3000` ## Step 4: Test in Move Everything 1. Open the **Move Everything** app 2. Go to **Settings** → Enable **Developer Mode** 3. In the **Mini App Testing** section: * Enter: `http://localhost:3000` * Click **Test App** Your mini app will open with the wallet already connected! *** ## What Just Happened? 1. **Auto-connected** - No wallet connection flow needed 2. **`window.movementSDK`** - Available globally in Move Everything 3. **`sendTransaction`** - Prompts user approval and submits to blockchain *** ## Next Steps ### Want to use React? ```bash npx create-next-app@latest my-app cd my-app npm install @movement-labs/miniapp-sdk ``` See the [Next.js Guide](/examples/nextjs) → ### Explore SDK Features * **Camera** - Capture photos * **Biometrics** - FaceID/TouchID auth * **Storage** - Save data locally * **Notifications** - Push notifications * **Location** - GPS access See [SDK Overview](/sdk/overview) → ### Deploy Your App Once tested, deploy to: * Vercel: `vercel deploy` * Netlify: `netlify deploy` * GitHub Pages: Push to `gh-pages` branch Then submit your app URL for review! *** ## Troubleshooting **SDK not found?** * Make sure you're testing inside the Move Everything app * Developer Mode must be enabled in Settings **Transaction fails?** * Check wallet has sufficient balance * Verify the transaction function format * See [Security Guide](/guides/security) for common issues *** ## Examples Check out these example apps: * **Coming Soon** - RPG Battle Game * **Coming Soon** - Token Swap Interface * **Coming Soon** - NFT Gallery Browse code on [GitHub](https://github.com/movementlabsxyz/miniapp-examples) --- --- url: /quick-start.md --- # Quick Start Build your first Movement mini app in 5 minutes. ::: tip Using AI Coding Assistants? Feed these docs directly to your AI tools for better code generation: * **[llms.txt](/llms.txt)** - Documentation index (quick context) * **[llms-full.txt](/llms-full.txt)** - Complete docs in one file (comprehensive) Works with Cursor, Claude Code, GitHub Copilot, and other AI assistants. ::: ## What You'll Build A simple app that: * Connects to the user's wallet automatically * Displays their address and balance * Sends a transaction on-chain ## Prerequisites * Node.js 18+ installed * Basic JavaScript/TypeScript knowledge * A code editor (VS Code recommended) ## Choose Your Path ::: code-group ```bash [Next.js (Recommended)] npx create-next-app@latest my-mini-app cd my-mini-app npm install @movement-labs/miniapp-sdk ``` ```bash [Vanilla JavaScript] mkdir my-mini-app cd my-mini-app npm init -y ``` ```bash [Unity] # Download Movement Unity SDK package # Import into your Unity project # Build for WebGL target ``` ::: ## Next Steps Continue with the installation guide: * **[Installing →](/quick-start/installing)** - Setup and installation guide ## Quick Links * **[Commands →](/quick-start/commands)** - How commands work * **[Responses →](/quick-start/responses)** - Handle results and errors * **[Testing →](/quick-start/testing)** - Test your app locally * **[Examples →](/examples/nextjs)** - Complete working examples --- --- url: /quick-start/responses.md --- # Responses Learn how to handle SDK command responses, results, and errors. ## Response Pattern All SDK commands return promises that resolve with results or reject with errors: ```typescript try { const result = await sdk.sendTransaction({...}); // Handle success console.log('Transaction hash:', result.hash); } catch (error) { // Handle error console.error('Error:', error.message); } ``` ## Success Responses ### Transaction Result ```typescript const result = await sdk.sendTransaction({...}); // Result structure { hash: '0x1234567890abcdef...', // Transaction hash success: true, // Whether it succeeded version: '12345678', // Ledger version vmStatus: 'Executed' // VM status } ``` ### Sign Message Result ```typescript const result = await sdk.signMessage({ message: 'Hello Movement', nonce: '12345' }); // Result structure { signature: '0xabcdef1234567890...', // Signature publicKey: '0x9876543210fedcba...', // Public key fullMessage: 'Hello Movement\nnonce: 12345' // Full signed message } ``` ### Balance Result ```typescript const balance = await sdk.getBalance(); // Returns string "1.25" // Balance in MOVE ``` ### Account Result ```typescript const account = await sdk.getAccount(); // Result structure { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', publicKey: '0x...', balance: '1.25' } ``` ## Error Responses ### Error Structure All errors include: ```typescript { code: 'ERROR_CODE', // Error code message: 'Human-readable...', // Error message details?: {...} // Optional details } ``` ### Common Error Codes | Code | Description | How to Handle | |------|-------------|---------------| | `USER_REJECTED` | User cancelled action | Don't show error, user intentionally cancelled | | `INSUFFICIENT_BALANCE` | Not enough balance | Show friendly message, suggest getting more funds | | `INVALID_SIGNATURE` | Signature verification failed | Check transaction format | | `RATE_LIMIT_EXCEEDED` | Too many requests | Wait before retrying | | `NETWORK_ERROR` | Connection issue | Retry with exponential backoff | | `INVALID_ADDRESS` | Malformed address | Validate address format | | `TRANSACTION_FAILED` | Transaction reverted | Check contract logic | ### Error Handling Patterns #### Basic Error Handling ```typescript try { await sdk.sendTransaction({...}); } catch (error) { console.error('Error:', error.message); showErrorToast(error.message); } ``` #### Specific Error Handling ```typescript try { await sdk.sendTransaction({...}); } catch (error) { switch (error.code) { case 'USER_REJECTED': // User cancelled - don't show error console.log('User cancelled transaction'); break; case 'INSUFFICIENT_BALANCE': showError('You don\'t have enough balance'); break; case 'RATE_LIMIT_EXCEEDED': showError('Too many transactions. Please wait.'); break; case 'NETWORK_ERROR': showError('Network error. Please check your connection.'); retryWithBackoff(); break; default: showError('Transaction failed: ' + error.message); logErrorToMonitoring(error); } } ``` #### Retry Logic ```typescript async function sendTransactionWithRetry(payload, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const result = await sdk.sendTransaction(payload); return result; } catch (error) { // Only retry on network errors if (error.code !== 'NETWORK_ERROR' || i === maxRetries - 1) { throw error; } // Exponential backoff const delay = Math.pow(2, i) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } } ``` ## Async vs Event Pattern ### Async Pattern (Recommended) Wait for command completion: ```typescript async function transfer() { try { const result = await sdk.sendTransaction({...}); if (result.success) { showSuccess('Transaction confirmed!'); } } catch (error) { showError(error.message); } } ``` ### Event Pattern (Advanced) Subscribe to real-time updates: ```typescript function transfer() { // Send transaction (non-blocking) sdk.sendTransaction({...}) .then(result => { console.log('Transaction submitted:', result.hash); }) .catch(error => { console.error('Error:', error); }); // Listen for updates sdk.on('transaction:submitted', (tx) => { showToast('Transaction submitted...'); }); sdk.on('transaction:confirmed', (tx) => { showSuccess('Transaction confirmed!'); refreshBalance(); }); sdk.on('transaction:failed', (error) => { showError('Transaction failed'); }); } ``` ## Transaction Status ### Wait for Confirmation ```typescript const result = await sdk.sendTransaction({...}); // Wait for transaction confirmation const status = await sdk.waitForTransaction(result.hash); if (status.status === 'success') { console.log('Confirmed! Gas used:', status.gasUsed); } else { console.error('Failed:', status.error); } ``` ### Real-time Monitoring ```typescript const result = await sdk.sendTransaction({...}); // Subscribe to updates const unsubscribe = sdk.onTransactionUpdate(result.hash, (status) => { console.log('Status:', status.status); // 'pending' | 'success' | 'failed' if (status.status === 'success') { showSuccess('Transaction confirmed!'); unsubscribe(); // Stop monitoring } else if (status.status === 'failed') { showError('Transaction failed: ' + status.error); unsubscribe(); } }); // Cleanup when component unmounts return () => unsubscribe(); ``` ## React Example ### With Hooks ```tsx import { useState } from 'react'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; function TransferButton() { const { sdk } = useMovementSDK(); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); async function handleTransfer() { setLoading(true); setError(''); try { const result = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [] }); await sdk.waitForTransaction(result.hash); await sdk.notify({ title: 'Success!', body: 'Transfer completed' }); } catch (err) { if (err.code !== 'USER_REJECTED') { setError(err.message); } } finally { setLoading(false); } } return (
{error &&

{error}

}
); } ``` ### With Real-time Updates ```tsx import { useState, useEffect } from 'react'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; function TransferStatus({ hash }) { const { sdk } = useMovementSDK(); const [status, setStatus] = useState('pending'); useEffect(() => { const unsubscribe = sdk.onTransactionUpdate(hash, (txStatus) => { setStatus(txStatus.status); }); return () => unsubscribe(); }, [hash]); return (
{status === 'pending' && '⏳ Confirming...'} {status === 'success' && '✅ Confirmed!'} {status === 'failed' && '❌ Failed'}
); } ``` ## Best Practices ::: tip ALWAYS HANDLE ERRORS Never ignore error cases: ```typescript // ❌ Bad const result = await sdk.sendTransaction({...}); // ✅ Good try { const result = await sdk.sendTransaction({...}); } catch (error) { handleError(error); } ``` ::: ::: tip USER FEEDBACK Provide clear feedback for all states: ```typescript setStatus('loading'); try { const result = await sdk.sendTransaction({...}); setStatus('success'); showToast('Transaction confirmed!'); } catch (error) { setStatus('error'); showToast(error.message); } ``` ::: ::: tip GRACEFUL DEGRADATION Handle SDK unavailability: ```typescript if (!sdk?.isInstalled()) { return (
Please open this app in Movement wallet
); } ``` ::: ::: warning DON'T RETRY USER REJECTIONS If user cancels, respect their choice: ```typescript catch (error) { if (error.code === 'USER_REJECTED') { // Don't show error or retry return; } // Handle other errors } ``` ::: ## Next Steps * **[Testing →](/quick-start/testing)** - Test your response handling * **[Commands →](/commands/)** - See all available commands * **Error Reference** *(Coming Soon)* - Complete error code list --- --- url: /examples/scaffold.md --- # Scaffold App A complete reference implementation demonstrating all Movement SDK methods and components. Perfect for exploring SDK capabilities and testing features. ## Overview The Scaffold App is a comprehensive demo that showcases: * All SDK methods with interactive testing * Real-time status and results * Error handling patterns * Native UI components * Storage and clipboard APIs ## Repository The scaffold app is available in the `mini-app-scaffold` directory of the [mini-app-examples](https://github.com/movementlabsxyz/miniapp-examples) repository. ## Quick Start ```bash cd mini-app-scaffold npm install npm run dev ``` Open the Movement Everything (ME) app on your mobile device and navigate to this mini app. ## Features Demonstrated ### Core SDK Methods * `isInstalled()` - Check if running inside ME app * `ready()` - Wait for SDK initialization * `connect()` - Connection status management ### Account & Balance * `getUserInfo()` - Get connection/address info * `getAccount()` - Detailed account information * `getBalance()` - Current MOVE token balance ### Transactions * `sendTransaction()` - Sign and submit transactions * `waitForTransaction()` - Wait for confirmation * `onTransactionUpdate()` - Real-time status updates ### Device & UI * `scanQRCode()` - Open camera for QR scanning * `showPopup()` - Native popup with custom buttons * `showAlert()` - Simple alert dialogs * `showConfirm()` - Confirmation dialogs * `notify()` - Push notifications ### Storage & Clipboard * `CloudStorage` - Persistent local storage (setItem, getItem, removeItem, getKeys) * `Clipboard` - System clipboard access (writeText, readText) ### Theme & Haptics * `getTheme()` - Current host theme * `haptic()` - Device vibration feedback (mobile only) ### Native UI Buttons * `MainButton` - Primary overlay button (setText, show, hide, onClick) * `SecondaryButton` - Secondary overlay button * `BackButton` - Top overlay back control ## Use Cases * **Learning the SDK** - Interactive exploration of all available methods * **Testing features** - Verify SDK functionality before implementing in your app * **Debugging** - See real-time status and error messages * **Reference** - Copy patterns and code snippets for your own app ## Project Structure ``` mini-app-scaffold/ ├── app/ │ ├── page.tsx # Main scaffold component │ ├── layout.tsx # App layout │ └── globals.css # Styles ├── package.json └── README.md ``` ## Next Steps * Explore the [SDK API Reference](/reference/sdk-api) for detailed method documentation * Check out other examples: [Next.js](/examples/nextjs), [Vanilla JS](/examples/vanilla), [Social App](/examples/social) * Read the [Commands guide](/quick-start/commands) to understand SDK usage patterns --- --- url: /commands/scan-qr.md --- # Scan QR Code Open the device camera to scan QR codes, typically for wallet addresses. ## Basic Usage ```typescript const scannedAddress = await sdk.scanQRCode(); console.log('Scanned:', scannedAddress); ``` ## Return Value Returns a `Promise` containing the scanned QR code data. ## Example ### Scan Recipient Address ```typescript async function handleScanRecipient() { try { const address = await sdk.scanQRCode(); // Validate address format if (!address.startsWith('0x')) { throw new Error('Invalid address scanned'); } setRecipientAddress(address); await sdk.haptic({ type: 'impact', style: 'light' }); } catch (error) { if (error.message !== 'QR code scan cancelled') { console.error('Scan failed:', error); showError('Failed to scan QR code'); } } } ``` ### React Component ```tsx import { useState } from 'react'; import { useMovementSDK } from '@movement-labs/miniapp-sdk'; function RecipientInput() { const { sdk } = useMovementSDK(); const [address, setAddress] = useState(''); async function handleScan() { try { const scanned = await sdk.scanQRCode(); setAddress(scanned); } catch (error) { if (error.message !== 'QR code scan cancelled') { alert('Failed to scan QR code'); } } } return (
setAddress(e.target.value)} placeholder="0x..." className="input pr-12" />
); } ``` ## Error Handling ```typescript try { const address = await sdk.scanQRCode(); } catch (error) { switch (error.code) { case 'PERMISSION_DENIED': showError('Camera permission denied'); break; case 'USER_CANCELLED': // User cancelled scan - don't show error console.log('Scan cancelled'); break; case 'NOT_SUPPORTED': showError('QR scanning not supported on this device'); break; default: showError('Failed to scan QR code'); } } ``` ## Use Cases * Scanning wallet addresses for transfers * Scanning payment request QR codes * Scanning deep links to other mini apps * Scanning authentication codes ## Best Practices ::: tip VALIDATION Always validate scanned data: ```typescript const scanned = await sdk.scanQRCode(); if (!isValidAddress(scanned)) { throw new Error('Invalid address format'); } ``` ::: ::: tip FEEDBACK Provide haptic feedback on successful scan: ```typescript const address = await sdk.scanQRCode(); await sdk.haptic({ type: 'notification', style: 'success' }); ``` ::: ::: warning PERMISSIONS Scanner may fail if camera permission is denied. Handle gracefully. ::: ## Related * [Haptic Feedback](/commands/haptic) - Provide tactile feedback * Camera *(Coming Soon)* - Capture photos --- --- url: /reference/sdk-api.md --- # SDK API Reference Complete reference for all Movement SDK methods and properties. ## Installation ```bash npm install @movement-labs/miniapp-sdk ``` ## SDK Status ### `isInstalled()` Check if the app is running inside Movement wallet. **Returns:** `boolean` ```typescript const isInMovementApp = window.movementSDK.isInstalled(); if (!isInMovementApp) { console.error('App must run in Movement wallet'); } ``` **When to use:** * Always check before using any SDK method * Show error messages if not in Movement app * Conditionally render UI based on environment **Example:** ```tsx function App() { if (!window.movementSDK?.isInstalled()) { return (

Not Available

Please open this app inside Movement wallet

); } return ; } ``` *** ### `ready()` Wait for the SDK to be fully initialized before using methods. **Returns:** `Promise` ```typescript await window.movementSDK.ready(); console.log('SDK is ready!'); ``` **When to use:** * After checking `isInstalled()` * Before calling any SDK methods * In initialization code **Example:** ```typescript async function initApp() { if (!window.movementSDK?.isInstalled()) { return; } // Wait for SDK to be ready await window.movementSDK.ready(); // Now safe to use SDK const account = await window.movementSDK.getAccount(); console.log('Account:', account); } ``` **Timeout:** The `ready()` method has a 2-second timeout. If initialization takes longer, it resolves anyway: ```typescript try { await window.movementSDK.ready(); } catch (error) { console.error('SDK initialization timeout:', error); } ``` *** ## Properties ### `isConnected` **Type:** `boolean` Whether a wallet is currently connected. ```typescript if (sdk.isConnected) { console.log('Wallet is connected'); } ``` *** ### `address` **Type:** `string | undefined` The connected wallet address (if connected). ```typescript const address = sdk.address; console.log('Address:', address); // Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb ``` *** ### `network` **Type:** `'mainnet' | 'testnet' | 'devnet'` Current network the wallet is connected to. ```typescript const network = sdk.network; console.log('Network:', network); // Network: mainnet ``` *** ## Account Methods ### `connect()` Connect to the user's wallet and request access. **Returns:** `Promise` ```typescript const account = await sdk.connect(); console.log('Connected:', account.address); ``` **Example:** ```tsx function ConnectButton() { const [account, setAccount] = useState(null); async function handleConnect() { try { const acc = await sdk.connect(); setAccount(acc); } catch (error) { console.error('Connection failed:', error); } } return ( ); } ``` *** ### `getAccount()` > Note: Not exposed on `window.movementSDK` in the current host. Use `window.aptos.account()` instead. A future update may expose `sdk.getAccount()` directly. Get the current account information. **Returns:** `Promise` ```typescript interface MovementAccount { address: string; publicKey: string; balance?: string; } ``` **Example:** ```typescript const account = await sdk.getAccount(); console.log('Account:', account); // { address: '0x...', publicKey: '0x...', balance: '1.5' } ``` *** ### `getBalance()` Get the current MOVE token balance. **Returns:** `Promise` ```typescript const balance = await sdk.getBalance(); console.log('Balance:', balance, 'MOVE'); // Balance: 1.5 MOVE ``` *** ### `getContext()` > Note: Handler exists in host, but `sdk.getContext()` is not exposed on `window.movementSDK` yet. Get comprehensive app context including user, app, and platform info. **Returns:** `Promise` ```typescript interface AppContext { user: { address: string; publicKey: string; verified: boolean; }; app: { id: string; name: string; version: string; }; platform: { os: 'ios' | 'android'; version: string; }; features: { haptics: boolean; notifications: boolean; camera: boolean; biometrics: boolean; location: boolean; }; } ``` **Example:** ```typescript const context = await sdk.getContext(); console.log('Platform:', context.platform.os); console.log('Features:', context.features); ``` *** ### `getTheme()` Get the current theme (light or dark mode) from the host app. **Returns:** `Promise` ```typescript interface ThemeInfo { colorScheme: 'light' | 'dark'; } ``` **Example:** ```typescript const theme = await sdk.getTheme(); if (theme.colorScheme === 'dark') { // Apply dark mode styles document.body.classList.add('dark-mode'); } else { // Apply light mode styles document.body.classList.remove('dark-mode'); } ``` *** ## Transaction Methods ### `view()` Call a Move view function (read-only, no wallet required). **Parameters:** ```typescript interface ViewPayload { function: string; // address::module::function function_arguments?: any[]; // function args (preferred key) type_arguments?: string[]; // generic type args // Backwards-compat: some hosts also accept camelCase functionArguments?: any[]; typeArguments?: string[]; } ``` **Returns:** `Promise` (the decoded view result) The result may be a primitive, struct, or array. Some hosts may wrap results in an extra array. **Example:** ```typescript const result = await sdk.view({ function: '0xabc::leaderboard::get_leaderboard', function_arguments: ['50'], type_arguments: [], }); // Handle wrapped arrays const entries = Array.isArray(result) ? (Array.isArray(result[0]) ? result[0] : result) : []; ``` ### `sendTransaction()` Send a transaction on the Movement blockchain. **Parameters:** ```typescript interface TransactionPayload { function: string; // Module function to call arguments: any[]; // Function arguments type_arguments: string[]; // Type arguments (generics) } ``` **Returns:** `Promise` ```typescript interface TransactionResult { hash: string; success: boolean; version?: string; vmStatus?: string; } ``` **Example:** ```typescript const result = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: ['0x123...', '1000000'], type_arguments: [] }); console.log('Transaction hash:', result.hash); ``` *** ### `waitForTransaction()` Wait for a transaction to be confirmed on-chain. **Parameters:** `hash: string` **Returns:** `Promise` ```typescript interface TransactionStatus { hash: string; status: 'pending' | 'success' | 'failed'; gasUsed?: string; timestamp?: number; error?: string; } ``` **Example:** ```typescript const result = await sdk.sendTransaction({...}); const status = await sdk.waitForTransaction(result.hash); if (status.status === 'success') { console.log('Transaction confirmed!'); } else { console.error('Transaction failed:', status.error); } ``` *** ### `onTransactionUpdate()` Subscribe to real-time transaction status updates. **Parameters:** `hash: string, callback: (status: TransactionStatus) => void` **Returns:** `() => void` (unsubscribe function) ```typescript // Subscribe to transaction updates const unsubscribe = sdk.onTransactionUpdate?.(result.hash, (status) => { console.log('Transaction status:', status.status); if (status.status === 'success') { console.log('Transaction confirmed!'); unsubscribe(); } else if (status.status === 'failed') { console.error('Transaction failed:', status.error); unsubscribe(); } }); // Clean up when component unmounts useEffect(() => { return () => unsubscribe?.(); }, []); ``` *** ### `signMessage()` > Note: Not yet implemented in the current host. Calls will fail. Prefer server-side challenges or wait for host support. Sign an arbitrary message with the user's private key. **Parameters:** ```typescript interface SignMessagePayload { message: string; nonce?: string; // Optional nonce for replay protection } ``` **Returns:** `Promise` ```typescript interface SignMessageResult { signature: string; publicKey: string; fullMessage?: string; } ``` **Example:** ```typescript const result = await sdk.signMessage({ message: 'Welcome to my app!', nonce: Date.now().toString() }); console.log('Signature:', result.signature); ``` *** ## MNS (Movement Name Service) The MNS API allows you to resolve human-readable `.move` names to wallet addresses and perform reverse lookups. ### `mns.getTargetAddress()` Resolve a `.move` name to its associated wallet address. **Parameters:** `name: string` - The name to resolve (e.g., "alice" or "alice.move") **Returns:** `Promise` - An AccountAddress object or null if not found The result is an `AccountAddress` object from the Movement SDK, which may need to be converted to a hex string: **Example:** ```typescript const result = await sdk.mns.getTargetAddress('alice.move'); if (!result) { console.log('Name not found'); return; } // Handle AccountAddress object - convert to hex string let address: string | null = null; if (typeof result === 'string') { address = result; } else if (result && typeof result === 'object' && 'data' in result) { // Convert byte array to hex address const data = (result as any).data; const bytes = Object.keys(data) .sort((a, b) => Number(a) - Number(b)) .map(k => data[k]); if (bytes.length > 0 && bytes.some((b: number) => b !== 0)) { address = '0x' + bytes.map((b: number) => b.toString(16).padStart(2, '0')).join(''); } } console.log('Resolved address:', address); ``` *** ### `mns.getPrimaryName()` Get the primary `.move` name associated with a wallet address (reverse lookup). **Parameters:** `address: string` **Returns:** `Promise` **Example:** ```typescript const name = await sdk.mns.getPrimaryName('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb...'); console.log('Name:', name); // alice ``` *** **Use Cases:** * Allow users to send tokens to `.move` names instead of addresses * Display human-readable names in transaction history * Verify name ownership before transfers *** ## Device Features ### `scanQRCode()` Open the camera to scan a QR code. **Returns:** `Promise` ```typescript const scannedData = await sdk.scanQRCode(); console.log('Scanned:', scannedData); ``` **Example:** ```tsx async function handleScan() { try { const address = await sdk.scanQRCode(); setRecipient(address); } catch (error) { if (error.message !== 'QR code scan cancelled') { console.error('Scan failed:', error); } } } ``` *** ### `haptic()` Provide tactile feedback to the user. **Parameters:** ```typescript interface HapticOptions { type: 'impact' | 'notification' | 'selection'; style?: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error'; } ``` **Example:** ```typescript // Button press await sdk.haptic({ type: 'impact', style: 'light' }); // Success feedback await sdk.haptic({ type: 'notification', style: 'success' }); // Selection change await sdk.haptic({ type: 'selection' }); ``` *** ### `notify()` Send a push notification to the user. **Parameters:** ```typescript interface NotificationOptions { title: string; body: string; data?: Record; } ``` **Example:** ```typescript await sdk.notify({ title: 'Transaction Complete', body: 'Your transfer was successful', data: { deepLink: '/transactions', txHash: result.hash } }); ``` *** ### `share()` Open the native share sheet. **Parameters:** ```typescript interface ShareOptions { title?: string; message: string; url?: string; } ``` **Returns:** `Promise<{ success: boolean }>` **Example:** ```typescript await sdk.share({ title: 'Check out this mini app!', message: 'Play this awesome game on Movement', url: 'https://moveeverything.app/apps/my-game' }); ``` *** ### `Clipboard` Copy and paste text. **Methods:** ```typescript // Copy text to clipboard await sdk.Clipboard.writeText(text: string): Promise // Read text from clipboard const text = await sdk.Clipboard.readText(): Promise ``` **Example:** ```typescript // Copy wallet address await sdk.Clipboard.writeText(address); await sdk.showAlert('Address copied to clipboard!'); // Paste from clipboard const pastedText = await sdk.Clipboard.readText(); setRecipientAddress(pastedText); ``` *** ### `camera` > Note: Not yet implemented in the current host. Access device camera and photo library. ```typescript // Take picture await sdk.camera.takePicture(options?: CameraOptions): Promise // Pick from library await sdk.camera.pickImage(options?: CameraOptions): Promise ``` **Options:** ```typescript interface CameraOptions { quality?: number; // 0-1 allowsEditing?: boolean; mediaTypes?: 'photo' | 'video' | 'all'; } ``` **Returns:** ```typescript interface CameraResult { uri: string; width: number; height: number; type: 'image' | 'video'; } ``` *** ### `location` > Note: Not yet implemented in the current host. Access device location. ```typescript // Get current position await sdk.location.getCurrentPosition(): Promise // Watch position changes const unsubscribe = sdk.location.watchPosition((position) => { console.log(position); }); ``` **LocationResult:** ```typescript interface LocationResult { latitude: number; longitude: number; accuracy: number; altitude?: number; heading?: number; speed?: number; } ``` *** ### `biometric` > Note: Not yet implemented in the current host. Authenticate with FaceID/TouchID. ```typescript // Check availability await sdk.biometric.isAvailable(): Promise // Authenticate await sdk.biometric.authenticate(options?: BiometricOptions): Promise ``` **Options:** ```typescript interface BiometricOptions { promptMessage?: string; cancelText?: string; fallbackToPasscode?: boolean; } ``` **Returns:** ```typescript interface BiometricResult { success: boolean; biometricType?: 'FaceID' | 'TouchID' | 'Fingerprint'; } ``` *** ## Storage ### `CloudStorage` Persistent storage for user data, preferences, and app state. **Limits:** 1024 items per user **Methods:** ```typescript // Store data await sdk.CloudStorage.setItem(key: string, value: string): Promise // Get data const value = await sdk.CloudStorage.getItem(key: string): Promise // Remove data await sdk.CloudStorage.removeItem(key: string): Promise // Get all keys const keys = await sdk.CloudStorage.getKeys(): Promise ``` **Example:** ```typescript // Save user preferences await sdk.CloudStorage.setItem('theme', 'dark'); await sdk.CloudStorage.setItem('language', 'en'); // Load preferences const theme = await sdk.CloudStorage.getItem('theme') || 'light'; const language = await sdk.CloudStorage.getItem('language') || 'en'; // Save game progress await sdk.CloudStorage.setItem('level', '5'); await sdk.CloudStorage.setItem('score', '1250'); // Load on app start const level = await sdk.CloudStorage.getItem('level'); if (level) { resumeGame(parseInt(level)); } // Get all saved data const allKeys = await sdk.CloudStorage.getKeys(); console.log('Saved keys:', allKeys); ``` **Use Cases:** * Save game progress * Store user preferences (theme, language) * Cache frequently accessed data * Persist form drafts *** ### `storage.get()` > Note: The simple `storage.*` API is not exposed in the current host. Use `CloudStorage.*` instead. Get a value from local storage. **Parameters:** `key: string` **Returns:** `Promise` ```typescript const value = await sdk.storage.get('user_preferences'); ``` *** ### `storage.set()` > Note: Use `CloudStorage.setItem(key, value)` instead. Set a value in local storage. **Parameters:** `key: string, value: string` **Returns:** `Promise` ```typescript await sdk.storage.set('user_preferences', JSON.stringify(prefs)); ``` *** ### `storage.remove()` > Note: Use `CloudStorage.removeItem(key)` instead. Remove a value from local storage. **Parameters:** `key: string` **Returns:** `Promise` ```typescript await sdk.storage.remove('user_preferences'); ``` *** ### `storage.clear()` > Note: Not exposed. You can emulate by iterating `CloudStorage.getKeys()` and removing each. Clear all storage data for this app. **Returns:** `Promise` ```typescript await sdk.storage.clear(); ``` *** ### `storage.getAll()` Get all stored key-value pairs. **Returns:** `Promise<{ key: string; value: string }[]>` ```typescript const allData = await sdk.storage?.getAll(); console.log('All stored data:', allData); // [{ key: 'theme', value: 'dark' }, { key: 'language', value: 'en' }] ``` *** ## UI Components ### `MainButton` Fixed bottom button for primary actions. **Methods:** ```typescript // Set button text sdk.MainButton.setText(text: string): void // Show button sdk.MainButton.show(): void // Hide button sdk.MainButton.hide(): void // Handle click sdk.MainButton.onClick(callback: () => void): void ``` **Example:** ```typescript // Set up send button sdk.MainButton.setText('Send 10 MOVE'); sdk.MainButton.show(); sdk.MainButton.onClick(async () => { const confirmed = await sdk.showConfirm('Send 10 MOVE to recipient?'); if (confirmed) { await sendTransaction(); } }); ``` *** ### `SecondaryButton` Secondary fixed bottom button. **Methods:** ```typescript sdk.SecondaryButton.setText(text: string): void sdk.SecondaryButton.show(): void sdk.SecondaryButton.hide(): void sdk.SecondaryButton.onClick(callback: () => void): void ``` **Example:** ```typescript sdk.MainButton.setText('Send'); sdk.SecondaryButton.setText('Cancel'); sdk.SecondaryButton.show(); sdk.SecondaryButton.onClick(() => { sdk.MainButton.hide(); sdk.SecondaryButton.hide(); resetForm(); }); ``` *** ### `BackButton` Back button for navigation. **Methods:** ```typescript sdk.BackButton.show(): void sdk.BackButton.hide(): void sdk.BackButton.onClick(callback: () => void): void ``` **Example:** ```typescript sdk.BackButton.show(); sdk.BackButton.onClick(() => { // Navigate back or close modal window.history.back(); }); ``` *** ### `showPopup()` Show a popup dialog with custom buttons. **Parameters:** ```typescript interface PopupOptions { title?: string; message: string; buttons?: PopupButton[]; } interface PopupButton { id?: string; type?: 'default' | 'ok' | 'close' | 'cancel' | 'destructive'; text: string; } ``` **Returns:** `Promise` ```typescript const result = await sdk.showPopup({ title: 'Confirm', message: 'Are you sure?', buttons: [ { id: 'yes', text: 'Yes', type: 'default' }, { id: 'no', text: 'No', type: 'cancel' } ] }); if (result.button_id === 'yes') { console.log('User confirmed'); } ``` *** ### `showAlert()` Show a simple alert message. **Parameters:** `message: string` **Returns:** `Promise` ```typescript await sdk.showAlert('Transaction submitted successfully!'); ``` *** ### `showConfirm()` Show a confirmation dialog with OK/Cancel buttons. **Parameters:** `message: string, okText?: string, cancelText?: string` **Returns:** `Promise` ```typescript const confirmed = await sdk.showConfirm( 'Delete this item?', 'Delete', 'Cancel' ); if (confirmed) { deleteItem(); } ``` *** ## React Hooks ### `useMovementSDK()` React hook for accessing the SDK. The hook automatically handles SDK initialization by calling `isInstalled()` and `ready()` internally. **How it works:** The hook internally performs these steps: 1. Checks if SDK is installed: `window.movementSDK?.isInstalled()` 2. Waits for SDK to be ready: `await window.movementSDK.ready()` 3. Returns the SDK instance and connection state The `isLoading` state is `true` while waiting for `ready()` to complete. Once `isLoading` is `false`, the SDK is ready to use. **Conceptually, the hook does this internally:** ```typescript // Simplified internal implementation function useMovementSDK() { const [sdk, setSdk] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { async function init() { // Step 1: Check if installed if (!window.movementSDK?.isInstalled()) { setIsLoading(false); return; } // Step 2: Wait for ready (this is what you don't see!) await window.movementSDK.ready(); // Step 3: SDK is ready setSdk(window.movementSDK); setIsLoading(false); } init(); }, []); return { sdk, isLoading, ... }; } ``` **Returns:** ```typescript interface UseMovementSDKResult { sdk: MovementSDK | null; isConnected: boolean; address: string | null; isLoading: boolean; // true while waiting for ready() to complete error: Error | null; connect: () => Promise; sendTransaction: (payload: TransactionPayload) => Promise; } ``` **Example:** ```tsx import { useMovementSDK } from '@movement-labs/miniapp-sdk'; function MyComponent() { const { sdk, isConnected, address, isLoading, error, connect, sendTransaction } = useMovementSDK(); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
{isConnected ? (

Connected: {address}

) : ( )}
); } ``` *** ### `useMovementAccount()` React hook for accessing account information. **Returns:** ```typescript interface UseMovementAccountResult { account: MovementAccount | null; isConnected: boolean; isLoading: boolean; error: Error | null; } ``` **Example:** ```tsx import { useMovementAccount } from '@movement-labs/miniapp-sdk'; function AccountInfo() { const { account, isLoading } = useMovementAccount(); if (isLoading) return
Loading account...
; return (

Address: {account?.address}

Balance: {account?.balance} MOVE

); } ``` *** ### `useMovementTheme()` React hook for accessing the app's current theme (light or dark mode). **Returns:** ```typescript interface UseMovementThemeResult { theme: ThemeInfo | null; isLoading: boolean; error: Error | null; } ``` **Example:** ```tsx import { useMovementTheme } from '@movement-labs/miniapp-sdk'; function App() { const { theme, isLoading, error } = useMovementTheme(); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; const isDark = theme?.colorScheme === 'dark'; return (
{/* Your app content */}
); } ``` *** ### `useAnalytics()` React hook for tracking analytics events in mini apps. Events are sent through the host app's analytics service with automatic mini app context enrichment. **Returns:** ```typescript interface UseAnalyticsResult { track: (eventName: string, properties?: Record) => Promise; identify: (userId: string, traits?: Record) => Promise; trackScreen: (screenName: string, properties?: Record) => Promise; setUserProperties: (properties: Record) => Promise; reset: () => Promise; isEnabled: boolean; optOut: () => Promise; optIn: () => Promise; isAvailable: boolean; } ``` **Example:** ```tsx import { useAnalytics } from '@movement-labs/miniapp-sdk'; function MyComponent() { const { track, trackScreen, isAvailable } = useAnalytics(); useEffect(() => { trackScreen('Home'); }, []); const handleButtonClick = () => { track('Button Clicked', { button_name: 'submit' }); }; return ; } ``` **Methods:** * `track(eventName, properties?)` - Track a custom event * `identify(userId, traits?)` - Identify the current user * `trackScreen(screenName, properties?)` - Track a screen/page view * `setUserProperties(properties)` - Set user properties without tracking an event * `reset()` - Reset analytics state (e.g., on logout) * `optOut()` - Opt out of analytics tracking * `optIn()` - Opt back into analytics tracking *** ## Helper Functions ### `isInMovementApp()` Check if app is running in Movement wallet (alternative to `sdk.isInstalled()`). **Returns:** `boolean` ```typescript import { isInMovementApp } from '@movement-labs/miniapp-sdk'; if (!isInMovementApp()) { console.error('Not in Movement app'); } ``` *** ### `waitForSDK()` Wait for SDK to be available with timeout. **Parameters:** `timeout?: number, config?: SecurityConfig` **Returns:** `Promise` ```typescript import { waitForSDK } from '@movement-labs/miniapp-sdk'; try { const sdk = await waitForSDK(5000); // 5 second timeout console.log('SDK ready:', sdk); } catch (error) { console.error('SDK not available:', error); } ``` *** ## Deep Linking Share direct links to your mini app. **Format:** `/apps/{appId}` **Examples:** * `/apps/send-tokens` - Opens send tokens app * `/apps/bridge` - Opens bridge app * `/apps/game-snake` - Opens your game **Usage:** ```typescript // Get your app's deep link const deepLink = `https://moveeverything.app/apps/my-app-id`; // Share via clipboard await sdk.Clipboard.writeText(deepLink); await sdk.showAlert('Link copied! Share with friends.'); // Or use native share await sdk.share({ title: 'Check out this mini app!', message: 'Play this awesome game on Movement', url: deepLink }); ``` **Use Cases:** * Share games with friends * Invite users to your app * Deep link from notifications * QR code generation *** ## Analytics The SDK provides built-in analytics capabilities that bridge to the host app's analytics service (Mixpanel). ### `analytics.track()` Track a custom event. **Parameters:** `eventName: string, properties?: Record` **Returns:** `Promise` ```typescript await sdk.analytics?.track('Button Clicked', { button_name: 'submit', screen: 'checkout' }); ``` *** ### `analytics.identify()` Identify the current user with optional traits. **Parameters:** `userId: string, traits?: Record` **Returns:** `Promise` ```typescript await sdk.analytics?.identify(userAddress, { premium_user: true, signup_date: '2024-01-15' }); ``` *** ### `analytics.trackScreen()` Track a screen/page view. **Parameters:** `screenName: string, properties?: Record` **Returns:** `Promise` ```typescript await sdk.analytics?.trackScreen('Home', { referrer: 'deep_link' }); ``` *** ### `analytics.setUserProperties()` Set user properties without tracking an event. **Parameters:** `properties: Record` **Returns:** `Promise` ```typescript await sdk.analytics?.setUserProperties({ level: 5, total_purchases: 3 }); ``` *** ### `analytics.reset()` Reset analytics state (e.g., on logout). **Returns:** `Promise` ```typescript await sdk.analytics?.reset(); ``` *** ### `analytics.optOut()` / `analytics.optIn()` Opt out of or back into analytics tracking. **Returns:** `Promise` ```typescript // User requests to opt out await sdk.analytics?.optOut(); // User opts back in await sdk.analytics?.optIn(); ``` *** ### `analytics.isEnabled()` Check if analytics is currently enabled. **Returns:** `Promise` ```typescript const enabled = await sdk.analytics?.isEnabled(); console.log('Analytics enabled:', enabled); ``` *** ## Security Configuration Configure SDK security settings. ```typescript import { getMovementSDK } from '@movement-labs/miniapp-sdk'; const sdk = getMovementSDK({ maxTransactionAmount: '500000000', // Max amount in octas rateLimitWindow: 60000, // Window in ms maxRequestsPerWindow: 20, // Max requests per window allowedOrigins: ['https://your-domain.com'], enableCSP: true, strictMode: true, }); ``` **SecurityConfig:** ```typescript interface SecurityConfig { maxTransactionAmount?: string; allowedOrigins?: string[]; rateLimitWindow?: number; maxRequestsPerWindow?: number; enableCSP?: boolean; strictMode?: boolean; } ``` *** ## TypeScript Types Import types for full type safety: ```typescript import type { MovementSDK, MovementAccount, NetworkInfo, TransactionPayload, TransactionResult, TransactionStatus, ViewPayload, SignMessagePayload, SignMessageResult, HapticOptions, NotificationOptions, PopupOptions, StorageOptions, AppContext, ThemeInfo, ShareOptions, CameraOptions, CameraResult, LocationResult, BiometricOptions, BiometricResult, SecurityConfig, AnalyticsAPI, AnalyticsEventProperties, AnalyticsUserProperties, MultiAgentTransactionPayload, FeePayerTransactionPayload, BatchTransactionPayload, BatchTransactionResult, ScriptComposerPayload, MNSAPI } from '@movement-labs/miniapp-sdk'; ``` *** ## Error Handling All SDK methods may throw errors. Always use try-catch: ```typescript try { const result = await sdk.sendTransaction({...}); } catch (error) { switch (error.code) { case 'USER_REJECTED': console.log('User rejected transaction'); break; case 'INSUFFICIENT_BALANCE': console.error('Not enough funds'); break; default: console.error('Transaction failed:', error.message); } } ``` Common error codes: * `USER_REJECTED` - User cancelled the operation * `INSUFFICIENT_BALANCE` - Not enough tokens for transaction * `NETWORK_ERROR` - Network connection issues * `INVALID_ADDRESS` - Invalid address format * `RATE_LIMIT_EXCEEDED` - Too many requests * `PERMISSION_DENIED` - Missing required permissions * `NOT_SUPPORTED` - Feature not available on this platform --- --- url: /sdk/overview.md --- # SDK Overview The Movement Mini App SDK provides a comprehensive set of APIs to build rich, native-feeling apps inside the Move Everything wallet. ## Core Capabilities ### Wallet & Blockchain Access users' wallets and execute blockchain transactions: ```typescript import { useMovementSDK } from '@movement-labs/miniapp-sdk'; const { sdk, isConnected, address } = useMovementSDK(); // Send transaction await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipientAddress, '1000000'], // 0.01 MOVE type_arguments: [], }); // Sign message const signature = await sdk.signMessage({ message: 'Verify ownership', nonce: '12345', }); // Get account info > Note: `sdk.getAccount()` is not exposed on `window.movementSDK` in the current host. Use `window.aptos.account()` instead. const account = await sdk.getAccount(); console.log(account.address, account.balance); ``` ### Native Device Features Leverage device capabilities for immersive experiences: #### Haptic Feedback ```typescript // Impact feedback await sdk.haptic?.({ type: 'impact', style: 'medium' }); // Notification feedback await sdk.haptic?.({ type: 'notification', style: 'success' }); // Selection feedback await sdk.haptic?.({ type: 'selection' }); ``` #### Push Notifications ```typescript await sdk.notify?.({ title: 'Quest Complete!', body: 'You earned 100 MOVE tokens', data: { questId: '123' }, sound: true, badge: 1, }); ``` #### Share ```typescript const result = await sdk.share?.({ title: 'Check out my high score!', message: 'I just scored 1000 points in Neon Racer', url: 'https://miniapp.com/share/score123', }); ``` ### Camera & Media Capture photos and videos directly in your app: ```typescript // Take a picture const photo = await sdk.camera?.takePicture({ quality: 0.8, allowsEditing: true, mediaTypes: 'photo', }); // Pick from gallery const image = await sdk.camera?.pickImage({ quality: 1.0, }); console.log(photo.uri, photo.width, photo.height); ``` ### Local Storage Persist data on the device: ```typescript // Save data await sdk.storage?.set('user_preferences', JSON.stringify({ theme: 'dark', notifications: true, })); // Retrieve data const prefs = await sdk.storage?.get('user_preferences'); const data = JSON.parse(prefs || '{}'); // Remove data await sdk.storage?.remove('user_preferences'); // Clear all await sdk.storage?.clear(); ``` ### Biometric Authentication Secure sensitive actions with FaceID/TouchID: ```typescript // Check availability const available = await sdk.biometric?.isAvailable(); if (available) { // Authenticate const result = await sdk.biometric?.authenticate({ promptMessage: 'Authenticate to complete transaction', cancelText: 'Cancel', fallbackToPasscode: true, }); if (result.success) { console.log('Authenticated with', result.biometricType); } } ``` ### Location Access device location for location-based features: ```typescript // Get current position const position = await sdk.location?.getCurrentPosition(); console.log(position.latitude, position.longitude); // Watch position const unsubscribe = sdk.location?.watchPosition((position) => { console.log('Position updated:', position); }); // Stop watching unsubscribe(); ``` ### Clipboard Copy and paste functionality: ```typescript // Copy to clipboard await sdk.clipboard?.copy('0x1234567890abcdef'); // Paste from clipboard const text = await sdk.clipboard?.paste(); console.log('Clipboard content:', text); ``` ## App Context Access information about the user and platform: ```typescript > Note: `sdk.getContext()` is not exposed on `window.movementSDK` yet. const context = await sdk.getContext(); console.log(context.user.address); // User's wallet address console.log(context.user.verified); // KYC status console.log(context.platform.os); // 'ios' or 'android' console.log(context.platform.version); // OS version console.log(context.app.id); // Your app ID console.log(context.app.name); // Your app name // Check feature availability if (context.features.camera) { // Camera is available } ``` ## Security Features The SDK includes built-in security measures: ### Rate Limiting Automatically prevents spam: ```typescript // SDK will throw error if rate limit exceeded try { await sdk.sendTransaction(payload); } catch (error) { if (error.message.includes('rate limit')) { alert('Too many requests. Please wait a moment.'); } } ``` ### Transaction Validation Validates payloads before sending: ```typescript // Invalid transactions are caught before reaching the blockchain await sdk.sendTransaction({ function: 'invalid::format', // ❌ Will throw error arguments: ['bad_address'], // ❌ Will throw error type_arguments: [], }); ``` ### Custom Security Config Configure security settings: ```typescript import { getMovementSDK, createSecurityManager } from '@movement-labs/miniapp-sdk'; const sdk = getMovementSDK({ maxTransactionAmount: '500000000', // 5 MOVE max rateLimitWindow: 60000, // 1 minute maxRequestsPerWindow: 20, // 20 requests per minute strictMode: true, // Enable strict validation }); ``` ## React Hooks Use convenient React hooks for common operations: ```typescript import { useMovementSDK, useMovementAccount } from '@movement-labs/miniapp-sdk'; // Main SDK hook const { sdk, isConnected, address, isLoading, error, connect, sendTransaction } = useMovementSDK(); // Account hook const { account, isConnected, isLoading, error } = useMovementAccount(); ``` ## Navigation Control app navigation: ```typescript // Open external URL await sdk.openUrl?.('https://example.com', 'external'); // Open in app browser await sdk.openUrl?.('https://example.com', 'in-app'); // Close mini app await sdk.close?.(); ``` ## Next Steps * **[API Reference](/reference/sdk-api)** - Complete API documentation * **[Security Best Practices](/guides/security)** - Secure your app * **\[Examples]https://github.com/movementlabsxyz/miniapp-examples** - See the SDK in action --- --- url: /guides/security.md --- # Security Best Practices Building secure mini apps is critical. The SDK includes built-in protections, but you should follow these guidelines to ensure your app is secure. *** ## Built-in Security Features ### Rate Limiting The SDK automatically rate-limits requests to prevent abuse: * **30 requests per minute** (configurable) * **1-minute window** (configurable) * Applies to: transactions, connections, message signing ```typescript // If rate limit is exceeded, SDK throws an error try { await sdk.sendTransaction(payload); } catch (error) { if (error.message.includes('rate limit')) { // Handle rate limit error console.log('Too many requests. Please slow down.'); } } ``` ### Transaction Validation All transactions are validated before sending: * **Function format** - Must be `0x...::module::function` * **Address validation** - Checks valid Movement address format * **Amount limits** - Prevents excessive transfers (default: 10,000 MOVE) * **Type safety** - Ensures arguments match expected types ```typescript // ❌ Invalid - will be rejected await sdk.sendTransaction({ function: 'bad format', arguments: ['not_an_address', '999999999999999'], type_arguments: [], }); // ✅ Valid await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: ['0x1234...', '1000000'], type_arguments: [], }); ``` ### Replay Attack Prevention Message signing includes nonce validation: ```typescript // SDK automatically generates nonce if not provided const signature = await sdk.signMessage({ message: 'Verify ownership', // nonce is auto-generated and validated }); // Or provide your own const signature = await sdk.signMessage({ message: 'Verify ownership', nonce: `${Date.now()}-${Math.random()}`, }); ``` ### Content Security Policy The SDK sets CSP headers to prevent XSS attacks: ``` default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' https://*.movementlabs.xyz https://*.movementlabs.xyz; ``` *** ## Configure Security Customize security settings for your app: ```typescript import { getMovementSDK } from '@movement-labs/miniapp-sdk'; const sdk = getMovementSDK({ // Maximum transaction amount (in octas) maxTransactionAmount: '500000000', // 5 MOVE // Rate limiting rateLimitWindow: 60000, // 1 minute maxRequestsPerWindow: 20, // 20 requests per minute // Allowed origins (for iframe embedding) allowedOrigins: [ 'https://your-domain.com', 'https://*.your-domain.com', ], // Enable Content Security Policy enableCSP: true, // Strict mode (extra validation) strictMode: true, }); ``` *** ## Transaction Security ### Validate User Input Always validate user input before creating transactions: ```typescript function sendTokens(recipient: string, amount: string) { // ❌ BAD: No validation await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [], }); // ✅ GOOD: Validate input if (!recipient.match(/^0x[a-fA-F0-9]{1,64}$/)) { throw new Error('Invalid recipient address'); } const amountNum = parseInt(amount); if (isNaN(amountNum) || amountNum <= 0) { throw new Error('Invalid amount'); } if (amountNum > 1000000000) { // 10 MOVE throw new Error('Amount exceeds limit'); } await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [], }); } ``` ### Confirm High-Value Transactions Always confirm large transactions with the user: ```typescript async function sendLargeAmount(recipient: string, amount: string) { const amountInMove = parseInt(amount) / 100000000; if (amountInMove > 10) { const confirmed = window.confirm( `Send ${amountInMove} MOVE to ${recipient.slice(0, 10)}...?` ); if (!confirmed) { return; } } await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipient, amount], type_arguments: [], }); } ``` ### Use Biometric Auth for Sensitive Actions Require biometric authentication for critical operations: ```typescript async function withdrawFunds(amount: string) { // Check if biometrics are available const available = await sdk.biometric?.isAvailable(); if (available) { const result = await sdk.biometric?.authenticate({ promptMessage: 'Authenticate to withdraw funds', cancelText: 'Cancel', }); if (!result.success) { throw new Error('Authentication failed'); } } // Proceed with transaction await sdk.sendTransaction({ function: '0x1::coin::transfer', arguments: [recipient, amount], type_arguments: ['0x1::aptos_coin::MovementCoin'], }); } ``` *** ## Data Security ### Never Store Private Keys **Never** store private keys, seed phrases, or sensitive data: ```typescript // ❌ NEVER DO THIS localStorage.setItem('privateKey', userPrivateKey); await sdk.storage?.set('seedPhrase', seedPhrase); // ✅ Store only non-sensitive data await sdk.storage?.set('preferences', JSON.stringify({ theme: 'dark', language: 'en', })); ``` ### Sanitize User Input Clean user input before displaying or storing: ```typescript function sanitizeInput(input: string): string { // Remove HTML tags return input.replace(/<[^>]*>/g, ''); } function displayUsername(username: string) { const clean = sanitizeInput(username); document.getElementById('username').textContent = clean; } ``` ### Encrypt Sensitive Data For sensitive data that must be stored locally: ```typescript async function storeEncryptedData(key: string, data: any) { const encrypted = await encryptData(data, userPublicKey); await sdk.storage?.set(key, encrypted); } async function retrieveEncryptedData(key: string) { const encrypted = await sdk.storage?.get(key); if (!encrypted) return null; return await decryptData(encrypted, userPrivateKey); } ``` *** ## Network Security ### Use HTTPS Only Always use HTTPS for API calls: ```typescript // ❌ BAD: HTTP const response = await fetch('http://api.example.com/data'); // ✅ GOOD: HTTPS const response = await fetch('https://api.example.com/data'); ``` ### Validate API Responses Never trust external API responses: ```typescript async function fetchPrice(token: string) { const response = await fetch(`https://api.example.com/price/${token}`); const data = await response.json(); // ✅ Validate response if (typeof data.price !== 'number' || data.price < 0) { throw new Error('Invalid price data'); } return data.price; } ``` *** ## Error Handling ### Don't Expose Sensitive Information Handle errors carefully to avoid leaking information: ```typescript try { await sdk.sendTransaction(payload); } catch (error) { // ❌ BAD: Exposes internal details alert(error.stack); // ✅ GOOD: Generic user-friendly message alert('Transaction failed. Please try again.'); console.error('Transaction error:', error); // Log for debugging } ``` *** ## Audit Logging Log security-related events for monitoring: ```typescript function logSecurityEvent(event: { type: string; details: string; userId?: string; }) { // Send to your backend fetch('/api/security-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timestamp: new Date().toISOString(), ...event, }), }); } // Log suspicious activity if (failedAttempts > 5) { logSecurityEvent({ type: 'suspicious_activity', details: 'Multiple failed login attempts', userId: address, }); } ``` *** ## Security Checklist Before launching your mini app: * \[ ] All user input is validated and sanitized * \[ ] High-value transactions require confirmation * \[ ] Sensitive actions use biometric authentication * \[ ] No private keys or seeds are stored * \[ ] All API calls use HTTPS * \[ ] API responses are validated * \[ ] Errors don't expose sensitive information * \[ ] Security events are logged * \[ ] Rate limiting is configured appropriately * \[ ] Transaction amount limits are set * \[ ] Content Security Policy is enabled *** ## Reporting Vulnerabilities If you discover a security vulnerability: 1. **DO NOT** open a public issue 2. Email security@movementlabs.xyz 3. Include detailed reproduction steps 4. We'll respond within 48 hours *** ## Additional Resources * **[OWASP Mobile Security](https://owasp.org/www-project-mobile-security/)** - Mobile security best practices * **[Web3 Security](https://github.com/Consensys/smart-contract-best-practices)** - Smart contract security * **[SDK Source Code](https://github.com/movementlabsxyz/miniapp-sdk)** - Review security implementations --- --- url: /commands/send-transaction.md --- # Send Transaction Execute blockchain transactions with user approval. Supports token transfers, smart contract interactions, and complex on-chain operations. ## Basic Usage ```typescript const result = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [recipientAddress, '1000000'], // 0.01 MOVE (8 decimals) type_arguments: [] }); console.log('Transaction hash:', result.hash); ``` ## Parameters ### `function` Fully qualified module function in format: `address::module::function` ```typescript function: '0x1::aptos_account::transfer' ``` ### `arguments` Array of function arguments. Types must match the Move function signature. ```typescript arguments: [ '0x123...', // address '1000000', // u64 as string true // bool ] ``` ### `type_arguments` Generic type arguments for the function. ```typescript type_arguments: ['0x1::aptos_coin::AptosCoin'] ``` ### Display Metadata Additional fields for transaction confirmation UI: ```typescript { title: 'Send MOVE', description: 'Transfer 0.01 MOVE', toAddress: recipientAddress, amount: '0.01' } ``` ## Return Value Returns a `Promise`: ```typescript { hash: string; // Transaction hash success: boolean; // Whether transaction succeeded version?: string; // Ledger version (if successful) vmStatus?: string; // VM execution status } ``` ## Examples ### Token Transfer ```typescript const result = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', '100000000' // 1 MOVE (8 decimals) ], type_arguments: [], // Optional: Display metadata title: 'Send MOVE', description: 'Transfer 1 MOVE to Alice', toAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', amount: '1' }); console.log('Transfer complete:', result.hash); ``` ### NFT Minting ```typescript const result = await sdk.sendTransaction({ function: '0x3::token::mint', arguments: [ collectionName, tokenName, description, imageUri, maxSupply ], type_arguments: [], title: 'Mint NFT', description: `Mint "${tokenName}" NFT` }); ``` ### Coin Transfer (Generic) For transferring coins other than MOVE: ```typescript const result = await sdk.sendTransaction({ function: '0x1::coin::transfer', type_arguments: [ '0x83121c9f9b0527d1f056e21a950d6bf3b9e9e2e8353d0e95ccea726713cbea39::coin::USDC' ], arguments: [ recipientAddress, '1000000' // 1 USDC (6 decimals) ], title: 'Send USDC', description: 'Transfer 1 USDC' }); ``` ### Smart Contract Interaction ```typescript const result = await sdk.sendTransaction({ function: '0xMyContract::game::claim_reward', arguments: [ playerId, rewardAmount, rewardType ], type_arguments: [], title: 'Claim Reward', description: 'Claim 100 game tokens' }); ``` ## Error Handling Always wrap transactions in try-catch: ```typescript try { const result = await sdk.sendTransaction({...}); if (result.success) { // Transaction confirmed await sdk.notify({ title: 'Success!', body: 'Transaction confirmed' }); } } catch (error) { if (error.code === 'USER_REJECTED') { console.log('User cancelled transaction'); } else if (error.code === 'INSUFFICIENT_BALANCE') { console.error('Not enough balance'); } else if (error.code === 'RATE_LIMIT_EXCEEDED') { console.error('Too many transactions, try again later'); } else { console.error('Transaction failed:', error.message); } } ``` ## Transaction Monitoring Wait for transaction confirmation: ```typescript const result = await sdk.sendTransaction({...}); // Option 1: Wait for confirmation const status = await sdk.waitForTransaction(result.hash); console.log('Confirmed!', status); // Option 2: Subscribe to updates sdk.onTransactionUpdate(result.hash, (status) => { if (status.status === 'success') { console.log('Transaction confirmed!'); } else if (status.status === 'failed') { console.error('Transaction failed:', status.error); } }); ``` ## Best Practices ::: tip AMOUNTS Always pass amounts as strings to avoid precision loss with large numbers. ```typescript // ✅ Good arguments: ['1000000000'] // ❌ Bad - may lose precision arguments: [1000000000] ``` ::: ::: tip VALIDATION Validate addresses and amounts before sending: ```typescript if (!recipientAddress.startsWith('0x')) { throw new Error('Invalid address format'); } const amount = parseFloat(amountInput); if (isNaN(amount) || amount <= 0) { throw new Error('Invalid amount'); } ``` ::: ::: warning USER FEEDBACK Always provide loading states and feedback: ```typescript setIsLoading(true); try { const result = await sdk.sendTransaction({...}); showSuccessMessage('Transaction sent!'); } catch (error) { showErrorMessage(error.message); } finally { setIsLoading(false); } ``` ::: ::: danger SECURITY Never expose private keys or seed phrases in your app code. The SDK handles all signing securely. ::: ## Rate Limits * **300 transactions per day** per user * Exceeding limit returns error code `429` * Resets at midnight UTC ## Common Errors | Error Code | Description | Solution | |------------|-------------|----------| | `USER_REJECTED` | User cancelled transaction | Handle gracefully, don't retry | | `INSUFFICIENT_BALANCE` | Not enough balance | Check balance before sending | | `INVALID_SIGNATURE` | Signature verification failed | Ensure correct transaction format | | `RATE_LIMIT_EXCEEDED` | Too many transactions | Wait before retrying | | `NETWORK_ERROR` | Connection issue | Retry with exponential backoff | ## Related * Sign Message *(Coming Soon)* - Sign messages without transactions * Multi-Agent Transactions *(Coming Soon)* - Multiple signers * Fee Payer Transactions *(Coming Soon)* - Sponsored gas fees * Batch Transactions *(Coming Soon)* - Multiple transactions at once --- --- url: /examples/social.md --- # Social App A decentralized social media mini app demonstrating on-chain posts, global feeds, and real-time updates using Move contracts and view functions. ## Overview The Social App demonstrates: * On-chain post storage using Move contracts * Global feed aggregation * View functions for reading blockchain data * Transaction-based post creation * Reaction system with on-chain state * Real-time feed updates ## Repository The Social app is available in the `mini-app-social` directory of the [mini-app-examples](https://github.com/movementlabsxyz/miniapp-examples) repository. ## Quick Start ### Prerequisites * Node.js 18+ and npm/pnpm * Movement Everything (ME) app installed on your mobile device * Deployed Move contract (or use the testnet deployment) ### Setup 1. **Install dependencies:** ```bash cd mini-app-social npm install # or pnpm install ``` 2. **Configure contract address:** Update `constants.ts` with your deployed contract address: ```typescript export const SOCIAL_MODULE_ADDRESS = '0x...'; ``` Or use the testnet deployment: ``` 0xe08098cd9db04d38b7962a4e2653c2b7362477943c47e976ed55c624b759580f ``` 3. **Run the development server:** ```bash npm run dev # or pnpm dev ``` 4. **Open in ME app:** Open the Movement Everything (ME) app on your mobile device and navigate to this mini app. ## Features ### On-Chain Posts * **Create posts** - Store messages permanently on the blockchain * **User feeds** - Each user has their own feed of posts * **Global feed** - Aggregated feed of all posts across all users * **Reactions** - React to posts with on-chain state ### View Functions The app uses `sdk.view()` to read on-chain data without requiring wallet connection: * `get_global_count()` - Total number of posts in global feed * `get_global_pointer(index)` - Get pointer to a post in global feed * `get_post(owner, index)` - Get a specific post by owner and index * `get_post_count(owner)` - Get number of posts for a user ### Transaction Functions * `post(content)` - Create a new on-chain post * `react(owner, index)` - React to a specific post ## Architecture ### Move Contract The `social.move` contract provides: * **Feed struct** - Stores posts for each user * **GlobalIndex** - Aggregates pointers to all posts * **Post struct** - Contains author, content, timestamp, reactions ### Frontend Flow 1. **Load feed** - Use `sdk.view()` to fetch global feed count 2. **Fetch posts** - Iterate through global pointers and fetch each post 3. **Create post** - Use `sdk.sendTransaction()` to call `post()` function 4. **React** - Use `sdk.sendTransaction()` to call `react()` function ### Key Files * `app/page.tsx` - Main social feed UI and logic * `constants.ts` - Contract address configuration * `move/sources/social.move` - On-chain social contract ## Contract Address The social contract is deployed on testnet at: ``` 0xe08098cd9db04d38b7962a4e2653c2b7362477943c47e976ed55c624b759580f ``` ## SDK Features Used * `view()` - Read posts and feed data from contract * `sendTransaction()` - Create posts and reactions * `getUserInfo()` - Get connected wallet address * `isInstalled()` - Check if running in ME app * `ready()` - Wait for SDK initialization ## Use Cases * **Social apps** - Learn how to build decentralized social networks * **On-chain data** - See how to store and retrieve data on blockchain * **View functions** - Understand read-only blockchain queries * **Feed aggregation** - Learn patterns for aggregating on-chain data * **Real-time updates** - See how to refresh data without transactions ## Code Examples ### Reading Posts with View Functions ```typescript // Get global feed count const count = await sdk.view({ function: `${SOCIAL_MODULE_ADDRESS}::social::get_global_count`, functionArguments: [] }); // Get a specific post const post = await sdk.view({ function: `${SOCIAL_MODULE_ADDRESS}::social::get_post`, functionArguments: [ownerAddress, postIndex] }); ``` ### Creating a Post ```typescript const result = await sdk.sendTransaction({ function: `${SOCIAL_MODULE_ADDRESS}::social::post`, functionArguments: [message] }); ``` ### Reacting to a Post ```typescript const result = await sdk.sendTransaction({ function: `${SOCIAL_MODULE_ADDRESS}::social::react`, functionArguments: [postOwner, postIndex] }); ``` ## Troubleshooting **"SDK not available"** - Make sure you're running inside the ME app **"View function failed"** - Check that the contract address is correct and the contract is deployed **"Posts not loading"** - Verify the contract address in `constants.ts` matches your deployment **"Transaction failed"** - Ensure your wallet is connected and has testnet MOVE tokens ## Next Steps * Explore the [SDK API Reference](/reference/sdk-api) for detailed method documentation * Learn about [View Functions](/commands/view) for reading on-chain data * Check out the [Scaffold App](/examples/scaffold) for more SDK examples * Read about [Send Transaction](/commands/send-transaction) for on-chain operations --- --- url: /examples/starter.md --- # Starter App A minimal counter mini app starter built with Next.js and Movement SDK. Perfect for developers who want a clean, simple starting point for building their own mini apps. ## Overview The Starter App demonstrates: * Simple on-chain counter with Move smart contract * Clean, minimal UI with lots of whitespace * SDK initialization and wallet connection * Entry functions (increment, reset) and view functions * Movement Design System integration ## Repository A starter app featuring the Movement Design System is available in the `mini-app-starter-ds` directory of the [mini-app-examples](https://github.com/moveindustries/mini-app-examples) repository. ## Quick Start ```bash cd mini-app-starter-ds npm install npm run dev ``` Open the Movement Everything (ME) app on your mobile device and navigate to this mini app. ## Features ### Move Smart Contract The app includes a simple Move contract with: * `initialize()` - One-time setup to create counter resource * `increment()` - Add 1 to the counter * `reset()` - Reset counter to 0 * `get_value(address)` - View function to read counter value ### Frontend Features * **Minimal Design** - Clean UI with lots of whitespace, inspired by create-vite-app * **Wallet Integration** - Automatic SDK initialization and wallet connection * **Error Handling** - Clear error messages and loading states * **Haptic Feedback** - Tactile feedback on button interactions * **Notifications** - Success messages after transactions ## Project Structure ``` mini-app-starter-ds/ ├── move/ │ ├── sources/ │ │ └── counter.move # Move smart contract │ └── Move.toml ├── src/ │ └── app/ │ ├── page.tsx # Main counter page │ ├── layout.tsx # App layout │ └── globals.css # Styles with Movement Design System ├── constants.ts # Contract address configuration ├── package.json └── README.md ``` ## Getting Started 1. **Deploy the Move contract:** ```bash cd move # Deploy using your preferred Move tooling ``` 2. **Update the contract address:** Edit `constants.ts` and set `COUNTER_MODULE_ADDRESS` to your deployed contract address. 3. **Run the app:** ```bash npm install npm run dev ``` 4. **Test in Movement wallet:** * Connect your wallet * Click "Initialize Counter" (one-time setup) * Use "Increment" and "Reset" buttons ## Code Highlights ### SDK Initialization The app automatically initializes the SDK and connects to the wallet: ```typescript useEffect(() => { const bootstrap = async () => { const instance = window.movementSDK; if (instance) { await instance.ready(); // Get user info and subscribe to wallet changes } }; bootstrap(); }, []); ``` ### Calling Entry Functions Send transactions to call Move entry functions: ```typescript const result = await sdk.sendTransaction({ function: `${COUNTER_MODULE_ADDRESS}::counter::increment`, type_arguments: [], arguments: [], title: "Increment Counter", description: "Add 1 to your counter", }); ``` ### Reading View Functions Query on-chain state without transactions: ```typescript const result = await sdk.view({ function: `${COUNTER_MODULE_ADDRESS}::counter::get_value`, type_arguments: [], function_arguments: [address], }); const value = Array.isArray(result) ? result[0] : result; ``` ## Use Cases * **Learning** - Understand the basics of Movement mini apps * **Starting Point** - Use as a template for your own mini app * **Reference** - See how to structure a simple app with Move contracts * **Customization** - Modify the counter to build your own features ## Next Steps * Explore the [SDK API Reference](/reference/sdk-api) for detailed method documentation * Check out other examples: [Scaffold App](/examples/scaffold), [Social App](/examples/social) * Read the [Quick Start guide](/quick-start/) to learn more about building mini apps * Review the [Move contract](/examples/starter#move-smart-contract) to understand on-chain logic --- --- url: /quick-start/testing.md --- # Testing Test your mini app locally before deploying to production. ## Overview Testing mini apps requires running them inside the Movement wallet app, since the SDK is only available in that environment. This guide covers local testing, tunneling, and debugging. ## Quick Setup 1. **Start your dev server** 2. **Expose it to your network** 3. **Open in Movement app** Let's go through each step: ## 1. Start Development Server Start your local development server: ::: code-group ```bash [Next.js] npm run dev # Running on http://localhost:3000 ``` ```bash [Vanilla] npx serve . # Running on http://localhost:3000 ``` ```bash [Unity] # Build for WebGL in Unity # Then serve the build folder: npx serve ./build ``` ::: ## 2. Enable Developer Mode In the Movement app: 1. Open **Settings** 2. Scroll to **Developer Options** 3. Toggle **Developer Mode** ON 4. You'll see **Mini App Testing** section appear ## 3. Access Your App You have three options to access your local app from your phone: ### Option A: Network URL (Same WiFi) If your phone and computer are on the **same WiFi network**: 1. Find your computer's local IP: ::: code-group ```bash [macOS/Linux] ifconfig | grep "inet " | grep -v 127.0.0.1 # Look for something like: 192.168.1.x ``` ```bash [Windows] ipconfig # Look for IPv4 Address under your WiFi adapter ``` ::: 2. In Movement app → **Mini App Testing**, enter: ``` http://192.168.1.x:3000 ``` 3. Tap **Launch App** ### Option B: Tunneling (Recommended) Use a tunnel service to expose your local server to the internet: #### ngrok (Most Popular) ```bash # Install npm install -g ngrok # Create tunnel ngrok http 3000 ``` You'll get a URL like: ``` https://abc123.ngrok.io ``` Enter this in Movement app → **Mini App Testing** → **Launch App** ::: tip NGROK ADVANTAGES * Works from anywhere (no WiFi required) * HTTPS by default (secure) * Free tier available * Persistent URLs with paid plan ::: #### Cloudflare Tunnel (Free, No Signup) ```bash # Install npm install -g cloudflared # Create tunnel cloudflared tunnel --url http://localhost:3000 ``` #### LocalTunnel (Simple Alternative) ```bash npx localtunnel --port 3000 ``` ### Option C: QR Code Generate a QR code for quick access: ```bash # Install qrcode-terminal npm install -g qrcode-terminal # Generate QR code qrcode-terminal "http://192.168.1.x:3000" ``` Scan the QR code with your phone's camera, then open in Movement app. ### Option D: Staging Deployment Deploy to a staging environment for persistent HTTPS URLs: #### Vercel (Free) ```bash # Install Vercel CLI npm install -g vercel # Deploy vercel # Or deploy with production build vercel --prod ``` You'll get a URL like: ``` https://your-app.vercel.app ``` Enter this in Movement app → **Mini App Testing** → **Launch App** ::: tip STAGING ADVANTAGES * ✅ Permanent HTTPS URL (no expiration) * ✅ Works from anywhere * ✅ Share with team members * ✅ Production-like environment * ✅ Free tier available * ✅ Automatic deployments on git push * ✅ Preview deployments for PRs ::: #### Other Options * **Netlify**: `npm install -g netlify-cli && netlify deploy` * **Railway**: Connect GitHub repo for auto-deploy * **Render**: Connect GitHub repo for auto-deploy All provide free HTTPS staging URLs perfect for testing. ## Testing Workflow ### Basic Workflow 1. **Make changes** to your code 2. **Save** - dev server auto-reloads 3. **Refresh** mini app in Movement wallet * Pull down to refresh * Or tap the reload button ### Advanced Workflow Use hot module replacement for instant updates: ```javascript // vite.config.js or next.config.js export default { server: { hmr: { host: '192.168.1.x', // Your local IP port: 3000 } } } ``` Now changes appear instantly without manual refresh! ## Debugging ### Browser DevTools (Desktop) For Next.js apps, you can debug in desktop browser first: ```bash npm run dev # Open http://localhost:3000 in Chrome ``` Use Chrome DevTools to: * Inspect console logs * Debug JavaScript * Test UI responsive modes * Profile performance ::: warning SDK NOT AVAILABLE `window.movementSDK` will be `undefined` in desktop browser. Use SDK detection: ```typescript if (!window.movementSDK) { // Mock SDK for desktop testing window.movementSDK = { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', isInstalled: () => true, sendTransaction: async (payload) => { console.log('Mock transaction:', payload); return { hash: '0x123...', success: true }; } }; } ``` ::: ### Mobile DevTools (Eruda) Add Eruda for mobile debugging: ```html ``` Now shake your device or tap the Eruda icon to open mobile DevTools! Features: * ✅ Console logs * ✅ Network requests * ✅ Local storage inspection * ✅ DOM inspector * ✅ Source code viewer ### React DevTools For React apps, add React DevTools: ```bash npm install --save-dev @react-devtools/core # Start standalone DevTools npx react-devtools ``` Then in your app: ```javascript // Only in development if (process.env.NODE_ENV === 'development') { import('@react-devtools/core').then(({ connectToDevTools }) => { connectToDevTools(); }); } ``` ### Console Logging Add detailed logs for debugging: ```typescript // Development logging utility const log = { info: (...args) => { if (process.env.NODE_ENV === 'development') { console.log('[INFO]', new Date().toISOString(), ...args); } }, error: (...args) => { console.error('[ERROR]', new Date().toISOString(), ...args); }, transaction: (payload) => { console.log('[TX]', { function: payload.function, args: payload.arguments, timestamp: new Date().toISOString() }); } }; // Usage log.info('App initialized'); log.transaction(txPayload); ``` ## Testing Checklist Before deploying, verify: ### Functionality * \[ ] App loads without errors * \[ ] SDK is detected correctly * \[ ] Wallet connection works * \[ ] Transactions can be sent * \[ ] Error states are handled * \[ ] Loading states are shown * \[ ] Success feedback is clear ### Performance * \[ ] Loads in < 3 seconds * \[ ] No console errors * \[ ] Images are optimized * \[ ] No memory leaks * \[ ] Smooth animations ### Compatibility * \[ ] Works on iOS * \[ ] Works on Android * \[ ] Portrait mode works * \[ ] Landscape mode works (if applicable) * \[ ] Dark mode supported * \[ ] All screen sizes (iPhone SE to iPad) ### User Experience * \[ ] Clear error messages * \[ ] Intuitive navigation * \[ ] Responsive touch targets (min 44x44px) * \[ ] Appropriate haptic feedback * \[ ] Proper loading indicators ## Common Issues ### SDK is undefined **Problem:** `window.movementSDK` is undefined **Solutions:** * ✅ Ensure you're testing in Movement app * ✅ Check Developer Mode is enabled * ✅ Try refreshing the mini app * ✅ Check console for errors ### Can't connect from phone **Problem:** Can't access `http://192.168.1.x:3000` **Solutions:** * ✅ Ensure phone and computer on same WiFi * ✅ Check firewall isn't blocking port 3000 * ✅ Use `0.0.0.0` instead of `localhost`: ```bash npm run dev -- --host 0.0.0.0 ``` * ✅ Try tunneling instead (ngrok) ### App not refreshing **Problem:** Changes don't appear after refresh **Solutions:** * ✅ Hard refresh: Pull down and release * ✅ Clear cache in Settings → Developer → Clear Cache * ✅ Restart dev server * ✅ Check HMR configuration ### Slow loading **Problem:** App takes >5 seconds to load **Solutions:** * ✅ Optimize images (use Next.js Image) * ✅ Enable code splitting * ✅ Reduce bundle size * ✅ Use production build for testing: ```bash npm run build npm run start ``` ### Transactions failing **Problem:** All transactions fail in testing **Solutions:** * ✅ Check you're on testnet, not mainnet * ✅ Ensure wallet has testnet MOVE * ✅ Verify transaction payload format * ✅ Check console for specific error codes ## Environment Configuration ### Development vs Production Use environment variables: ```bash # .env.local (development) NEXT_PUBLIC_MOVEMENT_NETWORK=testnet NEXT_PUBLIC_API_URL=https://testnet-api.movement.xyz NEXT_PUBLIC_DEBUG=true # .env.production NEXT_PUBLIC_MOVEMENT_NETWORK=mainnet NEXT_PUBLIC_API_URL=https://api.movement.xyz NEXT_PUBLIC_DEBUG=false ``` Access in code: ```typescript const network = process.env.NEXT_PUBLIC_MOVEMENT_NETWORK; const isDebug = process.env.NEXT_PUBLIC_DEBUG === 'true'; if (isDebug) { console.log('Debug mode enabled'); } ``` ### Testing on Testnet Always test on testnet first: ```typescript const sdk = window.movementSDK; // Check network if (sdk.network === 'mainnet') { console.warn('⚠️ Running on MAINNET'); } else { console.log('✅ Running on testnet'); } // Get testnet MOVE // Visit: https://faucet.movement.xyz ``` ## Automated Testing ### Unit Tests (Jest) ```typescript // hooks/__tests__/useMovementSDK.test.ts import { renderHook } from '@testing-library/react'; import { useMovementSDK } from '../useMovementSDK'; // Mock SDK global.window.movementSDK = { isInstalled: () => true, address: '0x123...', sendTransaction: jest.fn() }; test('should return SDK instance', () => { const { result } = renderHook(() => useMovementSDK()); expect(result.current.sdk).toBeDefined(); expect(result.current.isConnected).toBe(true); }); ``` ### Integration Tests (Playwright) ```typescript // tests/app.spec.ts import { test, expect } from '@playwright/test'; test('should connect wallet', async ({ page }) => { await page.goto('http://localhost:3000'); const address = await page.locator('[data-testid="wallet-address"]'); await expect(address).toBeVisible(); await expect(address).toContainText('0x'); }); ``` ## Next Steps * **[Responses →](/quick-start/responses)** - Handle test results * **[Design Guidelines →](/guidelines/design)** - Polish your UI * **Publishing** *(Coming Soon)* - Deploy your app ## Quick Reference | Task | Command | |------|---------| | Start dev server | `npm run dev` | | Create tunnel | `ngrok http 3000` | | Deploy to staging | `vercel` | | Find local IP | `ifconfig \| grep "inet "` | | Generate QR code | `qrcode-terminal "http://..."` | | Mobile DevTools | Add Eruda script tag | | Check SDK | `window.movementSDK.isInstalled()` | --- --- url: /examples/unity.md --- # Unity WebGL Build blockchain games with Unity and deploy them as Movement Mini Apps. ## Prerequisites * Unity 2021.3 LTS or newer * Basic knowledge of Unity and C# * Unity WebGL build support installed ## Setup ### 1. Create Unity Project 1. Open Unity Hub 2. Create new 3D or 2D project 3. Name it `MyMovementGame` ### 2. Install Movement SDK Download the Movement Unity SDK: * [MovementSDK.cs](https://github.com/movementlabsxyz/unity-sdk/blob/main/MovementSDK.cs) * [MovementBridge.jslib](https://github.com/movementlabsxyz/unity-sdk/blob/main/MovementBridge.jslib) Place files in your project: ``` Assets/ Scripts/ MovementSDK.cs Plugins/ WebGL/ MovementBridge.jslib ``` ### 3. Configure WebGL Build Settings 1. **File → Build Settings** 2. Select **WebGL** platform 3. Click **Switch Platform** 4. **Player Settings**: * Disable **WebGL 2.0** (use WebGL 1.0 for better compatibility) * Set **Compression Format** to **Disabled** for testing * Enable **Run in Background** ## SDK Integration ### Basic Setup Create `GameManager.cs`: ```csharp using UnityEngine; using UnityEngine.UI; public class GameManager : MonoBehaviour { public Text walletAddressText; public Text statusText; public Button connectButton; private void Start() { // Initialize Movement SDK MovementSDK.Instance.OnWalletConnected += HandleWalletConnected; MovementSDK.Instance.OnTransactionComplete += HandleTransaction; // Auto-connect if in Movement app if (MovementSDK.Instance.IsInMovementApp()) { MovementSDK.Instance.Connect(); } } private void HandleWalletConnected(string address) { walletAddressText.text = $"Wallet: {address}"; statusText.text = "Connected!"; } private void HandleTransaction(string hash) { statusText.text = $"Transaction sent: {hash}"; } public void OnConnectClicked() { MovementSDK.Instance.Connect(); } public void OnSendRewardClicked() { // Send 1 MOVE token as reward MovementSDK.Instance.SubmitTransaction( "0x1::aptos_account::transfer", new string[] { "0xRECEIVER_ADDRESS", "100000000" }, // 1 MOVE new string[] { } ); } } ``` ### Movement SDK API #### Connect Wallet ```csharp MovementSDK.Instance.Connect(); ``` #### Check Connection ```csharp if (MovementSDK.Instance.IsConnected) { string address = MovementSDK.Instance.WalletAddress; Debug.Log($"Connected: {address}"); } ``` #### Submit Transaction ```csharp MovementSDK.Instance.SubmitTransaction( function: "0x1::coin::transfer", arguments: new string[] { recipientAddress, amount }, typeArguments: new string[] { "0x1::aptos_coin::MovementCoin" } ); ``` #### Sign Message ```csharp MovementSDK.Instance.SignMessage("Game High Score: 1000"); ``` ## Example: Racing Game with Rewards ```csharp using UnityEngine; public class RacingGame : MonoBehaviour { public int currentScore = 0; public int highScore = 0; private bool hasClaimedReward = false; private void Start() { MovementSDK.Instance.OnWalletConnected += OnWalletReady; } private void OnWalletReady(string address) { Debug.Log($"Player wallet: {address}"); LoadPlayerData(); } public void OnRaceComplete(int score) { currentScore = score; if (score > highScore) { highScore = score; SaveHighScore(); // Reward player for new high score if (!hasClaimedReward) { ClaimReward(); } } } private void SaveHighScore() { // Sign high score on-chain for verification string message = $"HighScore:{highScore}:Timestamp:{System.DateTime.UtcNow}"; MovementSDK.Instance.SignMessage(message); } private void ClaimReward() { // Send reward transaction (0.1 MOVE) MovementSDK.Instance.SubmitTransaction( "0x1::aptos_account::transfer", new string[] { MovementSDK.Instance.WalletAddress, "10000000" // 0.1 MOVE }, new string[] { } ); hasClaimedReward = true; } private void LoadPlayerData() { // Load player's previous high score from PlayerPrefs or blockchain highScore = PlayerPrefs.GetInt("HighScore", 0); } } ``` ## Building and Testing ### Build for WebGL 1. **File → Build Settings** 2. Click **Build** 3. Choose output folder: `Build/WebGL` 4. Wait for build to complete ### Test Locally Option 1: Use Unity's built-in server (after build completes, click "Build and Run") Option 2: Use a local server: ```bash cd Build/WebGL npx serve . ``` ### Test in Movement Everything App 1. Open Movement Everything 2. Settings → Developer Mode → ON 3. Mini App Testing section 4. Enter your local URL (e.g., `http://localhost:3000`) 5. Click "Test App" ## Deployment ### Optimize Build Before deploying: 1. **Player Settings → Publishing Settings**: * Enable **Brotli** compression * Set **Code Optimization** to **Runtime Speed** * Disable **Development Build** 2. **Build Settings**: * Uncheck **Development Build** * Check **Enable Exceptions** to **None** ### Deploy to Hosting #### Vercel ```bash cd Build/WebGL vercel deploy ``` #### Netlify ```bash cd Build/WebGL netlify deploy --prod ``` #### GitHub Pages ```bash # Push Build/WebGL contents to gh-pages branch ``` ## Performance Tips 1. **Optimize Assets**: * Compress textures * Use asset bundles for large games * Reduce polygon count on models 2. **WebGL Settings**: * Keep builds under 50MB when possible * Use texture compression * Enable GPU instancing 3. **Mobile Optimization**: * Target 30-60 FPS * Test on mobile browsers * Minimize particle effects ## Example Projects * [Movement Racer](https://github.com/movementlabsxyz/unity-racer) - Racing game with leaderboards * [Coin Collector](https://github.com/movementlabsxyz/unity-coin-collector) - Simple arcade game ## Troubleshooting **Build fails**: Ensure WebGL build support is installed in Unity Hub **SDK not detected**: Check that MovementBridge.jslib is in `Assets/Plugins/WebGL/` **Transactions fail**: Verify wallet is connected before calling SDK methods ## Next Steps * Read the [Unity SDK API Reference](/reference/sdk-api) * Join [Movement Discord](https://discord.gg/movement) for help * Share your game in the community! --- --- url: /examples/vanilla.md --- # Vanilla JavaScript Build Movement Mini Apps with pure JavaScript - no framework needed. ## Prerequisites * Basic HTML, CSS, and JavaScript knowledge * Text editor (VS Code, Sublime, etc.) * Local development server ## Quick Start ### 1. Create Project Structure ```bash mkdir my-miniapp cd my-miniapp ``` Create these files: ``` my-miniapp/ ├── index.html ├── style.css └── app.js ``` ### 2. Basic HTML Setup `index.html`: ```html My Movement Mini App

Movement Mini App

Connecting to wallet...

``` ### 3. Add Styling `style.css`: ```css * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 600px; margin: 0 auto; } h1 { color: white; text-align: center; margin-bottom: 30px; font-size: 2rem; } .card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .address { font-family: 'Courier New', monospace; font-size: 14px; word-break: break-all; background: #f5f5f5; padding: 12px; border-radius: 8px; margin: 12px 0; } .btn { width: 100%; padding: 14px; background: #667eea; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s; } .btn:hover { background: #5568d3; } .btn:disabled { background: #ccc; cursor: not-allowed; } .status { background: white; border-radius: 16px; padding: 16px; font-size: 14px; display: none; } .status.show { display: block; } .status.success { background: #d4edda; color: #155724; } .status.error { background: #f8d7da; color: #721c24; } ``` ### 4. Implement SDK Integration `app.js`: ```javascript // Check if Movement SDK is available function checkSDK() { return typeof window.movementSDK !== 'undefined'; } // Show/hide UI elements function showElement(id) { document.getElementById(id).style.display = 'block'; } function hideElement(id) { document.getElementById(id).style.display = 'none'; } // Show status message function showStatus(message, type = 'info') { const statusEl = document.getElementById('status'); statusEl.textContent = message; statusEl.className = `status show ${type}`; setTimeout(() => { statusEl.classList.remove('show'); }, 5000); } // Initialize the app async function init() { if (!checkSDK()) { hideElement('connecting'); showElement('not-connected'); return; } const sdk = window.movementSDK; // Check if already connected if (sdk.isConnected) { onWalletConnected(sdk.address); } else { // Try to connect try { const account = await sdk.connect(); onWalletConnected(account.address); } catch (error) { console.error('Connection failed:', error); showStatus('Failed to connect wallet', 'error'); } } } // Handle successful wallet connection function onWalletConnected(address) { hideElement('connecting'); hideElement('not-connected'); showElement('connected'); document.getElementById('wallet-address').textContent = address; // Setup button click handler document.getElementById('send-btn').addEventListener('click', sendTransaction); showStatus('Wallet connected successfully!', 'success'); } // Send a transaction async function sendTransaction() { const sdk = window.movementSDK; const btn = document.getElementById('send-btn'); if (!sdk || !sdk.isConnected) { showStatus('Wallet not connected', 'error'); return; } try { btn.disabled = true; btn.textContent = 'Sending...'; // Example: Transfer 0.01 MOVE const result = await sdk.sendTransaction({ function: '0x1::aptos_account::transfer', arguments: [ '0xRECIPIENT_ADDRESS', // Replace with actual address '1000000' // 0.01 MOVE ], type_arguments: [] }); showStatus(`Transaction sent! Hash: ${result.hash}`, 'success'); } catch (error) { console.error('Transaction error:', error); showStatus(`Transaction failed: ${error.message}`, 'error'); } finally { btn.disabled = false; btn.textContent = 'Send Transaction'; } } // Start the app when page loads document.addEventListener('DOMContentLoaded', init); ``` ## Advanced Example: Token Balance Checker ```javascript async function getBalance() { const sdk = window.movementSDK; if (!sdk || !sdk.isConnected) { return null; } try { // Get APT coin balance const response = await fetch( `https://fullnode.mainnet.movementlabs.xyz/v1/accounts/${sdk.address}/resource/0x1::coin::CoinStore<0x1::aptos_coin::MovementCoin>` ); const data = await response.json(); const balance = parseInt(data.data.coin.value) / 100000000; // Convert from Octas return balance; } catch (error) { console.error('Failed to fetch balance:', error); return null; } } // Display balance async function displayBalance() { const balance = await getBalance(); if (balance !== null) { const balanceEl = document.getElementById('balance'); balanceEl.textContent = `${balance.toFixed(4)} APT`; } } ``` ## Example: Simple Game with Rewards ```html Coin Clicker

Coin Clicker

Score: 0

🪙
``` ## Development ### Run Locally Use any local server: ```bash # Python python3 -m http.server 3000 # Node.js npx serve . # PHP php -S localhost:3000 ``` ### Test in Movement App 1. Open Movement Everything 2. Settings → Developer Mode → ON 3. Mini App Testing section 4. Enter `http://localhost:3000` 5. Click "Test App" ## Deployment ### Build No build step needed! Just upload your files. ### Deploy #### GitHub Pages ```bash git init git add . git commit -m "Initial commit" git branch -M gh-pages git remote add origin https://github.com/username/repo.git git push -u origin gh-pages ``` #### Netlify ```bash # Drag and drop your folder to netlify.com # Or use CLI: netlify deploy --prod ``` #### Vercel ```bash vercel deploy ``` ## Tips 1. **Keep it simple**: Vanilla JS apps load faster 2. **Mobile first**: Design for touch interactions 3. **Test offline**: Handle network errors gracefully 4. **Progressive enhancement**: Work without SDK, enhance when available ## Next Steps * Explore the [SDK API Reference](/reference/sdk-api) * Check out [example projects](https://github.com/movementlabsxyz/miniapp-examples) * Join [Movement Discord](https://discord.gg/movement) --- --- url: /commands/view.md --- # View Function Call read-only Move view functions to fetch on-chain data. View calls are gasless and do not require a connected wallet. ## Basic Usage ```typescript const result = await sdk.view({ function: '0x1::some_module::some_view', function_arguments: [], type_arguments: [] }); ``` ## Parameters ### `function` Fully qualified function name: `address::module::function` ```typescript function: '0xabc::leaderboard::get_leaderboard' ``` ### `function_arguments` Array of function arguments. Use strings for u64 and other large numeric types. ```typescript function_arguments: [ '50' // u64 as string ] ``` ### `type_arguments` Generic type arguments. ```typescript type_arguments: ['0x1::aptos_coin::AptosCoin'] ``` ## Return Value Returns a `Promise` with the decoded result from the Move view function: ```typescript // Result can be: // - A primitive (number-like string, bool, address string) // - An object (struct with named fields) // - An array (vector) – sometimes wrapped like [[...]] ``` ## Examples ### Top 50 Leaderboard ```typescript const result = await sdk.view({ function: '0xabc::leaderboard::get_leaderboard', function_arguments: ['50'], type_arguments: [] }); // Handle wrapped arrays const entries = Array.isArray(result) && Array.isArray(result[0]) ? result[0] : result; ``` ### Read a Struct ```typescript const postRes = await sdk.view({ function: '0xsocial::social::get_post', function_arguments: [owner, `${index}`], type_arguments: [] }); // Unwrap if needed const post = Array.isArray(postRes) ? postRes[0] : postRes; console.log(post.author, post.content); ``` ### Read a Single Value ```typescript const feeRes = await sdk.view({ function: '0xabc::leaderboard::get_game_fee', function_arguments: [], type_arguments: [] }); const feeOctas = Array.isArray(feeRes) ? feeRes[0] : feeRes; const feeMove = Number(feeOctas) / 100_000_000; ``` ## Error Handling Always wrap view calls in try-catch: ```typescript try { const data = await sdk.view({ function: '0xabc::leaderboard::get_leaderboard', function_arguments: ['50'], type_arguments: [] }); // Use data console.log('Leaderboard:', data); } catch (error) { if (error.code === 'FUNCTION_NOT_FOUND') { console.error('View function does not exist'); } else if (error.code === 'INVALID_ARGUMENTS') { console.error('Invalid function arguments'); } else { console.error('View failed:', error.message); } } ``` ## Best Practices ::: tip NO WALLET REQUIRED You can call `view()` before a user connects their wallet. Great for loading public data in splash screens. ::: ::: tip STRINGS FOR NUMBERS Always pass large integers as strings to avoid precision loss in JS. ```typescript // ✅ Good function_arguments: ['100000000'] // ❌ Bad - may lose precision function_arguments: [100000000] ``` ::: ::: tip UNWRAP RESULTS The host app may wrap results in an extra array. Handle both `result` and `[result]` shapes. ```typescript // Safely unwrap possibly-wrapped arrays const unwrap = (val: any) => Array.isArray(val) && val.length === 1 && Array.isArray(val[0]) ? val[0] : val; const entries = unwrap(result); ``` ::: ::: tip CAMEL CASE COMPAT Some hosts also accept `functionArguments` and `typeArguments` (camelCase). Prefer snake\_case for consistency. ::: ## Common Errors | Error Code | Description | Solution | |------------|-------------|----------| | `FUNCTION_NOT_FOUND` | View function does not exist | Check function name and module address | | `INVALID_ARGUMENTS` | Arguments don't match function signature | Verify argument types and count | | `NETWORK_ERROR` | Connection issue | Retry with exponential backoff | | `RATE_LIMIT_EXCEEDED` | Too many requests | Wait before retrying | ## Related * [Send Transaction](./send-transaction.md) – write functions that modify state * [SDK API Reference](../reference/sdk-api.md) – complete API documentation