docs: add sub-project service docs + sync vault 2026-06-08

Add 10 - Services/ docs for all sub-projects: backend, frontend, scanner,
deployment (new), update amanat-assist. Update Scanner Architecture,
Telegram Mini App flow, and Activity Log. Add payment safety edge cases.
This commit is contained in:
Siavash Sameni
2026-06-08 16:22:52 +04:00
parent 181e8e9c2f
commit 67244223ec
13 changed files with 2734 additions and 311 deletions

View File

@@ -0,0 +1,206 @@
---
title: Payment Safety Edge Cases
tags: [testing, payment, safety, aml, scanner, edge-cases]
created: 2026-06-08
---
# Payment Safety Edge Cases
Automated tests live in `backend/__tests__/payment-edge-cases.test.ts` (38 tests, all passing).
This document records the design rationale, current system behaviour, and remaining gaps for each
of the five payment edge-case families.
## Edge Case Families
### 1 · Blacklisted / OFAC-Sanctioned Sender Wallet
**How the system works**
The OFAC SDN list is downloaded from US Treasury once per 24 h and cached locally. Address
screening runs when the seller has opted in (`requireAmlCheck=true` on the seller offer). For
on-chain (BSC Verifier) payments the buyer address comes from the ERC-20 Transfer log `from` field.
For direct-balance (AMN Scanner) payments the buyer address must be stored in
`amnScannerDirectBalance.buyerAddress` at intent-creation time.
**Implemented behaviour**
| Scenario | Result |
|---|---|
| OFAC-listed address + seller opted in | `block=true`, `reason=aml_sanctions` |
| OFAC-listed address + seller NOT opted in | `block=false`, `reason=aml_not_required` |
| OFAC provider unreachable + `amlBlockOnFailure=true` | `block=true` (fail-closed) |
| OFAC provider unreachable + `amlBlockOnFailure=false` | `block=false`, `providerUnavailable=true` (fail-open) |
| No buyer address (direct-balance, no tx hash) | `block=false`, `reason=no_buyer_address_to_screen` |
| Direct-balance + `buyerAddress` stored + sanctioned | `block=true` via `fundDirectBalancePayment` AML gate |
| BTC / XMR addresses in SDN XML | Ignored (only EVM `0x…` addresses parsed) |
**Remaining gaps**
- AML is opt-in per seller. A sanctioned address can pay any seller with `requireAmlCheck=false`.
Platform-level mandatory screening could be added via a `PLATFORM_AML_REQUIRED=1` env flag.
- SDN list can be up to 24 h stale.
**Relevant code**
- `src/services/payment/safety/ofacProvider.ts` — SDN download, parse, cache
- `src/services/payment/safety/amlScreeningService.ts``screenPaymentForAml()`
- `src/services/payment/amnScanner/directBalancePaymentService.ts``fundDirectBalancePayment()` AML gate
---
### 2 · Overpayment and Underpayment
**How the system works**
On-chain (BSC Verifier): amount ≥ expected → success; amount < expected → `insufficient_amount`.
Direct-balance (webhook): delta ≥ expected → `funded=true`; delta < expected → `funded=false`.
Overpay excess is accepted silently and remains locked at the derived destination address.
**Implemented behaviour**
| Scenario | Result |
|---|---|
| On-chain overpay (15 USDT vs 10 expected) | `success=true`, `actualAmount` returned |
| On-chain exact match | `success=true` |
| On-chain underpay by 1 wei | `failureReason=insufficient_amount`, both amounts returned |
| Direct-balance overpay | `funded=true`, `reason=paid` |
| Direct-balance exact match | `funded=true` |
| Direct-balance underpay | `funded=false`, `reason=underpaid:delta=…,expected=…` **+ `payment-underpaid` socket event** with `shortfall` |
| Direct-balance zero balance | `funded=false` |
**Remaining gaps**
- No dedicated `underpaid` payment status in the DB — payment stays `pending` until expiry.
- Overpay excess locked at derived address with no automated recovery.
**Relevant code**
- `src/services/payment/decentralizedPaymentService.ts``BSCTransactionVerifier.verifyTransfer()`
- `src/services/payment/amnScanner/directBalancePaymentService.ts``processDirectBalanceWebhook()`
---
### 3 · Native Coin (ETH / BNB) Sent Instead of ERC-20
**How the system works**
Native coin transfers emit no ERC-20 Transfer log. On-chain verification sees no Transfer events.
The AMN scanner watches a specific ERC-20 token balance; native coin does not affect it.
**Implemented behaviour**
| Scenario | Result |
|---|---|
| On-chain tx succeeded, empty logs | `failureReason=wrong_asset`**distinguishable from transfer_not_found** |
| On-chain tx with only Approval log | `failureReason=transfer_not_found` |
| Direct-balance webhook, ERC-20 balance unchanged | `funded=false` — scanner unaware of native coin arrival |
| Direct-balance API check, native baseline captured | `warning=native_coin_detected:delta=N` when native balance increased |
**Remaining gaps**
- The direct-balance **webhook** path cannot detect native coin because the scanner only reports
ERC-20 balance changes. A native-coin watch could be added to the scanner service separately.
- Native coin locked at derived address — no automated sweep path.
**`wrong_asset` vs `transfer_not_found`**
| failureReason | Meaning |
|---|---|
| `wrong_asset` | Tx succeeded on-chain with zero logs — almost certainly native coin was sent |
| `transfer_not_found` | Tx succeeded but logs exist yet none match token/recipient — likely wrong token or misconfigured tx |
**Relevant code**
- `decentralizedPaymentService.ts:verifyTransfer()``receipt.logs.length === 0``wrong_asset`
- `directBalancePaymentService.ts:createDirectBalancePayIntent()` — captures `nativeBaselineBalance`
- `directBalancePaymentService.ts:checkDirectBalancePayment()` — compares native balance to baseline
---
### 4 · Wrong ERC-20 Token Sent to Deposit Address
**How the system works**
On-chain: BSC Verifier detects token/recipient mismatches explicitly. Direct-balance webhook: the
scanner reports the token address in the payload; mismatches are caught at intake.
**Implemented behaviour**
| Scenario | Result |
|---|---|
| On-chain: USDC sent when USDT expected | `failureReason=wrong_token`, `actualToken` populated |
| On-chain: right token, wrong recipient | `failureReason=wrong_recipient`, `actualRecipient` populated |
| On-chain: completely random token + address | `failureReason=transfer_not_found` |
| Direct-balance webhook: wrong `tokenAddress` | `funded=false`, `reason=address-token-mismatch` **+ `payment-wrong-token` socket event** |
| Direct-balance webhook: wrong `chainId` | `funded=false`, `reason=address-token-mismatch` **+ `payment-wrong-token` socket event** |
| Direct-balance webhook: wrong `address` | `funded=false`, `reason=address-token-mismatch` **+ `payment-wrong-token` socket event** |
**Remaining gaps**
- Wrong-token funds are locked at the derived address — no automated recovery.
**Relevant code**
- `decentralizedPaymentService.ts``parseTransferLogs()`, `verifyTransfer()` wrong-token detection
- `directBalancePaymentService.ts:processDirectBalanceWebhook()` — mismatch emits `payment-wrong-token`
---
### 5 · Smart-Contract Sender
**How the system works**
The ERC-20 Transfer log `from` field (topics[1]) is the immediate sender. Smart contracts
(DEX routers, multisigs, mixers) appear here. The system has no built-in EOA detection — only
OFAC-listed contract addresses are blocked.
**Implemented behaviour**
| Scenario | Result |
|---|---|
| Contract address in Transfer log | Surfaced as `evidence.from`, passed to AML |
| OFAC-listed contract + seller opted in | `block=true` |
| Unlisted contract (e.g. mixer) + not on OFAC | `block=false`**gap** |
| Gnosis Safe (legitimate multisig) | `block=false` — indistinguishable from mixer by address alone |
| `requireEoaSender=true` + contract bytecode | `failureReason=contract_sender`, `success=false` |
| `requireEoaSender=true` + EOA (empty bytecode) | `success=true` as normal |
| `requireEoaSender` not set (default) | Contract sender passes — backward-compatible |
**Enabling EOA enforcement**
Set env flag at any scope (per-service or globally):
```
TRANSACTION_SAFETY_REQUIRE_EOA_SENDER=1
```
This is wired to all 5 `verifyTransfer` call sites:
- `transactionSafetyProvider.ts` (webhook safety evaluation)
- `paymentController.ts` (new + re-verify web3 payment paths)
- `paymentRoutes.ts`
- `marketplace/routes.ts`
**Remaining gaps**
- No bytecode check in `screenPaymentForAml()` itself — the AML layer cannot gate on sender type.
- No seller-level `requireEoaSender` flag — currently only a platform-wide env toggle.
- Gnosis Safe and unlisted mixers remain indistinguishable at address level. A known-multisig
factory allowlist would be required for principled permitting.
**Relevant code**
- `decentralizedPaymentService.ts:verifyTransfer()``requireEoaSender``eth_getCode``contract_sender`
- `transactionSafetyProvider.ts` — reads `TRANSACTION_SAFETY_REQUIRE_EOA_SENDER`
---
## Test File Reference
`backend/__tests__/payment-edge-cases.test.ts`
| Section | Tests | Status |
|---|---|---|
| 1 · Blacklisted / OFAC wallet | 10 | ✓ all pass |
| 2 · Overpayment and underpayment | 8 | ✓ all pass |
| 3 · Native coin (ETH/BNB) | 4 | ✓ all pass |
| 4 · Wrong ERC-20 token | 7 | ✓ all pass |
| 5 · Smart-contract sender | 9 | ✓ all pass |
| **Total** | **38** | **✓** |
Tests labelled `GAP ·` document known system limitations that pass because they describe current
(undesired) behaviour. Tests labelled `FIXED ·` confirm a gap was mitigated.
Run:
```bash
cd backend && node_modules/.bin/jest __tests__/payment-edge-cases.test.ts --no-coverage
```

View File

@@ -25,9 +25,14 @@ have an automation owner, a smoke command, and a UAT checklist.
| ESCROW-E2E-001 | Buyer creates request, multiple sellers bid, buyer accepts, pays, seller delivers, buyer confirms | P0 | Live tested on dev, two rounds | [[Escrow Marketplace E2E Procedure]] |
| PAY-SCAN-001 | BSC Testnet tUSDT direct-balance scanner confirms funded wallet transfer | P0 | Live tested on dev | [[Scanner BSC Testnet Payment Procedure]] |
| PAY-SCAN-002 | Scanner/backend/frontend token registry consistency for chain 97 | P0 | Smoke covered | [[Smoke and Regression Procedure]] |
| PAY-SCAN-003 | Wrong token contract does not confirm payment | P0 | Designed, needs automated negative test | [[Scanner BSC Testnet Payment Procedure#Negative scenarios]] |
| PAY-SCAN-004 | Underpayment does not confirm payment | P0 | Designed, needs automated negative test | [[Scanner BSC Testnet Payment Procedure#Negative scenarios]] |
| PAY-SCAN-005 | Wrong destination does not confirm payment | P0 | Designed, needs automated negative test | [[Scanner BSC Testnet Payment Procedure#Negative scenarios]] |
| PAY-SCAN-003 | Wrong token contract does not confirm payment | P0 | **Automated** (`payment-edge-cases.test.ts §4`) | [[Payment Safety Edge Cases#4 · Wrong ERC-20 Token Sent to Deposit Address]] |
| PAY-SCAN-004 | Underpayment does not confirm payment | P0 | **Automated** (`payment-edge-cases.test.ts §2`) | [[Payment Safety Edge Cases#2 · Overpayment and Underpayment]] |
| PAY-SCAN-005 | Wrong destination does not confirm payment | P0 | **Automated** (`payment-edge-cases.test.ts §4`) | [[Payment Safety Edge Cases#4 · Wrong ERC-20 Token Sent to Deposit Address]] |
| PAY-SAFE-001 | OFAC-sanctioned wallet blocked when seller opts in | P0 | **Automated** (`payment-edge-cases.test.ts §1`) | [[Payment Safety Edge Cases#1 · Blacklisted / OFAC-Sanctioned Sender Wallet]] |
| PAY-SAFE-002 | Native coin (ETH/BNB) returns `wrong_asset` instead of `transfer_not_found` | P1 | **Automated** (`payment-edge-cases.test.ts §3`) | [[Payment Safety Edge Cases#3 · Native Coin (ETH / BNB) Sent Instead of ERC-20]] |
| PAY-SAFE-003 | Smart-contract sender blocked when `TRANSACTION_SAFETY_REQUIRE_EOA_SENDER=1` | P1 | **Automated** (`payment-edge-cases.test.ts §5`) | [[Payment Safety Edge Cases#5 · Smart-Contract Sender]] |
| PAY-SAFE-004 | Underpaid direct-balance emits `payment-underpaid` event with shortfall | P1 | **Automated** (`payment-edge-cases.test.ts §2`) | [[Payment Safety Edge Cases#2 · Overpayment and Underpayment]] |
| PAY-SAFE-005 | Wrong-token direct-balance emits `payment-wrong-token` event | P1 | **Automated** (`payment-edge-cases.test.ts §4`) | [[Payment Safety Edge Cases#4 · Wrong ERC-20 Token Sent to Deposit Address]] |
| DELIVERY-001 | Seller delivery advances request to `delivery` | P0 | Live tested on dev | [[Escrow Marketplace E2E Procedure]] |
| DELIVERY-002 | Buyer delivery confirmation advances request to `delivered` | P0 | Live tested after `2.8.117` id fix | [[Escrow Marketplace E2E Procedure]] |
| DELIVERY-003 | Non-buyer cannot confirm delivery | P0 | Designed, should be regression test | [[Testing Expansion Backlog]] |

View File

@@ -65,17 +65,23 @@ Tests needed:
## P0 - Payment Negative Tests
Automate:
**Automated as of 2026-06-08** (`backend/__tests__/payment-edge-cases.test.ts`, 38 tests):
See [[Payment Safety Edge Cases]] for full detail.
- wrong token;
- wrong chain;
- wrong destination;
- underpayment;
- duplicate payment;
- wrong token (on-chain `wrong_token` + direct-balance `address-token-mismatch` + `payment-wrong-token` event)
- wrong chain (direct-balance `address-token-mismatch`)
- wrong destination (direct-balance `address-token-mismatch`)
- underpayment (`insufficient_amount` on-chain; `underpaid` direct-balance + `payment-underpaid` event)
- ✅ native coin sent instead of ERC-20 (`wrong_asset` on-chain; stays pending in direct-balance webhook)
- ✅ OFAC-sanctioned sender blocked (opt-in per seller; direct-balance `fundDirectBalancePayment` AML gate)
- ✅ smart-contract sender blocked via `TRANSACTION_SAFETY_REQUIRE_EOA_SENDER=1`
Still needs automation:
- duplicate payment (double-credit guard);
- late payment after cancelled/expired intent;
- payment with no gas;
- scanner unavailable during payment;
- scanner webhook signature invalid;
- scanner webhook signature invalid (partially covered by `amn-pay-adapter-webhook-signature.test.ts`);
- balance check baseline missing or stale.
## P0 - Authorization and ID Boundaries

View File

@@ -19,6 +19,7 @@ seller, payment scanner, delivery, payout, dispute, and deployment behavior.
|---|---|
| [[Test Environment and Data]] | Environments, accounts, wallets, tokens, and secret-handling rules. |
| [[Test Scenario Catalog]] | Canonical scenarios we have designed or need to extend. |
| [[Payment Safety Edge Cases]] | Five payment edge-case families (OFAC, underpay, native coin, wrong token, contract sender) — design rationale, current behaviour, gaps, and 38-test suite reference. |
| [[Escrow Marketplace E2E Procedure]] | Buyer/seller/request/bid/delivery procedure, including the current two-round flow. |
| [[Scanner BSC Testnet Payment Procedure]] | BSC Testnet tUSDT scanner payment procedure and failure modes. |
| [[Notification Assertion Procedure]] | Required notification checks after every E2E business step. |