npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

saksh-escrow

v2.0.4

Published

Production-ready escrow service — dispute resolution, SLA monitoring, role-based permissions

Readme

saksh-escrow

A production-ready escrow service library for Node.js, backed by MongoDB and the saksh-easy-wallet payment library.

Author: Susheel Kumar — [email protected] Open to freelance and full-time remote opportunities. Reach out anytime!


How it works

Escrow Flow:

  1. Sender initiates escrow → funds move to Escrow Admin wallet
  2. Escrow enters PENDING state
  3. Three possible paths:
    • Release: Admin releases funds to Receiver (COMPLETED)
    • Cancel: Admin refunds funds to Sender (CANCELED)
    • Dispute: Any party raises dispute (DISPUTED)

Dispute Resolution Flow:

  1. DISPUTED state with evidence collection
  2. Escalation levels: support → management → admin
  3. Optional MEDIATION with assigned mediator
  4. Resolution: either release (to receiver) or refund (to sender)

SLA Monitoring:

  • Automated cron job checks dispute age
  • Auto-escalates when SLA thresholds exceeded
  • Emits events for monitoring/alerting

Where it can be used

High-Trust / High-Value Use Cases:

  • Freelance platforms (milestone-based payments)
  • E-commerce (hold funds until delivery confirmed)
  • Real estate (deposit and deed transfer)

Speed-Sensitive / High-Volume Use Cases:

  • Gig economy (driver/delivery apps)
  • SaaS platforms (trial-to-subscription hold)
  • Gaming / NFTs (digital asset trading)

Features

  • Initiate, release, and cancel escrow transactions
  • Raise disputes with categorized reasons and URL evidence
  • Multi-level escalation: support → management → admin
  • Mediation support with an assigned mediator
  • Role-based permission system (sender, receiver, mediator, admin)
  • Automatic SLA compliance monitoring with event emission
  • Cryptographically secure reference IDs (crypto.randomInt)
  • Atomic fund transfers (MongoDB session-backed, idempotent)
  • Test seed data with one command
  • JSON import/export for database backup and restore

Requirements

  • Node.js 16+
  • MongoDB replica set (required for atomic transactions)
mongod --replSet rs0
mongosh --eval "rs.initiate()"

Installation

npm install saksh-escrow saksh-easy-wallet mongoose

Environment Variables

| Variable | Default | Description | |----------|---------|-------------| | ESCROW_ADMIN_EMAIL | [email protected] | Wallet account that holds funds in escrow | | ADMIN_EMAIL | [email protected] | Wallet admin for fee collection | | MONGO_URI | mongodb://localhost:27017/sakshescrow | MongoDB connection string | | NODE_ENV | development | Environment (production enables stricter validation) | | LOG_LEVEL | info | Logging level (debug, info, warn, error) |

Put these in a .env file — never commit it.


⚠️ Important Security Warning

Never use the default ESCROW_ADMIN_EMAIL in production.

The default [email protected] is publicly known. Attackers could attempt to:

  • Social engineer support to release funds
  • Create fake disputes targeting this account

Always override with your own secure admin email.


Test Data & Database Utilities

Load seed data (one command)

node scripts/db.js seed

This loads data/seed.json with ready-made users, wallets, and escrows in every state:

| Email | Balance | Role | |-------|---------|------| | [email protected] | USD 5,000 | Typical sender | | [email protected] | USD 3,000 | Typical receiver | | [email protected] | USD 8,000 | Sender with active dispute | | [email protected] | USD 1,200 | Receiver in mediation | | [email protected] | — | Mediator | | [email protected] | auto | Escrow admin wallet |

Pre-seeded escrows cover all six statuses: pending, completed, canceled, disputed, mediation.

All database commands

node scripts/db.js seed              # Load data/seed.json (safe to run twice — upserts)
node scripts/db.js reset             # Drop all managed collections
node scripts/db.js status            # Show document counts per collection
node scripts/db.js export            # Export to data/backup-<timestamp>.json
node scripts/db.js export my.json    # Export to a named file
node scripts/db.js import my.json    # Restore from any exported backup

Customise the seed data

Edit data/seed.json directly — it is plain JSON with $oid and $date extended syntax. Add users, adjust balances, create escrows in whatever state you need for your test scenario.


Quick Start

require('dotenv').config();
const mongoose      = require('mongoose');
const EscrowService = require('saksh-escrow');
const SakshWallet   = require('saksh-easy-wallet');

await mongoose.connect(process.env.MONGO_URI);

const wallet = new SakshWallet();
await wallet.credit('[email protected]', 1000, 'USD', 'SEED-1', 'Deposit');

const escrowService = new EscrowService('[email protected]', 'user');

const escrow = await escrowService.initiateEscrow(
    '[email protected]',
    '[email protected]',
    500, 'USD',
    'Payment for website'
);

await escrowService.releaseEscrow(escrow._id);

Examples

1. Happy Path — Initiate and Release

// examples/happy-path.js
const senderService = new EscrowService('[email protected]', 'user');

const escrow = await senderService.initiateEscrow(
    '[email protected]', '[email protected]',
    500, 'USD', 'Web design services'
);

await senderService.releaseEscrow(escrow._id);
// Funds move: escrow admin → bob

Run it: node examples/happy-path.js

2. Cancel and Refund

const escrow = await senderService.initiateEscrow(
    '[email protected]', '[email protected]',
    200, 'USD', 'Logo design'
);

await senderService.cancelEscrow(escrow._id);
// Funds refunded: escrow admin → alice

3. Full Dispute → Mediation → Resolution

const senderService   = new EscrowService('[email protected]',    'user');
const receiverService = new EscrowService('[email protected]',     'user');
const mediatorService = new EscrowService('[email protected]', 'mediator');
const adminService    = new EscrowService('[email protected]',    'admin');

const escrow = await senderService.initiateEscrow(
    '[email protected]', '[email protected]',
    800, 'USD', 'Mobile app MVP'
);

// Raise dispute
await senderService.raiseDispute(
    escrow._id,
    'App missing agreed features',
    'Product Not as Described'
);

// Both parties submit evidence
await senderService.submitEvidence(escrow._id, 'https://example.com/spec.pdf');
await receiverService.submitEvidence(escrow._id, 'https://example.com/delivery.zip');

// Escalate
await senderService.escalateDispute(escrow._id, 'management');
await adminService.escalateToMediation(escrow._id, '[email protected]');

// Mediator resolves
await mediatorService.resolveMediation(escrow._id, 'refund');
// carol gets her money back

Run it: node examples/dispute-flow.js

4. SLA Compliance Monitor (Cron Job)

// examples/sla-monitor.js
const systemService = new EscrowService('[email protected]', 'admin');

systemService.on('disputeAutoEscalated', ({ escrowId, nextLevel }) => {
    console.log(`Auto-escalated ${escrowId} to ${nextLevel}`);
    // send notification to support team
});

systemService.on('slaBreached', ({ escrowId, currentLevel }) => {
    console.error(`SLA breach: ${escrowId} at ${currentLevel}`);
    // page on-call admin
});

await systemService.checkSLACompliance();

Run it: node examples/sla-monitor.js

Schedule with cron: 0 0 * * * node /path/to/examples/sla-monitor.js

5. Listening to Events

const svc = new EscrowService('[email protected]', 'user');

svc.on('escrowInitiated',  ({ escrowId, amount, currency }) =>
    console.log(`New escrow ${escrowId}: ${amount} ${currency}`));

svc.on('disputeRaised',   ({ escrowId, reasonCategory }) =>
    notifySupport(escrowId, reasonCategory));

svc.on('slaBreached',     ({ escrowId, currentLevel }) =>
    pageAdmin(escrowId, currentLevel));

API Reference

new EscrowService(loggedInUser, role?)

| Parameter | Type | Description | |-----------|------|-------------| | loggedInUser | string | Email of the authenticated user | | role | string | 'user' (default), 'admin', or 'mediator' |

Methods

| Method | Description | Permitted Roles | |--------|-------------|-----------------| | initiateEscrow(sender, receiver, amount, currency, description) | Create escrow, move sender funds to admin | any | | releaseEscrow(escrowId) | Release funds to receiver | sender, admin | | cancelEscrow(escrowId) | Refund funds to sender | sender, admin | | raiseDispute(escrowId, reason, reasonCategory) | Open a dispute | sender, receiver, admin | | submitEvidence(escrowId, url) | Attach evidence URL | sender, receiver, admin | | escalateDispute(escrowId, level) | Escalate support level | sender, receiver, admin | | escalateToMediation(escrowId, mediatorId) | Assign a mediator | sender, receiver, admin | | resolveDispute(escrowId, resolution) | Resolve disputed escrow | admin, mediator | | resolveMediation(escrowId, resolution) | Resolve mediated escrow | admin, mediator | | checkSLACompliance() | Auto-escalate overdue disputes | system job |

Valid reasonCategory values: Fraud, Service Not Received, Product Not as Described, Other

Valid resolution values: 'release' (funds → receiver), 'refund' (funds → sender)

Valid level values: 'support', 'management', 'admin'


Events

escrowService.on('escrowInitiated',      ({ escrowId, senderId, receiverId, amount, currency }) => {});
escrowService.on('escrowReleased',       ({ escrowId }) => {});
escrowService.on('escrowCanceled',       ({ escrowId }) => {});
escrowService.on('disputeRaised',        ({ escrowId, reason, reasonCategory }) => {});
escrowService.on('disputeEscalated',     ({ escrowId, level }) => {});
escrowService.on('escalatedToMediation', ({ escrowId, mediatorId }) => {});
escrowService.on('disputeResolved',      ({ escrowId, resolution }) => {});
escrowService.on('disputeAutoEscalated', ({ escrowId, nextLevel }) => {});
escrowService.on('slaBreached',          ({ escrowId, currentLevel, date }) => {});

Error Handling

try {
    await escrowService.releaseEscrow(escrowId);
} catch (error) {
    if (error.message.includes('not found')) {
        // Escrow doesn't exist
    } else if (error.message.includes('permission')) {
        // User not authorized
    } else if (error.message.includes('PENDING')) {
        // Wrong status for action
    } else {
        // Fund transfer or database error
        console.error('Unexpected error:', error);
    }
}

Escrow Model Schema

Escrow {
  senderId:    String   (required, email)
  receiverId:  String   (required, email)
  amount:      Number   (required)
  currency:    String   (required)
  description: String   (required)
  status:      pending | canceled | completed | disputed | mediation
  dispute: {
    raisedBy:       String
    reason:         String
    reasonCategory: String
    resolved:       Boolean
    escalationLevel: String (support|management|admin)
    escalatedAt:    Date
    evidence:       [String]
    mediatorId:     String
    resolution:     String
  }
  createdAt:   Date
  updatedAt:   Date
}

Production Deployment Checklist

  • [ ] Set NODE_ENV=production
  • [ ] Configure MongoDB replica set (required for transactions)
  • [ ] Set strong ESCROW_ADMIN_EMAIL (not the default)
  • [ ] Implement rate limiting (e.g., express-rate-limit)
  • [ ] Add request idempotency keys for all mutations
  • [ ] Set up monitoring for SLA breach events
  • [ ] Configure webhook endpoints for escrow status changes
  • [ ] Run node scripts/db.js seed only in development
  • [ ] Implement database indexes (see below)
  • [ ] Add health check endpoint for your orchestrator

Database Indexes

Create these indexes for production performance:

// scripts/create-indexes.js
const Escrow = require('./models/Escrow');

async function createIndexes() {
    await Escrow.createIndexes([
        { status: 1 },
        { senderId: 1, status: 1 },
        { receiverId: 1, status: 1 },
        { createdAt: -1 },
        { 'dispute.resolved': 1, status: 1 }
    ]);
    console.log('Indexes created');
}

Project Structure

saksh-escrow/
├── config/
│   └── config.js           Statuses, reason categories, SLA timeframes
├── data/
│   └── seed.json           Ready-made test data (users, wallets, escrows)
├── examples/
│   ├── happy-path.js       Initiate → release
│   ├── dispute-flow.js     Full dispute → mediation → resolve
│   └── sla-monitor.js      Cron-style SLA check
├── models/
│   └── Escrow.js           Mongoose schema
├── scripts/
│   └── db.js               seed / reset / export / import / status
├── EscrowService.js        Main service class
├── PermissionManagement.js Role-based permission checks
└── index.js

FAQ

Q: Why do I need a MongoDB replica set?
A: Atomic transactions (required for fund transfers) only work with replica sets.

Q: Can I use this without saksh-easy-wallet?
A: No, it's a hard dependency for fund management.

Q: How do I handle partial refunds?
A: Not supported in v1. Create multiple escrows for milestone payments.

Q: What happens if the escrow admin loses funds?
A: The admin wallet should be a custodial account with proper backups.

Q: Is DisputeService still used?
A: No, all functionality is in EscrowService. DisputeService.js is deprecated.


Testing

# Unit tests (when implemented)
npm test

# Integration tests (requires MongoDB)
npm run test:integration

# Load testing
npm run test:load

License

MIT © 2026 Susheel Kumar ([email protected])


Hiring? The author is actively looking for freelance and full-time remote opportunities in Node.js, backend architecture, and fintech systems. Contact: [email protected]