DEV Community

Daniel Ioni
Daniel Ioni

Posted on

๐Ÿš€ Building a Decentralized Marketplace with Node.js, MongoDB and Monero

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
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ 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
};
Enter fullscreen mode Exit fullscreen mode

}

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 });
Enter fullscreen mode Exit fullscreen mode

} 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 });
Enter fullscreen mode Exit fullscreen mode

} 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

} 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
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ˆ 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
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“š 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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)