iGrant.io business wallet portal (umbrella)
When to use
A customer organisation wants its own business wallet portal - their
branding, their pages, only holder functions (receive, hold, present,
notifications) - on the iGrant.io OWS APIs. This skill is the route map: it
does not duplicate any contract; it tells you which skill to open for each
part and in which order. For a single missing fact (a path, an enum, a
payload), go straight to igrantio-api-holder.
Before you build: run the integrator intake in igrantio-ows-overview - environment, API key, tenancy, backend host, webhooks, frontend - one question at a time, a recommended default with each. Then add the portal questions below.
Portal intake (ask after the overview intake)
- Stack - which web stack? Recommend Next.js (App Router) +
TypeScript with Better Auth passwordless login (see "Recommended
stack" below); accept the customer's standing stack if they have one.
- Scope - which pages? Recommend all four: base configuration,
received credentials, shared credentials, notifications.
- Scaffold or custom - start from the ready
HolderPortal scaffold and
restyle, or build custom views on the hooks + engines?
Recommend the scaffold first; every view works standalone.
- Look - the customer's design system, or the default iGrant.io look
(
igrantio-usecase-ui)? The reference components are unstyled on purpose.
- EBW onboarding state - is the organisation's wallet unit already
valid (WUA + Owner ID/LPID issued)? If not, plan step 7.
Recommended stack
Unless the customer has a standing choice, recommend and scaffold:
- Next.js (App Router) + TypeScript for the portal
(
npx create-next-app@latest --typescript). All reference code is strict
TypeScript; mount the holder views in client components ("use client") -
they use EventSource, the clipboard, and window.
- Better Auth (https://www.better-auth.com) for passwordless portal
login - magic link or email OTP (add passkeys where wanted) so portal
users sign in without passwords. Protect the portal routes with the Better
Auth session (middleware). Portal login is orthogonal to wallet auth:
the OWS API key lives only in the holder backend, whoever is signed in.
- Run the holder backend (the Express reference) as its own service beside
the Next.js app. Set its
CORS_ORIGINS to the portal origin; for
multi-user portals, verify the Better Auth session in a proxy middleware
before forwarding to OWS.
Build order
| # |
Step |
Skill to open |
| 1 |
Architecture, glossary, intake |
igrantio-ows-overview |
| 2 |
Deploy the tenant backend: API-key-hiding proxy scoped to holder endpoints + notifications SSE relay (port 6003; no webhooks - the holder runs on notifications) |
igrantio-holder-backend |
| 3 |
Scaffold the portal in the Next.js + TypeScript app: vendor lib/ows, copy features/holder, mount <HolderPortal proxyBaseUrl="…/ows/<tenant>"/> in a client component |
igrantio-holder-frontend |
| 4 |
Portal login: passwordless sign-in (magic link / email OTP) guarding the portal routes |
Better Auth (see Recommended stack) |
| 5 |
Wire the live inbox: SSE stream, decision table (transaction code / authorize / deferred / respond / review), delete-as-handled |
igrantio-holder-notifications (already vendored by step 3) |
| 6 |
Restyle: swap the unstyled components into the customer's design system, or apply the default chrome |
customer design system, or igrantio-usecase-ui |
| 7 |
EBW onboarding: bring the wallet unit to valid - WUA, then the Owner ID (LPID) |
igrantio-ebw-owner-id |
| 8 |
Test every flow end to end |
DCQL workflow skills (below) |
Page map (portal page → where its rules and code live)
All UI rules: igrantio-holder-frontend/references/portal-ux-reference.md.
All endpoint contracts: igrantio-api-holder +
igrantio-holder-backend/references/holder-api-reference.md.
| Portal page |
Reference implementation |
| Base configuration (wallet settings + wallet-unit stepper) |
components/BaseConfigurationView.tsx, components/lifecycle.tsx |
| Received credentials (stats, active/archived, auto-present, refresh) |
components/ReceivedCredentialsView.tsx |
| Credential detail (per-format claims, blur, portraits, trust badge, accept/reject) |
components/CredentialDetail.tsx, ClaimsTable.tsx, TrustBadge.tsx |
| Receive flow (offer paste, transaction code, front-channel, deferred) |
components/ReceivePanel.tsx, useHolder.ts |
| Share wizard (filtered credentials, OPTION groups, claim sets, multiple) |
components/ShareWizard.tsx, shareSelection.ts (pure engine) |
| Shared credentials (stats-as-filter, disclosed claims, lifecycle) |
components/SharedCredentialsView.tsx |
| Notifications (bell, drawer inbox, state filter, snackbars) |
components/NotificationsInbox.tsx, NotificationSnackbars.tsx, ui.tsx |
EBW specifics
- The wallet-unit ladder gates receiving:
not_installed → installed → operational → valid; the "+ Receive" action needs operational or
valid. valid means WUA + Owner ID (LPID) held - issue it with
igrantio-ebw-owner-id.
- The credentials an EBW typically holds have schema skills
(
igrantio-credential-schema-lpid,
igrantio-credential-schema-certificate-of-registration, …); start from
igrantio-schema-discovery when unsure.
Testing the portal
Exercise every wizard branch with the ready-made verifier requests:
igrantio-dcql-credential-sets (OPTION groups),
igrantio-dcql-claim-sets (disclosure profiles),
igrantio-dcql-multiple-statements (multiple:true),
igrantio-dcql-student-pass (mandatory + optional),
igrantio-dcql-trusted-authority (trust-list badge). Issue test
credentials with igrantio-issuer-backend/igrantio-issuer-frontend or
the iGrant.io console.
Validation / done criteria
- The four pages work against a deployed holder backend; the browser bundle
holds no OWS API key.
- An end-to-end pass succeeds: receive an offer (with a transaction code) →
accept → a verifier's DCQL request → the wizard shares the selected
claims → the shared list shows "Presentation Shared" - with the
notifications inbox driving each step.
- Wallet-unit gating and trust-list badges behave per
portal-ux-reference.md.
Documentation & workflows
When anything is unclear, consult the iGrant.io documentation before guessing:
1---2name: igrantio-business-wallet-portal3description: Umbrella recipe for building a CUSTOM European Business Wallet (EBW) portal on the iGrant.io Organisation Wallet Suite - the path most customers take. Orchestrates the holder skills end to end - deploy the tenant backend (igrantio-holder-backend), scaffold or hand-build the portal UI (igrantio-holder-frontend), run everything on the live notifications inbox (igrantio-holder-notifications), and look up exact endpoints in igrantio-api-holder - with the page-by-page build order (base configuration, received credentials, shared credentials, share wizard, notifications), EBW onboarding to a valid wallet unit, and DCQL test requests. Use when an organisation wants its own business wallet portal with only holder functions.4license: Apache-2.05---67# iGrant.io business wallet portal (umbrella)89## When to use10A customer organisation wants its **own** business wallet portal - their11branding, their pages, only holder functions (receive, hold, present,12notifications) - on the iGrant.io OWS APIs. This skill is the route map: it13does not duplicate any contract; it tells you which skill to open for each14part and in which order. For a single missing fact (a path, an enum, a15payload), go straight to `igrantio-api-holder`.1617**Before you build**: run the integrator intake in `igrantio-ows-overview` - environment, API key, tenancy, backend host, webhooks, frontend - one question at a time, a recommended default with each. Then add the portal questions below.1819## Portal intake (ask after the overview intake)201. **Stack** - which web stack? _Recommend **Next.js (App Router) +21 TypeScript** with **Better Auth** passwordless login (see "Recommended22 stack" below); accept the customer's standing stack if they have one._232. **Scope** - which pages? _Recommend all four: base configuration,24 received credentials, shared credentials, notifications._253. **Scaffold or custom** - start from the ready `HolderPortal` scaffold and26 restyle, or build custom views on the hooks + engines?27 _Recommend the scaffold first; every view works standalone._284. **Look** - the customer's design system, or the default iGrant.io look29 (`igrantio-usecase-ui`)? _The reference components are unstyled on purpose._305. **EBW onboarding state** - is the organisation's wallet unit already31 `valid` (WUA + Owner ID/LPID issued)? If not, plan step 7.3233## Recommended stack34Unless the customer has a standing choice, recommend and scaffold:35- **Next.js (App Router) + TypeScript** for the portal36 (`npx create-next-app@latest --typescript`). All reference code is strict37 TypeScript; mount the holder views in client components (`"use client"`) -38 they use `EventSource`, the clipboard, and `window`.39- **Better Auth (<https://www.better-auth.com>) for passwordless portal40 login** - magic link or email OTP (add passkeys where wanted) so portal41 users sign in without passwords. Protect the portal routes with the Better42 Auth session (middleware). Portal login is **orthogonal to wallet auth**:43 the OWS API key lives only in the holder backend, whoever is signed in.44- Run the holder backend (the Express reference) as its own service beside45 the Next.js app. Set its `CORS_ORIGINS` to the portal origin; for46 multi-user portals, verify the Better Auth session in a proxy middleware47 before forwarding to OWS.4849## Build order5051| # | Step | Skill to open |52| --- | --- | --- |53| 1 | Architecture, glossary, intake | `igrantio-ows-overview` |54| 2 | Deploy the tenant backend: API-key-hiding proxy scoped to holder endpoints + notifications SSE relay (port 6003; **no webhooks** - the holder runs on notifications) | `igrantio-holder-backend` |55| 3 | Scaffold the portal in the Next.js + TypeScript app: vendor `lib/ows`, copy `features/holder`, mount `<HolderPortal proxyBaseUrl="…/ows/<tenant>"/>` in a client component | `igrantio-holder-frontend` |56| 4 | Portal login: passwordless sign-in (magic link / email OTP) guarding the portal routes | Better Auth (see Recommended stack) |57| 5 | Wire the live inbox: SSE stream, decision table (transaction code / authorize / deferred / respond / review), delete-as-handled | `igrantio-holder-notifications` (already vendored by step 3) |58| 6 | Restyle: swap the unstyled components into the customer's design system, or apply the default chrome | customer design system, or `igrantio-usecase-ui` |59| 7 | EBW onboarding: bring the wallet unit to `valid` - WUA, then the Owner ID (LPID) | `igrantio-ebw-owner-id` |60| 8 | Test every flow end to end | DCQL workflow skills (below) |6162## Page map (portal page → where its rules and code live)6364All UI rules: `igrantio-holder-frontend/references/portal-ux-reference.md`.65All endpoint contracts: `igrantio-api-holder` +66`igrantio-holder-backend/references/holder-api-reference.md`.6768| Portal page | Reference implementation |69| --- | --- |70| Base configuration (wallet settings + wallet-unit stepper) | `components/BaseConfigurationView.tsx`, `components/lifecycle.tsx` |71| Received credentials (stats, active/archived, auto-present, refresh) | `components/ReceivedCredentialsView.tsx` |72| Credential detail (per-format claims, blur, portraits, trust badge, accept/reject) | `components/CredentialDetail.tsx`, `ClaimsTable.tsx`, `TrustBadge.tsx` |73| Receive flow (offer paste, transaction code, front-channel, deferred) | `components/ReceivePanel.tsx`, `useHolder.ts` |74| Share wizard (filtered credentials, OPTION groups, claim sets, multiple) | `components/ShareWizard.tsx`, `shareSelection.ts` (pure engine) |75| Shared credentials (stats-as-filter, disclosed claims, lifecycle) | `components/SharedCredentialsView.tsx` |76| Notifications (bell, drawer inbox, state filter, snackbars) | `components/NotificationsInbox.tsx`, `NotificationSnackbars.tsx`, `ui.tsx` |7778## EBW specifics79- The wallet-unit ladder gates receiving: `not_installed → installed →80 operational → valid`; the "+ Receive" action needs `operational` or81 `valid`. `valid` means WUA + Owner ID (LPID) held - issue it with82 `igrantio-ebw-owner-id`.83- The credentials an EBW typically holds have schema skills84 (`igrantio-credential-schema-lpid`,85 `igrantio-credential-schema-certificate-of-registration`, …); start from86 `igrantio-schema-discovery` when unsure.8788## Testing the portal89Exercise every wizard branch with the ready-made verifier requests:90`igrantio-dcql-credential-sets` (OPTION groups),91`igrantio-dcql-claim-sets` (disclosure profiles),92`igrantio-dcql-multiple-statements` (`multiple:true`),93`igrantio-dcql-student-pass` (mandatory + optional),94`igrantio-dcql-trusted-authority` (trust-list badge). Issue test95credentials with `igrantio-issuer-backend`/`igrantio-issuer-frontend` or96the iGrant.io console.9798## Validation / done criteria99- The four pages work against a deployed holder backend; the browser bundle100 holds no OWS API key.101- An end-to-end pass succeeds: receive an offer (with a transaction code) →102 accept → a verifier's DCQL request → the wizard shares the selected103 claims → the shared list shows "Presentation Shared" - with the104 notifications inbox driving each step.105- Wallet-unit gating and trust-list badges behave per106 `portal-ux-reference.md`.107108## Documentation & workflows109110When anything is unclear, consult the iGrant.io documentation before guessing:111112- iGrant.io developer APIs (index): https://docs.igrant.io/docs/developer-apis113- Getting started: https://docs.igrant.io/docs/get-started/114- OpenID4VC API (issuer / verifier / webhook): https://docs.igrant.io/docs/category/openid4vc-api/issuer115- Workflow: issue a credential (OID4VCI): https://docs.igrant.io/docs/openID4vci-issue-credential-intime/116- Workflow: send and verify credentials (OID4VP): https://docs.igrant.io/docs/openID4vc-send-verify-credentials/