Sign in to Online Banking
Access your private client portal
Access your private client portal
Account Ref: VG-9482-0194-SIM
Real-time ledger updates from your current session
Bank routing details are not configured
By default, NORTHVAL runs in Self-Contained Demo Mode so you can test every feature immediately without external setup. To connect your own Supabase PostgreSQL project, provide your project URL and public anon key below.
service_role secret key in browser code. Only use the public anon key protected by Row-Level Security (RLS).
northval-trust-banking/ ├── index.html # Complete self-contained application (HTML/CSS/JS) ├── supabase/ │ └── schema_rls.sql # PostgreSQL tables, constraints, RPC & RLS policies └── README.md # Deployment & security documentation
Fictional Sandbox Login Credentials:
Email: client@northval.demo
Password: NORTHVAL!2025
Demo Auth vs Production Auth: Demo authentication verifies against a sandboxed session state in browser memory/sessionStorage so the prototype works out-of-the-box. Production authentication uses Supabase Auth (JWT + bcrypt) with Row-Level Security enforcing user isolation on PostgreSQL.
Run the following SQL script inside your Supabase SQL Editor. Notice that direct browser UPDATE on account balances is prohibited; balances are mutated exclusively via a server-side SECURITY DEFINER PostgreSQL transaction function (simulate_transfer_rpc) with row locking (FOR UPDATE) to prevent race conditions and client-side balance tampering.
-- ============================================================================
-- NORTHVAL PROTOTYPE — SUPABASE POSTGRESQL SCHEMA & RLS POLICIES
-- ============================================================================
-- Enable UUID generation
create extension if not exists "pgcrypto";
-- 1. PROFILES TABLE
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
display_name text not null check (char_length(trim(display_name)) between 2 and 80),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- 2. ACCOUNTS TABLE (Fictional Prototype Accounts)
create table if not exists public.accounts (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users(id) on delete cascade,
account_reference text not null unique,
simulated_balance numeric(15,2) not null default 148250.00 check (simulated_balance >= 0),
currency text not null default 'USD' check (currency in ('USD', 'EUR', 'GBP')),
status text not null default 'Active' check (status in ('Active', 'Frozen', 'Closed')),
created_at timestamptz not null default now()
);
create index if not exists idx_accounts_owner on public.accounts(owner_id);
-- 3. TRANSACTIONS TABLE (Immutable Simulated Ledger)
create table if not exists public.transactions (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
owner_id uuid not null references auth.users(id) on delete cascade,
transaction_type text not null check (transaction_type in ('transfer', 'deposit', 'withdrawal', 'fee')),
direction text not null check (direction in ('CREDIT', 'DEBIT')),
amount numeric(15,2) not null check (amount > 0),
currency text not null default 'USD' check (currency in ('USD', 'EUR', 'GBP')),
recipient_name text,
recipient_account text,
recipient_bank text,
description text not null,
status text not null default 'Completed' check (status in ('Completed', 'Pending', 'Failed')),
reference text not null unique,
created_at timestamptz not null default now()
);
create index if not exists idx_transactions_owner_created on public.transactions(owner_id, created_at desc);
-- 4. LOAN REQUESTS TABLE (Simulated Credit Applications)
create table if not exists public.loan_requests (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users(id) on delete cascade,
requested_amount numeric(15,2) not null check (requested_amount >= 1000 and requested_amount <= 5000000),
currency text not null default 'USD' check (currency in ('USD', 'EUR', 'GBP')),
loan_type text not null,
repayment_period text not null,
monthly_income numeric(15,2) check (monthly_income is null or monthly_income >= 0),
purpose text not null check (char_length(trim(purpose)) between 5 and 500),
status text not null default 'Submitted' check (
status in ('Draft', 'Submitted', 'Under Review', 'More Information Required', 'Approved', 'Declined')
),
reference text not null unique,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists idx_loans_owner_created on public.loan_requests(owner_id, created_at desc);
-- ============================================================================
-- ENABLE ROW LEVEL SECURITY (RLS) ON ALL TABLES
-- ============================================================================
alter table public.profiles enable row level security;
alter table public.accounts enable row level security;
alter table public.transactions enable row level security;
alter table public.loan_requests enable row level security;
-- PROFILES POLICIES
create policy "Users can view own profile"
on public.profiles for select
to authenticated
using (auth.uid() = id);
create policy "Users can insert own profile"
on public.profiles for insert
to authenticated
with check (auth.uid() = id);
create policy "Users can update own profile"
on public.profiles for update
to authenticated
using (auth.uid() = id)
with check (auth.uid() = id);
-- ACCOUNTS POLICIES (Read-only for clients; balance updates only via server RPC)
create policy "Users can view own accounts"
on public.accounts for select
to authenticated
using (auth.uid() = owner_id);
-- TRANSACTIONS POLICIES (Clients can view own ledger; inserts handled via atomic RPC)
create policy "Users can view own transactions"
on public.transactions for select
to authenticated
using (auth.uid() = owner_id);
-- LOAN REQUESTS POLICIES (Clients can view and submit with 'Submitted' status only)
create policy "Users can view own loan requests"
on public.loan_requests for select
to authenticated
using (auth.uid() = owner_id);
create policy "Users can submit loan requests without self-approval"
on public.loan_requests for insert
to authenticated
with check (
auth.uid() = owner_id
and status in ('Draft', 'Submitted')
);
-- ============================================================================
-- SERVER-SIDE ATOMIC SIMULATED TRANSFER RPC (PREVENTS CLIENT BALANCE TAMPERING)
-- ============================================================================
create or replace function public.simulate_transfer_rpc(
p_account_id uuid,
p_recipient_name text,
p_recipient_account text,
p_recipient_bank text,
p_amount numeric,
p_currency text,
p_description text,
p_reference text
)
returns jsonb
language plpgsql
security definer
set search_path = public
as $$
declare
v_account public.accounts%rowtype;
v_new_balance numeric(15,2);
v_tx_id uuid;
begin
if auth.uid() is null then
raise exception 'Authentication required';
end if;
if p_amount is null or p_amount <= 0 then
raise exception 'Transfer amount must be greater than zero';
end if;
-- Lock account row and verify ownership
select * into v_account
from public.accounts
where id = p_account_id and owner_id = auth.uid()
for update;
if not found then
raise exception 'Account not found or access denied';
end if;
if v_account.status <> 'Active' then
raise exception 'Account is not active';
end if;
if v_account.simulated_balance < p_amount then
raise exception 'Insufficient simulated funds for this transfer';
end if;
v_new_balance := v_account.simulated_balance - round(p_amount, 2);
update public.accounts
set simulated_balance = v_new_balance
where id = v_account.id;
insert into public.transactions (
account_id, owner_id, transaction_type, direction,
amount, currency, recipient_name, recipient_account,
recipient_bank, description, status, reference
) values (
v_account.id, auth.uid(), 'transfer', 'DEBIT',
round(p_amount, 2), p_currency, trim(p_recipient_name), trim(p_recipient_account),
nullif(trim(p_recipient_bank), ''), coalesce(nullif(trim(p_description), ''), 'Simulated Wire Transfer'),
'Completed', p_reference
) returning id into v_tx_id;
return jsonb_build_object(
'transaction_id', v_tx_id,
'new_balance', v_new_balance,
'reference', p_reference
);
end;
$$;
Important Regulatory Note: Deploying this website to Cloudflare Pages or Firebase Hosting puts your interactive prototype online with HTTPS, but web hosting does not provide banking licenses, payment rails, or financial custody.
index.html into a dedicated folder (e.g., Download/northval-bank/index.html) using your Android browser or a code editor app such as Acode or Spck Editor.
northval-prototype, set it to Private or Public, and tap Create repository.
index.html from your Android storage, enter a commit message, and tap Commit changes.
northval-prototype repository. Leave Build Command blank and Build Output Directory as /, then tap Save and Deploy.https://northval-prototype.pages.dev).
.pages.dev URL to Site URL and Redirect URLs.anon public key from Project Settings → API and enter them into the prototype's "Configure Live Supabase Backend" modal..pages.dev URL on your Android phone, sign in, test a simulated transfer and loan request, and verify layout responsiveness.
index.html on GitHub (or upload a new version), Cloudflare Pages automatically rebuilds and updates your live URL in under a minute.
| Feature / Capability | Works in Self-Contained Demo Mode | Works with Supabase Configured | Requires Regulated Financial Provider |
|---|---|---|---|
| Authentication & Protected Views | ✓ Simulated Session Auth | ✓ Real JWT Auth & Password Reset | KYC/AML Identity Verification (e.g., Alloy/Persona) |
| Balances & Transaction History | ✓ Session Persistence (sessionStorage) | ✓ PostgreSQL + RLS User Isolation | Core Banking Ledger & Custodial Accounts |
| Fund Transfers | ✓ Validated Simulation + Receipt | ✓ Server-Side Atomic RPC Simulation | Requires Licensed BaaS / Clearing (SWIFT, FedNow, SEPA) |
| Loan Requests & Underwriting | ✓ Submitted Status (Never Auto-Approved) | ✓ RLS-Protected Database Storage | Licensed Lender, Credit Bureau Pulls & Disbursement |
isSubmitting guard).Submitted status and never auto-approve.escapeHtml to prevent XSS.service_role keys present anywhere.