morphit/packages/asset-registry/scripts/first-buy-waiver-payment-agnostic-smoke.ts

185 lines
6.3 KiB
TypeScript

#!/usr/bin/env tsx
/**
* first-buy-waiver-payment-agnostic-smoke.
*
* Memory #23 + Ken's design answer for Part 121 question 3:
*
* "If their first buy order of BLURT is with USDT, that's fine."
*
* Translation: the first-buy waiver gate fires on (side='buy',
* asset='BLURT') REGARDLESS of what the buyer pays their seller
* with. The waiver covers the LISTING FEE, not the trade
* settlement. A new user buying BLURT and paying their
* counterparty in USDT, fiat, BTC, XMR, or anything else still
* gets the listing-fee waiver.
*
* This smoke asserts the waiver-gate code does NOT depend on
* `payment_methods`. If a future contributor adds a check like
* "only fire the waiver when payment_methods contains 'cash'" or
* "block the waiver for USDT-paid trades", this smoke fails.
*
* Sentinel-grep against `apps/indexer/src/indexer/handlers/order.ts`:
* - Find the waived_first_buy branch
* - Assert it contains the side check, the asset===BLURT check,
* and the amount-min check
* - Assert it does NOT contain `payment_methods` ANYWHERE
* within that branch
*
* Usage:
* tsx packages/asset-registry/scripts/first-buy-waiver-payment-agnostic-smoke.ts
*/
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const REPO_ROOT = join(__dirname, '..', '..', '..');
const ORDER_HANDLER = join(
REPO_ROOT,
'apps/indexer/src/indexer/handlers/order.ts'
);
let failed = 0;
let passed = 0;
function pass(name: string): void {
console.log(`${name}`);
passed++;
}
function fail(name: string, detail: string): void {
console.error(`${name}`);
console.error(` ${detail}`);
failed++;
}
console.log('\n── first-buy-waiver-payment-agnostic smoke ─────────────\n');
let body: string;
try {
body = readFileSync(ORDER_HANDLER, 'utf8');
} catch (err) {
fail(
'order.ts readable',
`could not read ${ORDER_HANDLER}: ${err}`
);
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(1);
}
// ── Scenario 1 — extract the waived_first_buy branch ─────────────
const branchStartRe = /if \(v\.fee_method === 'waived_first_buy'\) \{/;
const branchStart = body.search(branchStartRe);
if (branchStart < 0) {
fail(
`waived_first_buy branch present`,
`expected 'if (v.fee_method === 'waived_first_buy') {' in order.ts — couldn't find the waiver branch at all`
);
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(1);
} else {
pass(`waived_first_buy branch present in order.ts`);
}
// Find the matching closing brace by depth-counting from the `{`
let depth = 0;
let branchEnd = -1;
let started = false;
for (let i = branchStart; i < body.length; i++) {
const c = body[i];
if (c === '{') {
depth++;
started = true;
} else if (c === '}') {
depth--;
if (started && depth === 0) {
branchEnd = i + 1;
break;
}
}
}
if (branchEnd < 0) {
fail(
`waiver branch balanced`,
`couldn't find matching closing brace for the waiver branch — file structure is unexpected`
);
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(1);
} else {
pass(`waiver branch is brace-balanced (extractable as a unit)`);
}
const waiverBranch = body.slice(branchStart, branchEnd);
// The "gate" is everything from the branch start up to the first
// INSERT statement (the success-path persistence). References to
// `payment_methods` inside the INSERT are correct (it's a column
// being persisted, not a gate dependency). We only flag refs
// in the gate.
const gateEnd = waiverBranch.search(/INSERT INTO orders/);
const waiverGate = gateEnd > 0 ? waiverBranch.slice(0, gateEnd) : waiverBranch;
// ── Scenario 2 — waiver checks side === 'buy' ────────────────────
if (/v\.side !== 'buy'/.test(waiverGate)) {
pass(`waiver gate checks side !== 'buy'`);
} else {
fail(
`waiver gate checks side !== 'buy'`,
`expected 'v.side !== 'buy'' in the waiver gate; not found`
);
}
// ── Scenario 3 — waiver checks asset === 'BLURT' ─────────────────
if (/v\.asset !== 'BLURT'/.test(waiverGate)) {
pass(`waiver gate checks asset !== 'BLURT'`);
} else {
fail(
`waiver gate checks asset !== 'BLURT'`,
`expected 'v.asset !== 'BLURT'' in the waiver gate; not found`
);
}
// ── Scenario 4 — the GATE is PAYMENT-METHOD AGNOSTIC ─────────────
// This is the load-bearing check. Even one reference to
// payment_methods inside the gate (before the INSERT) would mean
// the waiver is dependent on what the buyer pays their seller
// with — which violates Ken's design for Part 121 question 3.
// Mentions inside the INSERT statement (column list, VALUES
// binding) are fine — they're persistence, not gating.
if (/payment_methods/.test(waiverGate)) {
fail(
`waiver gate does NOT reference payment_methods`,
`the waiver gate references 'payment_methods' — the waiver must fire regardless of how the buyer pays the seller (Memory #23, Part 121 Q3 answer). Find the gate condition and remove the dependence.`
);
} else {
pass(`waiver gate does NOT reference payment_methods (gate is payment-method agnostic)`);
}
// ── Scenario 5 — the GATE is NOT dependent on a specific ────────
// payment rail like 'cash', 'paypal', 'usdt' as a string literal.
const FORBIDDEN_RAILS = ['cash', 'paypal', 'zelle', 'wise', 'usdt', 'bitso', 'venmo', 'revolut'];
let railFound = false;
for (const rail of FORBIDDEN_RAILS) {
const re = new RegExp(`['"]${rail}['"]`, 'i');
if (re.test(waiverGate)) {
fail(
`waiver gate does NOT reference fiat payment rails`,
`waiver gate contains a '${rail}' string literal — the gate must not depend on the fiat-side payment rail`
);
railFound = true;
}
}
if (!railFound) {
pass(`waiver gate does NOT reference fiat payment rails (cash/paypal/zelle/etc.)`);
}
const total = passed + failed;
console.log(`\n${passed} passed, ${failed} failed (${total} total)`);
if (failed > 0) {
console.error('\nfirst-buy-waiver-payment-agnostic smoke FAILED');
process.exit(1);
}
// Canonical success line — run-smokes.sh greps for `^✓ all` to tally
console.log(`✓ all ${total} first-buy-waiver-payment-agnostic scenarios passed`);