Skip to content

Instantly share code, notes, and snippets.

@shopglobal
Last active August 19, 2024 22:10
Show Gist options
  • Save shopglobal/cbd66745583d94c048ac3863a9ef3f58 to your computer and use it in GitHub Desktop.
Save shopglobal/cbd66745583d94c048ac3863a9ef3f58 to your computer and use it in GitHub Desktop.
Simple monero web wallet

Below is a comprehensive guide to creating a mobile wallet application for Monero cryptocurrency using React Native for the frontend and PHP with MySQL for user authentication. This guide will cover setting up user authentication, integrating Monero transactions, and displaying transaction history.


1. Project Setup

Frontend (React Native)

  1. Initialize a new React Native project:
    npx react-native init MoneroWallet
    cd MoneroWallet
  2. Install necessary libraries:
    npm install axios monero-javascript

Backend (PHP and MySQL)

  1. Set up your MySQL database and create a table for users:

    CREATE TABLE users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(255) NOT NULL UNIQUE,
        email VARCHAR(255) NOT NULL UNIQUE,
        password VARCHAR(255) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
  2. Create your PHP files for user authentication.

config.php

<?php
$host = '127.0.0.1';
$db = 'monero_wallet';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
?>

register.php

<?php
require 'config.php';

$username = $_POST['username'];
$email = $_POST['email'];
$password = password_hash($_POST['password'], PASSWORD_BCRYPT);

$stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
if ($stmt->execute([$username, $email, $password])) {
    echo json_encode(['status' => 'success', 'message' => 'User registered successfully']);
} else {
    echo json_encode(['status' => 'error', 'message' => 'Registration failed']);
}
?>

login.php

<?php
require 'config.php';

$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();

if ($user && password_verify($password, $user['password'])) {
    echo json_encode(['status' => 'success', 'message' => 'Login successful', 'user' => $user]);
} else {
    echo json_encode(['status' => 'error', 'message' => 'Invalid credentials']);
}
?>

2. React Native Authentication

Create a basic authentication form in React Native to register and log in users.

AuthScreen.js

import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import axios from 'axios';

const AuthScreen = () => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const [message, setMessage] = useState('');

    const handleLogin = () => {
        axios.post('http://yourserver.com/login.php', {
            username,
            password,
        })
        .then(response => {
            if (response.data.status === 'success') {
                setMessage('Login successful');
            } else {
                setMessage('Login failed');
            }
        })
        .catch(error => {
            console.error(error);
            setMessage('An error occurred');
        });
    };

    return (
        <View>
            <TextInput
                placeholder="Username"
                value={username}
                onChangeText={setUsername}
                autoCapitalize="none"
            />
            <TextInput
                placeholder="Password"
                value={password}
                onChangeText={setPassword}
                secureTextEntry
            />
            <Button title="Login" onPress={handleLogin} />
            {message ? <Text>{message}</Text> : null}
        </View>
    );
};

export default AuthScreen;

3. Monero Wallet and Transaction Integration

Setting up the Monero wallet:

SendTransaction.js

import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import { MoneroWalletFull, MoneroNetworkType } from 'monero-javascript';

const SendTransaction = () => {
    const [wallet, setWallet] = useState(null);
    const [recipientAddress, setRecipientAddress] = useState('');
    const [amount, setAmount] = useState('');
    const [status, setStatus] = useState('');

    // Function to create and send a transaction
    const sendTransaction = async () => {
        if (!wallet) {
            setStatus('Wallet not loaded');
            return;
        }

        try {
            const amountAtomicUnits = BigInt(amount) * BigInt(1e12);

            const tx = await wallet.createTx({
                address: recipientAddress,
                amount: amountAtomicUnits.toString(),
                accountIndex: 0,
                subaddressIndex: 0
            });

            await wallet.commitTx(tx);
            setStatus(`Transaction sent: ${tx.getTxId()}`);
        } catch (error) {
            setStatus(`Error: ${error.message}`);
        }
    };

    const loadWallet = async () => {
        let wallet = await MoneroWalletFull.createWallet({
            path: "./wallets/wallet",
            password: "supersecretpassword",
            networkType: MoneroNetworkType.STAGENET,
            mnemonic: "your mnemonic seed",
        });
        setWallet(wallet);
    };

    return (
        <View>
            <TextInput
                placeholder="Recipient Address"
                value={recipientAddress}
                onChangeText={setRecipientAddress}
                autoCapitalize="none"
            />
            <TextInput
                placeholder="Amount (XMR)"
                value={amount}
                onChangeText={setAmount}
                keyboardType="numeric"
            />
            <Button title="Load Wallet" onPress={loadWallet} />
            <Button title="Send Transaction" onPress={sendTransaction} />
            {status ? <Text>{status}</Text> : null}
        </View>
    );
};

export default SendTransaction;

Displaying transaction history:

TransactionHistory.js

import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView } from 'react-native';
import { MoneroWalletFull, MoneroNetworkType } from 'monero-javascript';

const TransactionHistory = () => {
    const [wallet, setWallet] = useState(null);
    const [transactions, setTransactions] = useState([]);

    const loadTransactions = async () => {
        if (!wallet) {
            const walletInstance = await MoneroWalletFull.createWallet({
                path: "./wallets/wallet",
                password: "supersecretpassword",
                networkType: MoneroNetworkType.STAGENET,
                mnemonic: "your mnemonic seed",
            });
            setWallet(walletInstance);
        }

        try {
            const txs = await wallet.getTxs();
            setTransactions(txs);
        } catch (error) {
            console.error('Failed to fetch transactions:', error);
        }
    };

    useEffect(() => {
        loadTransactions();
    }, []);

    return (
        <ScrollView>
            <Text>Transaction History:</Text>
            {transactions.map((tx, index) => (
                <View key={index} style={{ marginVertical: 10 }}>
                    <Text>Transaction ID: {tx.getTxId()}</Text>
                    <Text>Amount: {(tx.getAmount() / BigInt(1e12)).toString()} XMR</Text>
                    <Text>Fee: {(tx.getFee() / BigInt(1e12)).toString()} XMR</Text>
                    <Text>Status: {tx.isConfirmed() ? 'Confirmed' : 'Pending'}</Text>
                </View>
            ))}
        </ScrollView>
    );
};

export default TransactionHistory;

4. Integrate Components into the Main App

App.js

import React from 'react';
import { View, Button } from 'react-native';
import AuthScreen from './AuthScreen';
import SendTransaction from './SendTransaction';
import TransactionHistory from './TransactionHistory';

const App = () => {
    return (
        <View style={{ padding: 20 }}>
            <AuthScreen />
            <Button title="Send Transaction" onPress={() => {/* Navigate to SendTransaction screen */}} />
            <Button title="View Transaction History" onPress={() => {/* Navigate to TransactionHistory screen */}} />
        </View>
    );
};

export default App;

5. Security and Testing

  1. Encryption: Ensure all sensitive data (like wallet seeds) is encrypted and stored securely.
  2. HTTPS: Use HTTPS for all communications between the app and your PHP backend.
  3. Testing: Use Monero’s testnet or stagenet during development to avoid real currency losses.
  4. Deployment: Test thoroughly on both iOS and Android devices, and deploy the app via app stores.

By following these steps, you will have a fully functional

To integrate Monero Wallet RPC for opening wallets for each user and storing the wallet ID in the database, follow these steps:

1. Set Up Monero Wallet RPC

Ensure that the Monero Wallet RPC is running on your server. You can start it with the following command:

monero-wallet-rpc --wallet-dir /path/to/wallets --rpc-bind-port 18082 --rpc-login user:password --daemon-address node.moneroworld.com:18089

2. Modify the PHP Backend

You'll need to modify the PHP backend to interact with the Monero Wallet RPC, create new wallets for users, and store their wallet IDs in the database.

config.php

<?php
$host = '127.0.0.1';
$db = 'monero_wallet';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    throw new \PDOException($e->getMessage(), (int)$e->getCode());
}

// Monero Wallet RPC configuration
$rpc_host = 'http://localhost:18082';
$rpc_user = 'user';
$rpc_pass = 'password';

function monero_rpc($method, $params = []) {
    global $rpc_host, $rpc_user, $rpc_pass;

    $data = [
        'jsonrpc' => '2.0',
        'id' => '0',
        'method' => $method,
        'params' => $params,
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $rpc_host . '/json_rpc');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_USERPWD, "$rpc_user:$rpc_pass");

    $headers = [];
    $headers[] = 'Content-Type: application/json';
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        throw new Exception('Error: ' . curl_error($ch));
    }
    curl_close($ch);

    return json_decode($result, true);
}
?>

register.php

<?php
require 'config.php';

$username = $_POST['username'];
$email = $_POST['email'];
$password = password_hash($_POST['password'], PASSWORD_BCRYPT);

try {
    // Create a new Monero wallet
    $wallet_name = "wallet_$username";
    $response = monero_rpc('create_wallet', [
        'filename' => $wallet_name,
        'password' => 'walletpassword',
        'language' => 'English',
    ]);

    if (isset($response['error'])) {
        throw new Exception($response['error']['message']);
    }

    // Store user in the database with the wallet ID
    $stmt = $pdo->prepare("INSERT INTO users (username, email, password, wallet_id) VALUES (?, ?, ?, ?)");
    if ($stmt->execute([$username, $email, $password, $wallet_name])) {
        echo json_encode(['status' => 'success', 'message' => 'User registered successfully']);
    } else {
        throw new Exception('Registration failed');
    }
} catch (Exception $e) {
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>

Database Schema Update:

Update your users table to store the wallet ID:

ALTER TABLE users ADD COLUMN wallet_id VARCHAR(255) NOT NULL;

3. React Native Integration

When a user registers, the wallet is created via the PHP backend, and the wallet ID is stored in the database. Your React Native app will continue to interact with the backend as before, with the added capability of interacting with the user's Monero wallet.

4. Security Considerations

  • Secure the RPC: Ensure that your Monero Wallet RPC is properly secured and only accessible from your backend server.
  • Encryption: Encrypt sensitive data like wallet passwords.
  • HTTPS: Use HTTPS for all communications between the React Native app and the backend.

5. Additional Features

You can expand this setup to include features like checking the wallet balance, sending transactions, and viewing transaction history by making additional RPC calls from your backend and exposing these as endpoints for the React Native app to consume.

This setup provides a secure and scalable way to manage Monero wallets for each user in your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment