Introduction
In this article, I'll show you how I built a complete decentralized marketplace using Monero for payments, Node.js for the backend, MongoDB as the database, and JWT for authentication. The system handles digital tokens, buy/sell orders, and cryptocurrency payments with real transaction monitoring.
๐ฆ System Architecture
text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client (Frontend) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MyZubster Gateway (Node.js + Express) โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Auth JWT โ โ Marketplace โ โ Monero Payments โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MongoDB โ โ Monero Wallet RPC โ
โ - Users โ โ - Subaddress Generation โ
โ - Tokens โ โ - Payment Verification โ
โ - Holdings โ โ - Transaction Monitoring โ
โ - OrderBook โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโ โ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ Monero Daemon โ
โ (Stagenet) โ
โโโโโโโโโโโโโโโโโโโโ
๐ ๏ธ Tech Stack
Node.js v20 - JavaScript runtime
Express.js - Web framework
MongoDB - NoSQL database
Mongoose - MongoDB ODM
JSON Web Tokens (JWT) - Authentication
Monero RPC - Payment integration
Systemd - Production service management
Axios - HTTP client for RPC calls
๐ Project Structure
text
MyZubsterGateway/
โโโ models/
โ โโโ User.js # User model
โ โโโ Token.js # Token model
โ โโโ TokenHolding.js # User holdings
โ โโโ OrderBook.js # Sell orders
โ โโโ MoneroTransaction.js # Monero transactions
โโโ routes/
โ โโโ auth.js # Login/Register
โ โโโ tokens.js # Token CRUD
โ โโโ marketplace.js # Sell/Buy/Orders
โ โโโ payments.js # Monero payments
โโโ services/
โ โโโ tokenService.js # Token logic
โ โโโ marketplaceService.js # Marketplace logic
โ โโโ moneroService.js # Monero integration
โโโ middleware/
โ โโโ auth.js # JWT verification
โโโ server.js # Entry point
โโโ .env # Configuration
๐ 1. JWT Authentication
Auth Middleware
javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
Login Route
javascript
// routes/auth.js
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ id: user._id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ success: true, token });
});
๐ 2. MongoDB Models
Token Model
javascript
// models/Token.js
const TokenSchema = new mongoose.Schema({
name: { type: String, required: true },
symbol: { type: String, required: true, unique: true },
totalSupply: { type: Number, required: true },
assetValue: { type: Number, required: true },
tokenPrice: { type: Number, required: true },
contractAddress: { type: String, required: true },
blockchain: {
type: String,
enum: ['tari', 'ethereum', 'polygon'],
default: 'tari'
},
assetType: {
type: String,
enum: ['realestate', 'equity', 'commodity', 'art', 'debt', 'revenue'],
required: true
},
assetDescription: { type: String, required: true },
assetLocation: { type: String, default: '' },
issuer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
status: {
type: String,
enum: ['draft', 'active', 'closed', 'paused'],
default: 'draft'
},
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Token', TokenSchema);
Order Book Model
javascript
// models/OrderBook.js
const OrderBookSchema = new mongoose.Schema({
token: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Token',
required: true
},
seller: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
amount: { type: Number, required: true },
price: { type: Number, required: true },
totalPrice: { type: Number, required: true },
status: {
type: String,
enum: ['open', 'filled', 'cancelled', 'expired'],
default: 'open'
},
moneroTxid: { type: String, default: null },
createdAt: { type: Date, default: Date.now },
expiresAt: {
type: Date,
default: () => new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
} // 7 days
});
module.exports = mongoose.model('OrderBook', OrderBookSchema);
Token Holding Model
javascript
// models/TokenHolding.js
const TokenHoldingSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
token: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Token',
required: true
},
amount: { type: Number, default: 0 },
lockedAmount: { type: Number, default: 0 }, // Tokens locked in open orders
purchasePrice: { type: Number, default: 0 },
moneroTxid: { type: String, default: null },
purchasedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('TokenHolding', TokenHoldingSchema);
Monero Transaction Model
javascript
// models/MoneroTransaction.js
const MoneroTransactionSchema = new mongoose.Schema({
orderId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderBook',
required: true
},
buyerId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
subaddress: { type: String, required: true },
amount: { type: Number, required: true },
amountPaid: { type: Number, default: 0 },
moneroTxid: { type: String, default: null },
status: {
type: String,
enum: ['pending', 'confirmed', 'expired', 'failed'],
default: 'pending'
},
confirmations: { type: Number, default: 0 },
expiresAt: {
type: Date,
default: () => new Date(Date.now() + 60 * 60 * 1000)
}, // 1 hour
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('MoneroTransaction', MoneroTransactionSchema);
๐ช 3. Marketplace Logic
Creating a Sell Order
javascript
// services/marketplaceService.js
const createSellOrder = async (sellerId, tokenId, amount, price) => {
// Verify seller has enough tokens
const holding = await TokenHolding.findOne({ user: sellerId, token: tokenId });
if (!holding || holding.amount < amount) {
throw new Error('Insufficient tokens');
}
const totalPrice = amount * price;
const order = new OrderBook({
token: tokenId,
seller: sellerId,
amount,
price,
totalPrice,
status: 'open',
});
await order.save();
// Lock tokens for sale
holding.lockedAmount = (holding.lockedAmount || 0) + amount;
await holding.save();
return order;
};
Buying from an Order
javascript
const buyFromOrder = async (orderId, buyerId, amount) => {
const order = await OrderBook.findById(orderId);
if (!order) throw new Error('Order not found');
if (order.status !== 'open') throw new Error('Order no longer available');
if (amount > order.amount) throw new Error('Requested quantity exceeds available');
const totalPrice = amount * order.price;
// Update order
order.amount -= amount;
if (order.amount === 0) {
order.status = 'filled';
}
await order.save();
// Transfer tokens from seller to buyer
const sellerHolding = await TokenHolding.findOne({
user: order.seller,
token: order.token
});
if (sellerHolding) {
sellerHolding.lockedAmount = Math.max(0, (sellerHolding.lockedAmount || 0) - amount);
sellerHolding.amount -= amount;
await sellerHolding.save();
}
let buyerHolding = await TokenHolding.findOne({
user: buyerId,
token: order.token
});
if (!buyerHolding) {
buyerHolding = new TokenHolding({
user: buyerId,
token: order.token,
amount: 0,
lockedAmount: 0
});
}
buyerHolding.amount += amount;
await buyerHolding.save();
return { order, amount, totalPrice };
};
Cancelling an Order
javascript
const cancelSellOrder = async (orderId, userId) => {
const order = await OrderBook.findById(orderId);
if (!order) throw new Error('Order not found');
if (order.seller.toString() !== userId.toString()) {
throw new Error('You are not the owner of this order');
}
if (order.status !== 'open') {
throw new Error('Order can no longer be cancelled');
}
// Unlock tokens
const holding = await TokenHolding.findOne({ user: userId, token: order.token });
if (holding) {
holding.lockedAmount = Math.max(0, (holding.lockedAmount || 0) - order.amount);
await holding.save();
}
order.status = 'cancelled';
await order.save();
return order;
};
๐ช 4. Monero Integration
Connecting to Wallet RPC
javascript
// services/moneroService.js
const axios = require('axios');
const WALLET_RPC_URL = process.env.MONERO_WALLET_RPC_URL || 'http://localhost:18083/json_rpc';
const DAEMON_RPC_URL = process.env.MONERO_DAEMON_RPC_URL || 'http://localhost:18081/json_rpc';
const rpcRequest = async (url, method, params = {}) => {
const response = await axios.post(url, {
jsonrpc: '2.0',
id: '0',
method,
params
});
if (response.data.error) throw new Error(response.data.error.message);
return response.data.result;
};
const walletRpc = async (method, params = {}) => {
return rpcRequest(WALLET_RPC_URL, method, params);
};
const daemonRpc = async (method, params = {}) => {
return rpcRequest(DAEMON_RPC_URL, method, params);
};
Generating Subaddress for Payment
javascript
const createSubaddress = async (accountIndex = 0, label = '') => {
const result = await walletRpc('create_address', {
account_index: accountIndex,
label
});
return {
address: result.address,
addressIndex: result.address_index
};
};
Creating a Payment
javascript
const createPayment = async (orderId, buyerId, amountXMR) => {
// Generate dedicated address for this payment
const subaddress = await createSubaddress(0, Order ${orderId});
const transaction = new MoneroTransaction({
orderId,
buyerId,
subaddress: subaddress.address,
amount: amountXMR,
status: 'pending'
});
await transaction.save();
return {
transactionId: transaction._id,
address: subaddress.address,
amount: amountXMR,
expiresAt: transaction.expiresAt
};
};
Checking Payment Status
javascript
const checkPayment = async (transactionId) => {
const transaction = await MoneroTransaction.findById(transactionId);
if (!transaction) throw new Error('Transaction not found');
// Get all incoming transfers
const transfers = await walletRpc('get_transfers', { in: true });
const amountAtomic = transaction.amount * 1e12; // XMR โ atomic units
// Find matching transaction
const matchedTx = transfers.in.find(tx =>
tx.amount >= amountAtomic &&
new Date(tx.timestamp * 1000) > transaction.createdAt
);
if (matchedTx) {
transaction.amountPaid = matchedTx.amount / 1e12;
transaction.moneroTxid = matchedTx.txid;
transaction.confirmations = matchedTx.confirmations || 0;
transaction.status = 'confirmed';
transaction.updatedAt = new Date();
await transaction.save();
// Complete the order
await completeOrder(transaction.orderId);
return {
status: 'confirmed',
txid: matchedTx.txid,
confirmations: matchedTx.confirmations || 0
};
}
return { status: 'pending' };
};
Completing Order After Payment
javascript
const completeOrder = async (orderId) => {
const order = await OrderBook.findById(orderId);
if (!order) throw new Error('Order not found');
if (order.status !== 'open') throw new Error('Order no longer available');
const transaction = await MoneroTransaction.findOne({
orderId,
status: 'confirmed'
});
if (!transaction) throw new Error('Transaction not found');
// Transfer tokens from seller to buyer
const sellerHolding = await TokenHolding.findOne({
user: order.seller,
token: order.token
});
if (sellerHolding) {
sellerHolding.lockedAmount = Math.max(0, (sellerHolding.lockedAmount || 0) - order.amount);
sellerHolding.amount -= order.amount;
await sellerHolding.save();
}
let buyerHolding = await TokenHolding.findOne({
user: transaction.buyerId,
token: order.token
});
if (!buyerHolding) {
buyerHolding = new TokenHolding({
user: transaction.buyerId,
token: order.token,
amount: 0,
lockedAmount: 0
});
}
buyerHolding.amount += order.amount;
await buyerHolding.save();
// Close the order
order.status = 'filled';
order.moneroTxid = transaction.moneroTxid;
await order.save();
return order;
};
๐ 5. API Routes
Marketplace Routes
javascript
// routes/marketplace.js
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const marketplaceService = require('../services/marketplaceService');
// GET /api/marketplace/orders/:tokenId - List open orders
router.get('/orders/:tokenId', async (req, res) => {
try {
const orders = await marketplaceService.getOpenOrders(req.params.tokenId);
res.json(orders);
} catch (error) {
console.error('Error fetching orders:', error);
res.status(500).json({ error: 'Error fetching orders' });
}
});
// POST /api/marketplace/sell - Create sell order
router.post('/sell', auth, async (req, res) => {
try {
const { tokenId, amount, price } = req.body;
const order = await marketplaceService.createSellOrder(
req.user._id,
tokenId,
amount,
price
);
res.status(201).json({ success: true, order });
} catch (error) {
console.error('Error creating order:', error);
res.status(500).json({ error: error.message || 'Error creating order' });
}
});
// POST /api/marketplace/buy/:orderId - Buy from an order
router.post('/buy/:orderId', auth, async (req, res) => {
try {
const { amount } = req.body;
const result = await marketplaceService.buyFromOrder(
req.params.orderId,
req.user._id,
amount
);
res.json({ success: true, ...result });
} catch (error) {
console.error('Error buying:', error);
res.status(500).json({ error: error.message || 'Error processing purchase' });
}
});
// DELETE /api/marketplace/order/:orderId - Cancel order
router.delete('/order/:orderId', auth, async (req, res) => {
try {
const order = await marketplaceService.cancelSellOrder(
req.params.orderId,
req.user._id
);
res.json({ success: true, order });
} catch (error) {
console.error('Error cancelling order:', error);
res.status(500).json({ error: error.message || 'Error cancelling order' });
}
});
module.exports = router;
Payment Routes
javascript
// routes/payments.js
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const moneroService = require('../services/moneroService');
const MoneroTransaction = require('../models/MoneroTransaction');
// POST /api/payments - Create a Monero payment
router.post('/', auth, async (req, res) => {
try {
const { orderId, amount } = req.body;
const payment = await moneroService.createPayment(
orderId,
req.user._id,
amount
);
res.status(201).json({ success: true, payment });
} catch (error) {
console.error('Error creating payment:', error);
res.status(500).json({ error: error.message || 'Error creating payment' });
}
});
// GET /api/payments/:id - Check payment status
router.get('/:id', auth, async (req, res) => {
try {
const transaction = await MoneroTransaction.findById(req.params.id);
if (!transaction) {
return res.status(404).json({ error: 'Transaction not found' });
}
const status = await moneroService.checkPayment(req.params.id);
res.json({ success: true, ...status });
} catch (error) {
console.error('Error checking payment:', error);
res.status(500).json({ error: error.message || 'Error checking payment' });
}
});
// GET /api/payments/order/:orderId - Get payment by order
router.get('/order/:orderId', auth, async (req, res) => {
try {
const transaction = await MoneroTransaction.findOne({
orderId: req.params.orderId,
buyerId: req.user._id
});
if (!transaction) {
return res.status(404).json({ error: 'Transaction not found' });
}
res.json({ success: true, transaction });
} catch (error) {
console.error('Error fetching payment:', error);
res.status(500).json({ error: error.message || 'Error fetching payment' });
}
});
module.exports = router;
Token Routes
javascript
// routes/tokens.js
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const tokenService = require('../services/tokenService');
// GET /api/tokens - List active tokens
router.get('/', async (req, res) => {
try {
const tokens = await tokenService.getActiveTokens();
res.json(tokens);
} catch (error) {
console.error('Error fetching tokens:', error);
res.status(500).json({ error: 'Error fetching tokens' });
}
});
// GET /api/tokens/holdings - User holdings (MUST be BEFORE /:id)
router.get('/holdings', auth, async (req, res) => {
try {
const holdings = await tokenService.getUserHoldings(req.user._id);
res.json(holdings);
} catch (error) {
console.error('Error fetching holdings:', error);
res.status(500).json({ error: 'Error fetching holdings' });
}
});
// GET /api/tokens/:id - Token details
router.get('/:id', async (req, res) => {
try {
const token = await tokenService.getTokenById(req.params.id);
if (!token) return res.status(404).json({ error: 'Token not found' });
res.json(token);
} catch (error) {
console.error('Error fetching token:', error);
res.status(500).json({ error: 'Error fetching token' });
}
});
// POST /api/tokens - Create token (issuer only)
router.post('/', auth, async (req, res) => {
try {
const {
name, symbol, totalSupply, assetValue, tokenPrice,
assetType, assetDescription, assetLocation, blockchain
} = req.body;
const tokenData = {
name,
symbol,
totalSupply,
assetValue,
tokenPrice,
assetType,
assetDescription,
assetLocation: assetLocation || '',
blockchain: blockchain || 'tari',
issuer: req.user._id,
};
const token = await tokenService.createToken(tokenData);
res.status(201).json({ success: true, token });
} catch (error) {
console.error('Error creating token:', error);
res.status(500).json({ error: error.message || 'Error creating token' });
}
});
module.exports = router;
๐ 6. Server Setup
server.js - Entry Point
javascript
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const authRoutes = require('./routes/auth');
const tokenRoutes = require('./routes/tokens');
const marketplaceRoutes = require('./routes/marketplace');
const paymentRoutes = require('./routes/payments');
const { startMonitoring } = require('./services/paymentMonitor');
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/tokens', tokenRoutes);
app.use('/api/marketplace', marketplaceRoutes);
app.use('/api/payments', paymentRoutes);
// Health check
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// MongoDB connection
mongoose.connect(process.env.MONGODB_URI)
.then(() => {
console.log('โ
Connected to MongoDB');
startMonitoring();
app.listen(PORT, () => {
console.log(๐ Server running on port ${PORT});
console.log(๐ URL: http://localhost:${PORT});
console.log(๐ Health check: http://localhost:${PORT}/api/health);
});
})
.catch(err => {
console.error('โ MongoDB connection error:', err);
process.exit(1);
});
Payment Monitor Service
javascript
// services/paymentMonitor.js
const MoneroTransaction = require('../models/MoneroTransaction');
const moneroService = require('./moneroService');
const checkInterval = 30000; // 30 seconds
let monitoringInterval = null;
const startMonitoring = () => {
if (monitoringInterval) {
clearInterval(monitoringInterval);
}
console.log('๐ [PaymentMonitor] Started (interval: 30000ms)');
monitoringInterval = setInterval(async () => {
await checkPendingTransactions();
}, checkInterval);
// Run immediately
checkPendingTransactions();
};
const stopMonitoring = () => {
if (monitoringInterval) {
clearInterval(monitoringInterval);
monitoringInterval = null;
console.log('โน๏ธ [PaymentMonitor] Stopped');
}
};
const checkPendingTransactions = async () => {
try {
console.log('[PaymentMonitor] ๐ Scanning payments...');
const pendingTransactions = await MoneroTransaction.find({
status: 'pending',
expiresAt: { $gt: new Date() }
});
console.log(`[PaymentMonitor] Found ${pendingTransactions.length} orders to verify.`);
for (const transaction of pendingTransactions) {
try {
const result = await moneroService.checkPayment(transaction._id);
if (result.status === 'confirmed') {
console.log(`โ
[PaymentMonitor] Payment confirmed for transaction ${transaction._id}`);
}
} catch (error) {
console.error(`โ [PaymentMonitor] Error checking transaction ${transaction._id}:`, error);
}
}
} catch (error) {
console.error('โ [PaymentMonitor] Error scanning payments:', error);
}
};
module.exports = {
startMonitoring,
stopMonitoring,
checkPendingTransactions
};
๐ณ 7. Environment Configuration
.env File
env
Server
PORT=3000
JWT_SECRET=your_super_secret_jwt_key_here
MongoDB
MONGODB_URI=mongodb://localhost:27017/myzubster
Monero RPC
MONERO_WALLET_RPC_URL=http://localhost:18083/json_rpc
MONERO_DAEMON_RPC_URL=http://localhost:18081/json_rpc
MONERO_RPC_USER=myzubster
MONERO_RPC_PASSWORD=your_secure_password
MONERO_NETWORK=stagenet
MONERO_WALLET_FILE=./myzubster_wallet
๐ง 8. Installation & Setup
Install Dependencies
bash
cd ~/MyZubsterGateway
npm init -y
npm install express cors mongoose dotenv jsonwebtoken bcrypt axios
npm install -D nodemon
Systemd Service
Create /etc/systemd/system/myzubster-gateway.service:
ini
[Unit]
Description=MyZubster Gateway
After=network.target mongod.service
[Service]
Type=simple
WorkingDirectory=/root/MyZubsterGateway
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
User=root
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Start Services
bash
Start MongoDB
systemctl start mongod
systemctl enable mongod
Start Monero Daemon (stagenet)
cd ~/monero
./monerod --stagenet --detach
Start Monero Wallet RPC
nohup ./monero-wallet-rpc \
--rpc-bind-port 18083 \
--daemon-address localhost:18081 \
--wallet-file ./myzubster_wallet \
--password 'your_password' \
--disable-rpc-login \
--trusted-daemon \
--stagenet > monero-wallet-rpc.log 2>&1 &
Start Gateway
systemctl start myzubster-gateway
systemctl enable myzubster-gateway
๐งช 9. Testing the System
Test Authentication
bash
Register
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test123!","username":"testuser"}'
Login
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test123!"}' | jq -r '.token')
echo "TOKEN: $TOKEN"
Test Token Creation
bash
curl -X POST http://localhost:3000/api/tokens \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "MyToken",
"symbol": "MTK",
"totalSupply": 1000,
"assetValue": 50000,
"tokenPrice": 50,
"contractAddress": "0x1234567890abcdef",
"assetType": "realestate",
"assetDescription": "A premium property in Milan",
"assetLocation": "Milan, Italy",
"blockchain": "tari"
}' | jq '.'
Test Marketplace
bash
TOKEN_ID="YOUR_TOKEN_ID"
Create sell order
ORDER_ID=$(curl -s -X POST http://localhost:3000/api/marketplace/sell \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tokenId":"'$TOKEN_ID'","amount":5,"price":1.5}' | jq -r '.order._id')
echo "ORDER_ID: $ORDER_ID"
View open orders
curl http://localhost:3000/api/marketplace/orders/$TOKEN_ID | jq '.'
Buy from order
curl -X POST http://localhost:3000/api/marketplace/buy/$ORDER_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount":2}' | jq '.'
Test Monero Payments
bash
Create payment
PAYMENT=$(curl -s -X POST http://localhost:3000/api/payments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId":"'$ORDER_ID'","amount":7.5}' | jq '.payment')
echo "PAYMENT: $PAYMENT"
Check payment status
curl http://localhost:3000/api/payments/$(echo $PAYMENT | jq -r '.transactionId') \
-H "Authorization: Bearer $TOKEN" | jq '.'
๐ 10. API Reference
Endpoint Method Auth Description
/api/auth/register POST No Register new user
/api/auth/login POST No Login user
/api/tokens GET No List active tokens
/api/tokens POST Yes Create token
/api/tokens/holdings GET Yes User holdings
/api/tokens/:id GET No Token details
/api/marketplace/orders/:tokenId GET No List open orders
/api/marketplace/sell POST Yes Create sell order
/api/marketplace/buy/:orderId POST Yes Buy from order
/api/marketplace/order/:orderId DELETE Yes Cancel order
/api/payments POST Yes Create payment
/api/payments/:id GET Yes Check payment
/api/payments/order/:orderId GET Yes Get order payment
/api/health GET No Health check
๐ฏ Key Features Implemented
โ
JWT Authentication - Secure user authentication with role-based access
โ
Token Management - Create and manage digital tokens with metadata
โ
Order Book - Sell/buy orders with price matching
โ
Token Holdings - Track user token balances with locked amounts
โ
Monero Integration - Real cryptocurrency payments with RPC
โ
Subaddress Generation - Unique addresses per transaction for privacy
โ
Payment Monitoring - Automatic transaction verification
โ
RESTful API - Clean and well-documented endpoints
โ
MongoDB Persistence - Reliable data storage with Mongoose ODM
โ
Production Ready - Systemd service management
๐ Security Considerations
JWT Tokens - Use strong secrets, short expiration times
Password Hashing - Use bcrypt with sufficient salt rounds
Monero RPC - Enable authentication, use HTTPS in production
Environment Variables - Never commit sensitive data
Input Validation - Validate all user inputs
Rate Limiting - Implement to prevent abuse
CORS - Restrict to trusted origins
๐ Future Improvements
WebSocket Support - Real-time order book updates
Multi-currency Support - Bitcoin, Ethereum, etc.
Order Matching Engine - Automated buy/sell matching
Market Data APIs - Price feeds and analytics
Admin Dashboard - User and transaction management
Mobile App - React Native or Flutter client
Smart Contract Integration - On-chain settlement
Analytics - Trading volume and user metrics
๐ Conclusion
This decentralized marketplace demonstrates how to build a production-ready application that combines traditional web technologies with cryptocurrency payments. The architecture is modular, extensible, and follows best practices for Node.js development.
The integration with Monero provides privacy-preserving payments, while the JWT-based authentication ensures secure access control. MongoDB offers flexible document storage for tokens, orders, and transactions.
๐ Resources
Node.js Documentation
Express.js Guide
MongoDB Manual
Monero RPC Documentation
JWT.io
Mongoose ODM
Top comments (0)