Skip to content

ma$ - Universal Wallet Integration Guide

Overview

ma$ (más = more in Spanish) gives users choice in how they connect their wallet to pay telecom bills and send cross-border remittances.

Supported Wallets

1. Browser Extension Wallets

  • MetaMask - Most popular crypto wallet
  • Coinbase Wallet - User-friendly wallet

2. Mobile Wallets (via WalletConnect)

  • Trust Wallet
  • Rainbow Wallet
  • Argent
  • Any WalletConnect-compatible wallet (100+ wallets)
  • Email - Magic link sent to email
  • SMS - Magic link sent via SMS
  • Google - OAuth login
  • Facebook - OAuth login

4. Custom Wallets

  • SMS/PIN Wallet - Your existing demo wallet

Quick Start

1. Install Dependencies

npm install @magic-sdk/admin @walletconnect/client @coinbase/wallet-sdk ethers

2. Backend Setup

// server.js
const MasWalletConnector = require('./services/wallet-connector');

// Initialize wallet connector
const walletConfig = {
    magicPublishableKey: process.env.MAGIC_PUBLISHABLE_KEY,
    rpcUrl: process.env.POLYGON_RPC_URL,
    chainId: 137 // Polygon mainnet
};

app.use('/api/wallet', require('./routes/wallet-routes')(walletConfig));

3. Frontend Integration

import WalletSelector from './components/WalletSelector';

function PaymentPage() {
    const [walletConnected, setWalletConnected] = useState(false);
    const [walletInfo, setWalletInfo] = useState(null);
    const [connector, setConnector] = useState(null);

    const handleWalletConnect = (info, connectorInstance) => {
        console.log('Wallet connected:', info);
        setWalletInfo(info);
        setConnector(connectorInstance);
        setWalletConnected(true);
    };

    return (
        <div>
            {!walletConnected ? (
                <WalletSelector
                    onConnect={handleWalletConnect}
                    config={walletConfig}
                />
            ) : (
                <PaymentFlow
                    wallet={walletInfo}
                    connector={connector}
                />
            )}
        </div>
    );
}

Use Cases

Use Case 1: Cross-Border Remittance

User wants to send $100 to family in Mexico to pay their Telcel bill.

Flow with ma$: 1. User selects "Pay Telcel Bill" 2. ma$ shows wallet options 3. User chooses MetaMask (they already have it) 4. MetaMask pops up → User approves connection 5. User enters: Amount ($100), Recipient phone (+52 555 123 4567) 6. ma$ shows transaction preview 7. User clicks "Send" 8. MetaMask pops up → User confirms transaction 9. Transaction sent to blockchain 10. Backend detects transaction → Processes Telcel recharge 11. Recipient gets SMS: "Your Telcel account has been recharged with $100"

Code:

// After wallet connected
const sendRemittance = async (amount, recipientPhone) => {
    // Convert USD to USDC
    const amountInUSDC = ethers.utils.parseUnits(amount, 6); // USDC has 6 decimals

    // Create transaction
    const tx = {
        to: process.env.TREASURY_ADDRESS, // Your treasury wallet
        value: 0, // Native token (we're sending USDC, not MATIC)
        data: await encodeUSDCTransfer(
            process.env.TREASURY_ADDRESS,
            amountInUSDC
        )
    };

    // Send transaction (works with ANY connected wallet!)
    const result = await connector.sendTransaction(tx);

    // Call your backend to process the recharge
    await fetch('/api/telecom/recharge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            txHash: result.hash,
            recipientPhone,
            amount,
            provider: 'telcel'
        })
    });

    return result;
};

User doesn't have a crypto wallet, just wants to pay their AT&T bill quickly.

Flow with ma$: 1. User selects "Pay AT&T Bill" 2. ma$ shows wallet options 3. User chooses Email (simple option) 4. User enters their email 5. Magic.link sends them a link 6. User clicks link → Automatically logged in 7. Wallet created in background (they don't see crypto complexity) 8. User enters bill amount 9. User chooses payment method (card, cash, bank) 10. Payment processed → AT&T bill paid

Code:

// Magic.link makes it simple for non-crypto users
const handleMagicLogin = async (email) => {
    const connector = new MasWalletConnector(config);

    // User just enters email - magic handles the rest
    const result = await connector.connect('magic', { email });

    // Now they have a wallet, but they don't need to know that!
    // Just let them pay their bill normally
    setWalletConnected(true);
};

Use Case 3: Existing SMS/PIN Wallet User

User already has your SMS/PIN wallet from demo, wants to use it for production.

Flow with ma$: 1. User selects "Pay Bill" 2. ma$ shows wallet options 3. User chooses PIN Wallet (they recognize it!) 4. User enters phone number + PIN 5. Wallet unlocked 6. User completes payment

Code:

const handlePinWallet = async (phone, pin) => {
    const connector = new MasWalletConnector(config);

    // Reuse existing wallet!
    const result = await connector.connect('custom', {
        phoneNumber: phone,
        pin: pin
    });

    // They're connected with their existing wallet
    setWalletConnected(true);
};

API Routes

Create these routes in your Express server:

// routes/wallet-routes.js
const express = require('express');
const router = express.Router();
const MasWalletConnector = require('../services/wallet-connector');

module.exports = (config) => {
    // Connect wallet (backend validation)
    router.post('/connect', async (req, res) => {
        try {
            const { walletType, options } = req.body;

            const connector = new MasWalletConnector(config);
            const result = await connector.connect(walletType, options);

            // Store wallet session
            req.session.wallet = {
                address: result.address,
                type: walletType
            };

            res.json({
                success: true,
                address: result.address,
                walletType
            });
        } catch (error) {
            res.status(400).json({
                success: false,
                error: error.message
            });
        }
    });

    // Get balance
    router.get('/balance/:address', async (req, res) => {
        try {
            const connector = new MasWalletConnector(config);
            // Recreate connection from session
            const balance = await connector.getBalance();

            res.json({
                success: true,
                balance: balance,
                currency: 'USDC'
            });
        } catch (error) {
            res.status(400).json({
                success: false,
                error: error.message
            });
        }
    });

    // Process payment
    router.post('/pay', async (req, res) => {
        try {
            const { amount, recipientPhone, provider } = req.body;

            // Verify wallet connected
            if (!req.session.wallet) {
                return res.status(401).json({
                    success: false,
                    error: 'No wallet connected'
                });
            }

            // Process payment through your existing latcom-fix flow
            const result = await processPayment({
                fromAddress: req.session.wallet.address,
                amount,
                recipientPhone,
                provider
            });

            res.json({
                success: true,
                ...result
            });
        } catch (error) {
            res.status(400).json({
                success: false,
                error: error.message
            });
        }
    });

    return router;
};

Security Best Practices

1. Session Management

// Store wallet info in secure session
app.use(session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true, // HTTPS only
        httpOnly: true,
        maxAge: 15 * 60 * 1000 // 15 minutes
    }
}));

2. Transaction Verification

// Always verify transactions on backend
const verifyTransaction = async (txHash, expectedAmount) => {
    const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
    const tx = await provider.getTransaction(txHash);
    const receipt = await provider.getTransactionReceipt(txHash);

    // Verify:
    // 1. Transaction is confirmed
    if (!receipt || receipt.status !== 1) {
        throw new Error('Transaction failed');
    }

    // 2. Transaction is to correct address
    if (tx.to.toLowerCase() !== TREASURY_ADDRESS.toLowerCase()) {
        throw new Error('Invalid recipient');
    }

    // 3. Amount is correct
    const tokenContract = new ethers.Contract(USDC_ADDRESS, USDC_ABI, provider);
    const decodedData = tokenContract.interface.parseTransaction(tx);
    const actualAmount = decodedData.args.value;

    if (!actualAmount.eq(expectedAmount)) {
        throw new Error('Amount mismatch');
    }

    return true;
};

3. Rate Limiting

// Prevent abuse
const rateLimit = require('express-rate-limit');

const walletLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // Max 5 connection attempts
    message: 'Too many wallet connection attempts'
});

app.use('/api/wallet/connect', walletLimiter);

Environment Variables

Add to your .env:

# ma$ Wallet Configuration
MAGIC_PUBLISHABLE_KEY=pk_live_your_key_here
POLYGON_RPC_URL=https://polygon-rpc.com
USDC_CONTRACT=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
TREASURY_ADDRESS=0xYourTreasuryAddress

# Session Secret
SESSION_SECRET=your_random_secret_here

Testing

// Test all wallet types
describe('ma$ Wallet Connector', () => {
    it('should connect MetaMask', async () => {
        const connector = new MasWalletConnector(config);
        const result = await connector.connect('metamask');
        expect(result.success).toBe(true);
        expect(result.address).toMatch(/^0x[a-fA-F0-9]{40}$/);
    });

    it('should connect Magic.link', async () => {
        const connector = new MasWalletConnector(config);
        const result = await connector.connect('magic', {
            email: 'test@example.com'
        });
        expect(result.success).toBe(true);
    });

    it('should connect custom PIN wallet', async () => {
        const connector = new MasWalletConnector(config);
        const result = await connector.connect('custom', {
            phoneNumber: '+15551234567',
            pin: '1234'
        });
        expect(result.success).toBe(true);
    });
});

Troubleshooting

MetaMask not detected

if (!window.ethereum) {
    alert('Please install MetaMask: https://metamask.io/download');
}

Wrong network

// Auto-switch to Polygon
if (chainId !== 137) {
    await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: '0x89' }] // 137 in hex
    });
}

Transaction failed

// Check gas, balance, approvals
const gasEstimate = await connector.signer.estimateGas(tx);
const balance = await connector.getBalance();
console.log('Gas needed:', gasEstimate.toString());
console.log('Balance:', balance);

Next Steps

  1. Deploy wallet connector to production
  2. Add to latcom-fix payment flow
  3. Test with real users
  4. Monitor transaction success rates
  5. Optimize user experience based on data

ma$ = más opciones, más simple, más rápido! 🚀