Skip to main content
ENKO

Transfer flow

The core user journey in Senco: sending money or crypto across borders. This pattern combines amount input, method comparison, recipient selection, and cost breakdown into a linear 3-step flow.

Everything else in the design system exists to make this flow work. It is the only irreversible action in the product, the one place where a mistake costs the user money, and the screen most of them will see on a mid-range phone with two bars of signal.

Flow structure

Send then Review then Confirm — a linear 3-step progression.

Each step validates before progressing. Users can go back without losing data.

Why three steps

Three is the smallest number that separates the three decisions a transfer requires: how much, to whom and by which rail, and are you sure. Collapsing review into confirm removes the user's last chance to check the fee against the total. Adding a fourth step means the flow has taken on something that belongs elsewhere.

StepThe questionWhat can still change
1 · SendHow much, and by which route?Everything
2 · ReviewIs this what I meant?Everything, by going back
3 · ConfirmAm I sure?Nothing after submit

State persistence

  • Data survives backward navigation, app backgrounding, and connection loss.
  • Returning to an abandoned transfer restores the step and the values: "You have an unfinished transfer to Maria Santos."
  • Only the user advances the flow. Nothing auto-progresses, including a successful rate refresh.
  • After submission the flow is replaced by the status screen, not layered over it. Leaving the form reachable behind a result is how the same transfer gets sent twice.

Where the rate is locked

The rate is quoted at step 1 and held through the flow. If it expires or moves materially before confirmation, step 3 re-quotes and states the change explicitly rather than charging a different rate silently. A user who reviewed 412,500 NGN and received 408,000 NGN has been given a number that turned out to be decorative.

Transfer comparison

Users choose between bank transfer (fiat FX) and crypto transfer (stablecoin). The comparison card shows:

  • Exchange rate or conversion rate
  • Service fee
  • Delivery speed
  • Total received amount
  • "Cheaper" / "Faster" badges on the better option

Always display both options side by side so users can make an informed choice.

Comparison, not configuration

Presented as a choice tree, this step asks the user to understand settlement rails before they can send money. Presented as a comparison, it shows the two numbers that actually decide it — total received and arrival time — and the user picks a row. Same capability, no expertise required. See Design principles.

The badges are the shortcut

"Cheapest" and "Fastest" let a user decide without reading either card in full, which is what most people do. Two rules keep them honest:

  • A badge is computed, never assigned. If bank transfer is both cheaper and faster, it carries both badges and crypto carries none.
  • A badge is part of the option's accessible name, so a screen reader user gets the same shortcut.

Neither option is pre-selected. Pre-selecting a fee-bearing route is a decision made on the user's behalf.

Accessibility

Built on radio card semantics — one Tab stop, arrow keys move and select. Selection is signalled by border weight, background tint, and indicator fill together; under a color vision deficiency the tint is nearly invisible and the weight is what survives.

Transfer comparison — Bank vs. Crypto
Bank transfer
FX rate1 = 1,380 KRW
Service fee$2.50
Speed1–2 days
They receive341,550 KRW
Crypto transfer
CryptoUSDT (1:1 USD)
Service fee$0.80
Speed~5 min
They receive249.20 USDT
CheaperFaster
Amount input with live rate
USD
1 USD = 1,380 KRW
KRW

Cost breakdown

Before confirmation, show the full fee breakdown:

  • Amount sent
  • Exchange rate or conversion
  • FX markup (bank only)
  • Service fee
  • Delivery time
  • Total received (highlighted)
  • Savings comparison vs the other method

No hidden fees. No asterisks. The total cost is always visible.

The breakdown is not behind a disclosure

The fee is visible at review, not inside a collapsed Details block. Details may hold the composition of a fee already shown as a total; it may never hold the existence of a cost.

This costs conversion. Some users who see the fee will stop — and a user who would not have sent had they seen the fee should not be sending. That trade is stated in Design principles and this is the screen where it is paid.

FX markup is a line item

The exchange margin is listed separately from the service fee. A single combined "fee" number hides the part most competitors hide, and the entire reason a user is comparing two rails is to see it.

Accessibility

  • Rendered as a description list so each label pairs with its value, and announced as structured data rather than two unrelated columns.
  • Amounts use tabular-nums and every one carries its currency code.
  • The total is bold and larger, not a different color — the emphasis survives greyscale.
Cost breakdown
Bank transfer — Fee breakdown
You send$250.00 USD
Exchange rate1 USD = 1,380 KRW
Service fee$2.50
Delivery1–2 business days
They receive341,550 KRW
Total cost: $2.50

Recipient selection

  • Surface recent/saved recipients first (smart default)
  • Search by name
  • Show country, payment method, and currency tag
  • Support bank account, mobile money, and wallet address

Selecting a recipient selects a rail

The same person can be a recipient by bank transfer and by mobile money, with different fees and arrival times. The contact card shows the method, because choosing "Maria" without choosing how is not a complete choice.

Verification before commitment

A recipient's account details are displayed for confirmation at review — masked except the last four digits, with the account name as the bank holds it. Name-mismatch is the most common cause of a rejected cross-border payment, and it is cheap to catch before submission and expensive afterwards.

Accessibility

  • Each contact card is a <button> whose accessible name covers everything visible: "Maria Santos, Nigeria, bank transfer."
  • Search matches name, country, and account fragment; the result count is announced politely.
  • The country flag is decorative — the country is already in the name.
Add recipient
The account number for their bank
Select recipient
AO
Adaeze O.
Korea · Bank
KRW
KM
Kwame M.
Ghana · Mobile
GHS
SK
Sarah K.
Kenya · Wallet
USDT

Target rate alert

Optional feature allowing users to set a target exchange rate and be notified when the rate is reached. Available on Step 1 (amount entry) for eligible currency pairs.

Eligibility

  • Only shown when the currency pair has a variable exchange rate
  • Hidden for pegged currencies (e.g., USDT → USD) or same-currency transfers
  • Auto-detected: if no variable-rate methods exist for the pair, the toggle is hidden

Flow

  1. User enters send amount and currency
  2. "Wait for a better rate?" toggle appears (Switch, lg size)
  3. When enabled:
    • Send method selector appears (only variable-rate methods)
    • Current rate is displayed as reference
    • Target rate input: user enters desired rate (must exceed current rate)
    • Expiry picker: "24 hours" or "7 days"
  4. Validation: target rate must be higher than current rate; empty or invalid values disable the submit button
  5. On submit: redirects to home with success toast confirmation

Interaction with send flow

Toggle stateMethod selectorRate alert fieldsSubmit button
OFFVisible (standard)Hidden"Continue to review"
ONHidden (replaced by alert method)Visible"Set rate alert"

Validation rules

RuleError message
Empty target rate"Enter a target rate"
Target ≤ current rate"Target must be higher than current rate"
No method selectedButton disabled (no error shown)

A rate alert is not a transfer

Enabling the toggle changes what the primary button does — from "Continue to review" to "Set rate alert" — and no money moves. The button label carries that difference, which is why it changes rather than staying "Continue". An expiring alert notifies rather than executing; Senco never sends money on a schedule the user did not confirm in the moment.

Design and accessibility

  • Toggle uses lg Switch (48×28px) for prominent visibility
  • Current rate displayed as read-only reference text
  • Target input uses standard Input with inputMode="decimal"
  • Expiry buttons are a two-option pill selector (not radio cards — too heavy for 2 options)
  • Success toast uses DS Sonner success style (green border, check icon)
  • Toggling the switch announces the change in available fields, since the form's shape changes underneath the user
  • The "no method selected" case disables the button without an error, which is a documented deviation from the forms rule that submit stays enabled — it is listed in the gaps below

Design and accessibility

  • Transfer comparison cards are keyboard-navigable (arrow keys)
  • Selected method has visible border + checkmark (not color alone)
  • Cost breakdown is announced to screen readers as a data table
  • Amount input supports both typed and pasted values
  • Currency selector is accessible via keyboard
  • Target rate toggle uses lg Switch with 48×28px touch target
  • Rate alert form fields validate inline with standard error patterns

Failure paths

The flow has to behave correctly at its worst moment, not its best:

FailureBehavior
Connection lost mid-flowData persists locally; the step is restored on return
Submission cannot reach the serverQueued, with "We'll send this when you're back online" — never silently dropped
Server rejects the transferError names the cause and states that no money moved and the balance is unchanged
Rate expired before confirmRe-quote, state the change, require re-confirmation
Double tap on confirmThe flow sets the confirm button's loading on the first press, before the request starts; the button ignores further presses until the flow clears it
Submitted but outcome unknownStatus screen shows Pending with a reference number — never a silent failure

The "outcome unknown" row is the one that matters most. A user whose confirm request timed out does not know whether their money moved, and the flow must resolve to a reference number they can quote rather than to an error that implies nothing happened.

Known gaps

  • Local persistence and offline queueing are specified across this page and Forms but not implemented as a shared mechanism.
  • The disabled-submit case in the rate alert contradicts the forms rule that submit stays enabled with an explanatory error.
  • No documented recipient verification step for wallet addresses, where a mistyped character is unrecoverable.