diff --git a/01 - Architecture/Scanner Architecture.md b/01 - Architecture/Scanner Architecture.md index 25996d0..97b7e67 100644 --- a/01 - Architecture/Scanner Architecture.md +++ b/01 - Architecture/Scanner Architecture.md @@ -92,7 +92,7 @@ for each tick: checkConfirmations(): for each confirming intent: confs = head - blockNumber + 1 - if confs >= required: finalizeIntent() + deliverWebhook() + if confs >= required: finalizeIntent(capped at required) + deliverWebhook() ``` **Reorg protection**: `ReorgBuffer()` re-scans `3 × confirmationThreshold` blocks before the checkpoint (clamped 20–500). This catches any log that appeared in a block that was later reorganised off the canonical chain. @@ -170,11 +170,11 @@ Two tables: | `status` | TEXT | `pending` / `confirming` / `confirmed` / `expired` / `webhook_failed` | | `callback_url` | TEXT | backend webhook endpoint | | `callback_secret` | TEXT | HMAC key (not returned in GET) | -| `confirmations_required` | INTEGER | from chain config or caller override | +| `confirmations_required` | INTEGER | from chain config or caller override, floored at the chain acceptance threshold | | `tx_hash` | TEXT NULL | transaction hash once seen | | `log_index` | INTEGER NULL | log position within tx (EVM) | | `block_number` | INTEGER NULL | block / timestamp when seen | -| `confirmations` | INTEGER | current confirmation depth | +| `confirmations` | INTEGER | current depth while confirming; capped at the accepted threshold after confirmation | | `salt` | TEXT | 32-byte random hex for reference derivation | | `webhook_delivered_at` | TEXT NULL | RFC3339 timestamp of successful delivery | | `created_at` / `updated_at` | DATETIME | | diff --git a/02 - Data Models/Payment.md b/02 - Data Models/Payment.md index 9daa594..59d437f 100644 --- a/02 - Data Models/Payment.md +++ b/02 - Data Models/Payment.md @@ -6,12 +6,12 @@ aliases: [Payment Record, Escrow, IPayment] # Payment -> **Last updated:** 2026-05-31 — added AMN scanner provider, oracle quote mirror, Postgres `payment_quotes` linkage, and webhook confirmation persistence. +> **Last updated:** 2026-05-31 — added AMN scanner provider, oracle quote mirror, Postgres `payment_quotes` linkage, webhook confirmation persistence, and capped accepted confirmation counts. Records every monetary movement in the marketplace: buyer pay-ins, seller payouts, and refunds. The current model is centered on Request Network pay-in, in-house checkout metadata, on-chain transaction verification, escrow state, and provider request IDs. The `provider` and `direction` discriminators let one collection hold incoming buyer payments, outgoing seller releases, refunds, and legacy/other provider records. > [!warning] Runtime store -> The `Payment` document is still created, read, and updated through Mongoose on normal request paths. Backend `2.6.81` can persist oracle quotes to Postgres `payment_quotes`, but that is conditional on `ORACLE_QUOTING_ENABLED=true` and does not make the whole payment domain PG-authoritative. See [[Postgres Runtime Cutover Status]]. +> The `Payment` document is still created, read, and updated through Mongoose on normal request paths. Backend `2.6.82` can persist oracle quotes to Postgres `payment_quotes`, but that is conditional on `ORACLE_QUOTING_ENABLED=true` and does not make the whole payment domain PG-authoritative. See [[Postgres Runtime Cutover Status]]. > [!note] Source > `backend/src/models/Payment.ts:3` — schema definition @@ -52,7 +52,7 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout | `blockchain.sender` | String | no | — | — | — | Source address. | | `blockchain.receiver` | String | no | — | — | — | Destination address. | | `blockchain.confirmedAt` | Date | no | — | — | — | When tx confirmed. | -| `blockchain.confirmations` | Number | no | `0` | — | — | Confirmation count persisted from transaction-safety verifier evidence, provider payload `confirmations`, or the configured per-chain threshold fallback when a webhook already reports `confirmed`/`completed`. | +| `blockchain.confirmations` | Number | no | `0` | — | — | Accepted confirmation count. For settled webhooks this is capped at the effective per-chain threshold (for example `50`, `200`, `300`) rather than an endlessly increasing live block count; payment screens render settled values with a `+` suffix. | | `status` | String | no | `pending` | enum: `pending` / `processing` / `confirmed` / `completed` / `failed` / `cancelled` / `refunded` | yes (compound) | Lifecycle status. ⚠️ `confirmed` vs `completed`: only `confirmed` is counted as a successful payment in stats. See status note below. | | `escrowState` | String | no | — | enum: `funded` / `releasable` / `released` / `refunded` / `releasing` / `failed` / `cancelled` / `partial` | — | Escrow lifecycle. Note the intermediate states `releasable` (delivery confirmed, ready to pay out) and `releasing` (payout in flight) between `funded` and `released`. | | `providerPaymentId` | String | no | — | — | yes (sparse) | External provider id for idempotency. | diff --git a/02 - Data Models/ScannerIntent.md b/02 - Data Models/ScannerIntent.md index dff7804..4cb5d9b 100644 --- a/02 - Data Models/ScannerIntent.md +++ b/02 - Data Models/ScannerIntent.md @@ -54,11 +54,11 @@ CREATE TABLE intents ( | `status` | TEXT | Intent lifecycle state (see below) | | `callback_url` | TEXT | URL the scanner POSTs to on confirmation | | `callback_secret` | TEXT | HMAC-SHA256 key for webhook signature. Never returned in API responses | -| `confirmations_required` | INTEGER | Number of blocks required before confirmation (EVM). Defaults to chain config | +| `confirmations_required` | INTEGER | Accepted confirmation floor for the intent. Defaults to chain config and cannot be lowered below the chain floor by the create-intent request | | `tx_hash` | TEXT NULL | Transaction hash once a matching transfer is detected | | `log_index` | INTEGER NULL | Log position within the transaction (EVM only; 0 for Tron/TON) | | `block_number` | INTEGER NULL | Block number (EVM/Tron) or Unix timestamp seconds (TON) when the tx was seen | -| `confirmations` | INTEGER | Current confirmation depth. Incremented each scan cycle for `confirming` intents | +| `confirmations` | INTEGER | Current confirmation depth while `confirming`; capped at `confirmations_required` once the intent is `confirmed` | | `salt` | TEXT | 32-byte random hex. Combined with `intent_id` and `destination` to derive `payment_reference`. Prevents reference collisions across retried payments | | `webhook_delivered_at` | TEXT NULL | RFC3339 timestamp when the webhook was successfully delivered. Used for startup crash recovery | | `created_at` / `updated_at` | DATETIME | UTC timestamps | diff --git a/03 - API Reference/Payment API.md b/03 - API Reference/Payment API.md index 9c2bef8..8ce5362 100644 --- a/03 - API Reference/Payment API.md +++ b/03 - API Reference/Payment API.md @@ -5,7 +5,7 @@ tags: [api, payment, reference, request-network, escrow] # Payment API -> **Last updated:** 2026-05-31 — Postgres integration promotion, oracle quote persistence, AMN scanner rail-switch fix, webhook confirmation persistence, and partial gasless permit endpoints. +> **Last updated:** 2026-05-31 — Postgres integration promotion, oracle quote persistence, AMN scanner rail-switch fix, capped webhook confirmation persistence, and partial gasless permit endpoints. The payment surface is split across provider-neutral payment routers, Request Network checkout/webhook routes, derived-destination custody routes, and admin safety routes: @@ -23,7 +23,7 @@ The payment surface is split across provider-neutral payment routers, Request Ne Core model: [[Payment]]. Coordination logic to avoid race conditions when multiple sources update the same payment is in `paymentCoordinator.ts`. > [!warning] Persistence status -> Payment APIs still create/read/update Mongo `Payment` documents on backend `2.6.81`. The Postgres branch adds schemas, repos, migrations, and optional quote persistence, but it is not a full payment-domain cutover. `/api/payment/request-network/intents` can write `payment_quotes` only when `ORACLE_QUOTING_ENABLED=true`; the payment record itself remains Mongo-backed unless future service wiring changes that boundary. +> Payment APIs still create/read/update Mongo `Payment` documents on backend `2.6.82`. The Postgres branch adds schemas, repos, migrations, and optional quote persistence, but it is not a full payment-domain cutover. `/api/payment/request-network/intents` can write `payment_quotes` only when `ORACLE_QUOTING_ENABLED=true`; the payment record itself remains Mongo-backed unless future service wiring changes that boundary. ## Configuration / health @@ -208,7 +208,7 @@ Core model: [[Payment]]. Coordination logic to avoid race conditions when multip **Description:** Request Network posts settlement updates here. The route verifies `x-request-network-signature` over the raw body, deduplicates delivery IDs, evaluates the Transaction Safety Provider, and coordinates the payment/ledger update. **Auth required:** No (signature-protected) **Response:** `200` when processed or duplicate; `202` when accepted but safety checks are pending; `401` for invalid signature. -**Side effects:** For confirmed/completed events, `blockchain.confirmations` is stored from safety verifier evidence, provider payload `confirmations`, or the configured per-chain threshold fallback before the payment/ledger update is emitted. +**Side effects:** For confirmed/completed events, `blockchain.confirmations` is stored as the accepted confirmation count capped at the effective per-chain threshold before the payment/ledger update is emitted. > [!note] RN payout/release/refund routes > `POST /api/payment/request-network/:paymentId/payout/initiate`, `POST /api/payment/request-network/:paymentId/payout/confirm`, `POST /api/payment/request-network/:paymentId/release/confirm`, and `POST /api/payment/request-network/:paymentId/refund/confirm` are registered in `requestNetworkRoutes.ts` but are stub-level implementations. They accept the request and return a 200 but do not yet drive the ledger-gated release/refund orchestration. Use `POST /api/payment/:id/release` and `POST /api/payment/:id/refund` for actual escrow releases. @@ -223,7 +223,7 @@ AMN Pay Scanner is a custom in-house blockchain scanner that replaces the hosted **Auth required:** No (signature-protected via `AMN_SCANNER_WEBHOOK_SECRET`) **Request body:** `{ intentId, status, txHash?, transactionHash?, chainId?, confirmations?, ... }` — scanner-specific envelope. Current scanner payloads usually use `txHash`; `confirmations` may be omitted once the scanner has already waited for the configured threshold. **Response:** `200` processed; `401` bad signature; `400` missing `intentId` or unknown format; `404` payment not found. -**Side effects:** Same as the RN webhook — updates [[Payment]], advances [[PurchaseRequest]], accepts/rejects offers, emits socket events when safety checks pass. Backend `2.6.81+` treats scanner `status: "confirmed"` as a settlement status for Transaction Safety Provider evaluation and confirmation persistence; if neither verifier evidence nor payload `confirmations` exists, it stores the configured chain threshold so the dashboard does not show a paid scanner transaction with `0` confirmations. +**Side effects:** Same as the RN webhook — updates [[Payment]], advances [[PurchaseRequest]], accepts/rejects offers, emits socket events when safety checks pass. Backend `2.6.82+` treats scanner `status: "confirmed"` as a settlement status for Transaction Safety Provider evaluation and confirmation persistence; if neither verifier evidence nor payload `confirmations` exists, it stores the effective chain threshold so the dashboard does not show a paid scanner transaction with `0` confirmations. Settled confirmation counts are capped at the accepted threshold instead of continuing to grow. > [!note] Provider value > Payments created via the AMN Pay Scanner have `provider: 'amn.scanner'` in the database. This is distinct from `request.network` and `shkeeper`. @@ -591,8 +591,8 @@ Escrow state (`escrowState`): `unfunded` → `funded` → `released` | `refunded { "success": true, "data": [ - { "chainId": 56, "threshold": 12, "source": "default" }, - { "chainId": 1, "threshold": 3, "source": "config" } + { "chainId": 56, "threshold": 200, "source": "default" }, + { "chainId": 1, "threshold": 50, "source": "config" } ] } ``` @@ -600,14 +600,14 @@ Escrow state (`escrowState`): `unfunded` → `funded` → `released` | `refunded ### `PATCH /api/admin/settings/confirmation-thresholds/:chainId` **Auth:** Admin only -**Body:** `{ "threshold": 3 }` -**Description:** Updates the runtime confirmation threshold for a chain. The in-memory cache is invalidated immediately so the next `TransactionSafetyProvider` evaluation uses the new value. +**Body:** `{ "threshold": 250 }` +**Description:** Updates the runtime confirmation threshold for a chain. Values below the chain's built-in acceptance floor are clamped to that floor; higher values are allowed. The in-memory cache is invalidated immediately so the next `TransactionSafetyProvider` evaluation uses the effective value. **Response 200:** ```json { "success": true, - "message": "Confirmation threshold for chain 56 updated to 3", - "data": { "chainId": 56, "threshold": 3 } + "message": "Confirmation threshold for chain 56 updated to 250", + "data": { "chainId": 56, "threshold": 250 } } ``` @@ -664,7 +664,7 @@ Escrow state (`escrowState`): `unfunded` → `funded` → `released` | `refunded "blockExplorer": "https://bscscan.com", "proxyAddress": "0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9", "nativeCurrency": { "name": "BNB", "symbol": "BNB", "decimals": 18 }, - "confirmationThreshold": 12, + "confirmationThreshold": 200, "tokens": [ { "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "symbol": "USDC", "decimals": 18, "name": "Binance-Peg USD Coin" }, { "address": "0x55d398326f99059ff775485246999027b3197955", "symbol": "USDT", "decimals": 18, "name": "Binance-Peg BSC-USD" } diff --git a/03 - API Reference/Scanner API.md b/03 - API Reference/Scanner API.md index 05d0587..2d65cdf 100644 --- a/03 - API Reference/Scanner API.md +++ b/03 - API Reference/Scanner API.md @@ -41,7 +41,7 @@ Register a new payment intent. The scanner will watch the specified chain for a | `amount` | string | yes | Amount in smallest unit (wei / token decimals) as a base-10 integer string | | `callbackUrl` | string | yes | URL the scanner POSTs to on confirmation | | `callbackSecret` | string | yes | HMAC key for `X-AMN-Signature` verification | -| `confirmations` | integer | no | Override chain default confirmation count (0 = use chain default) | +| `confirmations` | integer | no | Requested confirmation count. The scanner raises it to the chain acceptance floor if lower. | **Example request:** @@ -52,9 +52,9 @@ Register a new payment intent. The scanner will watch the specified chain for a "tokenAddress": "0x55d398326f99059ff775485246999027b3197955", "destination": "0xAbCd1234...", "amount": "10000000000000000000", - "callbackUrl": "https://api.amn.gg/api/payment/scanner-callback", + "callbackUrl": "https://api.amn.gg/api/payment/amn-scanner/webhook", "callbackSecret": "abc123...", - "confirmations": 12 + "confirmations": 200 } ``` @@ -79,6 +79,8 @@ Register a new payment intent. The scanner will watch the specified chain for a } ``` +**Confirmation floor**: Built-in accepted thresholds are currently BSC `200`, Ethereum `50`, Polygon `300`, Arbitrum `2400`, Base `300`, Tron `200`, and TON `120`. Callers may raise a requirement but cannot lower an intent below the chain floor. + **Idempotency**: If `intentId` already exists the existing intent's checkout block is returned (no error). **Error cases:** @@ -201,6 +203,7 @@ When an intent is confirmed the scanner POSTs to `callbackUrl`: "paymentReference": "0x1a2b3c4d5e6f7a8b", "txHash": "0xdeadbeef...", "blockNumber": 39000010, + "confirmations": 200, "amount": "10000000000000000000", "token": "0x55d398326f99059ff775485246999027b3197955", "chainId": 56, @@ -208,6 +211,8 @@ When an intent is confirmed the scanner POSTs to `callbackUrl`: } ``` +`confirmations` is the accepted confirmation count. Once the intent is `confirmed`, the scanner caps this value at `confirmationsRequired`; it does not keep reporting a live, ever-growing block count. + **Retry schedule** (on non-2xx or network error): 5 s → 30 s → 2 min → 10 min → 1 h → `webhook_failed`. The backend should verify `X-AMN-Signature` to reject forged callbacks: @@ -234,7 +239,7 @@ if (!timingSafeEqual(Buffer.from(received), Buffer.from(expected))) reject(); "paymentReference": "0x1a2b3c4d", "topicRef": "0xdeadbeef...", "status": "pending | confirming | confirmed | expired | webhook_failed", - "confirmationsRequired": 12, + "confirmationsRequired": 200, "txHash": null, "logIndex": null, "blockNumber": null, diff --git a/04 - Flows/Payment Flow - Scanner.md b/04 - Flows/Payment Flow - Scanner.md index 6e0732f..1313abc 100644 --- a/04 - Flows/Payment Flow - Scanner.md +++ b/04 - Flows/Payment Flow - Scanner.md @@ -6,7 +6,7 @@ created: 2026-05-30 # Payment Flow — AMN Pay Scanner (In-House) -> **Last updated:** 2026-05-31 — documented backend `2.6.81` confirmation persistence for scanner `confirmed` webhooks. +> **Last updated:** 2026-05-31 — documented backend `2.6.82` / scanner `0.1.7` capped accepted confirmation floors. End-to-end payment flow using the in-house AMN Pay Scanner, replacing the Request Network integration. The scanner is a separate microservice; the backend talks to it over an internal HTTP API. @@ -61,7 +61,7 @@ Authorization: Bearer "amount": "10000000000000000000", "callbackUrl": "https://api.amn.gg/api/payment/amn-scanner/webhook", "callbackSecret": "", - "confirmations": 12 + "confirmations": 200 } ``` @@ -93,7 +93,7 @@ The buyer signs and broadcasts the transaction using their wallet. The scanner i 1. `eth_getLogs` returns a `TransferWithReferenceAndFee` log matching `topicRef` 2. `validateLogMatchesIntent` verifies token address, destination, and amount 3. Intent moves to `confirming`; scanner waits for N blocks -4. Once `confirmationsRequired` blocks have been built on top, intent moves to `confirmed` +4. Once `confirmationsRequired` blocks have been built on top, intent moves to `confirmed`. The scanner stores and reports the accepted threshold count, not an ever-growing live count. **Tron path:** 1. TronGrid `Transfer` event matches `destination` (EVM-hex normalized) @@ -118,14 +118,14 @@ The scanner POSTs to `callbackUrl` with: "amount": "10000000000000000000", "token": "0x55d...", "chainId": 56, - "confirmations": 12, + "confirmations": 200, "status": "confirmed" } ``` Header `X-AMN-Signature` = `HMAC-SHA256(body, callbackSecret)`. -The backend verifies the signature, matches the `intentId` to a Payment record, and marks it paid. Backend `2.6.81+` treats scanner `status: "confirmed"` as final enough to run Transaction Safety Provider checks and persist `blockchain.confirmations`. The stored confirmation count comes from verifier evidence first, then the webhook payload, then the configured per-chain threshold fallback when the scanner has already waited for enough blocks but did not send a raw count. +The backend verifies the signature, matches the `intentId` to a Payment record, and marks it paid. Backend `2.6.82+` treats scanner `status: "confirmed"` as final enough to run Transaction Safety Provider checks and persist `blockchain.confirmations`. The stored confirmation count comes from verifier evidence first, then the webhook payload, then the configured per-chain threshold fallback, but settled counts are capped at the accepted threshold so the UI can show values like `200+` instead of chasing the live chain height forever. ### Step 6 — Backend acknowledges diff --git a/07 - Development/Environment Variables.md b/07 - Development/Environment Variables.md index 7975b66..daa2054 100644 --- a/07 - Development/Environment Variables.md +++ b/07 - Development/Environment Variables.md @@ -125,7 +125,7 @@ Request Network is the current primary payment provider. See [[PRD - Request Net | `TRANSACTION_SAFETY_ENABLED` | backend | optional | `true` | `true` | Enables the Transaction Safety Provider gate before Request Network pay-ins are marked completed. | | `TRANSACTION_SAFETY_REQUIRE_TX_HASH` | backend | optional | `true` | `true` | Blocks completion when provider evidence does not include a transaction hash. | | `TRANSACTION_SAFETY_REQUIRE_TRANSFER_MATCH` | backend | optional | `true` | `true` | Requires on-chain token/recipient/amount evidence to match the expected payment. | -| `TRANSACTION_SAFETY_MIN_CONFIRMATIONS` | backend | optional | `12` | `12` | Minimum chain confirmations required by the Transaction Safety Provider. | +| `TRANSACTION_SAFETY_MIN_CONFIRMATIONS` | backend | optional | `12` | chain floor | Fallback minimum confirmations for unknown chains. Known chains use built-in acceptance floors unless a higher admin-configured value exists. | | `TRANSACTION_SAFETY_AML_PROVIDER` | backend | optional | `none` | `none` | AML/sanctions provider adapter name. Non-`none` values should block until implemented/configured. | | `PAYMENT_LEDGER_ENFORCEMENT` | backend | optional | `false` | `true` | Enforce ledger gates for release/refund | | `PAYMENT_RECONCILIATION_ENABLED` | backend | optional | `false` | `true` | Enable scheduled provider reconciliation jobs | diff --git a/08 - Operations/Scanner Operations.md b/08 - Operations/Scanner Operations.md index 9a845fc..ce071d9 100644 --- a/08 - Operations/Scanner Operations.md +++ b/08 - Operations/Scanner Operations.md @@ -123,7 +123,7 @@ Edit `supported-chains.json`. Fields: | `rpcUrl` | Primary RPC endpoint | | `publicRpcUrl` | Fallback RPC (EVM only) | | `proxyAddress` | ERC20FeeProxy address (EVM); USDT contract (Tron); USDT Jetton master (TON) | -| `confirmationThreshold` | Blocks required (EVM); ignored for Tron/TON | +| `confirmationThreshold` | Chain acceptance floor. EVM workers wait this many blocks; Tron/TON use it as the accepted confirmation count reported to backend | | `verified` | `true` to activate the worker; `false` to disable without deleting | > [!important] diff --git a/09 - Audits/Activity Log.md b/09 - Audits/Activity Log.md index 06a806d..18c893d 100644 --- a/09 - Audits/Activity Log.md +++ b/09 - Audits/Activity Log.md @@ -11,6 +11,19 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`. --- +### 2026-05-31 — backend@a4d72df, frontend@07db9b0, scanner@ca62e7a — cap confirmations at per-chain acceptance floors + +**Commits:** backend `a4d72df`, frontend `07db9b0`, scanner `ca62e7a` (backend `2.6.82`, frontend `2.7.22`, scanner `0.1.7`) +**Touched:** +- Backend: `src/services/payment/safety/confirmationThresholdService.ts`, `src/routes/amnScannerWebhookRoutes.ts`, `src/services/payment/requestNetwork/requestNetworkRoutes.ts`, `src/services/payment/adapters/amnPayAdapter.ts`, `src/services/admin/confirmationThresholdRoutes.ts`, `src/services/payment/requestNetwork/supportedChains.json`, `__tests__/confirmation-threshold-service.test.ts`, `package.json`, `package-lock.json` +- Frontend: `src/sections/payment/payment-table-row.tsx`, `src/sections/payment/view/payment-details-view.tsx`, `package.json`, `package-lock.json` +- Scanner: `supported-chains.json`, `webhook.go`, `chain.go`, `tron_chain.go`, `ton_chain.go`, `main.go`, `README.md`, `VERSION`, `webhook_test.go` +**Why:** Confirmation depth should be a chain-specific acceptance floor, roughly a ten-minute safety window, not an endlessly increasing block counter. The backend now clamps runtime settings below the chain floor, caps stored settled confirmations at the effective threshold, sends the same threshold to AMN scanner intents, and the scanner includes the capped accepted count in webhooks. Frontend payment views show settled confirmation counts with a `+` suffix. +**Verification:** Backend `npm test -- --runTestsByPath __tests__/confirmation-threshold-service.test.ts __tests__/transaction-safety-provider.test.ts`; backend `npm run typecheck`; backend `BASE_URL=https://dev.amn.gg ./scripts/smoke/rn-webhook.sh`; backend `git diff --check`; scanner `go test ./...`; scanner `git diff --check`; frontend `npx tsc --noEmit --ignoreDeprecations 6.0`; frontend `git diff --check`. +**Linked docs updated:** [[Payment]], [[Payment API]], [[Scanner API]], [[Payment Flow - Scanner]], [[ScannerIntent]], [[Scanner Architecture]], [[Scanner Operations]], [[Environment Variables]] + +--- + ### 2026-05-31 — backend@896f17f, frontend@fd8e797 — persist webhook confirmations for scanner payments **Commits:** backend `896f17f`, frontend `fd8e797` (backend `2.6.81`, frontend `2.7.21`)