SecureXchange: Technical Architecture & System Documentation
SecureXchange is a premium-tier, real-time, bi-directional currency conversion and transaction processing web application designed specifically for the Thai Baht (THB) ↔ Myanmar Kyat (MMK) exchange pair. The platform integrates a high-end dark obsidian visual design with strict financial precision, real-time reactive data streaming, role-based access controls (RBAC), and deep mobile integration via the LINE Messaging API and LINE LIFF SDK.
1. System & Architecture Overview
SecureXchange is engineered on a serverless, decoupled modern stack that ensures immediate state synchronization, tight security boundaries, and frictionless user experiences inside the LINE application ecosystem.
graph TD
subgraph Client [Client-Side Layer (React 18 / Vite / TS)]
A[LINE LIFF Browser / Web Client] -->|Fetch Identity / Profile| B(useLiffIdentity Hook)
A -->|Subscribe Exchange Rates| C(useExchangeRates Hook)
A -->|Submit Swap Requests| D(ExchangeForm Component)
A -->|Access Control Gating| E(Admin Portal & Queue)
end
subgraph Serverless [Serverless & Real-time Layer]
F[(Supabase Postgres Database)]
G((Supabase Realtime Engine))
H[Deno Edge Function: send-line-exchange]
end
subgraph Messaging [LINE Messaging Ecosystem]
I[LINE Messaging API Gateway]
J[Merchant Line Account]
K[Customer Line Chat]
end
C <==>|Realtime Subscription / TLS Websocket| G
G <==>|CDC Change Data Capture| F
D -->|REST / CORS API POST Payload| H
H -->|Validate Rate Limit / Persist SQL| F
H -->|Secure RPC Push Notification| I
I -->|Direct Message Alert| J
I -->|Receipt Auto-Reply| K
E -->|Write Rates / Update Status| F
Architecture Key Principles:
- Reactive Rate Updates: Eliminates polling overhead. Exchange rates are bound to a PostgreSQL CDC (Change Data Capture) stream over WebSockets via Supabase Realtime.
- LINE-First Execution: The application detects if it is running inside the LINE LIFF custom tab context, automatically authenticates user identity, and feeds metadata (LINE display name, unique ID) down to transactional pipelines.
- RBAC at Database Layer: Role validation is enforced at both frontend routes and PostgreSQL Row-Level Security (RLS) policies using customized security handler functions.
- Synchronous Serverless Edge Processing: Rather than relying on heavy persistent workers or async job queues, transactional exchanges and merchant push operations are processed synchronously inside lightweight Deno V8 isolates (Supabase Edge Functions).
2. Database Schema Design (PostgreSQL)
The database schema is highly optimized for security, rate synchronization, and auditability. It leverages built-in Postgres triggers for automatic role provisioning and strict RLS mechanisms.
2.1 Table: exchange_rates
Manages the current bid/ask parameters for the currency pair. This table has exactly one active row which is updated by administrators and listened to by all active clients.
| Column | Type | Constraints | Default / Description |
| :--- | :--- | :--- | :--- |
| id | bigint | PRIMARY KEY, GENERATED BY DEFAULT AS IDENTITY | Auto-incrementing identifier. |
| buying_rate | numeric | NOT NULL | High-precision multiplier for THB → MMK calculations. |
| selling_rate | numeric | NOT NULL | High-precision divisor for MMK → THB calculations. |
| updated_at | timestamp with time zone | NOT NULL | timezone('utc'::text, now()) - Tracks rate freshness. |
RLS Policies on exchange_rates:
- Select Policy (
Enable read access for all users): Open to public/authenticated users. - Write Policy (
Enable insert/update/delete for admins only): Restricted to users who pass the customized role validation helper function:(auth.uid() IN ( SELECT user_roles.user_id FROM user_roles WHERE (user_roles.role = 'admin'::text)))
2.2 Table: transactions
Records user-submitted trade requests, tracking statuses and associated LINE identifiers for automated notifications.
| Column | Type | Constraints | Default / Description |
| :--- | :--- | :--- | :--- |
| id | uuid | PRIMARY KEY | gen_random_uuid() - Cryptographically secure unique key. |
| user_id | uuid | FOREIGN KEY references auth.users(id) | Identifies the authenticated creator. |
| from_currency | text | NOT NULL | The source currency (e.g., 'THB' or 'MMK'). |
| to_currency | text | NOT NULL | The destination currency (e.g., 'MMK' or 'THB'). |
| amount | numeric | NOT NULL | Swap volume up to 4 decimal places. |
| rate | numeric | NOT NULL | The exchange rate locked in at submission time. |
| status | text | CHECK (status IN ('pending', 'approved', 'completed', 'rejected')) | Defaults to 'pending'. |
| line_user_id | text | NULLABLE | Customer's identifier parsed from LINE LIFF context. |
| line_display_name | text | NULLABLE | Customer's display name parsed from LINE LIFF context. |
| idempotency_key | text | UNIQUE | Prevents double-charging during concurrent requests. |
| created_at | timestamp with time zone | NOT NULL | timezone('utc'::text, now()) |
RLS Policies on transactions:
- Admin Access (
Admins can do everything): Granted full select, insert, update, and delete access based on the'admin'user role. - User Access (
Users can view their own transactions): Selective filtering usingauth.uid() = user_id. - User Insertion (
Users can insert their own transactions): Restricted to insertion where the inserted record'suser_idmatchesauth.uid().
2.3 Table: user_roles
Coordinates the granular role allocation (RBAC) supporting the SecureXchange security layer.
| Column | Type | Constraints | Default / Description |
| :--- | :--- | :--- | :--- |
| id | uuid | PRIMARY KEY | gen_random_uuid() |
| user_id | uuid | FOREIGN KEY references auth.users(id), UNIQUE | One role per user. |
| role | text | CHECK (role IN ('admin', 'user')) | Defaults to 'user'. |
| created_at | timestamp with time zone | NOT NULL | timezone('utc'::text, now()) |
Custom Database Functions & Database Triggers:
- Trigger Function:
handle_new_user: Automatically executes upon new user registration insideauth.users. It dynamically awards administrative rights to the very first user registering on the platform, while all subsequent registrations default to'user'.CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE is_first BOOLEAN; BEGIN -- Check if any user already exists in user_roles SELECT NOT EXISTS (SELECT 1 FROM public.user_roles) INTO is_first; IF is_first THEN INSERT INTO public.user_roles (user_id, role) VALUES (new.id, 'admin'); ELSE INSERT INTO public.user_roles (user_id, role) VALUES (new.id, 'user'); END IF; RETURN new; END; $$; - Helper RPC:
has_role: A security helper exposed via POST RPC endpoints to quickly verify authorization context in SQL and UI:CREATE OR REPLACE FUNCTION public.has_role(role_to_check text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM public.user_roles WHERE user_id = auth.uid() AND role = role_to_check ); END; $$;
3. Backend Edge Functions (Deno Runtime)
The platform backend resides in a highly focused Deno edge worker, /supabase/functions/send-line-exchange/index.ts, processing transaction requests and interacting directly with the LINE Messaging API.
3.1 Flow & Execution Diagram
sequenceDiagram
autonumber
actor Client as React Client
participant EF as Edge Function (send-line-exchange)
participant DB as Postgres Database
participant LINE as LINE Messaging API
Client->>EF: POST transaction payload + User Auth Bearer JWT
Note over EF: Validates CORS headers<br/>Parses authorization bearer token
EF->>DB: Check last transaction timestamp (Rate Limit verification)
DB-->>EF: Return last transaction timestamp
alt Time elapsed < 60 seconds
EF-->>Client: HTTP 429 [Too Many Requests] (Rate Limit Triggered)
else Rate Limit Cleared
EF->>DB: Query current exchange_rates table
DB-->>EF: Return active rates
Note over EF: Recalculates amount & rate in sandbox<br/>Ensures no tampering by the client
EF->>DB: Insert new transaction with 'pending' status
DB-->>EF: Transaction persisted & UUID returned
EF->>LINE: Send Push Notification (To Merchant Account)
LINE-->>EF: Status 200 OK
alt User submitted from LINE LIFF context
EF->>LINE: Send Confirmation Push (To User line_user_id)
LINE-->>EF: Status 200 OK
end
EF-->>Client: HTTP 200 OK + Transaction Object
end
3.2 Secure Rate Limiting and Verification Logic
To maintain the financial integrity of the system and prevent denial of service (DoS):
- Rate Limiting Constraint: An active user (
user_id) or a distinct LINE identity (line_user_id) is strictly restricted to one transaction per minute (60 seconds). This prevents double-click race conditions and transaction flooding. - Server-Side Rate Recalculation: The Edge Function never trusts the calculated total or conversion rate passed from the client-side UI. Instead, the function queries the
exchange_ratesdatabase table directly at the moment of receipt and reconstructs all conversion metrics using secure server-side arithmetic. - CORS Guard: Strict Pre-flight
OPTIONShandling ensures external unauthorized client environments are immediately blocked at the edge.
3.3 Deno Environment Variables (Secrets)
The Edge Function requires the following variables configured via supabase secrets set:
LINE_CHANNEL_ACCESS_TOKEN: Authorization bearer token configured within the LINE Developers Console.MERCHANT_LINE_ID: The unique user ID of the merchant receiving transactional alerts.SUPABASE_URL: Local or production backend URL.SUPABASE_SERVICE_ROLE_KEY: Service bypass key used to authenticate system-level database insertions.
4. Frontend Client Architecture
The frontend is developed using React 18, compiled using Vite, and structured to allow bi-directional switching alongside instant rate updates.
src/
├── components/
│ ├── admin/
│ │ ├── MarginControls.tsx # Set exchange rate inputs
│ │ └── TransactionQueue.tsx # Queue state list & status updates
│ ├── exchange/
│ │ ├── ExchangeForm.tsx # Converter layout & submit trigger
│ │ ├── IndexHeader.tsx # Navigation, status, language toggle
│ │ └── TrustBadges.tsx # UX security signals
│ ├── ui/ # Radix & shadcn primitive definitions
│ ├── BottomNav.tsx # Context-aware bottom bar
│ ├── CurrencyInput.tsx # Form-validated numeric input
│ ├── ExchangeRateDisplay.tsx # Real-time rates with gold glow animation
│ ├── LanguageToggle.tsx # Dual-lingual translation selection
│ ├── ThemeToggle.tsx # Context theme manager (Obsidian force-dark)
│ └── theme-provider.tsx # React Context Theme Provider
├── config/
│ └── liff.ts # Configuration mapping the LINE LIFF App ID
├── hooks/
│ ├── useExchangeRates.ts # Core TanStack hook with Postgres stream listener
│ └── useLiffIdentity.ts # LIFF profile initialization and state hook
├── i18n/
│ ├── locales/
│ │ ├── en.json # English translations
│ │ └── my.json # Myanmar translations (Burmese)
│ └── config.ts # react-i18next setup engine
├── integrations/
│ └── supabase/
│ ├── client.ts # supabase-js initialization
│ └── types.ts # Database auto-generated TypeScript schema
├── lib/
│ └── utils.ts # Tailwind dynamic style merging tool (cn)
├── pages/
│ ├── Admin.tsx # Locked administrator console
│ ├── Auth.tsx # Supabase Email & Password / OTP gateway
│ ├── Faq.tsx # Local rules, requirements & FAQ portal
│ ├── Guide.tsx # Step-by-step conversion walk-through
│ ├── History.tsx # Authenticated transaction tracking panel
│ ├── Index.tsx # Main transactional converter landing
│ └── NotFound.tsx # Fallback Page
├── App.css
├── App.tsx # App container wrapping providers
├── index.css # Premium custom HSL colors and animation keyframes
└── main.tsx # Client DOM rendering target
4.1 Real-time State Synchronization: useExchangeRates
This custom hook utilizes TanStack Query to fetch rates with an infinite stale duration, coupled with an active Supabase real-time database channel that listens to specific target alterations:
// Query cache initialization
const { data: initialRates } = useQuery({
queryKey: ["exchange_rates"],
queryFn: async () => {
const { data, error } = await supabase
.from("exchange_rates")
.select("*")
.single();
if (error) throw error;
return data;
},
staleTime: Infinity, // Avoid polling overhead
});
// Postgres CDC Stream Subscription
useEffect(() => {
const channel = supabase
.channel("exchange-rates-changes")
.on(
"postgres_changes",
{ event: "UPDATE", schema: "public", table: "exchange_rates" },
(payload) => {
// Automatically inject new stream value into TanStack cache
queryClient.setQueryData(["exchange_rates"], payload.new);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [queryClient]);
4.2 LINE Identity Hook: useLiffIdentity
Initializes the LINE LIFF environment. When the application loads inside a LINE environment, it extracts metadata without forcing redirects, caching authentication state seamlessly:
import liff from "@line/liff";
export const useLiffIdentity = () => {
const [profile, setProfile] = useState<{ userId: string; displayName: string; pictureUrl?: string } | null>(null);
const [isLiff, setIsLiff] = useState(false);
useEffect(() => {
liff.init({ liffId: "2008550373-7J1o2qld" })
.then(() => {
setIsLiff(liff.isInClient());
if (liff.isLoggedIn()) {
liff.getProfile().then((p) => setProfile(p));
} else if (liff.isInClient()) {
liff.login();
}
})
.catch((err) => console.error("LIFF initialization failed", err));
}, []);
return { profile, isLiff };
};
5. UI/UX Style Token Guidelines
SecureXchange implements an customized premium dark-obsidian aesthetic. Standard Tailwind configurations are overlaid with custom smooth glassmorphism structures and visual cues.
5.1 Design System Color Palette (HSL Specs)
Custom HSL tokens are embedded directly within index.css:
:root {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 43 100% 49%; /* #F5A623 - Gold */
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 43 100% 49%; /* Active gold halo highlights */
}
5.2 Key Premium Visual Utilities (Vanilla CSS Styles)
- Obsidian Dark Backdrop (
obsidian-gradient): A multi-stop dynamic radial-gradient centered around an deep rich onyx backdrop:radial-gradient(circle at top, #141416 0%, #08080a 100%) - Glass Panel overlay (
glass-panel): Provides clean glass layering usingbackdrop-filter: blur(16px)and subtle transparency borders to distinguish overlay components. - Gold Pulse Indicator (
gold-glow): Highlights live status changes using smooth gold CSS box-shadow expansion animations. - Tactile Feedback micro-animations: Vibrant CSS transitions applied on interaction targets to deliver physical tactile reactions during fast swaps.
6. Internationalization (i18n)
The dual-lingual engine ensures native readability for both English and Myanmar speakers. Key phrases, operational messages, warnings, and transaction logs are mapped across translation indices.
Language Configurations:
- Default Locale:
'my'(Myanmar Burmese) is the default selected locale, with seamless toggle switching to'en'(English). - Fallback engine: Automatically handles user preference caching inside local cookies/storage and falls back to
'my'to avoid empty string errors in runtime environments. - Translation resources structure:
src/i18n/locales/en.json: Houses English localized labels.src/i18n/locales/my.json: Houses highly optimized Myanmar Burmese localizations.
7. Operational Workflows & Life Cycles
7.1 Real-time Conversion Workflow (Bi-directional)
The conversion relies on simple, mathematically sound rules to avoid floating-point drift:
THB to MMK:
Output_MMK = Input_THB * Buying_Rate
MMK to THB:
Output_THB = Input_MMK / Selling_Rate
- Calculations are restricted to exactly 4 decimal places when rendering final results.
- Clicking the Swap Currency Button swaps the
fromandtovalues, dynamically updating conversion inputs, calculation indicators, and placeholder values instantly.
7.2 Transaction Lifecycle Workflow
The application handles transactions through a strict workflow with role-based checks.
stateDiagram-v2
[*] --> Pending : User Submits via Edge Function
Pending --> Approved : Admin Clicks 'Approve' (Verifies Funds)
Pending --> Rejected : Admin Clicks 'Reject' (Invalid/No payment)
Approved --> Completed : Admin Confirms Final Settlement
Rejected --> [*]
Completed --> [*]
- Pending State: Submitted swaps are immediately visible in the Admin Transaction queue. Rates are locked in upon submission.
- Verification: Merchant receives an instant LINE push notification alert containing user details, rates, and cash-in metrics.
- Settlement Execution: Once cash transfer is validated, the administrator marks the order as Approved or completes it to close the lifecycle.
8. Observability & Testing
8.1 Error Tracking and Analytics
- Sentry: Integrated for comprehensive error tracking, performance monitoring, and tracing unhandled exceptions in the React frontend.
- PostHog: Configured for capturing user product usage telemetry, tracking onboarding progress via the
react-joyrideintegration, and general behavior analytics.
8.2 Automated Testing
- Vitest: The platform leverages Vitest for executing fast, lightweight unit tests to ensure stability across React components and core functional logic.
9. Deployment & Environmental Configuration
9.1 Frontend Deployment (Vite + Vercel)
- Verify
.env.localcontains valid production variables:VITE_SUPABASE_URL=https://[YOUR_SUPABASE_PROJECT].supabase.co VITE_SUPABASE_PUBLISHABLE_KEY=[YOUR_ANON_KEY] - Compile build package (optimized with manual chunk splitting for faster load times):
npm run build - Deploy static folder contents to Vercel or your hosting environment.
9.2 Backend Deployment (Supabase Edge Function)
Deploy the edge workers and set environment variables:
# Link project to local Supabase configuration
supabase link --project-ref [YOUR-PROJECT-ID]
# Deploy the send-line-exchange Deno serverless function
supabase functions deploy send-line-exchange
# Set mandatory production secrets for LINE integration
supabase secrets set LINE_CHANNEL_ACCESS_TOKEN="your-channel-token"
supabase secrets set MERCHANT_LINE_ID="merchant-line-id"
10. Verification & Troubleshooting
Local Function Testing
To verify Deno serverless worker performance without deploying live, invoke local hosting:
supabase functions serve send-line-exchange --no-verify-jwt
Submit sample payloads containing authorization headers to verify CORS handlers, rate recalculation triggers, and correct PostgreSQL interactions locally.