Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

approval-queue

Deposit and withdrawal approvals for an investment platform admin panel. Decides what can clear automatically and what a human has to look at, and moves requests between states without letting them go somewhere they should not.

Zero dependencies, one file, runs anywhere ES modules run.

Try the queue in your browser

Why it exists

Every platform starts with manual approval, which is correct. Then the queue gets to two hundred a day and somebody adds an auto approve, usually as an if amount < x in a controller, and the rules end up scattered across four files with no record of which one fired.

This keeps the decision in one place, gives you the reasons back so the panel can show the operator why a request is in front of them, and refuses the state changes that break a ledger. An approved request cannot be approved again. A rejection without a reason is an error, not a nullable column.

The states

pending  ->  approved | rejected | held
held     ->  approved | rejected
approved ->  nothing
rejected ->  nothing

Held is the one people leave out. Without it your only options are to pay something that looks wrong or to reject a customer you have not finished checking, and you will end up doing neither and letting it sit in pending with no marker on it.

Deciding

import { createRequest, evaluate } from './approvals.js';

const request = createRequest({
  id: 'w-4192',
  userId: 'u-88',
  kind: 'withdrawal',
  amount: 2500,            // 25.00 in minor units
  address: 'bc1qexample',
});

evaluate(request, {
  autoApproveUnder: 5000,
  holdAbove: 500000,
  dailyUserLimit: 100000,
  velocity: { count: 3, windowMinutes: 60 },
}, { now: new Date(), history: settledRequests });

Comes back as one of three actions with the reasons attached:

{ action: 'review', reasons: ['address-not-used-before'] }
  • auto clears without a human
  • review goes to the normal queue
  • hold goes to the queue flagged, something looks off

The worst outcome wins. A request that trips both a review rule and a hold rule is held.

The rules

Policy What it does
autoApproveUnder anything at or over this goes to a human
holdAbove large amounts are flagged rather than merely queued
firstRequestIsManual a user with nothing approved yet is always looked at, on by default
newAddressIsManual first withdrawal to an unseen address is looked at, on by default
requireProof deposits with no payment proof attached go to a human, on by default
dailyUserLimit counts what the user already had approved in the last 24 hours
velocity too many requests inside a window gets held
manualOnly switch the whole thing off, everything goes to a human

Running the queue

import { process } from './approvals.js';

const { approved, review, held } = process(queue, policy, { now, history }, { actor: 'policy' });

Automatic approvals are applied and everything else comes back untouched, sorted into the two piles a person works from. Anything cleared inside the batch becomes history for the requests after it, so a user cannot slide three small withdrawals past a daily limit by sending them together.

Automatic approvals are recorded with the actor you pass in, policy by default, so the audit trail does not pretend a person was sitting there.

Applying a decision

import { transition, APPROVED, REJECTED } from './approvals.js';

const done = transition(request, { to: APPROVED, actor: 'anthony' });
const no = transition(request, { to: REJECTED, actor: 'anthony', reason: 'address does not match the one on file' });

Returns a new object and appends to audit. Credit the balance on the result of this call, never before it.

Dashboard numbers

summarise(requests);
// { pending, held, approved, rejected, openCount, openAmount, approvedAmount }

waiting(requests, { targetMinutes: 60 });
// { oldestMinutes, breached, queue }

openAmount is money you have promised and not yet sent. breached is everything past the window you advertise, oldest first, which is the list worth working before anything else.

What it does not do

No balances, no wallets, no sending. It decides and it records. Actually moving money is the part that should stay in your own code where you can see it.

Tests

node test.js

Twenty five of them, no framework.

Notes

Written while building investment platform software at investmentscript.com. The panel these rules were pulled out of is walked through screen by screen in the admin panel overview, including the withdrawal queue and the deposit proof step.

MIT licensed.

About

Deposit and withdrawal approval rules and state machine for an investment platform admin panel. Zero dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages