π MyZubster Escrow System: Deep Dive Guide
Complete technical guide to multi-signature escrow β from order creation to dispute resolution
π What is the Escrow System?
MyZubster's escrow system is a multi-signature smart contract that secures transactions between buyers and sellers. It ensures that:
β
Funds are locked until service is delivered
β
Buyer is protected β money is only released when satisfied
β
Seller is protected β guaranteed payment if service is delivered
β
Dispute resolution β fair arbitration for conflicts
π§© Architecture Overview
text
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MyZubster Escrow System β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Order Lifecycle β β
β β β β
β β PENDING ββββΊ PAID ββββΊ IN_PROGRESS ββββΊ COMPLETED β β
β β β β β β β β
β β βΌ βΌ βΌ βΌ β β
β β ββββββββββ ββββββββββ ββββββββββ ββββββββββ β β
β β β Locked β βLocked β βLocked β βReleasedβ β β
β β β XMR β β XMR β β XMR β β XMR β β β
β β ββββββββββ ββββββββββ ββββββββββ ββββββββββ β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Buyer β β Seller β β Arbitrator β β
β β 1 key β β 1 key β β 1 key β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β β
β βΌ βΌ βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 2-of-3 Multi-Signature Escrow β β
β β (Funds require 2 of 3 signatures) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π οΈ Escrow Lifecycle
The Complete Flow
text
ββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ
β Buyer β β Marketplaceβ β Gateway β β Monero β
β β β β β (Escrow) β β Blockchain β
ββββββ¬ββββββ βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ
β β β β
β 1. Creates β β β
β Order with β β β
β Skill ID β β β
βββββββββββββββββββΆβ β β
β β β β
β β 2. Request β β
β β Payment β β
β β (POST /initiate) β β
β ββββββββββββββββββββΆβ β
β β β β
β β β 3. Generate β
β β β Subaddress β
β β ββββββββββββββββββββΆβ
β β β β
β β β 4. Return β
β β β Subaddress β
β β 5. Return βββββββββββββββββββββ
β 6. Show β Subaddress β β
β Address βββββββββββββββββββββ β
ββββββββββββββββββββ β β
β β β β
β 7. Send β β β
β Monero to β β β
β Subaddress β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΆβ
β β β β
β β β 8. Monitor β
β β β Blockchain β
β β β (every 60s) β
β β ββββββββββββββββββββΆβ
β β β β
β β β 9. Payment β
β β β Detected β
β β βββββββββββββββββββββ
β β β β
β β 10. Webhook β β
β β (Payment Conf) β β
β βββββββββββββββββββββ β
β β β β
β β 11. Update β β
β 12. Order β Order Status β β
β Pending β Paid β to "paid" β β
ββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β β β
β 13. Request β β β
β Service β β β
βββββββββββββββββββΆβ β β
β β β β
β β 14. Seller β β
β β Starts Work β β
β β (status: β β
β 15. Seller β in_progress) β β
β Working βββββββββββββββββββββ β
ββββββββββββββββββββ β β
β β β β
β 16. Delivers β β β
β Service β β β
βββββββββββββββββββΆβ β β
β β β β
β 17. Buyer β β β
β Confirms β β β
β Completion β β β
βββββββββββββββββββΆβ β β
β β β β
β β 18. Release β β
β β Funds (Escrow) β β
β ββββββββββββββββββββΆβ β
β β β β
β β β 19. Transfer β
β β β Funds to Seller β
β 20. Order β ββββββββββββββββββββΆβ
β Completed β β β
ββββββββββββββββββββ β β
β β β β
π Complete File Structure
text
marketplace/
βββ models/
β βββ Order.js # Order model with escrow status
β βββ Escrow.js # Escrow transaction model
β βββ Dispute.js # Dispute resolution model
βββ routes/
β βββ orders.js # Order management routes
β βββ escrow.js # Escrow release routes
β βββ disputes.js # Dispute resolution routes
βββ services/
β βββ escrowService.js # Core escrow logic
β βββ disputeService.js # Dispute handling
βββ middleware/
β βββ escrowAuth.js # Escrow-specific auth
β βββ rateLimiter.js # Rate limiting
βββ tests/
βββ escrow.test.js
βββ disputes.test.js
βββ release.test.js
π§ Complete Code Walkthrough
- Escrow Model (models/Escrow.js)
File: marketplace/models/Escrow.js
javascript
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Escrow = sequelize.define('Escrow', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
orderId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Orders',
key: 'id'
}
},
amount: {
type: DataTypes.FLOAT,
allowNull: false
},
currency: {
type: DataTypes.STRING,
defaultValue: 'USD'
},
moneroAmount: {
type: DataTypes.FLOAT,
allowNull: false
},
moneroAddress: {
type: DataTypes.STRING,
allowNull: false
},
moneroTxHash: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.ENUM('pending', 'funded', 'released', 'refunded', 'disputed'),
defaultValue: 'pending'
},
buyerPublicKey: {
type: DataTypes.TEXT,
allowNull: true
},
sellerPublicKey: {
type: DataTypes.TEXT,
allowNull: true
},
arbitratorPublicKey: {
type: DataTypes.TEXT,
allowNull: true
},
releaseSignatures: {
type: DataTypes.JSON,
defaultValue: []
},
disputedAt: {
type: DataTypes.DATE,
allowNull: true
},
releasedAt: {
type: DataTypes.DATE,
allowNull: true
},
refundedAt: {
type: DataTypes.DATE,
allowNull: true
},
expiresAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
timestamps: true,
tableName: 'Escrows'
});
// Associations
Escrow.associate = (models) => {
Escrow.belongsTo(models.Order, { foreignKey: 'orderId' });
};
return Escrow;
};
- Dispute Model (models/Dispute.js)
File: marketplace/models/Dispute.js
javascript
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Dispute = sequelize.define('Dispute', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
escrowId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Escrows',
key: 'id'
}
},
initiatorId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
reason: {
type: DataTypes.TEXT,
allowNull: false
},
evidence: {
type: DataTypes.JSON,
defaultValue: []
},
status: {
type: DataTypes.ENUM('open', 'investigating', 'resolved_buyer', 'resolved_seller', 'resolved_partial', 'dismissed'),
defaultValue: 'open'
},
resolution: {
type: DataTypes.TEXT,
allowNull: true
},
arbitratorId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'Users',
key: 'id'
}
},
resolvedAt: {
type: DataTypes.DATE,
allowNull: true
},
resolutionAmount: {
type: DataTypes.FLOAT,
allowNull: true
}
}, {
timestamps: true,
tableName: 'Disputes'
});
Dispute.associate = (models) => {
Dispute.belongsTo(models.Escrow, { foreignKey: 'escrowId' });
Dispute.belongsTo(models.User, { as: 'initiator', foreignKey: 'initiatorId' });
Dispute.belongsTo(models.User, { as: 'arbitrator', foreignKey: 'arbitratorId' });
};
return Dispute;
};
- Order Model with Escrow (models/Order.js)
File: marketplace/models/Order.js (updated)
javascript
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Order = sequelize.define('Order', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
buyerId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
sellerId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
skillId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Skills',
key: 'id'
}
},
amount: {
type: DataTypes.FLOAT,
allowNull: false,
validate: {
min: 0
}
},
currency: {
type: DataTypes.STRING,
defaultValue: 'USD'
},
status: {
type: DataTypes.ENUM(
'pending',
'paid',
'in_progress',
'completed',
'cancelled',
'disputed',
'refunded'
),
defaultValue: 'pending'
},
// Escrow details
escrowId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'Escrows',
key: 'id'
}
},
moneroAddress: {
type: DataTypes.STRING,
allowNull: true
},
moneroTxHash: {
type: DataTypes.STRING,
allowNull: true
},
paidAt: {
type: DataTypes.DATE,
allowNull: true
},
completedAt: {
type: DataTypes.DATE,
allowNull: true
},
cancelledAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
timestamps: true,
tableName: 'Orders'
});
// Associations
Order.associate = (models) => {
Order.belongsTo(models.User, { as: 'buyer', foreignKey: 'buyerId' });
Order.belongsTo(models.User, { as: 'seller', foreignKey: 'sellerId' });
Order.belongsTo(models.Skill, { foreignKey: 'skillId' });
Order.belongsTo(models.Escrow, { foreignKey: 'escrowId' });
};
return Order;
};
- Escrow Service (services/escrowService.js)
File: marketplace/services/escrowService.js
javascript
const { Order, Escrow, User } = require('../models');
const { Op } = require('sequelize');
const crypto = require('crypto');
// ============================================
// CREATE ESCROW FOR ORDER
// ============================================
const createEscrow = async (orderId, amount, moneroAddress, moneroAmount) => {
try {
// Get order with buyer and seller
const order = await Order.findByPk(orderId, {
include: [
{ association: 'buyer' },
{ association: 'seller' }
]
});
if (!order) {
throw new Error('Order not found');
}
// Check if escrow already exists
const existingEscrow = await Escrow.findOne({ where: { orderId } });
if (existingEscrow) {
return existingEscrow;
}
// Create escrow
const escrow = await Escrow.create({
orderId: order.id,
amount: amount || order.amount,
currency: order.currency || 'USD',
moneroAmount: moneroAmount,
moneroAddress: moneroAddress,
status: 'pending',
buyerPublicKey: order.buyer.pgpPublicKey || null,
sellerPublicKey: order.seller.pgpPublicKey || null,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days expiry
});
// Update order with escrow ID
await order.update({ escrowId: escrow.id });
return escrow;
} catch (error) {
console.error('β Error creating escrow:', error);
throw error;
}
};
// ============================================
// RELEASE FUNDS TO SELLER
// ============================================
const releaseFunds = async (orderId, signature, signedBy) => {
try {
const order = await Order.findByPk(orderId, {
include: [
{ association: 'buyer' },
{ association: 'seller' },
{ association: 'escrow' }
]
});
if (!order) {
throw new Error('Order not found');
}
if (order.status !== 'paid' && order.status !== 'in_progress') {
throw new Error('Order not in correct status for release');
}
if (order.escrowId === null) {
throw new Error('No escrow found for this order');
}
const escrow = await Escrow.findByPk(order.escrowId);
if (!escrow) {
throw new Error('Escrow not found');
}
// Verify signature (in production, use proper PGP/ECDSA verification)
const isValidSignature = await verifySignature(escrow, signature, signedBy);
if (!isValidSignature) {
throw new Error('Invalid signature');
}
// Add signature to release signatures
const signatures = escrow.releaseSignatures || [];
if (!signatures.includes(signedBy)) {
signatures.push(signedBy);
await escrow.update({ releaseSignatures: signatures });
}
// Check if enough signatures (2 of 3)
const totalSignatures = signatures.length;
const requiredSignatures = 2;
if (totalSignatures >= requiredSignatures) {
// Release funds!
escrow.status = 'released';
escrow.releasedAt = new Date();
await escrow.save();
order.status = 'completed';
order.completedAt = new Date();
await order.save();
// Trigger Monero payment to seller (via Gateway)
await triggerSellerPayment(orderId, escrow);
return { success: true, escrow, order };
}
return { success: true, signatures: totalSignatures, required: requiredSignatures, escrow };
} catch (error) {
console.error('β Error releasing funds:', error);
throw error;
}
};
// ============================================
// REFUND BUYER
// ============================================
const refundFunds = async (orderId, signature, signedBy) => {
try {
const order = await Order.findByPk(orderId, {
include: [
{ association: 'buyer' },
{ association: 'seller' },
{ association: 'escrow' }
]
});
if (!order) {
throw new Error('Order not found');
}
if (order.status === 'completed') {
throw new Error('Order already completed');
}
if (order.escrowId === null) {
throw new Error('No escrow found for this order');
}
const escrow = await Escrow.findByPk(order.escrowId);
if (!escrow) {
throw new Error('Escrow not found');
}
// Check if refund is valid
const isValid = await verifyRefund(escrow, signature, signedBy);
if (!isValid) {
throw new Error('Invalid refund request');
}
// Process refund
escrow.status = 'refunded';
escrow.refundedAt = new Date();
await escrow.save();
order.status = 'refunded';
order.cancelledAt = new Date();
await order.save();
// Trigger Monero refund to buyer (via Gateway)
await triggerBuyerRefund(orderId, escrow);
return { success: true, escrow, order };
} catch (error) {
console.error('β Error refunding funds:', error);
throw error;
}
};
// ============================================
// VERIFY SIGNATURE
// ============================================
const verifySignature = async (escrow, signature, signedBy) => {
try {
// In production, implement proper PGP or ECDSA verification
// For now, we'll do a simple check
const dataToVerify = `${escrow.id}:${escrow.moneroAddress}:${escrow.amount}`;
const expectedSignature = crypto
.createHash('sha256')
.update(dataToVerify + escrow.orderId)
.digest('hex');
// For test purposes, accept if signature matches expected
// In production, use actual PGP/ECDSA library
return signature === expectedSignature || true; // Simplified for demo
} catch (error) {
console.error('β Error verifying signature:', error);
return false;
}
};
// ============================================
// VERIFY REFUND
// ============================================
const verifyRefund = async (escrow, signature, signedBy) => {
try {
// Similar logic to verify refund request
const dataToVerify = REFUND:${escrow.id}:${escrow.moneroAddress}:${escrow.amount};
const expectedSignature = crypto
.createHash('sha256')
.update(dataToVerify + escrow.orderId)
.digest('hex');
return signature === expectedSignature || true; // Simplified for demo
} catch (error) {
console.error('β Error verifying refund:', error);
return false;
}
};
// ============================================
// TRIGGER SELLER PAYMENT
// ============================================
const triggerSellerPayment = async (orderId, escrow) => {
try {
const order = await Order.findByPk(orderId, {
include: [
{ association: 'seller' },
{ association: 'buyer' }
]
});
// Call Gateway to release funds to seller
const axios = require('axios');
const response = await axios.post(
`${process.env.MYZUBSTER_API_URL}/payments/release`,
{
orderId: order.id,
escrowId: escrow.id,
sellerAddress: order.seller.moneroAddress,
amount: escrow.amount,
moneroAmount: escrow.moneroAmount
},
{
headers: {
'Authorization': `Bearer ${process.env.MYZUBSTER_API_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log(`β
Seller payment triggered for order ${orderId}`);
return response.data;
} catch (error) {
console.error('β Error triggering seller payment:', error);
throw error;
}
};
// ============================================
// TRIGGER BUYER REFUND
// ============================================
const triggerBuyerRefund = async (orderId, escrow) => {
try {
const order = await Order.findByPk(orderId, {
include: [
{ association: 'buyer' }
]
});
// Call Gateway to refund buyer
const axios = require('axios');
const response = await axios.post(
`${process.env.MYZUBSTER_API_URL}/payments/refund`,
{
orderId: order.id,
escrowId: escrow.id,
buyerAddress: order.buyer.moneroAddress,
amount: escrow.amount,
moneroAmount: escrow.moneroAmount
},
{
headers: {
'Authorization': `Bearer ${process.env.MYZUBSTER_API_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log(`β
Buyer refund triggered for order ${orderId}`);
return response.data;
} catch (error) {
console.error('β Error triggering buyer refund:', error);
throw error;
}
};
// ============================================
// GET ESCROW STATUS
// ============================================
const getEscrowStatus = async (orderId) => {
try {
const order = await Order.findByPk(orderId, {
include: [
{ association: 'escrow' }
]
});
if (!order) {
throw new Error('Order not found');
}
if (!order.escrow) {
return { status: 'no_escrow', orderStatus: order.status };
}
return {
escrowId: order.escrow.id,
status: order.escrow.status,
amount: order.escrow.amount,
moneroAmount: order.escrow.moneroAmount,
moneroAddress: order.escrow.moneroAddress,
createdAt: order.escrow.createdAt,
expiresAt: order.escrow.expiresAt,
releaseSignatures: order.escrow.releaseSignatures.length,
requiredSignatures: 2,
orderStatus: order.status
};
} catch (error) {
console.error('β Error getting escrow status:', error);
throw error;
}
};
module.exports = {
createEscrow,
releaseFunds,
refundFunds,
getEscrowStatus
};
- Escrow Routes (routes/escrow.js)
File: marketplace/routes/escrow.js
javascript
const express = require('express');
const router = express.Router();
const { Order } = require('../models');
const {
releaseFunds,
refundFunds,
getEscrowStatus
} = require('../services/escrowService');
const auth = require('../middleware/auth');
// ============================================
// GET /api/escrow/status/:orderId
// Check escrow status
// ============================================
router.get('/status/:orderId', auth, async (req, res) => {
try {
const status = await getEscrowStatus(req.params.orderId);
res.json(status);
} catch (error) {
console.error('β Error fetching escrow status:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/escrow/release/:orderId
// Release funds to seller (buyer signs)
// ============================================
router.post('/release/:orderId', auth, async (req, res) => {
try {
const { signature } = req.body;
// Verify the user is the buyer
const order = await Order.findByPk(req.params.orderId);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
if (order.buyerId !== req.user.id) {
return res.status(403).json({ error: 'Only the buyer can release funds' });
}
const result = await releaseFunds(req.params.orderId, signature, req.user.id);
res.json(result);
} catch (error) {
console.error('β Error releasing funds:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/escrow/refund/:orderId
// Refund buyer (seller signs)
// ============================================
router.post('/refund/:orderId', auth, async (req, res) => {
try {
const { signature } = req.body;
// Verify the user is the seller
const order = await Order.findByPk(req.params.orderId);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
if (order.sellerId !== req.user.id) {
return res.status(403).json({ error: 'Only the seller can request refund' });
}
const result = await refundFunds(req.params.orderId, signature, req.user.id);
res.json(result);
} catch (error) {
console.error('β Error refunding funds:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/escrow/dispute/:orderId
// Open a dispute
// ============================================
router.post('/dispute/:orderId', auth, async (req, res) => {
try {
const { reason, evidence } = req.body;
const order = await Order.findByPk(req.params.orderId);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
// Only buyer or seller can open a dispute
if (order.buyerId !== req.user.id && order.sellerId !== req.user.id) {
return res.status(403).json({ error: 'Not authorized to dispute this order' });
}
// Update order status to disputed
await order.update({ status: 'disputed' });
// Update escrow status to disputed
const escrow = await Escrow.findOne({ where: { orderId: order.id } });
if (escrow) {
await escrow.update({
status: 'disputed',
disputedAt: new Date()
});
}
// Create dispute record
const Dispute = require('../models/Dispute');
const dispute = await Dispute.create({
escrowId: escrow.id,
initiatorId: req.user.id,
reason: reason || 'Dispute opened by user',
evidence: evidence || [],
status: 'open'
});
// Notify admin/arbitrator (in production, use email/notification)
console.log(`β οΈ Dispute #${dispute.id} opened for order ${order.id}`);
res.json({
success: true,
disputeId: dispute.id,
orderId: order.id,
status: 'disputed'
});
} catch (error) {
console.error('β Error opening dispute:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;
- Dispute Service (services/disputeService.js)
File: marketplace/services/disputeService.js
javascript
const { Dispute, Escrow, Order, User } = require('../models');
const { refundFunds, releaseFunds } = require('./escrowService');
// ============================================
// RESOLVE DISPUTE
// ============================================
const resolveDispute = async (disputeId, resolution, arbitratorId) => {
try {
const dispute = await Dispute.findByPk(disputeId, {
include: [
{ association: 'escrow' }
]
});
if (!dispute) {
throw new Error('Dispute not found');
}
if (dispute.status !== 'open' && dispute.status !== 'investigating') {
throw new Error('Dispute already resolved');
}
const escrow = await Escrow.findByPk(dispute.escrowId);
if (!escrow) {
throw new Error('Escrow not found');
}
// Update dispute
dispute.status = 'resolved_' + resolution.type;
dispute.resolution = resolution.text;
dispute.arbitratorId = arbitratorId;
dispute.resolvedAt = new Date();
dispute.resolutionAmount = resolution.amount || escrow.amount;
await dispute.save();
// Process resolution
const order = await Order.findByPk(escrow.orderId);
if (resolution.type === 'buyer') {
// Refund buyer
await refundFunds(order.id, 'arbitrator_signature', arbitratorId);
} else if (resolution.type === 'seller') {
// Release funds to seller
await releaseFunds(order.id, 'arbitrator_signature', arbitratorId);
} else if (resolution.type === 'partial') {
// Partial resolution (split funds)
// In production, implement partial release logic
// For now, release full amount to seller
await releaseFunds(order.id, 'arbitrator_signature', arbitratorId);
}
return { success: true, dispute, escrow, order };
} catch (error) {
console.error('β Error resolving dispute:', error);
throw error;
}
};
// ============================================
// GET DISPUTES
// ============================================
const getDisputes = async (filters = {}) => {
try {
const where = {};
if (filters.status) where.status = filters.status;
if (filters.arbitratorId) where.arbitratorId = filters.arbitratorId;
if (filters.initiatorId) where.initiatorId = filters.initiatorId;
const disputes = await Dispute.findAll({
where,
include: [
{ association: 'initiator' },
{ association: 'arbitrator' },
{ association: 'escrow' }
],
order: [['createdAt', 'DESC']]
});
return disputes;
} catch (error) {
console.error('β Error fetching disputes:', error);
throw error;
}
};
module.exports = {
resolveDispute,
getDisputes
};
π§ Environment Variables (Additional)
File: marketplace/.env (updated)
env
Existing variables...
PORT=4000
NODE_ENV=development
DATABASE_URL=sqlite:./database.sqlite
JWT_SECRET=your_jwt_secret
Escrow settings
ESCROW_EXPIRY_DAYS=30
MIN_CONFIRMATIONS=10
ESCROW_COMMISSION=2.0
ARBITRATOR_FEE=5.0
API Settings
MYZUBSTER_API_URL=http://localhost:3000/api
MYZUBSTER_API_TOKEN=your_admin_token
WEBHOOK_SECRET=your_webhook_secret
π§ͺ Test Commands
Create Order (with Escrow)
bash
curl -X POST http://localhost:4000/api/orders \
-H "Authorization: Bearer $BUYER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"skillId":1}'
Check Escrow Status
bash
curl -X GET http://localhost:4000/api/escrow/status/1 \
-H "Authorization: Bearer $BUYER_TOKEN"
Release Funds (Buyer)
bash
curl -X POST http://localhost:4000/api/escrow/release/1 \
-H "Authorization: Bearer $BUYER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"signature":"buyer_signature"}'
Open Dispute
bash
curl -X POST http://localhost:4000/api/escrow/dispute/1 \
-H "Authorization: Bearer $BUYER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason":"Service not delivered as described","evidence":["screenshot1.png","screenshot2.png"]}'
π Security Best Practices
Practice Implementation
Multi-Signature 2-of-3 keys required for release
Signature Verification PGP/ECDSA signatures for each party
Expiration Escrow expires after 30 days
Dispute Resolution Fair arbitration process
Commission 2% fee on successful transactions
Audit Trail All escrow events logged
π Escrow Status Transitions
text
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Escrow Status Transitions β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββ β
β β PENDING β β
β ββββββ¬ββββββ β
β β β
β βΌ β
β ββββββββββββ Payment confirmed ββββββββββββ β
β β FUNDED ββββββββββββββββββββββββΆβ PAID β β
β ββββββ¬ββββββ ββββββ¬ββββββ β
β β β β
β β Dispute β Dispute β
β βΌ βΌ β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β DISPUTED ββββββΆβRELEASED βββββββIN_PROGRESSβ β
β ββββββ¬ββββββ ββββββββββββ ββββββββββββ β
β β β β
β β βΌ β
β β ββββββββββββ β
β ββββββββββββββββΆβ REFUNDED β β
β ββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Common Issues & Solutions
"Escrow expired"
javascript
// Check if escrow is expired
const escrow = await Escrow.findByPk(escrowId);
if (escrow.expiresAt < new Date()) {
// Auto-refund or extend
await refundFunds(orderId, 'system_signature', 'system');
}
"Insufficient signatures"
javascript
// Check signature count
const signatures = escrow.releaseSignatures || [];
const requiredSignatures = 2;
if (signatures.length >= requiredSignatures) {
// Release funds
} else {
// Wait for more signatures
}
"Dispute opened"
javascript
// Handle dispute
const dispute = await Dispute.create({ ... });
// Notify arbitrator
await notifyArbitrator(dispute);
π Resources
Monero Escrow: monero-escrow
PGP Encryption: openpgpjs
MyZubster GitHub: github.com/DanielIoni-creator
β Next Steps
Implement PGP verification β Replace simplified signature checks
Add email notifications β Notify users of escrow events
Implement dispute arbitration β Admin dashboard for dispute management
Add auto-refund β For expired escrows
Write tests β Unit and integration tests for escrow logic
Built with β€οΈ for privacy, freedom, and decentralization.
Happy coding! π
Top comments (0)