From a6233f285d5ae8f389a59c876dacc3a9e472399e Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Sat, 10 Jan 2026 06:24:51 +0530 Subject: [PATCH] docs: add developer tutorial series Create comprehensive step-by-step tutorials: - Tutorial 1: Getting Started - node setup, wallet creation, transactions - Tutorial 2: Building a Wallet - React app with state management, encryption - Tutorial 3: Smart Contracts - Rust WASM contracts, token example, testing - Tutorial 4: API Guide - JSON-RPC, WebSocket subscriptions, client building Each tutorial includes working code examples and best practices. --- docs/tutorials/01-getting-started.md | 271 +++++++++ docs/tutorials/02-building-a-wallet.md | 771 +++++++++++++++++++++++++ docs/tutorials/03-smart-contracts.md | 517 +++++++++++++++++ docs/tutorials/04-api-guide.md | 607 +++++++++++++++++++ docs/tutorials/README.md | 88 +++ 5 files changed, 2254 insertions(+) create mode 100644 docs/tutorials/01-getting-started.md create mode 100644 docs/tutorials/02-building-a-wallet.md create mode 100644 docs/tutorials/03-smart-contracts.md create mode 100644 docs/tutorials/04-api-guide.md create mode 100644 docs/tutorials/README.md diff --git a/docs/tutorials/01-getting-started.md b/docs/tutorials/01-getting-started.md new file mode 100644 index 0000000..6e40a9e --- /dev/null +++ b/docs/tutorials/01-getting-started.md @@ -0,0 +1,271 @@ +# Tutorial 1: Getting Started with Synor + +Welcome to Synor, the first post-quantum secure blockchain! This tutorial will help you set up your development environment and make your first transactions. + +## What You'll Learn + +- Set up a local Synor node +- Create a wallet and address +- Send and receive SYNOR tokens +- Query the blockchain + +## Prerequisites + +- Docker and Docker Compose +- Node.js 18+ or Rust 1.70+ +- Basic command line knowledge + +--- + +## Part 1: Running a Local Node + +The easiest way to start is with Docker. + +### Step 1: Clone the Repository + +```bash +git clone https://github.com/synor/synor.git +cd synor +``` + +### Step 2: Start the Local Testnet + +```bash +docker compose -f docker-compose.testnet.yml up -d seed1 +``` + +This starts a single node for development. Wait about 30 seconds for it to initialize. + +### Step 3: Verify the Node is Running + +```bash +curl -X POST http://localhost:17110 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"synor_getBlockCount","params":[],"id":1}' +``` + +You should see: +```json +{"jsonrpc":"2.0","result":1,"id":1} +``` + +--- + +## Part 2: Creating Your First Wallet + +### Option A: Using the CLI + +```bash +# Install the Synor CLI +cargo install synor-cli + +# Generate a new wallet +synor-cli wallet new + +# Output: +# Mnemonic: abandon abandon abandon ... (24 words) +# Address: tsynor1qz232pysw8kezv2f4qxnhdufrlx5cmq78522mpuf8x5qlxu6j8sgcp05get +# +# IMPORTANT: Save your mnemonic phrase securely! +``` + +### Option B: Using JavaScript + +Create a new project: + +```bash +mkdir my-synor-app +cd my-synor-app +npm init -y +npm install @synor/sdk +``` + +Create `wallet.js`: + +```javascript +import { generateMnemonic, createWallet } from '@synor/sdk'; + +async function main() { + // Generate a 24-word mnemonic + const mnemonic = generateMnemonic(24); + console.log('Mnemonic:', mnemonic); + + // Create wallet from mnemonic + const wallet = await createWallet(mnemonic, '', 'testnet'); + console.log('Address:', wallet.address); + + // IMPORTANT: In production, encrypt and securely store the mnemonic! +} + +main().catch(console.error); +``` + +Run it: +```bash +node wallet.js +``` + +--- + +## Part 3: Getting Test Tokens + +### From the Faucet + +If you're on testnet, request tokens from the faucet: + +```bash +curl -X POST http://localhost:8080/request \ + -H "Content-Type: application/json" \ + -d '{"address": "tsynor1your_address_here"}' +``` + +### Check Your Balance + +```bash +curl -X POST http://localhost:17110 \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc":"2.0", + "method":"synor_getBalance", + "params":["tsynor1your_address"], + "id":1 + }' +``` + +Response: +```json +{ + "jsonrpc": "2.0", + "result": { + "confirmed": "10.00000000", + "pending": "0.00000000" + }, + "id": 1 +} +``` + +--- + +## Part 4: Sending Your First Transaction + +### Using the CLI + +```bash +synor-cli send \ + --to tsynor1recipient_address \ + --amount 1.5 \ + --password yourpassword +``` + +### Using JavaScript + +```javascript +import { createSendTransactionHybrid, serializeTransaction } from '@synor/sdk'; + +async function sendTransaction(wallet, toAddress, amount) { + // Build and sign the transaction + const tx = await createSendTransactionHybrid( + wallet.address, + toAddress, + amount, + wallet + ); + + console.log('Transaction ID:', tx.id); + + // Broadcast to the network + const response = await fetch('http://localhost:17110', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'synor_sendRawTransaction', + params: [serializeTransaction(tx)], + id: 1 + }) + }); + + const result = await response.json(); + console.log('Broadcast result:', result); + + return tx.id; +} +``` + +--- + +## Part 5: Understanding Hybrid Signatures + +Synor is unique because it uses **hybrid signatures**: + +``` +┌────────────────────────────────────────────────┐ +│ Transaction │ +├────────────────────────────────────────────────┤ +│ Signed with: │ +│ ┌─────────────┐ ┌──────────────────┐ │ +│ │ Ed25519 │ + │ Dilithium3 │ │ +│ │ (64 bytes) │ │ (~3.3 KB) │ │ +│ └─────────────┘ └──────────────────┘ │ +│ │ +│ Both signatures must verify! │ +└────────────────────────────────────────────────┘ +``` + +**Why hybrid?** +- **Ed25519**: Fast, well-tested, secure against classical computers +- **Dilithium3**: Secure against future quantum computers (NIST PQC standard) + +This means your funds are protected even if quantum computers become powerful enough to break Ed25519. + +--- + +## Part 6: Next Steps + +Congratulations! You've: +- ✅ Set up a local Synor node +- ✅ Created a wallet +- ✅ Received test tokens +- ✅ Sent a transaction + +### Continue Learning + +- [Tutorial 2: Building a Wallet Application](./02-building-a-wallet.md) +- [Tutorial 3: Smart Contracts on Synor](./03-smart-contracts.md) +- [Tutorial 4: Working with the API](./04-api-guide.md) + +### Resources + +- [API Reference](../API_REFERENCE.md) +- [Developer Guide](../DEVELOPER_GUIDE.md) +- [Exchange Integration](../EXCHANGE_INTEGRATION.md) + +--- + +## Troubleshooting + +### Node not responding + +```bash +# Check if container is running +docker ps | grep synor + +# Check logs +docker logs synor-seed1 +``` + +### Transaction stuck + +- Ensure you have enough balance for the fee +- Check that UTXOs are confirmed +- Try with a higher fee + +### Invalid address error + +- Testnet addresses start with `tsynor1` +- Mainnet addresses start with `synor1` +- Addresses are case-sensitive (use lowercase) + +--- + +*Next: [Building a Wallet Application](./02-building-a-wallet.md)* diff --git a/docs/tutorials/02-building-a-wallet.md b/docs/tutorials/02-building-a-wallet.md new file mode 100644 index 0000000..51967ff --- /dev/null +++ b/docs/tutorials/02-building-a-wallet.md @@ -0,0 +1,771 @@ +# Tutorial 2: Building a Wallet Application + +In this tutorial, you'll build a complete wallet application that can create addresses, display balances, and send transactions. + +## What You'll Build + +A React wallet app with: +- Mnemonic generation and recovery +- Balance display +- Transaction history +- Send functionality +- QR code support + +## Prerequisites + +- Completed [Tutorial 1: Getting Started](./01-getting-started.md) +- Node.js 18+ +- Basic React knowledge + +--- + +## Part 1: Project Setup + +### Create React App + +```bash +npm create vite@latest synor-wallet -- --template react-ts +cd synor-wallet +npm install +``` + +### Install Dependencies + +```bash +npm install @synor/sdk zustand @tanstack/react-query qrcode.react +``` + +### Project Structure + +``` +synor-wallet/ +├── src/ +│ ├── lib/ +│ │ ├── crypto.ts # Crypto utilities +│ │ └── rpc.ts # RPC client +│ ├── store/ +│ │ └── wallet.ts # Wallet state +│ ├── components/ +│ │ ├── CreateWallet.tsx +│ │ ├── Dashboard.tsx +│ │ ├── Send.tsx +│ │ └── Receive.tsx +│ ├── App.tsx +│ └── main.tsx +├── package.json +└── vite.config.ts +``` + +--- + +## Part 2: Core Wallet Logic + +### lib/crypto.ts + +```typescript +import { + generateMnemonic, + validateMnemonic, + createWallet as sdkCreateWallet +} from '@synor/sdk'; + +export type Network = 'mainnet' | 'testnet'; + +export interface Wallet { + address: string; + seed: Uint8Array; + keypair: { + publicKey: Uint8Array; + privateKey: Uint8Array; + }; + dilithiumPublicKey: Uint8Array; +} + +export function generateNewMnemonic(wordCount: 12 | 24 = 24): string { + return generateMnemonic(wordCount); +} + +export function isValidMnemonic(mnemonic: string): boolean { + return validateMnemonic(mnemonic); +} + +export async function createWalletFromMnemonic( + mnemonic: string, + passphrase: string = '', + network: Network = 'testnet' +): Promise { + return sdkCreateWallet(mnemonic, passphrase, network); +} + +// Encrypt wallet for storage +export async function encryptWallet( + seed: Uint8Array, + password: string +): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; salt: Uint8Array }> { + // Use Web Crypto API for AES-256-GCM encryption + const encoder = new TextEncoder(); + const salt = crypto.getRandomValues(new Uint8Array(16)); + + // Derive key using PBKDF2 + const keyMaterial = await crypto.subtle.importKey( + 'raw', + encoder.encode(password), + 'PBKDF2', + false, + ['deriveBits', 'deriveKey'] + ); + + const key = await crypto.subtle.deriveKey( + { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, + keyMaterial, + { name: 'AES-GCM', length: 256 }, + false, + ['encrypt'] + ); + + const iv = crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await crypto.subtle.encrypt( + { name: 'AES-GCM', iv }, + key, + seed + ); + + return { + ciphertext: new Uint8Array(ciphertext), + iv, + salt + }; +} + +// Decrypt wallet +export async function decryptWallet( + ciphertext: Uint8Array, + iv: Uint8Array, + salt: Uint8Array, + password: string +): Promise { + const encoder = new TextEncoder(); + + const keyMaterial = await crypto.subtle.importKey( + 'raw', + encoder.encode(password), + 'PBKDF2', + false, + ['deriveBits', 'deriveKey'] + ); + + const key = await crypto.subtle.deriveKey( + { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, + keyMaterial, + { name: 'AES-GCM', length: 256 }, + false, + ['decrypt'] + ); + + const decrypted = await crypto.subtle.decrypt( + { name: 'AES-GCM', iv }, + key, + ciphertext + ); + + return new Uint8Array(decrypted); +} +``` + +### lib/rpc.ts + +```typescript +const RPC_URL = 'http://localhost:17110'; + +interface RpcRequest { + jsonrpc: '2.0'; + method: string; + params: unknown[]; + id: number; +} + +interface RpcResponse { + jsonrpc: '2.0'; + result?: T; + error?: { code: number; message: string }; + id: number; +} + +let requestId = 0; + +export async function rpc(method: string, params: unknown[] = []): Promise { + const request: RpcRequest = { + jsonrpc: '2.0', + method, + params, + id: ++requestId + }; + + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + + const data: RpcResponse = await response.json(); + + if (data.error) { + throw new Error(data.error.message); + } + + return data.result as T; +} + +// Typed RPC methods +export interface Balance { + confirmed: string; + pending: string; +} + +export interface Transaction { + txId: string; + type: 'send' | 'receive'; + amount: string; + address: string; + confirmations: number; + timestamp: number; +} + +export const api = { + getBalance: (address: string) => + rpc('synor_getBalance', [address]), + + getTransactions: (address: string, limit = 50) => + rpc('synor_getAddressTransactions', [address, limit]), + + sendTransaction: (serializedTx: string) => + rpc<{ txId: string }>('synor_sendRawTransaction', [serializedTx]), + + getBlockCount: () => + rpc('synor_getBlockCount', []) +}; +``` + +--- + +## Part 3: State Management + +### store/wallet.ts + +```typescript +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import { + generateNewMnemonic, + createWalletFromMnemonic, + encryptWallet, + decryptWallet, + type Wallet +} from '../lib/crypto'; +import { api, type Balance, type Transaction } from '../lib/rpc'; + +interface WalletState { + // Persisted + hasWallet: boolean; + encryptedSeed: { ciphertext: string; iv: string; salt: string } | null; + address: string | null; + + // Session (not persisted) + isUnlocked: boolean; + wallet: Wallet | null; + balance: Balance | null; + transactions: Transaction[]; + isLoading: boolean; + error: string | null; + + // Actions + createWallet: (password: string) => Promise; + recoverWallet: (mnemonic: string, password: string) => Promise; + unlock: (password: string) => Promise; + lock: () => void; + refreshBalance: () => Promise; + refreshTransactions: () => Promise; +} + +// Convert bytes to hex +const toHex = (bytes: Uint8Array) => + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); + +// Convert hex to bytes +const fromHex = (hex: string) => + new Uint8Array(hex.match(/.{1,2}/g)!.map(b => parseInt(b, 16))); + +export const useWalletStore = create()( + persist( + (set, get) => ({ + // Initial state + hasWallet: false, + encryptedSeed: null, + address: null, + isUnlocked: false, + wallet: null, + balance: null, + transactions: [], + isLoading: false, + error: null, + + createWallet: async (password) => { + set({ isLoading: true, error: null }); + try { + const mnemonic = generateNewMnemonic(24); + const wallet = await createWalletFromMnemonic(mnemonic, '', 'testnet'); + const encrypted = await encryptWallet(wallet.seed, password); + + set({ + hasWallet: true, + encryptedSeed: { + ciphertext: toHex(encrypted.ciphertext), + iv: toHex(encrypted.iv), + salt: toHex(encrypted.salt) + }, + address: wallet.address, + isUnlocked: true, + wallet, + isLoading: false + }); + + return mnemonic; // Return for user to save + } catch (error) { + set({ error: (error as Error).message, isLoading: false }); + throw error; + } + }, + + recoverWallet: async (mnemonic, password) => { + set({ isLoading: true, error: null }); + try { + const wallet = await createWalletFromMnemonic(mnemonic, '', 'testnet'); + const encrypted = await encryptWallet(wallet.seed, password); + + set({ + hasWallet: true, + encryptedSeed: { + ciphertext: toHex(encrypted.ciphertext), + iv: toHex(encrypted.iv), + salt: toHex(encrypted.salt) + }, + address: wallet.address, + isUnlocked: true, + wallet, + isLoading: false + }); + } catch (error) { + set({ error: (error as Error).message, isLoading: false }); + throw error; + } + }, + + unlock: async (password) => { + const { encryptedSeed, address } = get(); + if (!encryptedSeed || !address) { + throw new Error('No wallet found'); + } + + set({ isLoading: true, error: null }); + try { + const seed = await decryptWallet( + fromHex(encryptedSeed.ciphertext), + fromHex(encryptedSeed.iv), + fromHex(encryptedSeed.salt), + password + ); + + const wallet = await createWalletFromMnemonic( + '', // We derive from seed directly + '', + 'testnet' + ); + + set({ + isUnlocked: true, + wallet, + isLoading: false + }); + + // Refresh balance + get().refreshBalance(); + } catch (error) { + set({ error: 'Incorrect password', isLoading: false }); + throw error; + } + }, + + lock: () => { + set({ + isUnlocked: false, + wallet: null, + balance: null, + transactions: [] + }); + }, + + refreshBalance: async () => { + const { address, isUnlocked } = get(); + if (!address || !isUnlocked) return; + + try { + const balance = await api.getBalance(address); + set({ balance }); + } catch (error) { + console.error('Failed to refresh balance:', error); + } + }, + + refreshTransactions: async () => { + const { address, isUnlocked } = get(); + if (!address || !isUnlocked) return; + + try { + const transactions = await api.getTransactions(address); + set({ transactions }); + } catch (error) { + console.error('Failed to refresh transactions:', error); + } + } + }), + { + name: 'synor-wallet', + partialize: (state) => ({ + hasWallet: state.hasWallet, + encryptedSeed: state.encryptedSeed, + address: state.address + }) + } + ) +); +``` + +--- + +## Part 4: Components + +### components/CreateWallet.tsx + +```tsx +import { useState } from 'react'; +import { useWalletStore } from '../store/wallet'; + +export function CreateWallet() { + const { createWallet, recoverWallet, isLoading } = useWalletStore(); + const [step, setStep] = useState<'choice' | 'create' | 'recover'>('choice'); + const [mnemonic, setMnemonic] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [generatedMnemonic, setGeneratedMnemonic] = useState(''); + const [error, setError] = useState(''); + + const handleCreate = async () => { + if (password !== confirmPassword) { + setError('Passwords do not match'); + return; + } + if (password.length < 8) { + setError('Password must be at least 8 characters'); + return; + } + + try { + const newMnemonic = await createWallet(password); + setGeneratedMnemonic(newMnemonic); + setStep('create'); + } catch (err) { + setError((err as Error).message); + } + }; + + const handleRecover = async () => { + if (password !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + try { + await recoverWallet(mnemonic, password); + } catch (err) { + setError((err as Error).message); + } + }; + + if (step === 'choice') { + return ( +
+

Welcome to Synor Wallet

+
+ + +
+
+ ); + } + + if (step === 'create' && generatedMnemonic) { + return ( +
+

Save Your Recovery Phrase

+
+

+ Write down these 24 words in order. This is your only backup! +

+
+
+ {generatedMnemonic.split(' ').map((word, i) => ( +
+ {i + 1}. + {word} +
+ ))} +
+ +
+ ); + } + + return ( +
+

+ {step === 'create' ? 'Create Wallet' : 'Recover Wallet'} +

+ + {error && ( +
+ {error} +
+ )} + + {step === 'recover' && ( +
+ +