From ee25f22865eb1274fbc3e33057a554493f96a822 Mon Sep 17 00:00:00 2001 From: Carlys17 Date: Thu, 30 Jul 2026 03:54:52 +0200 Subject: [PATCH] feat: add review-before-submit step to asset registration wizard - Add validation summary, issuer info, network details to review step - Add warning states for high-supply and restricted jurisdictions - Add tests covering review states and warnings - Add docs/asset-registration-review.md Closes #176 --- docs/asset-registration-review.md | 46 ++++++++++++ .../components/AssetCreationWizard.test.tsx | 19 +++++ .../components/AssetCreationWizard.tsx | 72 ++++++++++++++++++- 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 docs/asset-registration-review.md diff --git a/docs/asset-registration-review.md b/docs/asset-registration-review.md new file mode 100644 index 0000000..2927ff9 --- /dev/null +++ b/docs/asset-registration-review.md @@ -0,0 +1,46 @@ +# Asset Registration Review Step + +The RWA asset creation wizard (`src/features/asset-creation/components/AssetCreationWizard.tsx`) +requires a review-before-submit step so issuers confirm what they are sending +for compliance review. This documents the review flow and its warning states. + +## Flow + +``` +form -> review -> success +``` + +1. **Form** — the issuer enters asset name, ticker, asset class, initial + requested supply, and jurisdiction. Field-level validation runs on + "Review request". +2. **Review** — a read-only summary of the request. The issuer must + explicitly confirm before anything is submitted. +3. **Success** — an admin action receipt confirms the request was created in + `pending` status. + +## Review step contents + +The review screen shows: + +- **Validation summary** — a checklist confirming required fields, ticker + format/uniqueness, and supply bounds passed. +- **Request details** — asset name, ticker, asset class, issuer (the + submitting wallet, truncated), initial requested supply, jurisdiction, and + the target **network** (from `NEXT_PUBLIC_NETWORK_PASSPHRASE`, resolved via + `formatNetworkLabel`). +- **Warning states** — non-blocking amber warnings surfaced when: + - requested supply exceeds 50% of the soft cap + (`DEFAULT_ASSET_CREATION_MAX_AMOUNT`), or + - the jurisdiction is outside the supported list. + +Warnings are informational only — they do not block submission, but they flag +requests that compliance review should scrutinise more closely. + +## Notes + +- The issuer address is truncated for display (`ABCDEF…7890`) and is never + used to make a legal or regulatory determination. +- The review is a protocol-level compliance check only and is not legal or + financial advice. +- Submitting creates an `IssuanceRequest` in `pending` status; the asset + becomes mintable only after a separate compliance approval step. diff --git a/src/features/asset-creation/components/AssetCreationWizard.test.tsx b/src/features/asset-creation/components/AssetCreationWizard.test.tsx index f8b8368..fc1c696 100644 --- a/src/features/asset-creation/components/AssetCreationWizard.test.tsx +++ b/src/features/asset-creation/components/AssetCreationWizard.test.tsx @@ -61,6 +61,25 @@ describe('AssetCreationWizard', () => { expect(screen.getByText('Request submitted')).toBeInTheDocument(); }); + it('shows review details including issuer, network, and warnings', () => { + render(); + + fireEvent.change(screen.getByLabelText(/asset name/i), { + target: { value: 'Frankfurt Logistics Fund' }, + }); + fireEvent.change(screen.getByLabelText(/ticker/i), { target: { value: 'FR-LOG2' } }); + fireEvent.change(screen.getByLabelText(/initial requested supply/i), { + target: { value: '600000000' }, + }); + + fireEvent.click(screen.getByText('Review request')); + + expect(screen.getByText('ABCDEF…7890')).toBeInTheDocument(); + expect(screen.getByText(/Network/)).toBeInTheDocument(); + expect(screen.getByText(/Validation summary/)).toBeInTheDocument(); + expect(screen.getByRole('alert')).toHaveTextContent(/exceeds 50% of the soft cap/i); + }); + it('lets the user go back from review to fix the form', () => { render(); fillValidForm(); diff --git a/src/features/asset-creation/components/AssetCreationWizard.tsx b/src/features/asset-creation/components/AssetCreationWizard.tsx index 09f5d1d..dcd9041 100644 --- a/src/features/asset-creation/components/AssetCreationWizard.tsx +++ b/src/features/asset-creation/components/AssetCreationWizard.tsx @@ -1,9 +1,11 @@ import { useState } from 'react'; +import { AlertTriangle, CheckCircle2 } from 'lucide-react'; import { validateAssetCreationRequest, ASSET_CREATION_ERROR_MESSAGES, SUPPORTED_JURISDICTIONS, ASSET_CLASS_OPTIONS, + DEFAULT_ASSET_CREATION_MAX_AMOUNT, type AssetCreationErrorCode, } from '@/lib/assetCreationRequest'; import { useFormErrors, FormFieldError, FormError } from '@/features/forms/validation'; @@ -11,6 +13,7 @@ import { AdminActionReceiptView, mapAdminActionReceipt, } from '@/features/admin/receipts'; +import { getTargetNetwork, formatNetworkLabel } from '@/lib/environment'; import type { IssuanceRequest } from '@/fixtures/issuer'; type WizardStep = 'form' | 'review' | 'success'; @@ -170,9 +173,50 @@ export default function AssetCreationWizard({ } if (step === 'review') { + const networkLabel = formatNetworkLabel(getTargetNetwork()); + const parsedAmount = Number(amount); + const isHighSupply = parsedAmount > DEFAULT_ASSET_CREATION_MAX_AMOUNT * 0.5; + const isRestrictedJurisdiction = !SUPPORTED_JURISDICTIONS.includes( + jurisdiction.trim().toUpperCase() as (typeof SUPPORTED_JURISDICTIONS)[number], + ); + const warnings: string[] = []; + if (isHighSupply) { + warnings.push( + 'Requested supply exceeds 50% of the soft cap. Ensure compliance review can accommodate this volume.', + ); + } + if (isRestrictedJurisdiction) { + warnings.push( + 'Jurisdiction is outside the supported list. Additional compliance review may be required.', + ); + } + return (

Review issuance request

+ + {/* Validation summary */} +
+

+

+
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+
Asset name
@@ -188,18 +232,44 @@ export default function AssetCreationWizard({
Asset class
{assetClass}
+
+
Issuer
+
+ {requestedBy ? `${requestedBy.slice(0, 6)}…${requestedBy.slice(-4)}` : 'Unknown'} +
+
Initial requested supply
{Number(amount).toLocaleString('en-US')}
-
+
Jurisdiction
{jurisdiction.trim().toUpperCase()}
+
+
Network
+
{networkLabel}
+
+ {/* Warning states */} + {warnings.length > 0 && ( +
+ {warnings.map((warning, idx) => ( +
+
+ ))} +
+ )} +