---
title: Escrow Marketplace E2E Procedure
tags: [testing, e2e, escrow, marketplace, buyer, seller]
created: 2026-06-06
---

# Escrow Marketplace E2E Procedure

This procedure validates the marketplace flow with one buyer and at least two
sellers. Use it for live dev validation after payment, marketplace, delivery,
or scanner changes.

## Preconditions

- Dev API is reachable: `GET https://dev.amn.gg/api/version`.
- Backend, frontend, and scanner containers are healthy.
- Admin credentials are available through a secure local channel, not docs.
- BSC Testnet wallet has enough tBNB for gas and enough canonical tUSDT.
- Backend/scanner chain 97 registry points to `0x109F54Dab34426D5477986b0460aE5dFBA65f022`.
- Local wallet private key or mnemonic is stored only in ignored `.env`.
- Notification checks are enabled in the runner. See [[Notification Assertion Procedure]].

## Actors

| Actor | Count | Role |
|---|---:|---|
| Buyer | 1 | Creates request, accepts offer, funds payment, confirms delivery. |
| Sellers | 2 minimum, 3 preferred | Submit competing offers and delivery evidence. |
| Admin | 1 | Creates test users and can inspect/repair state. |

## High-Level Procedure

Use the implemented backend runner for repeatable execution:

```bash
BASE_URL=https://dev.amn.gg \
PAYMENT_MODE=status \
CONCURRENCY_LEVELS=1 \
ROUNDS=2 \
bash scripts/smoke/marketplace-e2e-notifications.sh
```

Use `PAYMENT_MODE=live` for the funded BSC Testnet tUSDT rail and
`PAYMENT_MODE=record` only when explicitly testing the legacy marketplace
payment-record route. See [[Marketplace E2E Smoke Runner]].

1. Generate a run id.
2. Admin creates one buyer and at least two sellers.
3. Assert initial notification baselines for buyer and sellers.
4. Buyer creates purchase request with a min/max USDT budget.
5. Assert purchase-request notifications for expected sellers.
6. Sellers submit bids inside the budget range.
7. After each seller bid, assert offer notifications for the buyer.
8. Randomize or vary:
   - bid amount,
   - delivery timing,
   - seller note,
   - delivery method.
9. Buyer selects a bid.
10. Assert offer-accepted notification for selected seller and offer-rejected/updated notifications for non-selected sellers when implemented.
11. Backend creates scanner payment intent.
12. Assert payment-pending or payment-started notification where implemented; if missing, record as notification coverage gap.
13. Buyer pays with BSC Testnet tUSDT.
14. Scanner confirms the payment.
15. Assert payment-confirmed notifications for buyer and selected seller.
16. Seller marks delivery.
17. Assert delivery notification for buyer.
18. Buyer confirms delivery.
19. Assert delivery-confirmed notification for selected seller and buyer status notification if implemented.
20. Record current block: flow pauses before automated release policy.

## Expected State Transitions

| Step | Purchase request expectation | Payment expectation |
|---|---|---|
| Request created | request visible to sellers | no payment |
| Offers submitted | `received_offers` or equivalent offer-ready state | no payment |
| Offer accepted | selected offer stored | payment intent can be created |
| Payment sent | payment/check pending or processing | scanner sees chain/token/destination/amount |
| Scanner confirms | request can proceed to delivery path | status paid/confirmed/completed depending endpoint |
| Seller delivers | `delivery` | escrow remains held |
| Buyer confirms | `delivered`, `deliveryConfirmed=true` | release policy not automatic yet |

## Notification Assertions After Every Step

Notification verification is mandatory after every state-changing step. A step
is not complete until the test runner has either:

1. observed the expected notification for every recipient; or
2. recorded a known coverage gap with route/action, expected recipient, and linked issue/backlog item.

Use [[Notification Assertion Procedure]] for the exact API/socket checks.

| Step | Expected recipients | Required notification assertion |
|---|---|---|
| Baseline after user creation | buyer, every seller | Capture unread count and latest notification id for each actor before business mutations. |
| Buyer creates request | eligible sellers | Sellers receive a request/new-opportunity notification, or the gap is recorded. |
| Seller submits bid | buyer | Buyer receives a new-offer notification with `relatedId` or action URL pointing to the request/offer. |
| Buyer selects bid | selected seller; non-selected sellers if rejection notices are implemented | Selected seller receives acceptance notification. Non-selected sellers receive rejection/update notification where supported. |
| Payment intent created | buyer | Buyer receives payment-started/pending notification where supported. Missing `pending_payment` coverage is a known notification gap and must be recorded. |
| Scanner confirms payment | buyer, selected seller | Buyer and seller receive payment-confirmed/funded notification. |
| Seller marks delivery | buyer | Buyer receives delivery/proof submitted notification. |
| Buyer confirms delivery | selected seller; buyer if status-change notices are implemented | Seller receives delivery-confirmed notification. |
| Dispute raised, when added | buyer, seller, admin/mediator | All parties receive dispute-created/hold notification. |
| Release/refund, when added | buyer, seller, admin | Funds movement notification is persisted and pushed. |

Minimum notification evidence per assertion:

| Evidence | Source |
|---|---|
| unread count before and after | `GET /api/notifications/unread-count` |
| latest notification payload | `GET /api/notifications?limit=5` |
| recipient id/email | authenticated actor profile or test user record |
| notification category/type/title/actionUrl | notification payload |
| socket event, if runner supports it | `new-notification` on `user-<userId>` |
| elapsed time from action to notification visibility | runner timestamp |

## Buyer Request Template

Use unique values so live data can be filtered later:

```json
{
  "title": "Scanner BSC Testnet E2E <runId> R<round>",
  "description": "Automated scanner payment test round <round>",
  "productType": "physical_product",
  "productLink": "https://example.test/e2e/<runId>/<round>",
  "quantity": 1,
  "budget": {
    "min": 0.18,
    "max": 0.48,
    "currency": "USDT"
  },
  "urgency": "medium"
}
```

## Seller Bid Rules

For each round:

- create at least two bids;
- prefer three bids so ranking/selection is clearer;
- keep all bids within buyer min/max budget;
- vary delivery timing, for example 1 day, 4 days, 7 days;
- pick a deterministic winner rule for automation, such as lowest price.

Example bid set:

| Seller | Amount | Delivery |
|---|---:|---|
| seller1 | `0.31 USDT` | `7 days` |
| seller2 | `0.28 USDT` | `5 days` |
| seller3 | `0.26 USDT` | `4 days` |

## Delivery Proof

Seller delivery should include structured proof where the API supports it:

```json
{
  "proof": {
    "type": "e2e",
    "runId": "<runId>",
    "round": 1,
    "note": "Seller delivery proof for automated dev test"
  }
}
```

Buyer confirmation may use similar metadata:

```json
{
  "proof": {
    "type": "e2e-confirmation",
    "runId": "<runId>",
    "round": 1
  }
}
```

## Assertions

Minimum assertions per round:

| Assertion | Required |
|---|---|
| Buyer login succeeds | yes |
| Seller logins succeed | yes |
| Request id is created | yes |
| At least two offer ids are created | yes |
| Selected offer id matches accepted seller | yes |
| Payment id is created | yes |
| Payment chain id is `97` | yes |
| Payment token is canonical tUSDT | yes |
| On-chain tx hash exists | yes |
| Scanner check returns paid/confirmed | yes |
| Seller delivery returns HTTP 200 | yes |
| Buyer confirm delivery returns HTTP 200 | yes |
| Final request status is `delivered` | yes |
| Notification assertion executed after each state-changing step | yes |
| Notification gaps are recorded with expected recipient and route/action | yes |

## Reference Execution - 2026-06-06

Run id:

```text
20260606043238
```

Generated users:

| Actor | Email |
|---|---|
| Buyer | `amn-e2e-20260606043238-buyer@example.test` |
| Seller 1 | `amn-e2e-20260606043238-seller1@example.test` |
| Seller 2 | `amn-e2e-20260606043238-seller2@example.test` |
| Seller 3 | `amn-e2e-20260606043238-seller3@example.test` |

Round 1:

| Field | Value |
|---|---|
| purchaseRequestId | `732e9de8-9631-484e-a5ac-bb657ca55020` |
| paymentId | `f7f02ba4-9154-4408-984b-d3481d1ec5fa` |
| selectedOfferId | `263b30f0-7ee6-4e63-9d0a-af2d7624fdde` |
| selected seller | `seller3` |
| amount | `0.26 USDT` |
| token | `0x109F54Dab34426D5477986b0460aE5dFBA65f022` |
| txHash | `0x7a5c2785161df3367374574d8e1af00c548131c8a44c3fa06b592966920e3edc` |
| scanner result | paid |
| final request status | `delivered` |

Round 2:

| Field | Value |
|---|---|
| purchaseRequestId | `da34d9bc-2b2d-4dc3-98f0-aa1a07a55ebb` |
| paymentId | `2e8582eb-3ac3-4793-b10f-ea721b6466d4` |
| selectedOfferId | `36e3a912-2121-4f07-87e6-4c73b1adf224` |
| selected seller | `seller2` |
| amount | `0.32 USDT` |
| token | `0x109F54Dab34426D5477986b0460aE5dFBA65f022` |
| txHash | `0x861a1197d7d345609f5a46b1a3723a29877ba9929bd1e7b21f7060381a1b14d0` |
| scanner result | paid |
| final request status | `delivered` |

Important finding from this run:

- Payment confirmation succeeded after correcting the chain 97 token registry to the actual tUSDT contract.
- Buyer delivery confirmation initially failed with HTTP 403 because user ids were compared across stores directly. Backend `2.8.117` fixed the id comparison; both confirmations then returned HTTP 200.

## Current Product Boundary

The procedure currently stops at `delivered`.

Release policy is not complete:

- physical-product grace period is not implemented;
- gift-card/digital immediate release is not implemented;
- dispute-to-release/refund automation is not complete.

Use [[Testing Expansion Backlog]] before extending this scenario into fund release.
