Design · docs/ios-notes.md
iOS app notes (apps/ios/CBAApp)
The CBA Hackathon app is a native SwiftUI app (iOS 17+, bundle au.com.cba.hackathon.wallet). It's generated with xcodegen and embeds MATTR's mDocs Holder SDK (MobileCredentialHolderSDK, Swift package) as its wallet, so the CommBank ID stays inside the CommBank app. It's an internal mock of the CommBank app, built for the rental income demo.
Build and run
cd apps/ios/CBAApp
xcodegen generate
xcodebuild -project CBAApp.xcodeproj -scheme CBAApp -scmProvider system \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' build # or: test
open CBAApp.xcodeproj- Config. Every base URL is in
Config/Base.xcconfig, which has a local set and a Railway set.CBA_CORE_BANKING_URL(CBA core banking mock, CONTRACTS v1.9 §54) is localhttp://localhost:7800, Railwayhttps://cba-core-banking-production.up.railway.app. The values reach the app through Info.plist (CBAEnvironments). To override anything (your team id, your Mac's LAN IP for a device build), copyConfig/Local.xcconfig.exampletoConfig/Local.xcconfig, which is gitignored. - Railway hostnames. The Railway URLs are placeholders (
*-production.up.railway.app). Fix them once the services are deployed. - Debug panel. Long-press the CommBank diamond, either on the log-on screen or at the top right of Home. From there you can:
- switch between local and Railway;
- change customer;
- use an offline token for UI-only work;
- force the PNG error screens at Review;
- fire
cbaapp://income-verify; - paste an offer straight into the MATTR wallet;
- reset state, including the wallet's credentials;
- see trust-list status (issuer and verifier list provider, issue, date, entries, source, errors; the SDK's trusted issuers and verifiers) and refresh the lists now.
- Customers.
Resources/customers.jsonis a names-and-cbaSubcopy ofdata/customers.json. Refresh it withscripts/sync-customers.sh. - Log on. The app calls
POST {ADR_CDR_KIT_URL}/dev/token {sub}. Real PingFederate log-on sits behind a toggle and is still a TODO.
Flows
- Flow A (no CommBankID).
- Carousel (3 pages). Page 3 adds an "Also verify my income" toggle, preselected from the QR, with the listing fetched from
GET {QR_LANDING_URL}/api/listings/{id}. - PIN, then confirm PIN, then Review your details.
- With income on: Verify your income (
VerifyIncomeView, logic inFlow/IncomeSourcesModel.swift, CONTRACTS v1.9):- "Your CommBank accounts" from
GET {CBA_CORE_BANKING_URL}/accounts(CBA token), each with a toggle; accounts withreceivesIncomeare preselected. If the accounts can't load, the customer can retry or carry on with another bank only. - "Add accounts from another bank" explains the CDR in plain words and calls
POST /offers {incomeSources: {cbaAccountIds, cdr: true, addBank: true}}through a no-redirectURLSessionTaskDelegate. The 302 opens inASWebAuthenticationSession(callback schemecbaapp, ephemeral). Afterconsent-completethe screen polls the pod and lists "Connected banks" (active arrangements: gathering / income found / no regular income found), with "Add another bank" for a further consent. - Continue needs a CommBank account or a connected bank, and sends
incomeSources {cbaAccountIds, cdr}(cdr= a connected bank whose income can count).
- "Your CommBank accounts" from
- CommBank only (
cdr: false): 200 straight away, a short "Calculating your income from your CommBank accounts" screen (no pod steps), then issuance. A 422no_cba_incomekeeps the customer on Verify your income with "We couldn't find a regular income in the accounts you picked…". - With CDR: a 409 leads to the "Gathering your income…" screen, which polls
GET /pods/{sub}/statusevery 1.5s and retries/offerswith the sameincomeSources. A 302 is tolerated for 30s after consent. - The card's income is the CBA part (200
incomeSources.cba) plus the pod's CDR income (IncomeSummary.combined). The offer goes toWalletStore.shared.accept(offer:)(the MATTR wallet). - Claim your ID card (total, CommBank / other banks breakdown, sources, and the proof note, e.g. "ZK proof covers your total income: CommBank + other banks via CDR"), then Setting up, then You're all set. The CommBank account ids chosen are kept with the income (
IncomeSummary.cbaAccountIds) for later proofs.
- Carousel (3 pages). Page 3 adds an "Also verify my income" toggle, preselected from the QR, with the listing fetched from
- Flow B (has a CommBankID). The "Add income verification" intro leads to the same Verify your income screen, then wait → re-issue.
- Rental deep links.
https://{QR_LANDING_HOST}/rent/verify?listing=…andcbaapp://income-verify?listing=…go to whichever applies:- set-up, with income on, for a new customer;
- Flow B (Verify your income), for a customer who has an ID but no income;
- straight to Share income proof, for a customer whose ID already carries income (any source).
- Share income proof. The ZK proof covers total income (CommBank + other banks via CDR), so it is offered whenever the CommBankID carries income, CommBank-only included. The threshold is checked against the total; below it the screen explains and makes no call. Copy: "The proof covers your CommBank and other-bank income" (CommBank only: "covers your CommBank income"). The app calls
POST {CBA_CORE_BANKING_URL}/proofs/income {threshold, listingId?, cbaAccountIds?}with the CBA token (cbaAccountIds= the accounts chosen at issuance, omitted when unknown) and uses the response'sverifierUrlas-is for the QR and share link. 422threshold_not_met, 409pod_not_ready, 401 are shown as messages. The pod'sPOST /pods/{sub}/proofs/incomeis no longer called by the app. The app shows it as a QR code and a share link:{ZK_VERIFIER_URL}/?bundle=base64url(JSON). The JSON is the pod's response plussubandlistingId. - Data sharing (CDR dashboard). Lists the arrangements, including the v1.1 fields
retainUntil,endedAt,endReasonandfailure, and the pod'sdeletionblock. Stop sharing callsDELETE /pods/{sub}/arrangements/{id}and then shows{status, podDestroyed, adrRevoke, deletionReceipt}as a deletion receipt. - Errors. The PNG's three error screens (under 16, details out of date, error on our end), plus CDR cancelled, CDR denied/error, and pod failed or timed out.
- Income labels. Income is shown as net (after-tax) annualised income, per v1.1. The app reads the pod's part with
purpose=consumer-view. From the stored mDoc it readsincome_total_annualised(elseincome_annualised),income_cba_annualised,income_cdr_annualised,income_sources,income_employers,income_commitment_cbaandincome_commitment_cdr; older CDR-only credentials (income_annualised+income_commitment) still show as CDR income and can still be proved. - UI-only mode. With the debug "offline token" (or
-demoSimulateBackend YES), Verify your income shows fake CommBank accounts (CBAAccount.previewList) and "Add accounts from another bank" connects a scripted Westpac. The debug panel can also put a mock CommBank + CDR income on the card.
Wallet: MATTR mDocs Holder SDK
The wallet is MATTR's iOS mDocs Holder SDK (MobileCredentialHolderSDK 6.2.1), added in project.yml as a Swift package from https://github.com/mattrsdk/ios-mobile-credential-holder-sdk (exact version). It replaced the ID Partners vc-wallet-sdk; that repo and its cba-hackathon branch are untouched.
- Access. The distribution repo is private. MATTR grants access per GitHub account ("Apply for access" at learn.mattr.global).
dphhylandhas it. Resolving over HTTPS uses your git credential helper (gh auth git-credential), so build from the CLI with-scmProvider system, or sign Xcode into that GitHub account. Licence: MATTR's proprietary terms (LICENSE.txt, "All rights reserved"). No licence key is needed while the SDK is untethered. - Tethered.
initializegets aPlatformConfiguration(tenantHost:applicationId:)for the tenant's holder application "CBA Hackathon wallet (iOS)" (MATTR_HOLDER_APPLICATION_IDinConfig/Base.xcconfig; teamJH6RX4DRG2, bundleau.com.cba.hackathon.wallet, App Attest not required). On first launch the SDK registers an instance and fetches a licence token. You can see it under the holder application on the tenant. Wallet attestation is available if an issuer asks for it (CBA's issuance doesn't). Clear the setting to run untethered. Setup:participants/cba-issuer/mattr/setup-tenant.pystep 7. - Issuance.
MattrWalletBackend.acceptcallsretrieveCredentials(credentialOffer:clientId:…)with MATTR's sample clientios-sample-mobile-credential-holder-app. The SDK runs the authorization-code flow itself in anASWebAuthenticationSession. It catches the redirectio.mattrlabs.sample.mobilecredentialholderapp://credentials/callback(CredentialIssuanceConfiguration.redirectUri), then does token, proof and credential, and stores the mDoc. Its authorize request already carriesscope=mso_mdoc:au.com.cba.commbank-idand the offer'slogin_hint.MattrLiveOfferTestschecks this against the tenant. - Trust lists (VICAL / RICAL). The wallet trusts what the CBA Hackathon Trust Network publishes on MATTR's Digital Trust Service. The Holder SDK has no VICAL or RICAL reader, so
Sources/Wallet/TrustLists.swift(SDK-free) does it. It fetches the lists (MATTR_VICAL_URL,MATTR_RICAL_URL) and the DTS root (MATTR_DTS_ROOTS_URL, pinned byMATTR_DTS_ROOT_SHA256). It verifies each COSE_Sign1 signature with CryptoKit, the signer's EKU, and the chain to the root withSecTrust, then decodes the CBOR. Lists are cached in Application Support. A bad update never replaces the last good list.WalletStorechecks every 15 minutes and re-fetches afterMATTR_TRUST_LIST_REFRESH_SECS(6 h).MattrWalletBackendthen:- initialises with
autoTrustMobileCredentialIaca: falsewhen it has a VICAL, and syncs the VICAL's IACAs intoaddTrustedIssuerCertificates/deleteTrustedIssuerCertificate, so only listed issuers are accepted. With no VICAL at first launch (offline), it falls back to trusting the offer's issuer metadata, and the debug panel says so. - syncs the RICAL's roots into
addTrustedVerifierCertificates/deleteTrustedVerifierCertificate.createOnlinePresentationSession(authorizationRequestUri:requireTrustedVerifier:)reportsverifiedBy..certificate(cn)under a RICAL root shows "Verified by CBA Hackathon Trust Network" on the Share/Decline sheet..domainshows a "Not on the trust list" warning. SetMATTR_REQUIRE_TRUSTED_VERIFIER = YESto refuse unknown verifiers instead. - marks a credential
trustedBythe network when itsissuerInfo.trustedIssuerCertificateIdis a VICAL IACA listed for its doc type. The card shows "Verified by CBA Hackathon Trust Network". - All hackathon1 verifiers share one reader root, so the sheet can say "on the network" but can't name the agency (
participants/cba-issuer/mattr/README.md, "Trust lists").
- initialises with
- User auth.
.onDeviceKeyAccess+.userPresence: Face ID or passcode when a device key is used (presenting), not at launch. - App code.
Sources/Wallet/WalletService.swiftholds the SDK-free types andWalletStore(the observable the UI uses:accept(offer:),credentials,commBankID,detail(id:), deletes, deep links).MattrWalletBackend.swiftis the only file that imports the SDK. - UI. ID Card draws the card and the identity + income claims from the stored mDoc (
CredentialClaimsSection). "View credential in wallet" lists everything held. "Delete CommBankID" and debug "Reset app state" now delete the credentials too. - Presentation.
openid4vp://andmdoc-openid4vp://links startcreateOnlinePresentationSession.PresentationConsentSheetshows what's asked, then Share or Decline. Proximity (BLE) presentation isn't wired.
Unit tests (simulator, -skip-testing:CBAAppTests/MattrLiveOfferTests): 52, including IncomeSourcesModelTests (accounts + preselection, toggles, canContinue, request bodies for CBA-only / add bank / CDR, 200/302/409/422 handling, connected banks from pod status, income breakdown, total-income proof gating and the core-banking proof request) and the v1.9 offers client cases.
Run the live test with a fresh offer (minted with the tenant's API creds, as test-issuance.py does):
TEST_RUNNER_MATTR_TEST_OFFER='openid-credential-offer://?credential_offer=...' \
xcodebuild -project CBAApp.xcodeproj -scheme CBAApp -scmProvider system \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' test -only-testing:CBAAppTests/MattrLiveOfferTestsProposed contract additions
POST /dev/token. Pin the response as{ "access_token", "token_type", "expires_in" }. The app also acceptstoken,accessToken, or a bare JWT.GET {QR_LANDING_URL}/api/listings/{id}. Not in CONTRACTS yet. The app expects{ id, address, rentPerWeek, incomeThreshold?, agency? }, whereincomeThresholdis net annual income. Without it, the app defaults torentPerWeek * 52, matching the 39,000 example.- ZK verifier bundle. Specify the
bundleformat as base64url(JSON) of the proof response plussubandlistingId. Alternatively, have the app use the response'sverifierUrlas is. The app currently builds the link fromZK_VERIFIER_URL. deletionandfailureshapes. Please pin these down. The app renders them generically as key/value lines.- Consent callback. Add
status=denied(orerror=access_denied) so a refusal at the DH can be told apart from a technical error. The app treats both as "Your bank didn't share your data". - SSE. The app polls.
eventsURL(sub:token:)(?access_token=) is implemented, so SSE can be wired in later. - Universal Links. The AASA on
qr-landingmust listappIDs: ["<TEAMID>.au.com.cba.hackathon.wallet"]with path/rent/verify*.