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)
3. Social Login Wallets (via Magic.link)¶
- ✅ 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¶
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;
};
Use Case 2: Quick Bill Payment with Magic.link¶
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¶
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¶
- ✅ Deploy wallet connector to production
- ✅ Add to latcom-fix payment flow
- ✅ Test with real users
- ✅ Monitor transaction success rates
- ✅ Optimize user experience based on data
ma$ = más opciones, más simple, más rápido! 🚀