# External Wallet Access Mode

## 1. User Onboarding

User Onboarding in External Wallet Access Mode is designed for Web3 wallet users to apply for a UR Account. The process focuses on cryptographically verifying wallet ownership and minting the URID (Identity NFT) directly to the user's wallet, ensuring they maintain full control of their identity from day one.

{% @mermaid/diagram content="sequenceDiagram

```
autonumber

actor User

participant App as Partner App

participant UR as UR Backend

participant Contract as UR Contract (Mantle)

participant Fiat24 as Fiat24

rect rgb(240, 248, 255)

note right of User: Phase 1: Identity Initialization (Tourist)

User->>App: Request Fiat Account (IBAN)

App->>User: Require email for registration

User->>App: Input email

App->>UR: POST /kyc/emailStatus (validate email)

note right of App: Invalid if email already exists on system<br/>User must provide different email

alt Email Valid & Available

    App->>UR: POST /mint (walletAddress, email, partnerId)

    note right of UR: UR validates & prepares mint transaction

    UR->>Contract: Mint URID NFT (Status: Tourist)

    Contract-->>UR: Tx Hash & Confirmation

    UR-->>App: Return URID, Status (Tourist), Tx Hash

    note right of App: Store URID locally for session



else Email Invalid or Taken

    UR-->>App: Error: Email unavailable

    App->>User: Display error & request new email

end

end


rect rgb(255, 250, 240)

note right of User: Phase 2: KYC Data Collection

App->>UR: POST /kyc/token (URID)

UR-->>App: Return Sumsub Token

note right of UR: Token scoped to user URID

App->>User: Launch Sumsub SDK (Face + ID Verification)

User-->>App: Complete KYC verification flow

note right of User: User submits documents directly to Sumsub

UR->>App: Webhook: /kyc/completion (URID, status)

note right of UR: Triggered when Sumsub data collection completes<br/>Does NOT mean approval—just data received

end

rect rgb(240, 255, 240)

note right of User: Phase 3: Document Signature & On-Chain Submission

UR->>App: Webhook: /kyc/signatureRequest

note right of UR: Payload contains:<br/>- Full formatted legal text (documentText)<br/>- Structured user data (documentData)<br/>- Document hash to sign<br/>- EIP-712 typed data structure

App->>User: Display Form A with formatted text + signature prompt
note right of User: User reviews legal declaration<br/>Partner must display documentText verbatim<br/>User signs with Ant Wallet (EIP-712)
User-->>App: Return wallet signature (v, r, s)

App->>UR: POST /kyc/submit (URID, signature, documentHash)
note right of UR: UR validates:<br/>- Signature matches documentHash<br/>- Signer is URID owner<br/>- Hash hasn't been used before

UR->>Fiat24: Submit KYC package for compliance review

note right of Fiat24: Asynchronous approval process<br/>Duration: 1-5 business days typical
UR-->>App: Return 202 Accepted (pending review)

App->>User: Display "Under Review" status

end

rect rgb(230, 230, 250)
note right of User: Phase 4: Approval & Finalization

alt Fiat24 Approves Account
    Fiat24-->>UR: Webhook: Account Approved + IBAN details
    UR->>Contract: Update URID Status → Live

    Contract-->>UR: Tx Hash & Confirmation
    UR->>App: Webhook: /kyc/result (status: LIVE, URID)

    App->>UR: GET /kyc/document (URID)

    note right of UR: Returns finalized, Fiat24-stamped PDF
    UR-->>App: Return final signed KYC PDF
    App->>UR: GET /profile (URID, signature)

    UR-->>App: Return account details (IBAN, account number, address)

    App->>User: Display account active + IBAN details

else Fiat24 Rejects Account

    Fiat24-->>UR: Webhook: Account Rejected + reason code

    UR->>Contract: Update URID Status → Rejected

    Contract-->>UR: Tx Hash & Confirmation

    UR->>App: Webhook: /kyc/result (status: REJECTED, reason)

    App->>User: Display rejection reason + next steps

end

end" %}
```

**Flow Description:**

* Identity Initialization: The Partner App first[ validates the user's email availability](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-2.1.2-email-verification) and, upon success, triggers the [minting](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-2.1.4-mint-create-tokenid) of a "Tourist" status URID NFT associated with the user's wallet address.
* KYC Data Collection: The Partner integrates the Sumsub SDK. The partner's frontend [requests a specific Sumsub token](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-2.1.5-get-sumsub-sdk-token) scoped to the user's URID and launches the SDK to collect identity documents and biometric data. A [webhook notifies the Partner](/developer-resources/webhook.md#event-sumsub_kyc_result) when data collection is complete.

{% hint style="warning" %}
**NFC scanning requires a mobile app.** The Sumsub SDK's Passport/National ID NFC scan step is only supported in the **Sumsub mobile SDK** (iOS/Android). It is not available in the Sumsub web SDK. Partners integrating via the External Wallet Access Mode must surface this KYC step through their **mobile app**. Users on a web browser cannot complete NFC-based identity verification. If your platform is web-only, contact us to discuss alternatives.
{% endhint %}

* Document Signature: UR generates 'Form A' — a legal declaration required by regulation — for users to sign. Upon receiving the webhook notification confirming the completion of KYC data collection, the Partner must[ fetch the user's KYC document](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-2.1.8-get-user-kyc-document-form-a) and display the information to the user. The user cryptographically signs this document using their external wallet (EIP-712 standard), and the Partner [submits this signature to UR](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-2.1.9-submit-user-signature) for compliance validation.
* Approval & Finalization: Once the asynchronous compliance review is completed, UR updates the on-chain status to "Live" and[ notifies the Partner](/developer-resources/webhook.md#event-kyc_status). The Partner can then [retrieve the user's profile details ](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.1-get-user-profile)(IBAN, etc.).

## 2. Card

Card Operations in External Wallet Access Mode are designed for users to manage their co-branded debit card directly from their self-custody wallet. High risk actions, such as setting spending limits, card activation, and viewing card details, are secured through user's offline signatures.

{% @mermaid/diagram content="
sequenceDiagram
autonumber
actor User
participant App as Partner App
participant UR as UR Backend
participant Contract as UR Contract
participant Webview as Secure Webview

```
rect rgb(240, 248, 255)
note right of User: Phase 1: Check Card Status
User->>App: Access Card Section
App->>UR: API: GET /card
UR-->>App: Return Card Info OR Empty/404
end

alt User has NO Card (Activation Flow)
    rect rgb(255, 250, 240)
    note right of User: Phase 2: Activation & Permit

    User->>App: Request for a card
    App->>User: Request a signature
    App->>UR: API: POST /card  (API request includes signature)
    UR-->>App: Return Basic Card Info
    App->>User: Prompt: Select Currency to Enable
    User-->>App: Select Currency (e.g. USD)
    App->>User: Request Offline Signature (Permit)
    note right of User: User signs EIP-2612 Message<br/>(No Gas Fee)
    User-->>App: Return Signature
    App->>UR: API: POST /token-permit (with Signature)
    note right of UR: UR pays Gas to submit<br/>authorization to chain
    UR->>Contract: Execute Permit & Approve
    Contract-->>UR: Success
    UR-->>App: Return: Authorization Enabled
    end
else User HAS Card
    note right of App: Skip Activation
end

rect rgb(240, 255, 240)
note right of User: Phase 3: Display & Sensitive Data
App->>User: Show Basic Info<br/>(Masked PAN, Limit, Design)
User->>App: Click "Show Card Details"
App->>Webview: Raise up Webview and load marqeta sdk
Note over Webview: PCI DSS Scope (Secure Zone)
Webview->>User: Render Full Card Details

end" %}
```

* Status Check: The Partner App [queries the current card status](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.1-get-user-profile) to determine if the user needs to activate a new card or view an existing one.
* [Activation](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.2-create-card) (If applicable): If no card exists, the user initiates an activation request. This involves signing a request to create the card.
* [Set Allowance](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.8-permit-token-approval): The user grants spending authority over their UR account balance (on-chain Fiat tokens) by generating an EIP-2612 "Permit" signature. This offline signature authorizes the default currency allowance, with UR handling the subsequent on-chain submission and gas payment.
* Secure Display: To view sensitive data (PAN, CVV), the user first calls the [card information API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.3-get-card-info). The response includes a `cardToken` alongside basic card details. The Partner Frontend uses this token to [launch a secure Webview ](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.4-get-card-details)to display the sensitive data within a secure zone.

## 3. Off-ramp Flow (Crypto-to-Fiat)

This flow enables users to convert held crypto assets into fiat balances within their UR account. UR supports two chain families:

* **EVM chains** (Ethereum, Arbitrum, Mantle, etc.) — the user interacts directly with [UR Contracts](/developer-resources/smart-contracts.md#contract-addresses) via their EVM wallet. See [§4.1 Offramp API for EVM](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-4.1.1-get-quote).
* **Solana** — the user deposits USDC via a Solana wallet. The Partner calls [§4.2 Offramp API for Solana](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#4.2-offramp-api-for-solana) to get a pre-built transaction, which the user signs and submits on Solana. The USDC is bridged cross-chain to Mantle via LayerZero and converted to fiat.

The diagrams below illustrate the **EVM** path. For the Solana-specific flow (quote → sign → send → cross-chain settlement), refer to [§4.2](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#4.2-offramp-api-for-solana) in the API Reference.

{% @mermaid/diagram content="
sequenceDiagram
autonumber
actor User
participant App as Partner App
participant UR as UR Backend
participant Contract as UR Contract

```
rect rgb(240, 248, 255)
note right of User: Phase 1: Quotation
User->>App: Input USDC Amount (e.g. 100 USDC)
App->>UR: GET /quote/deposit (Amount, Chain)
UR-->>App: Return Quote<br/>(Exchange Rate, Fees, Est. Fiat Output, Error code)
App-->>User: Display Estimated Fiat Received
end

rect rgb(255, 250, 240)
note right of User: Phase 2: Permit & Execution
User->>App: Confirm Transaction
App->>User: Request Wallet Signature (requires gas fee)
User->>Contract: Sign the offramp transaction

Contract-->>App: Return txHash

App-->>User: Show "Status: Pending / Processing"
end
rect rgb(240, 255, 240)

note right of User: Phase 3: Settlement
```

App->>Contract: Start Monitoring (Listening for Event)

```
alt Event Received (Within 5 mins)
    Contract-->>App: Event: DepositSuccess
    par Update UI
        App-->>User: Show "Deposit Successful"
    end
else Timeout (No Event > 5 mins)

    Note right of App: Monitoring exceeded time limit
        App->>UR: API: Report Timeout
        UR->>Ops: 🚨 Alert: Deposit Stuck (TxHash)
        Ops->>UR: Fix status
        UR->>App: Fix status
end
end" %}
```

* Quotation: The user requests a conversion amount (e.g., USDC), and the Partner frontend[ fetches a quote](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-4.1.1-get-quote) containing the exchange rate, fees, and estimated fiat output.
* Execution: Upon confirmation, the Partner App prompts the user to sign and submit the transaction directly to the UR Contract.
  * > Note: In this mode, the user pays the gas fee for the on-chain transaction.
* Settlement: The Partner App monitors the blockchain for the specific `DepositSuccess` event. If confirmed within the timeout window, the UI is updated; otherwise, the Partner reports the timeout to UR operations for reconciliation.

## 4. On-ramp Flow (Fiat-to-Crypto)

The On-ramp process in External Wallet Access Mode is designed to convert UR fiat balances into crypto assets using a gasless execution model. Instead of broadcasting the transaction themselves, the user provides a [EIP-2612 signature (Permit) ](https://eips.ethereum.org/EIPS/eip-2612)authorizing the trade, which the Partner submits to onchain via API to help users to abstract gas fees from the transaction.

{% @mermaid/diagram content="
sequenceDiagram
autonumber
actor User
participant App as Partner App
participant Webview as Liveness Webview
participant UR as UR Backend
participant Contract as UR Contract

```
rect rgb(240, 248, 255)
note right of User: Phase 1: Quotation & Risk Check
User->>App: Input Onramp Request<br/>(e.g., 1000 EUR to ETH)
App->>UR: API: GET /quote/onramp (User, Amount)

UR->>UR: Check Risk Rules
note right of UR: 1. Monthly Quota(will failed if a TX used the limit up)<br/>2. Min/Max Limit(Unacceptable input)<br/>3. Liveness Status(user have to pass a check if this is true)

UR-->>App: Return Quote + Risk Flags<br/>{rate, fee, needsLiveness: T/F}
end

alt If Liveness Check Required (Risk Triggered)
    rect rgb(255, 230, 230)
    note right of User: Phase 1.5: Step-up Verification
    App->>User: Prompt "Verification Needed"
    User->>App: Confirm
    App->>Webview: Initialize Liveness URL
    User->>Webview: Perform Face Scan
    Webview->>UR: Verify Biometrics
    UR->>UR: Update User Risk Profile
    UR-->>App: Webhook/Callback: Verification Success

    App->>UR: GET /quote/onramp (Re-fetch)
    note right of App: Refresh Quote with<br/>new permissions
    UR-->>App: Return Final Quote (Allowed)
    end
else Liveness Not Required
    note right of App: Proceed with existing Quote
end

rect rgb(255, 250, 240)
note right of User: Phase 2: Execution (Gasless)
User->>App: Accept Quote & Confirm
App->>User: Request Offline Signature (Permit)
User-->>App: Return Signature

App->>UR: API: POST /onramp (Signature, QuoteID)
UR->>UR: Validate Sig & Quote Expiry
UR->>Contract: Execute permitOnramp()
note right of UR: UR pays Gas Fee
UR-->>App: Return txHash
end

rect rgb(240, 255, 240)
note right of User: Phase 3: Settlement
App->>Contract: Monitor txHash
```

alt Transaction Confirmed (Within 5 mins)
Contract-->>App: Event: Success
par Notify User
App-->>User: Show "Onramp Successful"
end

```
else Timeout (No Confirmation > 5 mins)
    Note right of App: Monitoring exceeded limit

    par User Feedback
        App-->>User: Show "Status: Pending / Processing"
        Note right of User: "Transaction submitted.<br/>Waiting for network confirmation."
    and Escalation
        App->>UR: Report Timeout Status
        UR->>Ops: 🚨 Alert: Onramp Stuck (TxHash)
        Note right of Ops: Re-trigger
        Ops->>UR: Fixing status
        UR->>App: Return the estimated processing time.
    end
end
end
```

" %}

* Quotation & Risk: The Partner App requests an [onramp quote](/developer-resources/api-reference-external-wallet-access-mode.md#onramp). UR's backend performs real-time risk checks and returns the quote along with any required verification flags.
* Step-up Verification (If required): If the transaction triggers a risk rule (e.g., Liveness Check), the Partner App must redirect the user to a verification [Webview](/developer-resources/api-reference-external-wallet-access-mode.md#onramp) before the user can continue the onramp request.
* Gasless Execution: The user accepts the quote and provides an offline signature (Permit). The Partner frontend [submits this signature via API](/developer-resources/api-reference-external-wallet-access-mode.md#onramp). UR validates the signature and executes the onramp contract on-chain, covering the gas fees on behalf of the user.
* Settlement: The Partner App monitors the transaction hash for confirmation and updates the user interface upon success or reports a timeout if the network is congested.

## Fiat-to-Fiat (FX)

The FX flow allows users to exchange one fiat token for another by signing an off-chain authorization and letting UR execute the transaction on-chain.

{% @mermaid/diagram content="sequenceDiagram
autonumber
actor User
participant App as Partner App
participant UR as UR Backend
participant Contract as UR Contract

```
rect rgb(240, 248, 255)
note right of User: Phase 1: Quotation
User->>App: Input Fiat Amount (e.g., 100 EUR24 to USD24)
App->>UR: POST /fx/quote
UR-->>App: Return Quote (rate, fee, estimated output, errors)
App-->>User: Display Estimated Output
end

rect rgb(255, 250, 240)
note right of User: Phase 2: Permit & Execution
User->>App: Confirm Transaction
App->>User: Request Wallet Signature
User-->>App: Sign permit payload
App->>UR: POST /fx-permit
UR->>Contract: Execute FX using user permit
Contract-->>UR: Return txHash
UR-->>App: Return txHash
App-->>User: Show "Status: Pending / Processing"
end

rect rgb(240, 255, 240)
note right of User: Phase 3: Status Update
alt FX Executed
    UR-->>App: Webhook /webhook/fx.approved
    App-->>User: Show "Status: Completed"
else FX Rejected
    UR-->>App: Webhook /webhook/fx.rejected
    App-->>User: Show "Status: Rejected"
end
end" %}
```

**Flow Description:**

* Quotation: The Partner frontend calls `POST /fx/quote` with input token, output token, and amount, then shows the user the estimated output, fees, and effective rate.
* Permit & Execution: After user confirmation, the app collects a wallet signature and submits `POST /fx-permit`. UR verifies the signature and executes the exchange on-chain.
* Status Update: The Partner receives asynchronous webhook updates (`fx.approved` or `fx.rejected`) and updates UI and balances.
* Fee Handling: Per PRD, network costs are deducted from the user's input fiat amount.

## Cash Pay-in

Users' bank account details can be retrieved via the Profile API. Currently, bank transfers are limited to same-name accounts in EUR and CHF only.

## Cash Pay-out (Bank Transfer)

This flow covers direct bank transfer payouts with recipient verification, signed authorization, and asynchronous transfer/refund webhooks.

{% @mermaid/diagram content="sequenceDiagram
autonumber
actor User
participant App as Partner App
participant UR as UR Backend
participant Contract as UR Contract

```
rect rgb(240, 248, 255)
note right of User: Phase 1: Create/Select Recipient
User->>App: Select transfer currency
App->>UR: GET /profile (contact list)
App->>UR: GET /payment-purposes

alt Existing recipient
    User->>App: Select contact + purpose + reference
    App->>UR: POST /verify-reference
    UR-->>App: Return refId
else New recipient
    User->>App: Create new transfer
    App->>UR: GET /banks
    User->>App: Select recipient bank country
    alt Country supports IBAN metadata
        User->>App: Input IBAN
        App->>UR: GET /banks/iban/{ibanNo}
        UR-->>App: Return bank details
    else Country requires full bank details
        User->>App: Select bank and input bank account info
    end
    App->>UR: GET /country-cities
    User->>App: Input recipient details + purpose + reference
    App->>UR: POST /verify-contact (bankPaymentRequest)
    UR-->>App: Return contactId, purposeId, refId
end
end

rect rgb(255, 250, 240)
note right of User: Phase 2: Permit & Transfer
User->>App: Input amount and confirm transfer
App->>UR: POST /banks/fees
UR-->>App: Return transfer fee
App->>User: Request wallet signature
User-->>App: Sign permit (refId, contactId, purposeId)
App->>UR: POST /payout-with-permit
UR->>Contract: Execute client payout
Contract-->>UR: Return txHash
UR-->>App: Return txHash
App-->>User: Show "Status: Pending / Processing"
end

rect rgb(240, 255, 240)
note right of User: Phase 3: Review & Status
alt Transfer approved
    UR-->>App: Webhook /webhook/transfer.approved
    App-->>User: Show "Funds sent"
else Transfer rejected then refunded
    UR-->>App: Webhook /webhook/transfer.rejected
    App-->>User: Show "Transfer failed, refunding..."
    UR-->>App: Webhook /webhook/transfer.refunded
    App-->>User: Show "Refund completed"
end
end" %}
```

**Flow Description:**

* Recipient Setup: The user either [selects an existing contact or creates a new recipient](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1-create-select-recipient).
  * Create a contact:
    * Get the supported bank\&country list from the [bank country API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1.1-get-supported-banks). User can select the country from the returned list.
      * If the selected country has `ibanMetadata` , you can let users to input IBAN, and use [this API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1.2-get-bank-by-iban) to verify and retrieve bank info .
      * If the selected country is a non-IBAN country (no `ibanMetadata` value), you will need users to select the bank name from the list and input the bank account.
    * Recipient's personal info should be input carefully, includes input name, select [country and city](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1.3-get-supported-recipient-countries-and-cities), select [payout purpose](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1.4-get-payment-purpose-list), inout address and and reference.
      * All the info should be [verified by this API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.1.6-verify-contact-bank-payment-request), if the recipient info is valid, a `contactId`, a `refId` and a `purposeId` will be returned. These three parameters are mandatory prerequisites for proceeding to the payout.
  * Select a contact:
    * Recipient Selection: Users can select a pre-saved recipient retrieved via the [profile API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-3.1.1-get-user-profile). The `contactId` and are automatically derived from the selected recipient profile.
      * Reference Validation: The user manually inputs the payment reference, which is then verified [via the API](/developer-resources/api-reference-external-wallet-access-mode.md) to generate the `refId` and `purposeId` .
      * With the 3 parameters collected, user can proceed the payout.
* Permit and Payout : The app requests a [payout quote](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.2.1-get-fees), then [collects a wallet signature](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.2.2-create-payout-permit-request) tied to `refId`, `contactId`, and `purposeId` before calling the [payout API](https://docs.ur.app/integration-methods/pages/yEG7tyYvgaB42helZKpJ#id-6.2.2-create-payout-permit-request).
* On-chain Execution: UR submits the payout transaction and returns `txHash` for partner-side status tracking.
* Asynchronous Final State: Final outcome is delivered by webhooks (`transfer.approved`, `transfer.rejected`, `transfer.refunded`). If rejected, refund completion is notified separately.
* Input Constraints: Name/address/reference fields should use Latin characters only. Gas fee is deducted from the user's fiat transfer amount.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ur.app/integration-methods/direct-contract-access.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
