# API Reference: External Wallet Access Mode

## 1. Overview

The UR API consists of two distinct services hosted on independent domains: **UR-API** (UR's private API) and **UR-OPEN-API** (Open API for third parties).

* **UR-API:** Provides functionalities for User Registration, KYC Verification, Crypto Deposits, Foreign Exchange (FX), and Crypto On/Off Ramps.
* **UR-OPEN-API:** Provides Third-party Authentication, Corporate (B-side) Minting, and Corporate Transaction History queries.

Sections 1.1 and 1.2 introduce the base information and authentication methods for **UR-API** (Client-side). Section 8 introduces the base information and authentication methods for **UR-OPEN-API** (Partner-side).

### 1.1 API Base Information

#### Base URLs

The API is deployed across different environments. Please use the appropriate base URL for your integration stage .

| Environment    | Base URL                       |
| -------------- | ------------------------------ |
| **Testnet**    | `https://urapi3-qa.ur-inc.xyz` |
| **Preview**    | `https://api2-preview.ur.app`  |
| **Production** | `https://api.ur.app`           |

#### Authentication Method: Header Authentication

All API requests must include the following specific headers for authentication and verification .

| Header Field | Type   | Required | Description                                |
| ------------ | ------ | -------- | ------------------------------------------ |
| `tokenId`    | string | **Yes**  | User's URID                                |
| `network`    | string | **Yes**  | The blockchain network identifier.         |
| `sign`       | string | **Yes**  | The cryptographic signature from the user. |
| `hash`       | string | **Yes**  | The hash of the original request data.     |
| `deadline`   | string | **Yes**  | The expiration timestamp for the request.  |

**Field Definitions** :

* **tokenId**: The User's URID.
* **network**: Use `5000` for Mainnet and `5003` for Testnet.
* **sign**: The signature generated by the user's wallet.
* **hash**: The SHA3/Keccak256 hash of the original request payload.
* **deadline**: Server timestamp + validity window (max 20 minutes). This is controlled by the partner to ensure signature validity.

#### Data Format

* **Format**: JSON

#### Universal Response Format

All API responses follow this standard JSON structure :

```json
{
  "retCode": 0, // Return Code. 0 indicates success; non-0 indicates failure.
  "retMsg": "success", // Return Message. Contains error details if retCode != 0.
  "result": {}, // Result Data. The specific business data (JSON object).
  "timeNow": 1703123456789 // Server Timestamp.
}
```

### 1.2 Signature and Verify

UR APIs are categorized into three authentication levels based on data sensitivity and operation type. For a complete guide on signature formats and verification logic for both Partners and Users, please refer to [Signature and Verification](/developer-resources/signature-and-verify.md).

#### A. No Auth (Public Interfaces)

* **Scenarios**: Querying public information such as supported countries, UR server timestamp, exchange rates, or OTP login.
* **Method**: No authentication information is required in the request.

#### B. Basic Auth

**Scenarios**: Operations that do not involve fund movements but require basic user identification, such as User Registration (Minting) or generating tokens. **Method**: The request must include the `tokenId`(User's URID) in the Header.

#### C. Full Auth

* **Scenarios**: Operations involving fund movements or sensitive data where every call requires a user signature to prevent replay attacks. Examples include Currency Exchange (FX), Permit, and querying Transaction History.

> **Note**: In the following documentation, every API will indicate its specific **Authentication Level**. Callers must strictly adhere to these levels. Additionally, some interfaces may have specific requirements regarding the User Status.

## 2. Account

This section covers API endpoints and webhooks related to user account management.

### 2.1 Account API

#### 2.1.1 Get Server Timestamp

**1. Description** Retrieves the current server timestamp from the UR service. This timestamp is required for calculating the signature `deadline`.

**2. Request**

| Item            | Value               | Note                      |
| --------------- | ------------------- | ------------------------- |
| **HTTP Method** | `GET`               |                           |
| **URI**         | `/api/v1/timestamp` |                           |
| **Auth Level**  | **No Auth**         |                           |
| **Headers**     | `Content-Type`      | Fixed: `application/json` |

|

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": "{}",
  "timeNow": 1703123456789
}
```

**4. Request Example**

```bash
curl 'https://get.ur.app/api/v1/timestamp' \
-H 'content-type: application/json; charset=UTF-8'

```

**5. Error Codes**

#### 2.1.2 Email Verification

**1. Description** Checks if the user's email address is available for registration. If the email is unavailable, the interface returns an error message, and the user must provide a different email to open an account.

**2. Request**

| Item            | Value                  | Note |
| --------------- | ---------------------- | ---- |
| **HTTP Method** | `POST`                 |      |
| **URI**         | `/api/v2/email-status` |      |
| **Auth Level**  | **No Auth**            |      |

**Request Parameters**

```json
{
  "email": "xxx@gmail.com"
}
```

**3. Response**

**Scenario: Email already exists (Unavailable)**

```json
{
  "retCode": 10019,
  "retMsg": "Email already registered, please use a different email",
  "result": null,
  "timeNow": 0
}
```

**Scenario: Email is available (Success)**

```json
{
  "retCode": 0,
  "retMsg": "OK",
  "result": null,
  "timeNow": 0
}
```

**4. Request Example**

```bash
curl -X POST http://localhost:8888/api/v2/email-status \
--data '{"email": "abc@gmail.com"}'

```

**5. Error Codes**

#### 2.1.3 Get User Status

**1. Description** Retrieves detailed status information of the user account, including account status, KYC process progress, SumSub KYC info, and novice guidance progress .

**Scenarios:**

* Query account status after user login.
* Determine accessible features based on current status.
* Display the current progress of the KYC verification flow.
* Track novice guidance completion.
* Determine if the user needs to complete specific verification steps .

**2. Request**

| Item            | Value                    | Note                      |
| --------------- | ------------------------ | ------------------------- |
| **HTTP Method** | `GET`                    |                           |
| **URI**         | `/api/v2/account-status` |                           |
| **Auth Level**  | **Basic Auth**           | No status restriction     |
| **Headers**     | `Content-Type`           | Fixed: `application/json` |
|                 | `tokenId`                | User's URID               |
|                 | `network`                | Network Identifier        |

**3. Response**

**Success Response (HTTP 200):**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": "{\"status\":3,\"statusStr\":\"Live\",\"kycFlow\":{\"currentStep\":4,\"currentStepStr\":\"Review\",\"currentStepActionTypes\":[],\"failReason\":\"\"},\"sumsubKycInfo\":{\"userId\":\"app123456\",\"latestKycHasCompleted\":true,\"reviewStatus\":\"completed\",\"reviewAnswer\":\"GREEN\",\"reviewRejectType\":\"\"},\"noviceGuidanceProcessStep\":5,\"noviceGuidanceProcessStepLatestTimeMs\":1703123456789,\"crsInfo\":{\"needCrs\":true,\"restrictDate\":1778402365685,\"url\":\"https://ur-fe2.qa4.gomantle.org/info-provide/crs?hash=D9cEvsYk&source=ur\"}}",
  "timeNow": 1703123456789
}
```

`**result` Field Breakdown:

| Field Name                              | Type   | Description                                                                                          |
| --------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------- |
| `status`                                | int    | Account status code (`0`=Na, `1`=Soft\_Blocked, `2`=Tourist, `3`=Blocked, `4`=Closed, `5`=Live)      |
| `statusStr`                             | string | Account status string ("Na", "Soft\_Blocked", "Tourist", "Blocked", "Closed", "Live".)               |
| `kycFlow`                               | object | KYC flow information (see `KycFlowResponse` structure below)                                         |
| `sumsubKycInfo`                         | object | SumSub KYC information (see `SumsubKycInfo` structure below)                                         |
| `noviceGuidanceProcessStep`             | int    | Current step of novice guidance (0 indicates not started or unfinished; queried only in Live status) |
| `noviceGuidanceProcessStepLatestTimeMs` | int64  | Latest update timestamp for novice guidance (milliseconds)                                           |

`**KycFlowResponse` Structure:

| Field Name               | Type    | Description                                                                                    |
| ------------------------ | ------- | ---------------------------------------------------------------------------------------------- |
| `currentStep`            | int     | Current KYC step (`0`=UNKNOWN, `1`=FormA, `2`=IDScan, `3`=SignFormA, `4`=Review, `5`=Rejected) |
| `currentStepStr`         | string  | Current step name ("FormA", "IDScan", "SignFormA", "Review", "Rejected")                       |
| `currentStepActionTypes` | string] | Action types required for the current step (e.g., `["sumsub"]`, `["sign"]`)                    |
| `failReason`             | string  | Reason for KYC failure (value exists only when `currentStep` is Rejected)                      |

`**SumsubKycInfo` Structure:

| Field Name              | Type   | Description                                                                    |
| ----------------------- | ------ | ------------------------------------------------------------------------------ |
| `userId`                | string | SumSub Applicant ID, used for querying KYC status                              |
| `latestKycHasCompleted` | bool   | Whether the latest KYC process has been completed                              |
| `reviewStatus`          | string | Review status ("init", "pending", "prechecked", "completed", etc.)             |
| `reviewAnswer`          | string | Review result ("GREEN"=Passed, "RED"=Rejected)                                 |
| `reviewRejectType`      | string | Rejection type (e.g., "RETRY", "FINAL", etc.; value exists only when rejected) |

`**CrsInfo` Structure:

| Field Name     | Type   | Description                                                                                                                                                                     |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `needCrs`      | bool   | Indicates whether the user is required to complete the CRS process.                                                                                                             |
| `restrictDate` | int64  | Represents the deadline or restriction timestamp for completing the CRS process, usually in Unix timestamp format (milliseconds). After this time, the user may be softBlocked. |
| `url`          | string | The CRS submission link that directs the user to the CRS information collection page.                                                                                           |

**Response Field Details (Status):**

* `0 (Na)`: New user, NFT not minted.
* `1 (Tourist)`: NFT minted, KYC not completed.
* `2 (PendingSign)`: KYC completed, waiting to sign Form A.
* `3 (Live)`: All verifications completed, transactions enabled.
* `4 (SignCompleted)`: Form A signed.
* `5 (SoftBlocked)`: Soft blocked status.
* `6 (Blocked)`: Account frozen.
* `7 (Closed)`: Account closed.

**4. Request Example**

```bash
curl -X GET "https://urapi2-qa.ur-inc.xyz/api/v2/account-status" \
-H "tokenId: 12345" \
-H "network: mainnet"

```

**5. Error Codes**

| Code    | Type          | Description                                | Solution                                                  |
| ------- | ------------- | ------------------------------------------ | --------------------------------------------------------- |
| `10001` | Auth Failure  | Invalid `tokenId` or user does not exist   | Check if `tokenId` is correct and user is registered      |
| `10002` | Parse Error   | Missing or malformed Header parameters     | Ensure valid `tokenId` and `network` Headers are provided |
| `10009` | Invalid Param | `tokenId` format error                     | Provide a valid NFT Token ID (positive integer)           |
| `10000` | System Error  | Database query failed or service exception | Retry later or contact technical support                  |

#### 2.1.4 MINT (Create User's URID)

| Item            | Value          | Note                                              |
| --------------- | -------------- | ------------------------------------------------- |
| **HTTP Method** | `POST`         |                                                   |
| **URI**         | `/api/v2/mint` |                                                   |
| **Auth Level**  | **Basic Auth** | User status must be `Na` (0)                      |
| **Headers**     | `Content-Type` | Fixed: `application/json`                         |
|                 | `tokenId`      | Pre-generated URID (via generate-token interface) |
|                 | `network`      | Network Identifier (e.g., "mainnet")              |

| Parameter         | Type   | Required | Description                             | Example           |
| ----------------- | ------ | -------- | --------------------------------------- | ----------------- |
| `address`         | string | **Yes**  | User's Ethereum wallet address.         | `"0x123..."`      |
| `lotNumber`       | string | **Yes**  | GeeTest verification session ID.        | `"7a8f..."`       |
| `captchaOutput`   | string | **Yes**  | GeeTest verification output result.     | `"XyZ123..."`     |
| `passToken`       | string | **Yes**  | GeeTest pass token.                     | `"token_..."`     |
| `genTime`         | string | **Yes**  | GeeTest generation timestamp (seconds). | `"1703123456"`    |
| `image`           | string | No       | NFT Image (Base64).                     | `"data:image..."` |
| `backGroundColor` | string | No       | Background color (Hex).                 | `"#FFFFFF"`       |
| `land`            | string | No       | Land information.                       | `"land1"`         |

| Field     | Type   | Description                                      |
| --------- | ------ | ------------------------------------------------ |
| `tokenId` | string | User's URID                                      |
| `txHash`  | string | Blockchain transaction hash for tracking status. |

| Code    | Type           | Description                     | Solution                                         |
| ------- | -------------- | ------------------------------- | ------------------------------------------------ |
| `10002` | Parse Error    | Request parameter format error  | Check JSON format and field types                |
| `10003` | ID Not Found   | Mint Token ID does not exist    | Call `/api/v1/generate-token` first              |
| `10004` | Mismatch       | Token ID does not match address | Confirm TokenID relationship with wallet address |
| `10005` | Duplicate Mint | Address already has an NFT      | Use existing Token ID                            |
| `10007` | Duplicate Mint | Token already used for minting  | Same as 10005                                    |
| `10009` | Invalid Param  | Missing required fields         | Check `address`, `lotNumber`, etc.               |
| `10010` | GeeTest Fail   | Verification failed or expired  | Retry GeeTest verification                       |
| `10006` | Status Error   | Account status is not `Na`      | Check account status                             |

Please refer to [**OpenAPI Mint**](/developer-resources/openapis.md#mint-urid) for the URID minting.

#### 2.1.5 Get Sumsub SDK Token

**1. Description** Creates an access token for SumSub KYC verification. This token is used to initialize the SumSub SDK (Web or Mobile) and start the identity verification process .

{% hint style="warning" %}
**NFC scanning requires a mobile app.** The Passport/National ID NFC scan step within the Sumsub SDK is only supported in the **Sumsub mobile SDK** (iOS/Android). It is not available in the Sumsub web SDK. Partners 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 %}

**Call Scenarios:**

* When a user starts the KYC identity verification process.
* When the frontend needs to initialize the SumSub SDK.
* When the token expires during verification and needs refreshing.
* During KYC retry scenarios (supporting various retry levels).

**2. Request**

| Item            | Value                                | Note                      |
| --------------- | ------------------------------------ | ------------------------- |
| **HTTP Method** | `POST`                               |                           |
| **URI**         | `/api/v1/sumsub/create-access-token` |                           |
| **Auth Level**  | **Full Auth**                        |                           |
| **Headers**     | `Content-Type`                       | Fixed: `application/json` |
|                 | `tokenId`                            | User's URID               |
|                 | `network`                            | Network Identifier        |
|                 | `sign`                               | Wallet Signature          |
|                 | `hash`                               | Original request hash     |
|                 | `deadline`                           | Signature deadline        |

**Request Parameters**

| Parameter             | Type   | Required | Description                                                                                                                                                                        | Example               |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `userId`              | string | No       | Optional. When omitted, the server derives the internal Sumsub user id from Full Auth context (e.g. `tokenId`). Only pass this if UR gave you an explicit pattern for your tenant. | `"12345"`             |
| `levelName`           | string | No       | Specific SumSub Level name. Empty uses default.                                                                                                                                    | `"Basic information"` |
| `ttl`                 | int    | No       | Token time-to-live (seconds). Default configured by server.                                                                                                                        | `3600`                |
| `isRetryVerification` | bool   | No       | Indicates if this is a KYC retry scenario.                                                                                                                                         | `false`               |
| `retryLevel`          | int32  | No       | Retry level (1-7), valid only if `isRetryVerification=true`.                                                                                                                       | `1`                   |
| `stepType`            | string | No       | Specific verification step to reset (e.g., "IDENTITY").                                                                                                                            | `"IDENTITY"`          |
| `failureReason`       | string | No       | Reason for failure, used for logging.                                                                                                                                              | `"Document expired"`  |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": "{\"token\":\"act-abc123xyz...\",\"userId\":\"12345\"}",
  "timeNow": 1703123456789
}
```

**Result Field Description**

| Field    | Type   | Description                                                                   |
| -------- | ------ | ----------------------------------------------------------------------------- |
| `token`  | string | SumSub Access Token (usually starts with "act-"). Used to initialize the SDK. |
| `userId` | string | SumSub Applicant ID, used for subsequent queries.                             |

**4. Request Example**

```bash
curl -X POST "https://urapi2-qa.ur-inc.xyz/api/v1/sumsub/create-access-token" \
-H "Content-Type: application/json" \
-H "tokenId: 12345" \
-H "network: mainnet" \
-H "sign: 0x..." \
-H "hash: ..." \
-H "deadline: ..." \
-d '{}'

```

You may include `"userId": "…"` in the JSON body only when UR instructs you to use a tenant-specific pattern.

**5. Error Codes**

| Code    | Type          | Description                        | Solution                                    |
| ------- | ------------- | ---------------------------------- | ------------------------------------------- |
| `10002` | Parse Error   | Request parameter format error     | Check JSON format and optional body fields  |
| `10009` | Invalid Param | Invalid or inconsistent parameters | Verify headers and body per tenant guidance |
| `10000` | System Error  | SumSub service exception           | Retry later or contact support              |

#### 2.1.6 Get User KYC Document (Form A)

**1. Description** Retrieves the content and structure of the KYC Form A (Client Due Diligence). This interface is called before the user signs Form A to display the declaration text that requires the user's signature . Form A is a critical KYC document containing compliance information such as financial status, source of funds, and account purpose.

**Call Scenarios:**

* After the user completes SumSub verification, they must fill out/sign Form A.
* The frontend retrieves the specific content to display to the user.
* Retrieves the self-declaration text for the user to confirm via signature.

**2. Request**

| Item            | Value                     | Note                      |
| --------------- | ------------------------- | ------------------------- |
| **HTTP Method** | `GET`                     |                           |
| **URI**         | `/api/v2/kyc/form-a-info` |                           |
| **Auth Level**  | **Full Auth**             |                           |
| **Headers**     | `Content-Type`            | Fixed: `application/json` |
|                 | `tokenId`                 | User's URID               |
|                 | `network`                 | Network Identifier        |
|                 | `sign`                    | Wallet Signature          |
|                 | `hash`                    | Original request hash     |
|                 | `deadline`                | Signature deadline        |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "ok",
  "result": {
    "kycSelfDec": "I declare that I am the beneficial owner of the account..."
  },
  "timeNow": 1678888888
}
```

**Result Field Description**

| Field        | Type   | Description                                                                                                                                                              |
| ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `kycSelfDec` | string | The complete Form A text content retrieved from the SumSub system. Contains user personal info and compliance declarations. The user must read and sign this exact text. |

**4. Request Example**

```bash
curl -X GET 'https://api.ur-bank.com/api/v2/kyc/form-a-info' \
-H "Content-Type: application/json" \
-H "tokenId: 12345" \
-H "network: mainnet" \
-H "sign: 0x..." \
-H "hash: ..." \
-H "deadline: ..." \

```

**5. Error Codes**

| Code    | Type          | Description                                 | Solution                                             |
| ------- | ------------- | ------------------------------------------- | ---------------------------------------------------- |
| `10001` | Auth Failure  | Invalid `tokenId` or user does not exist    | Check if `tokenId` is correct and user is registered |
| `10002` | Parse Error   | Header parameters missing or malformed      | Ensure valid `tokenId` and `network` Headers         |
| `10009` | Invalid Param | `tokenId` format error                      | Provide valid NFT Token ID                           |
| `10000` | System Error  | KYC Flow status incorrect or data exception | Check detailed error message                         |

#### 2.1.7 Submit User Signature (Submit Form A)

**1. Description** Submits the user's signature for the KYC Form A (Customer Due Diligence Form). After reading the Form A content, the user uses their wallet to sign the content and submits the signature via this interface to complete the final step of the KYC process.

**Call Scenarios:**

* User has retrieved Form A content via `/api/v2/kyc/form-a-info`.
* User reads and confirms the Form A content.
* User uses the wallet to sign the Form A content.
* Submit the signature to complete KYC certification .

**2. Request**

| Item            | Value                       | Note                       |
| --------------- | --------------------------- | -------------------------- |
| **HTTP Method** | `POST`                      |                            |
| **URI**         | `/api/v2/kyc/submit-form-a` |                            |
| **Auth Level**  | **Full Auth**               |                            |
| **Header**      | `Content-Type`              | Fixed: `application/json`  |
| **Header**      | `tokenId`                   | User's URID                |
| **Header**      | `network`                   | Network Identifier: `5000` |
| **Header**      | `sign`                      | Wallet Signature           |
| **Header**      | `hash`                      | Original request hash      |
| **Header**      | `deadline`                  | Signature deadline         |

**Request Parameters**

| Parameter        | Type   | Required | Description                                                                                                                    | Example                            |
| ---------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- |
| `kycSelfDec`     | string | **Yes**  | Form A self-declaration text content. **Must be exactly the same** as the `dec` field returned by the `form-a-info` interface. | `"I, John Doe, hereby declare..."` |
| `kycSelfDecSign` | string | **Yes**  | ECDSA signature of the Form A text by the user. (65-byte hex string).                                                          | `"0x1234...abcd"`                  |

**Parameter Notes:**

* **kycSelfDec:** Must include the complete Form A text content. Cannot be modified or truncated.
* **kycSelfDecSign:**
* Uses the user wallet's private key to sign `kycSelfDec`.
* Format: 65-byte hexadecimal string (130 chars, starting with `0x`).
* Structure: `r` (32 bytes) + `s` (32 bytes) + `v` (1 byte).
* Adopts standard **EIP-191 (Ethereum Signed Message)** mechanism.
* **Verification Logic:** The backend will prefix the `kycSelfDec` text with `\x19Ethereum Signed Message:\n<length>`, calculate the Keccak256 hash, recover the public key address from the signature, and compare it with the current user's wallet address . **Signature Generation Example (Frontend/JS):**

```javascript
// 1. Prepare text (Assume fetched from GET /api/v2/kyc/form-a-info)
const kycSelfDec = "I hereby declare that I am the beneficial owner...";
const userAddress = "0xYourWalletAddress...";

// 2. Sign using Ethers.js
// wallet is a Signer object connected to a Provider
const signature = await wallet.signMessage(kycSelfDec);

// OR 3. Sign using Web3.js
// const signature = await web3.eth.personal.sign(kycSelfDec, userAddress, "password(optional)");

console.log("kycSelfDecSign:", signature);
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": "{}",
  "timeNow": 1703123456789
}
```

**Response Description:**

* `retCode`: `0` indicates signature verification passed and saved successfully.
* `result`: Empty JSON object `{}`.
* **Note:** After returning success, wait for **3 seconds** to allow downstream services to process the status update (interface has internal sleep) .

**4. Request Example**

```bash
curl -X POST "https://urapi2-qa.ur-inc.xyz/api/v2/kyc/submit-form-a" \
-H "Content-Type: application/json" \
-H "tokenId: 12345" \
-H "network: mainnet" \
-H "sign: 0x..." \
-H "hash: ..." \
-H "deadline: ..." \
-d '{
  "kycSelfDec": "I, John Doe, holder of passport number XX123456, hereby declare that:\n\n1. The source of funds...",
  "kycSelfDecSign": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
}'

```

**5. Error Codes**

| Code    | Error Type    | Description                                                      | Solution                                                    |
| ------- | ------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- |
| `10001` | Auth Failure  | Invalid `tokenId` or user does not exist.                        | Check if `tokenId` is correct.                              |
| `10002` | Parse Error   | JSON format error or parameter type mismatch.                    | Ensure `dec` and `sign` are both strings and JSON is valid. |
| `10009` | Invalid Param | Required fields missing or empty.                                | Ensure both `kycSelfDec` and `kycSelfDecSign` are provided. |
| `10000` | System Error  | Signature verification failed, database error, or Kafka failure. | Check error message details (e.g., signature mismatch).     |

### 2.2 Account Webhook

Webhook definitions are centralized in [Webhooks](/developer-resources/webhook.md). For account-related events, see `sumsub_kyc_result` and `kyc_status`.

## 3. Card

This section covers API endpoints related to UR debit card management and banking operations.

### 3.1 Card API

#### 3.1.1 Get User Profile

**1. Description** Retrieves the user's banking profile information, including IBAN, account holder name, supported currencies, billing address, and card eligibility status.

**Call Scenarios:**

* Displaying account details and transaction limits to the user.
* Checking if the user has already issued a card or is eligible to do so.

**2. Request**

| Item            | Value          | Note                              |
| --------------- | -------------- | --------------------------------- |
| **HTTP Method** | `GET`          |                                   |
| **URI**         | `/api/v2/br`   |                                   |
| **Auth Level**  | **Full Auth**  | Requires wallet signature         |
| **Headers**     | `Content-Type` | Fixed: `application/json`         |
|                 | `tokenId`      | User's URID                       |
|                 | `network`      | Network Identifier (e.g., `5000`) |
|                 | `sign`         | Wallet Signature                  |
|                 | `hash`         | Original request hash             |
|                 | `deadline`     | Signature deadline                |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "ok",
  "result": {
    "tokenId": 12345,
    "br": "John Doe",
    "iban": "CH93 0076 2011 6238 5295 7",
    "email": "john@example.com",
    "mobile": "+41791234567",
    "debitCard": "MSTD",
    "isCardEligible": true,
    "cards": [],
    "cardActivation": {
      "amount": 100,
      "currency": "CHF"
    },
    "street": "Bahnhofstrasse 1",
    "postalCode": "8001",
    "city": "Zurich",
    "country": "CHE",
    "limits": {
      "restartDate": "2024-02-01",
      "restartDateMs": 1706745600000,
      "used": 500,
      "available": 9500,
      "max": 10000
    },
    "contacts": {
      "CHF": [],
      "EUR": [
        {
          "id": "EA-00082721",
          "name": "Mark Lee",
          "account": "•••• 8271",
          "fullAccount": "CH26 9323 0923 1234 9876 8",
          "bank": "SR Saphirstein AG",
          "isSameOwner": true,
          "isIBAN": true,
          "country": "CH",
          "lastPaymentDate": 1771459200000
        }
      ],
      "USD": []
    },
    "depositBank": {
      "CHF": {
        "account": "CH93 0076 ...",
        "bank": "Hypothekarbank Lenzburg AG",
        "BIC": "HYPCH22",
        "payee": "Fiat24 AG",
        "city": "Lenzburg"
      }
    }
  },
  "timeNow": 1737460800000
}
```

**Result Field Description**

| Field            | Type    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `br`             | string  | Account Holder Name.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `iban`           | string  | User's IBAN (formatted with spaces).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `country`        | string  | ISO3 Country Code (e.g., "CHE" for Switzerland).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `isCardEligible` | boolean | Indicates if the user is eligible to issue a card.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `cards`          | array   | List of existing cards.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `cardActivation` | object  | Activation fee info (Amount/Currency). Returned only if `isCardEligible=true` and `cards` is empty.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `limits`         | object  | The `limits` object defines the user's monthly spending capacity, denominated in CHF (Swiss Franc), within a rolling 30-day cycle.It consists of `max`, representing the total allowable limit allocated to the user , and `used`, which tracks the cumulative volume of all fiat-related operations (including FX, Card Spending, On-ramps, and Cash Payouts). The `available` balance is dynamically calculated as the difference between `max` and `used`.To ensure transaction success, partners must verify that any requested transaction amount does not exceed the `available` limit, as operations surpassing this threshold will automatically fail. |
| `depositBank`    | object  | Bank account details for topping up the account (grouped by currency).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| contacts         | object  | The user's bank payout contact list.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

**4. Request Example**

```bash
curl -X GET 'https://urapi3-qa.ur-inc.xyz/api/v2/br' \
-H 'tokenid: 12345' \
-H 'network: 5000' \
-H 'sign: 0x1234567890abcdef...' \
-H 'hash: 0xabcdef1234567890...' \
-H 'deadline: 1737500000'

```

**5. Error Codes**

| Code    | Type         | Description                   | Solution                                                        |
| ------- | ------------ | ----------------------------- | --------------------------------------------------------------- |
| `10000` | DefError     | Default/Upstream Error        | Check request parameters and system status.                     |
| `10001` | Auth Failure | Signature Verification Failed | Check headers, signature, deadline validity, and address match. |
| `10002` | Parse Error  | Request Parsing Failed        | Validate JSON format and Content-Type.                          |
| `10006` | Status Error | Account Status Not Supported  | User account must be in a valid state (e.g., Live).             |
| `10016` | Rate Limit   | Too Many Requests             | Retry later (exponential backoff).                              |
| `10999` | Blocked      | Trade Blocked                 | Account restricted; contact support.                            |

#### 3.1.2 Create Card

**1. Description:** After passing KYC verification and meeting card issuance conditions, the user calls this interface to apply for a new virtual card. This process typically involves an initial top-up amount and currency specification.

**2. Request**

| Item            | Value          | Note                                     |
| --------------- | -------------- | ---------------------------------------- |
| **HTTP Method** | `POST`         |                                          |
| **URI**         | `/api/v2/card` |                                          |
| **Auth Level**  | **Full Auth**  | Requires wallet signature                |
| **Headers**     | `Content-Type` | Fixed: `application/json`                |
|                 | `tokenId`      | User's URID                              |
|                 | `network`      | Network Identifier (e.g., `5000`/`5003`) |
|                 | `sign`         | Wallet Signature                         |
|                 | `hash`         | Original request hash                    |
|                 | `deadline`     | Signature deadline                       |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "status": 200,
    "data": "Card created correctly"
  },
  "timeNow": 1700000000
}
```

**4. Request Example**

```bash
curl -X POST 'https://urapi3-qa.ur-inc.xyz/api/v2/card' \
-H 'Content-Type: application/json' \
-H 'tokenid: 1001' \
-H 'network: 5000' \
-H 'sign: 0x5a2...3b1' \
-H 'hash: Hello world' \
-H 'deadline: 1735689600' \
-d '{}'

```

**5. Error Codes**

| Code    | Type     | Description                                           | Solution                                                                                               |
| ------- | -------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `10000` | DefError | Default error containing specific business error info | Return 400 (Parameter error or conditions not met), Unauthorized signature, or Service Internal Error. |

#### 3.1.3 Get Card Info

**1. Description** Retrieves card information and the `cardToken`. The `cardToken` is used to initialize the frontend component to securely display sensitive card details such as the CVV.

**2. Request**

| Item            | Value          | Note                               |
| --------------- | -------------- | ---------------------------------- |
| **HTTP Method** | `GET`          |                                    |
| **URI**         | `/api/v2/card` |                                    |
| **Auth Level**  | **Full Auth**  | Requires wallet signature          |
| **Headers**     | `Content-Type` | Fixed: `application/json`          |
|                 | `tokenId`      | User's URID                        |
|                 | `network`      | Network Identifier (`5000`/`5003`) |
|                 | `sign`         | Wallet Signature                   |
|                 | `hash`         | Original request hash              |
|                 | `deadline`     | Signature deadline                 |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "security": {
      "contactlessEnabled": true,
      "withdrawalEnabled": false,
      "internetPurchaseEnabled": true,
      "overallLimitsEnabled": true
    },
    "currencies": ["EUR", "CHF", "USD", "RMB"],
    "tokenId": 106654866313,
    "limits": {
      "account": {
        "restartDate": "01.02.2026 9:47",
        "restartDateMs": 1769939264000,
        "used": 33645.39,
        "available": 760005.39,
        "max": 793650.79
      },
      "withdrawal": { "used": 0, "max": 0 },
      "internetPurchase": { "used": 4858.63, "max": 165010 }
    },
    "cardDesign": "MSTDMNT",
    "cardHolder": "Shawn XX",
    "status": "Active",
    "currency": "CNH",
    "masked": {
      "cardNumber": ".... 3083",
      "cvv2": "...",
      "expiry": "../.."
    },
    "cardToken": "eyJ0b2tlbiI6IjQ1Y2IwMjU5LTk2ZjgtNGMzMi1hZDIzLWEwNWZmYzkxOWI5YSZ...",
    "activeTokens": [
      {
        "id": "704ab18a...",
        "type": "iPhone 16 pro (Apple Pay)",
        "createdAt": "2026-01-06T16:40:26Z"
      }
    ],
    "externalId": "1758893252"
  },
  "timeNow": 1769440233208
}
```

**4. Request Example**

```bash
curl -X GET 'https://urapi3-qa.ur-inc.xyz/api/v2/card' \
-H 'accept: application/json' \
-H 'tokenid: 6654866313' \
-H 'network: 5000' \
-H 'sign: 0xe6be0e52...' \
-H 'hash: UR' \
-H 'deadline: 1769410673'

```

**5. Error Codes**

| Code    | Type     | Description                                            | Reason                                                                                                                                                                             |
| ------- | -------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10000` | DefError | Default error containing specific business error info. | 1. Request parameters error (unsupported currency, card not found/not created). 2. Unauthorized: Signature verification failed. 3. Service Internal Error. 4. Data parsing failed. |

#### 3.1.4 Get Card Details

The `[/cards` interface]\(api-reference-external-wallet-access-mode.md#id-3.1.3-get-card-token) does not expose sensitive debit card data (such as CVV, expiry date). To [display the card details](https://docs.fiat24.com/developer/integration-guide/part-2-api-reference#id-2.-get-cards), call the external `fiat24card.js` service using the `cardToken` provided by the `/cards` interface .

#### 3.1.5 Set Default Transaction Currency

**1. Description** Sets the default transaction currency for the user's card.

**2. Request**

| Item            | Value                   | Note                               |
| --------------- | ----------------------- | ---------------------------------- |
| **HTTP Method** | `POST`                  |                                    |
| **URI**         | `/api/v2/card-currency` |                                    |
| **Auth Level**  | **Full Auth**           |                                    |
| **Headers**     | `Content-Type`          | Fixed: `application/json`          |
|                 | `tokenId`               | User's URID                        |
|                 | `network`               | Network Identifier (`5000`/`5003`) |
|                 | `sign`                  | Wallet Signature                   |
|                 | `hash`                  | Original request hash              |
|                 | `deadline`              | Signature deadline                 |

**Request Parameters**

| Parameter     | Type   | Description                                                                                                                                                 |
| ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cardTokenId` | string | The Card's Token ID (Note: This is the card's specific ID, not the user's URID). Can be retrieved from the `/cards` interface, should be a value 'active '. |
| `currency`    | string | The default transaction currency to set (e.g., "USD").                                                                                                      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "status": 200,
    "data": "currency"
  },
  "timeNow": 1700000000
}
```

**4. Request Example**

```bash
curl -X POST 'https://urapi3-qa.ur-inc.xyz/api/v2/card-currency' \
-H 'Content-Type: application/json' \
-H 'tokenid: 1001' \
-H 'network: 5000' \
-H 'sign: 0x5a2...3b1' \
-H 'hash: Hello world' \
-H 'deadline: 1735689600' \
-d '{
  "cardTokenId": "1001",
  "currency": "USD"
}'

```

**5. Error Codes**

| Code    | Type     | Description                                            | Solution                                                                                                                           |
| ------- | -------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `10000` | DefError | Default error containing specific business error info. | 1. Parameter errors (e.g., unsupported currency, card does not exist), 2. Signature verification failed 3. Upstream service error. |

#### 3.1.6 Get Transaction History

**1. Description** Retrieves the transaction history for the user's account. Supports date range filtering and pagination.

**2. Request**

| Item            | Value                  | Note                              |
| --------------- | ---------------------- | --------------------------------- |
| **HTTP Method** | `GET`                  |                                   |
| **URI**         | `/api/v2/transactions` |                                   |
| **Auth Level**  | **Full Auth**          | Requires wallet signature         |
| **Headers**     | `Content-Type`         | Fixed: `application/json`         |
|                 | `tokenId`              | User's URID                       |
|                 | `network`              | Network Identifier (e.g., `5000`) |
|                 | `sign`                 | Wallet Signature                  |
|                 | `hash`                 | Original request hash             |
|                 | `deadline`             | Signature deadline                |

**Query Parameters**

| Parameter | Type | Required | Description                     |
| --------- | ---- | -------- | ------------------------------- |
| `from`    | long | No       | Start timestamp (milliseconds). |
| `to`      | long | No       | End timestamp (milliseconds).   |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "ok",
  "result": {
    "transactions": [
      {
        "amount": -15.5,
        "currency": "USD",
        "merchantName": "Starbucks",
        "merchantCity": "New York",
        "merchantCountry": "USA",
        "mcc": "5812",
        "status": "APPROVED",
        "date": "2023-10-27T10:30:00Z",
        "dateMs": 1698402600000,
        "type": "POS_PURCHASE"
      },
      {
        "amount": 1000.0,
        "currency": "EUR",
        "merchantName": "Topup",
        "status": "COMPLETED",
        "date": "2023-10-26T09:00:00Z",
        "dateMs": 1698310800000,
        "type": "DEPOSIT"
      }
    ]
  },
  "timeNow": 1698489000000
}
```

**Result Field Description**

| Field          | Type   | Description                                                                                     |
| -------------- | ------ | ----------------------------------------------------------------------------------------------- |
| `amount`       | float  | Transaction amount. Negative values indicate spending; positive values indicate income/top-ups. |
| `currency`     | string | Transaction currency (e.g., "USD", "EUR").                                                      |
| `merchantName` | string | Name of the merchant or counterparty.                                                           |
| `status`       | string | Transaction status (e.g., `APPROVED`, `DECLINED`, `PENDING`, `COMPLETED`).                      |
| `type`         | string | Transaction type (e.g., `POS_PURCHASE`, `DEPOSIT`, `WITHDRAWAL`).                               |
| `dateMs`       | long   | Transaction timestamp in milliseconds.                                                          |

**4. Request Example**

```bash
curl -X GET 'https://urapi3-qa.ur-inc.xyz/api/v2/transactions?from=1698300000000&to=1698500000000' \
-H 'tokenid: 12345' \
-H 'network: 5000' \
-H 'sign: 0x123...' \
-H 'hash: HistoryQuery' \
-H 'deadline: 1735689600'

```

**5. Error Codes**

| Code    | Type         | Description                                            | Solution                                             |
| ------- | ------------ | ------------------------------------------------------ | ---------------------------------------------------- |
| `10000` | DefError     | Default error containing specific business error info. | Check request parameters or upstream service status. |
| `10001` | Auth Failure | Signature Verification Failed                          | Check headers, signature, and deadline validity.     |

Yes, you are correct. **3.1.7** is **Update Card Status** (Lock/Unlock). The FX section comes later.

Here is the corrected section.

#### 3.1.7 Update Card Status

**1. Description** Modifies the status of the user's debit card. This is primarily used to "Freeze" (Block) the card to prevent unauthorized usage or "Unfreeze" (Unblock) it to resume normal operations.

**2. Request**

| Item            | Value          | Note                              |
| --------------- | -------------- | --------------------------------- |
| **HTTP Method** | `POST`         |                                   |
| **URI**         | `/api/v2/-tus` |                                   |
| **Auth Level**  | **Full Auth**  | Requires wallet signature         |
| **Headers**     | `Content-Type` | Fixed: `application/json`         |
|                 | `tokenId`      | User's URID                       |
|                 | `network`      | Network Identifier (e.g., `5000`) |
|                 | `sign`         | Wallet Signature                  |
|                 | `hash`         | Original request hash             |
|                 | `deadline`     | Signature deadline                |

**Request Parameters**

| Parameter     | Type   | Required | Description                                                                                                                                           | Example  |
| ------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `cardTokenId` | string | **Yes**  | The Card's Token (Note: This is the card's specific ID, not the user's URID). The value is 'cardToken', can be retrieved from the `/cards` interface. | `"1001"` |
| `status`      | int    | **Yes**  | Target status code. `0`: Inactive (Blocked/Frozen) `1`: Active (Unblocked)                                                                            | `0`      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "status": 200,
    "data": "Status updated"
  },
  "timeNow": 1700000000
}
```

**4. Request Example**

```bash
curl -X POST 'https://urapi3-qa.ur-inc.xyz/api/v2/card-status' \
-H 'Content-Type: application/json' \
-H 'tokenid: 12345' \
-H 'network: 5000' \
-H 'sign: 0xabcdef...' \
-H 'hash: UpdateStatus' \
-H 'deadline: 1735689600' \
-d '{
  "cardTokenId": "1001",
  "status": 0
}'

```

**5. Error Codes**

| Code    | Type         | Description                                            | Solution                                                        |
| ------- | ------------ | ------------------------------------------------------ | --------------------------------------------------------------- |
| `10000` | DefError     | Default error containing specific business error info. | Card not found, invalid status code, or upstream service error. |
| `10001` | Auth Failure | Signature Verification Failed                          | Check signature and headers.                                    |

#### 3.1.8 Permit (Token Approval)

**1. Description** Submits an EIP-2612 compatible permit signature. This allows the UR contracts to spend tokens from the user's wallet (When spending with the card, the UR contract will deduct the fiat balance from the user's UR account.) without the user needing to execute an on-chain `approve` transaction.

**2. Request**

| Item            | Value            | Note                                |
| --------------- | ---------------- | ----------------------------------- |
| **HTTP Method** | `POST`           |                                     |
| **URI**         | `/api/v2/permit` |                                     |
| **Auth Level**  | **Full Auth**    | Requires API-level wallet signature |
| **Headers**     | `Content-Type`   | Fixed: `application/json`           |
|                 | `tokenId`        | User's URID                         |
|                 | `network`        | Network Identifier (e.g., `5000`)   |
|                 | `sign`           | Signature and Verify Signature      |
|                 | `hash`           | Original request hash               |
|                 | `deadline`       | API Signature deadline              |

**Request Parameters**

| Parameter      | Type   | Required | Description                                                          | Example      |
| -------------- | ------ | -------- | -------------------------------------------------------------------- | ------------ |
| `tokenAddress` | string | **Yes**  | The contract address of the token being approved (e.g., USDC, USDT). | `"0x..."`    |
| `owner`        | string | **Yes**  | The user's wallet address.                                           | `"0x..."`    |
| `spender`      | string | **Yes**  | The contract address authorized to spend the tokens (UR contract).   | `"0x..."`    |
| `value`        | string | **Yes**  | The amount to approve (in wei/smallest unit).                        | `"1000000"`  |
| `deadline`     | int    | **Yes**  | The timestamp (seconds) until which the permit is valid.             | `1735689600` |
| `v`            | int    | **Yes**  | ECDSA signature component `v`.                                       | `28`         |
| `r`            | string | **Yes**  | ECDSA signature component `r` (32 bytes).                            | `"0x..."`    |
| `s`            | string | **Yes**  | ECDSA signature component `s` (32 bytes).                            | `"0x..."`    |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "status": 200,
    "txHash": "0xabc123..."
  },
  "timeNow": 1700000000
}
```

**Result Field Description**

| Field    | Type   | Description                                                                                                |
| -------- | ------ | ---------------------------------------------------------------------------------------------------------- |
| `txHash` | string | The transaction hash of the permit execution (if the server relays it immediately) or status confirmation. |

**4. Request Example**

```bash
curl -X POST 'https://urapi3-qa.ur-inc.xyz/api/v2/permit' \
-H 'Content-Type: application/json' \
-H 'tokenid: 12345' \
-H 'network: 5000' \
-H 'sign: 0xApiSign...' \
-H 'hash: PermitReq' \
-H 'deadline: 1735689600' \
-d '{
  "tokenAddress": "0xTokenAddr...",
  "owner": "0xUserAddr...",
  "spender": "0xSpenderAddr...",
  "value": "1000000000000000000",
  "deadline": 1740000000,
  "v": 28,
  "r": "0x...",
  "s": "0x..."
}'

```

You can find the token addresses [at here](/developer-resources/smart-contracts.md#contract-addresses).

**5. Error Codes**

| Code    | Type          | Description                       | Solution                                                          |
| ------- | ------------- | --------------------------------- | ----------------------------------------------------------------- |
| `10000` | System Error  | Execution failed                  | Check if the deadline has expired or if the signature is invalid. |
| `10001` | Auth Failure  | API Signature Verification Failed | Check headers and API-level authentication.                       |
| `10009` | Invalid Param | Malformed signature parameters    | Ensure `v`, `r`, `s` are correctly formatted.                     |

#### 3.1.9 Get Supported Chain Config

**1. Description** Queries the chain configuration information supported by the UR-Bank application, including the list of tokens on each chain, user balances, card issuance eligibility status, and Token activity configurations.

**Note:** This interface supports queries in both **logged-in** and **non-logged-in** states.

* **Non-logged-in:** Only queries supported chains and contract addresses.
* **Logged-in:** Additionally returns the user's asset information.

**Call Scenarios:**

* Getting supported chains and token lists when the App starts.
* Refreshing balances when the user enters the asset page.
* Querying token info on the target chain before top-up.
* Querying supported chains and contract addresses before withdrawal .

**2. Request**

| Item            | Value                          | Note                       |
| --------------- | ------------------------------ | -------------------------- |
| **HTTP Method** | `GET`                          |                            |
| **URI**         | `/api/v3/config/chain-configs` |                            |
| **Auth Level**  | **Full Auth**                  |                            |
| **Headers**     | `Content-Type`                 | Fixed: `application/json`  |
|                 | `tokenId`                      | User's URID                |
|                 | `network`                      | Network Identifier: `5000` |
|                 | `sign`                         | Wallet Signature           |
|                 | `hash`                         | Original request hash      |
|                 | `deadline`                     | Signature deadline         |

**3. Response**

**Success Response (HTTP 200):**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "chains": [
      {
        "chainIdentifier": "eip155:5000",
        "chainName": "Mantle",
        "chainLogoUrl": "https://example.com/mantle.png",
        "tokens": [
          {
            "tokenIdentifier": "0x09Bc1633B9f7B10517C089d825C8C3D9AA153103",
            "symbol": "USDC",
            "name": "USD Coin",
            "logoUrl": "https://example.com/usdc.png",
            "decimals": 6,
            "displayDecimals": 2,
            "isFiat": false,
            "isNative": false,
            "canDeposit": true,
            "canWithdraw": true,
            "aggregatorSupported": true,
            "priority": 1,
            "minFxAmount": "10",
            "maxFxAmount": "1000000",
            "minTopUpAmount": "10",
            "maxTopUpAmount": "5000000"
          },
          {
            "tokenIdentifier": "0x0000000000000000000000000000000000000000",
            "symbol": "MNT",
            "name": "Mantle",
            "isNative": true,
            "canDeposit": false,
            "canWithdraw": false
          }
        ],
        "nativeToken": {
          "symbol": "MNT",
          "isNative": true
        },
        "depositContract": "0x8C922114d626305E8ebC38559f17A84b31f31f17",
        "eip7702DelegationContract": "0x7A5e0CaE6F66d6D0E2b4060347C07B9d3D3Fb9e6",
        "bufferPoolContract": "0x1234567890abcdef1234567890abcdef12345678"
      },
      {
        "chainIdentifier": "eip155:1",
        "chainName": "Ethereum",
        "tokens": [
          {
            "tokenIdentifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
            "symbol": "USDC",
            "decimals": 6,
            "balance": "500.00",
            "balanceUsd": "500.00",
            "canDeposit": true,
            "canWithdraw": true
          }
        ],
        "depositContract": "0x..."
      }
    ],
    "canActivateCard": true,
    "cardActivationMessage": "You are eligible to apply for a card!",
    "tokenActivities": [
      {
        "symbol": "USDe",
        "title": "Earn 15.4% APY",
        "targetUrl": "https://ur-bank.com/earn/usde",
        "targetType": "webview"
      }
    ]
  },
  "timeNow": 1716259400000
}
```

**Result Field Breakdown:**

**Main Response Structure:**

| Field Name              | Type    | Description                               |
| ----------------------- | ------- | ----------------------------------------- |
| `chains`                | array   | List of supported chain configurations    |
| `canActivateCard`       | boolean | Whether the card can be activated         |
| `cardActivationMessage` | string  | Message prompt for card activation status |
| `tokenActivities`       | array   | List of Token activity configurations     |

`**chains` Array Element Structure (ChainInfo):

| Field Name                  | Type   | Description                                               |
| --------------------------- | ------ | --------------------------------------------------------- |
| `chainIdentifier`           | string | Unique chain identifier (CAIP-2 format, e.g., `eip155:1`) |
| `chainName`                 | string | Human-readable name of the chain                          |
| `chainLogoUrl`              | string | URL of the chain Logo                                     |
| `tokens`                    | array  | List of tokens supported on this chain                    |
| `nativeToken`               | object | Native token information                                  |
| `depositContract`           | string | Deposit contract address                                  |
| `eip7702DelegationContract` | string | EIP-7702 Delegation contract address                      |
| `bufferPoolContract`        | string | Buffer Pool contract address (for Onramp)                 |

`**tokens` Array Element Structure (TokenInfo):

| Field Name            | Type    | Description                                                 |
| --------------------- | ------- | ----------------------------------------------------------- |
| `tokenIdentifier`     | string  | Unique token identifier (contract address or special value) |
| `symbol`              | string  | Token symbol (USDC, ETH, etc.)                              |
| `name`                | string  | Full name of the token                                      |
| `logoUrl`             | string  | URL of the token Logo                                       |
| `decimals`            | int     | Token decimals                                              |
| `displayDecimals`     | int     | Frontend display decimals                                   |
| `isFiat`              | boolean | Whether it is a fiat token                                  |
| `totalBalance`        | string  | User balance (Decimal String)                               |
| `totalBalanceUsd`     | string  | USD value of the balance                                    |
| `isNative`            | boolean | Whether it is a native token                                |
| `canDeposit`          | boolean | Whether deposit is supported                                |
| `canWithdraw`         | boolean | Whether withdrawal is supported                             |
| `aggregatorSupported` | boolean | Whether DEX aggregator is supported                         |
| `priority`            | int     | Sorting priority                                            |
| `minFxAmount`         | string  | Minimum exchange amount                                     |
| `maxFxAmount`         | string  | Maximum exchange amount                                     |
| `minTopUpAmount`      | string  | Minimum top-up amount                                       |
| `maxTopUpAmount`      | string  | Maximum top-up amount                                       |

`**tokenActivities` Array Element Structure (TokenActivity):

| Field Name   | Type   | Description                               |
| ------------ | ------ | ----------------------------------------- |
| `symbol`     | string | Associated token symbol                   |
| `title`      | string | Activity title (may contain dynamic APY)  |
| `targetUrl`  | string | Link to jump to on click                  |
| `targetType` | string | Jump type (`webview`/`native`/`external`) |

**Response Field Details:**

* **chains**: List of supported chains.
* Sorted by priority, usually the Mantle chain comes first.
* Each chain contains configuration for tokens, contract addresses, etc.
* `balance` field is not included for non-logged-in users .
* **canActivateCard**: Card issuance eligibility.
* Valid only in logged-in state.
* Logic: Checks if any bank fiat balance on the Mantle chain is ≥ 1 USD.
* `true`: User meets the conditions for card issuance.
* `false`: Does not meet conditions .
* **cardActivationMessage**: Card issuance prompt text.
* Used in conjunction with `canActivateCard`.
* Informs the user of the card issuance status or guides the operation .
* **tokenIdentifier**: Token identifier.
* ERC20 Token: Contract address.
* Native Token: Usually `0x0000000000000000000000000000000000000000` or `"NATIVE"` .
* **CAIP-2 Format**:
* Chain identifiers use the CAIP-2 standard.
* Format: `<blockchainId>:<chainId>`
* Example: `eip155:1` (Ethereum), `eip155:5000` (Mantle) .
* **Decimal String**:
* Amount fields use string format.
* Avoids JavaScript large integer precision issues.
* Example: `"1000.00"`, `"0.000001"` .

**4. Request Example**

**Basic Query - Non-logged-in State:**

```bash
curl -X GET "https://urapi3-qa.ur-inc.xyz/api/v3/config/chain-configs"

```

**Logged-in State - Get Balance and Card Status:**

```bash
curl -X GET "https://urapi3-qa.ur-inc.xyz/api/v3/config/chain-configs" \
-H "tokenId: 12345" \
-H "User-Agent: URBank/3.3.0 (105)"

```

**Full Request Example:**

```bash
curl -X GET "https://urapi3-qa.ur-inc.xyz/api/v3/config/chain-configs" \
-H "User-Agent: URBank/3.3.0 (Android)" \
-H "tokenId: 12345" \
-H "internal-address: 0x742d35Cc6634C0532925a3b844Bc9e7595f4bFA5"

```

**5. Error Codes**

| Error Code | Error Type    | Description                                          | Solution                                        |
| ---------- | ------------- | ---------------------------------------------------- | ----------------------------------------------- |
| `10000`    | System Error  | Database query failed, Nacos config retrieval failed | Retry later or contact technical support        |
| `10001`    | Auth Failure  | Invalid `tokenId` format                             | Check if `tokenId` format is correct            |
| `10009`    | Invalid Param | `address` parameter format error                     | Ensure `address` is a valid hexadecimal address |

**Error Response Examples:**

```json
{
  "retCode": 10000,
  "retMsg": "DefError: database connection failed",
  "result": null,
  "timeNow": 1716259400000
}
```

```json
{
  "retCode": 10009,
  "retMsg": "ParamInvalid: invalid address format",
  "result": null,
  "timeNow": 1716259400000
}
```

**Common Error Troubleshooting:**

**i. Nacos Config Retrieval Failure (10000):**

* Token activities may not show.
* The main flow (chain configuration) is unaffected.
* Check Nacos service status .

**ii. Balance Query Timeout (10000):**

* May be encountered by logged-in users.
* Likely due to slow chain node response.
* Balances for some chains may be missing .

**iii. Address Format Error (10009):**

* Ensure the address starts with `0x`.
* Ensure it is a valid hexadecimal string.
* Address length should be 42 characters (`0x` + 40 hex) .

**Usage Instructions:**

* **Cache Strategy:**
* Recommended cache time: 5-10 minutes.
* Balance data needs frequent refreshing .
* **Version Compatibility:**
* Older versions of the App will automatically filter `USDe`.
* Ensure App version ≥ 3.3.0 to access full features .
* **Chain List Sorting:**
* Sorted by the `priority` field.
* Mantle chain usually ranks first .
* **Token Filtering:**
* App version < 3.3.0 will filter `USDe`.
* Filtering logic can be controlled via `User-Agent` .

## 4. Offramp

This section introduces APIs and webhooks related to Offramp operations. Subsections are organized by chain family (e.g. EVM vs non-EVM); **§4.1** covers EVM-compatible networks, and **§4.2** covers Solana.

### 4.1 Offramp API for EVM

**Note:** The APIs and contract-oriented flows in **§4.1** apply to **EVM-compatible chains** only—for example Ethereum mainnet (L1), Arbitrum One, Base, Mantle, and other supported networks that use CAIP-2 `eip155:*` chain IDs. **Solana** offramp is documented in **§4.2**.

#### 4.1.1 Get Quote

**1. Description** Retrieves the best quote for a cross-chain cryptocurrency deposit. The system aggregates multiple DEXs (such as 1inch, Odos, etc.) to find the optimal exchange route, calculates network fees, cross-chain fees, and the final output amount, providing the user with complete deposit quote information.

**Business Scenarios:**

* **Cross-chain Deposit Process:** Users holding tokens on other chains (e.g., Ethereum, Arbitrum) want to deposit into UR-Bank.
* **USDC Direct Deposit:** Users holding USDC can deposit directly without exchange.
* **Non-USDC Token Deposit:** Users holding other tokens (e.g., ETH, USDT) need to exchange them for USDC before depositing .
* **Fee Estimation:** Users need to understand the complete fee structure and the final amount to be received before executing a deposit.
* **Quote Caching:** To avoid repeated queries, the system caches quotes for 60 seconds to improve response speed.

**DEX Aggregation Mechanism:**

* **Multi-Source Quotes:** Queries multiple DEX aggregators (1inch, Odos, etc.) simultaneously.
* **Optimal Selection:** Selects the best route based on expected output amount, price impact, Gas fees, etc.
* **Redis Caching:** Quotes with identical parameters are cached for 60 seconds.
* **Slippage Protection:** Default slippage is 0.5% (50bps), customizable .

**2. Request**

| Item            | Value                           | Note                      |
| --------------- | ------------------------------- | ------------------------- |
| **HTTP Method** | `POST`                          |                           |
| **URI**         | `/api/v1/partner/quote/deposit` |                           |
| **Auth Level**  | **Basic Auth**                  | No status restriction     |
| **Headers**     | `Content-Type`                  | Fixed: `application/json` |
|                 | `tokenId`                       | User's URID               |

**Request Parameters (DepositQuoteReq)**

| Parameter     | Type   | Required | Description                                               | Example                                              |
| ------------- | ------ | -------- | --------------------------------------------------------- | ---------------------------------------------------- |
| `chainId`     | string | **Yes**  | Source Chain ID (CAIP-2 format).                          | `"eip155:1"` (Ethereum), `"eip155:42161"` (Arbitrum) |
| `userAddress` | string | **Yes**  | User wallet address (used to build the route).            | `"0x1234...5678"`                                    |
| `fromToken`   | string | **Yes**  | Source token address (Use `0x00...00` for native tokens). | `"0xA0b8...B48"` (USDC)                              |
| `toToken`     | string | **Yes**  | Target fiat token identifier (Fiat type after deposit).   | `"USD"`, `"EUR"`, `"CNY"`                            |
| `amount`      | string | **Yes**  | Deposit amount (in smallest unit, e.g., Wei).             | `"1000000000000000000"` (1 ETH)                      |

**Parameter Notes:**

* **chainId:** Follows CAIP-2 standard (`eip155:<chainId>`). Common values: `eip155:1` (Ethereum), `eip155:5000` (Mantle), `eip155:42161` (Arbitrum One), `eip155:10` (Optimism).
* **fromToken:** Use the zero address `0x0000000000000000000000000000000000000000` for native tokens (e.g., ETH, MATIC).
* **amount:** The input token amount, must be in the token's smallest unit. (e.g., 1 ETH = 10^18 Wei; 1 USDC = 10^6 units).
* **slippageBps:** `slippageBps` is the maximum allowable deviation of the final output value from the estimated output during trade execution. This value determines the minOutputAmount. For example, 50 equals a 0.5% tolerance.
* **toToken:** You can get all the fiat token addresses from here: [token contract addresses](/developer-resources/smart-contracts.md#contract-addresses-1)

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "quoteId": "ur_1772002152589294701",
    "chainId": "eip155:84532",
    "best": {
      "aggregator": "ur",
      "to": "0x0000000000000000000000000000000000000000",
      "swapCalldata": "0x",
      "minUsdcAmount": "4950000",
      "expectedUsdcAmount": "5000000",
      "slippageBps": 50,
      "deadline": 1772002211,
      "priceImpact": "0"
    },
    "inputAmount": "5000000",
    "outputAmount": "5",
    "exchangeRate": "1",
    "crossChainFee": "111598233453575",
    "networkFee": "3109867200000"
  },
  "timeNow": 1772002152589
}
```

**Response Parameters (DepositQuoteResp)**

| Parameter       | Type      | Description                                                                                                                 | Example                            |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `quoteId`       | string    | Unique quote identifier (Format: `<source>_<timestamp>`).                                                                   | `"1inch_1705..."`, `"ur_1705..."`  |
| `chainId`       | string    | Source Chain ID.                                                                                                            | `"eip155:1"`                       |
| `best`          | BestQuote | Details of the best quote (see table below).                                                                                |                                    |
| `networkFee`    | string    | Estimated network fee (denominated in Native Token).                                                                        | `"1250000000000000"` (0.00125 ETH) |
| `crossChainFee` | string    | Cross-chain fee (denominated in Native Token).                                                                              | `"500000000000000"` (0.0005 ETH)   |
| `outputAmount`  | string    | The estimated net amount of target fiat currency to be received, after deducting all applicable fees from the 'best quote'. | `95.5` for 95.5 USD                |
| `inputAmount`   | string    | Deposit amount (in smallest unit, e.g., Wei).                                                                               | `"5000000"`                        |
| `exchangeRate`  | string    | Exchange rate (1 USDC = ? Target Fiat).                                                                                     | `"1.0"`                            |

**BestQuote Structure**

| Parameter            | Type   | Description                                                                                                                                                                                                                                       | Example                              |
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `aggregator`         | string | DEX aggregator name.                                                                                                                                                                                                                              | `"1inch"`, `"odos"`, `"ur"` (Direct) |
| `to`                 | string | Exchange contract address (Zero address for direct USDC deposit). If a user interacts with [offramp contract](/developer-resources/smart-contracts.md#methods-4) directly, this value is required as the contract input parameter `\_aggregator`. | `"0x1111..."`                        |
| `swapCalldata`       | string | Call data for the exchange (`"0x"` for direct USDC deposit). If a user interacts with [offramp contract](/developer-resources/smart-contracts.md#methods-4) directly, this value is required as the contract input parameter `\_swapCalldata`.    | `"0x12aa..."`                        |
| `minUsdcAmount`      | string | Minimum USDC output amount (after slippage).                                                                                                                                                                                                      | `"990000000"` (990 USDC)             |
| `expectedUsdcAmount` | string | Expected USDC output amount.                                                                                                                                                                                                                      | `"1000000000"` (1000 USDC)           |
| `slippageBps`        | int32  | Slippage basis points.                                                                                                                                                                                                                            | `50` (0.5%)                          |
| `deadline`           | int64  | Quote expiration timestamp (Unix seconds).                                                                                                                                                                                                        | `1705123516`                         |
| `priceImpact`        | string | Price impact percentage.                                                                                                                                                                                                                          | `"0.05"` (0.05%)                     |

**4. Request Examples**

**Basic Example (ETH Deposit):**

```bash
curl -X POST 'https://urapi3-qa.ur-inc.xyz/api/v1/partner/quote/deposit' \
-H 'Content-Type: application/json' \
-H 'tokenId: 12345' \
-d '{
  "chainId": "eip155:1",
  "userAddress": "0x1234567890123456789012345678901234567890",
  "fromToken": "0x0000000000000000000000000000000000000000",
  "toToken": "USD",
  "amount": "1000000000000000000"
}'

```

**Arbitrum USDC Direct Deposit:**

```bash
curl -X POST 'https://api.urbank.io/api/v1/partner/quote/deposit' \
-H 'Content-Type: application/json' \
-H 'tokenId: 12345' \
-d '{
  "chainId": "eip155:42161",
  "userAddress": "0xYourWalletAddress",
  "fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "toToken": "USD",
  "amount": "1000000000"
}'

```

**5. Error Codes**

| Code    | Description          | Reason                                                    |
| ------- | -------------------- | --------------------------------------------------------- |
| `10001` | Auth Failure         | Token invalid or expired.                                 |
| `10002` | Parse Error          | Malformed request body.                                   |
| `10006` | Status Not Supported | Account not active (Non-Live status).                     |
| `10009` | Invalid Param        | Missing `chainId`/`fromToken`/`amount`, or `amount` <= 0. |
| `10051` | Invalid Chain ID     | Unsupported `chainId`.                                    |
| `10052` | No Available Quotes  | All DEX queries failed.                                   |
| `10053` | No Best Route        | Could not determine best route from DEX quotes.           |
| `10054` | Calculation Failed   | Failed to calculate network or cross-chain fees.          |

**6. Common Error Troubleshooting**

| Error Message                      | Reason                                   | Solution                                                                  |
| ---------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- |
| `"invalid params"`                 | `chainId`/`fromToken`/`amount` is empty. | Check if all required parameters are complete.                            |
| `"amounts must be greater than 0"` | `amount` <= 0 or format error.           | Ensure `amount` is a valid positive number string.                        |
| `"invalid chain"`                  | `chainId` not in supported list.         | Use a valid CAIP-2 format Chain ID.                                       |
| `"no available quotes"`            | All DEX aggregator queries failed.       | Check network connection or retry later; liquidity might be insufficient. |
| `"no best route found"`            | Cannot select best route from quotes.    | Retry with adjusted `amount`.                                             |
| `"calc fee amount: ..."`           | Fee calculation failed.                  | Check if Gas Price interface is available.                                |

#### 4.1.2 Initiate Offramp

In **External Wallet Access Mode**, users are expected to interact directly with the smart contract via the Partner Frontend by default.

Contract Paremeters:

| Parameter           | Type    | Required | Description                                               | Example                                              |
| ------------------- | ------- | -------- | --------------------------------------------------------- | ---------------------------------------------------- |
| `_inputToken`       | address | **Yes**  | Source token address (Use `0x00...00` for native tokens). | `"eip155:1"` (Ethereum), `"eip155:42161"` (Arbitrum) |
| `_outputToken`      | address | **Yes**  | Target fiat token identifier (Fiat type after deposit).   | `"0x1234...5678"`                                    |
| `_amount`           | uint256 | **Yes**  | Deposit amount (in smallest unit, e.g., Wei).             | For USDC: "10000000" as 10.000000                    |
| `_aggregator`       | address | **Yes**  | Exchange contract address get from the quote API          |                                                      |
| `_swapCalldata`     | bytes   | **Yes**  | Get from the quote API response                           | "0x" for USDC direct deposit.                        |
| `_minUsdcAmount`    | uint256 | Yes      | Get from the quote API response.                          |                                                      |
| `_feeAmountViaUsdc` | unit256 | Yes      | Put "0" when user call the contract directly.             |                                                      |

If you still require the ability for users to complete Offramp operations via an API, please contact the UR team.

### 4.2 Offramp API for Solana

**Note:** **§4.2** applies to **Solana** only. Today, Solana deposit (offramp) supports **USDC on Solana** swapped into **fiat** balance on the UR side. Support for additional tokens via **Jupiter** (and similar aggregators) is planned; this page will be updated when that ships.

#### 4.2.1 Get Quote

**1. Description**

Returns execution parameters and a **fiat-credit quote** for a Solana **USDC** deposit. The endpoint **does not submit** any on-chain transaction: it returns addresses, fee estimates, optional **server-built** unsigned Solana transactions, and rule flags the partner must respect when building or signing.

**2. Request**

| Item            | Value                      | Note                                                                                                                                                           |
| --------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **HTTP Method** | `POST`                     |                                                                                                                                                                |
| **URI**         | `/v1/solana/deposit/quote` | Relative to Partner OpenAPI base URL                                                                                                                           |
| **Auth**        | Partner request signing    | [Part A: Partner Authentication (UR-OPEN-API & Webhooks)](/developer-resources/signature-and-verify.md#part-a-partner-authentication-ur-open-api-and-webhooks) |
| **Headers**     | `Content-Type`             | Fixed: `application/json`                                                                                                                                      |

**Request body parameters**

| Parameter          | Type   | Required | Description                                                                                                                                                                            |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `urId`             | int64  | **Yes**  | User URID.                                                                                                                                                                             |
| `solanaAddress`    | string | **Yes**  | User’s Solana address (Base58); must match the key that will sign the deposit transaction.                                                                                             |
| `usdcAmount`       | string | **Yes**  | Planned USDC input amount in **smallest units** (USDC uses **6** decimals), e.g. `"10000000"` for 10 USDC.                                                                             |
| `outputCurrency`   | string | **Yes**  | Target fiat currency code (e.g. `"USD"`).                                                                                                                                              |
| `minUsdcAmount`    | string | No       | Client-side slippage floor in smallest units; must not exceed `usdcAmount`. If omitted or `"0"`, the on-chain slippage check is skipped. See **minUsdcAmount notes** below.            |
| `network`          | string | No       | Solana cluster: `"mainnet"` (or `"mainnet-beta"`) for production, `"devnet"` for testing. **Defaults to `"devnet"` if omitted — production callers must pass `"mainnet"` explicitly.** |
| `computeUnitPrice` | uint64 | No       | Solana **priority fee** in **micro-lamports per compute unit**. If omitted, the server default is **1000**. Raise under congestion if needed.                                          |

**Parameter notes**

* **usdcAmount:** Minimum deposit is **5 USDC** (`"5000000"`). Requests below this threshold are rejected with code `20003`.
* **minUsdcAmount:** The on-chain contract checks `net_amount >= minUsdcAmount`, where `net_amount = usdcAmount - fee_amount`. Because `fee_amount` is deducted before the check, passing a `minUsdcAmount` equal to `usdcAmount` will cause the transaction to revert with `SlippageExceeded` when fees are non-zero. **Recommended:** omit this field (or pass `"0"`) for USDC-only deposits; only set it when integrating a swap path with meaningful slippage.
* **network:** The server resolves program addresses, RPC endpoints, and LayerZero configuration based on this value. Using the wrong value (or omitting it in production) will return `50002 — solana chain config not found`.

**Example request body (mainnet)**

```json
{
  "urId": 7537715481,
  "solanaAddress": "9E6fwJhowA1QHwtUXWNF9LnzwE5nXv5AxteaUFErTaeT",
  "usdcAmount": "10000000",
  "outputCurrency": "USD",
  "network": "mainnet"
}
```

**Example request body (devnet)**

```json
{
  "urId": 7537715481,
  "solanaAddress": "9B1GtWbjXHnTmWeb945SKpmWsx3S5fBCdEdXaEXriAwn",
  "usdcAmount": "10000000",
  "outputCurrency": "USD",
  "network": "devnet",
  "computeUnitPrice": 1000
}
```

**3. Response**

Successful responses use `code: 0` and return details under `data`. Field names are stable; some **fiat / fee** fields may be omitted depending on environment.

**Example response (mainnet)**

```json
{
  "code": 0,
  "message": "",
  "data": {
    "network": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
    "programId": "DgiyAgiEJDt4hXw56mpKDd7mcYRYUaxrKg3ConyeupYX",
    "oapp": "Gnd23LHfEWX4BU8haprQpwJUG3aMjnRF2v8H96YMKDD",
    "endpointProgramId": "76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6",
    "dstEid": 30181,
    "usdcMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "depositUsdcAccount": "7bmkazNHa595zYyVRJ99PjXZuifu1U8FF98vxeTxGbeQ",
    "feeReceiver": "BbggVSW9ZVmeNG3Fsw4jnNiE9N3XRahjg62etMcxWW5u",
    "evmOutputToken": "0x4e32ce01bd170aa80e30885041af1b56a04ad141",
    "nativeFeeLamports": "5000000",
    "nativeFeeLamportsWithBuffer": "6000000",
    "rules": {
      "onlyUsdc": true,
      "swapDataMustBeEmpty": true,
      "jupiterRemainingAccountsLenMustBeZero": true,
      "userMustHaveUsdcAta": true,
      "feePayerMustBeUser": true
    },
    "transaction": "AQAAAA...base64...",
    "lastValidBlockHeight": 412280000,
    "outputAmount": "9.9",
    "outputAmountBeforeFee": "10",
    "exchangeRate": "1",
    "processingFee": "0.1",
    "networkFee": "0",
    "totalFee": "0.1"
  }
}
```

**Example response (devnet)**

```json
{
  "code": 0,
  "message": "",
  "data": {
    "network": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
    "programId": "FgWV99azAfJLSnSQprTKVyq7zCyLJEK4QiJ3ri3GPrAo",
    "oapp": "4j6at8yw9co4dzZhEH8qnQhwzVXgotW2CMbw8FcHkcJR",
    "endpointProgramId": "76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6",
    "dstEid": 40246,
    "usdcMint": "4ArjZDc2B1jLXtqpKCkPfXrkkfFoqsi19bxcDoTJmpiL",
    "depositUsdcAccount": "76TAaJR4P5KQtow2WpVsFh3KShXVR2Ft6trWDVevX3Uo",
    "feeReceiver": "38Ygm7MRkDxBP4wGCgeSrR3atv3SyhachRRwAzMyF24P",
    "evmOutputToken": "0xdf79470986629ae4893BfCE0c6C0F4d085E99741",
    "nativeFeeLamports": "834617",
    "nativeFeeLamportsWithBuffer": "1001541",
    "rules": {
      "onlyUsdc": true,
      "swapDataMustBeEmpty": true,
      "jupiterRemainingAccountsLenMustBeZero": true,
      "userMustHaveUsdcAta": true,
      "feePayerMustBeUser": true
    },
    "transaction": "AQAAAA...base64...",
    "lastValidBlockHeight": 385942100,
    "outputAmount": "10",
    "outputAmountBeforeFee": "10",
    "exchangeRate": "1",
    "processingFee": "0.05",
    "networkFee": "0",
    "totalFee": "0"
  }
}
```

**Response fields (`data`)**

| Field                         | Type   | Description                                                                                                                                                                                                      |
| ----------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `network`                     | string | Solana genesis hash (first 32 bytes, Base58). Mainnet: `5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`; devnet: `EtWTRABZaYq6iMfeYKouRu166VU2xqa1`. Use this value to confirm the response matches the cluster you requested. |
| `programId`                   | string | Solana OApp program id (Base58).                                                                                                                                                                                 |
| `oapp`                        | string | Store PDA for the OApp (Base58).                                                                                                                                                                                 |
| `endpointProgramId`           | string | LayerZero Endpoint program id (Base58).                                                                                                                                                                          |
| `dstEid`                      | uint32 | LayerZero destination endpoint id (Mantle side).                                                                                                                                                                 |
| `usdcMint`                    | string | Solana USDC mint (Base58).                                                                                                                                                                                       |
| `depositUsdcAccount`          | string | USDC token account that receives the deposit (Base58).                                                                                                                                                           |
| `feeReceiver`                 | string | Protocol fee receiver (Base58).                                                                                                                                                                                  |
| `evmOutputToken`              | string | Mantle-side output token contract address (`0x…`).                                                                                                                                                               |
| `nativeFeeLamports`           | string | Estimated LayerZero messaging fee in **lamports** (1 SOL = 10^9 lamports).                                                                                                                                       |
| `nativeFeeLamportsWithBuffer` | string | Recommended lamport budget with **\~20% buffer**; prefer passing this as the transaction’s native fee / value where applicable.                                                                                  |
| `rules`                       | object | Enforced constraints on the Solana side (see **Rules object** below).                                                                                                                                            |
| `transaction`                 | string | Optional Base64 **`VersionedTransaction` (v0)**, **unsigned**. If empty or missing, build the transaction manually from the other fields.                                                                        |
| `lastValidBlockHeight`        | uint64 | Upper bound block height for the blockhash inside `transaction`. If the chain passes this height before confirmation, **request a new quote**.                                                                   |
| `outputAmount`                | string | Estimated net fiat amount (may be omitted).                                                                                                                                                                      |
| `outputAmountBeforeFee`       | string | Fiat amount before fees (may be omitted).                                                                                                                                                                        |
| `exchangeRate`                | string | USDC → target fiat rate (may be omitted).                                                                                                                                                                        |
| `processingFee`               | string | Processing fee component (may be omitted).                                                                                                                                                                       |
| `networkFee`                  | string | EVM-side network fee; often `"0"` in Solana quote context (may be omitted).                                                                                                                                      |
| `totalFee`                    | string | Total fees (may be omitted).                                                                                                                                                                                     |

**Rules object (`data.rules`)**

| Field                                   | Type | Typical meaning                                                     |
| --------------------------------------- | ---- | ------------------------------------------------------------------- |
| `onlyUsdc`                              | bool | Deposit path must use USDC only.                                    |
| `swapDataMustBeEmpty`                   | bool | No inline swap payload unless officially supported.                 |
| `jupiterRemainingAccountsLenMustBeZero` | bool | Jupiter remaining accounts must be empty (future aggregator hooks). |
| `userMustHaveUsdcAta`                   | bool | User must already have a USDC ATA; create it first if missing.      |
| `feePayerMustBeUser`                    | bool | Fee payer on the Solana transaction must be the user.               |

**Response signature (optional)**

The server may return `x-api-signature` and `x-api-publicKey` headers so the client can verify the response body against UR’s signer (same pattern as other Partner OpenAPI responses).

**4. Error codes**

| Code    | Description                                                                                                      |
| ------- | ---------------------------------------------------------------------------------------------------------------- |
| `10001` | Partner authentication failed (invalid signature, wrong key, or expired deadline).                               |
| `20003` | Invalid parameters: bad `urId`, malformed address, unsupported `outputCurrency`, or user not under this partner. |
| `50002` | Internal configuration error (e.g. missing Solana program config).                                               |
| `50003` | On-chain RPC failure or quote engine failure.                                                                    |

**5. Common troubleshooting**

| Symptom                                        | Likely cause                                                                                                             | What to do                                                                                                                                                                     |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `50002 — solana chain config not found`        | `network` field is missing or wrong; omitting it defaults to `"devnet"` which does not exist in production config.       | Pass `"network": "mainnet"` explicitly for production calls.                                                                                                                   |
| `20003 — amount must be at least 5 USDC`       | `usdcAmount` is below the minimum 5 USDC (`5000000`).                                                                    | Increase the amount to at least `"5000000"`.                                                                                                                                   |
| `20003 — invalid minUsdcAmount`                | `minUsdcAmount` is non-numeric or exceeds `usdcAmount`.                                                                  | Omit the field or pass a value ≤ `usdcAmount`.                                                                                                                                 |
| `SlippageExceeded` (on-chain `0x1773`)         | `minUsdcAmount` set to `usdcAmount` while on-chain fee is > 0; contract checks `net_amount (post-fee) >= minUsdcAmount`. | Omit `minUsdcAmount` or set it to `"0"` for USDC-only deposits.                                                                                                                |
| `20003` after fixing params                    | User record not provisioned for this partner / chain                                                                     | Ensure the user exists for this partner (provisioning flows are documented elsewhere).                                                                                         |
| Transaction never lands                        | Blockhash expired                                                                                                        | Compare current block height to `lastValidBlockHeight`; call quote again.                                                                                                      |
| Simulation failure (`ProgramFailedToComplete`) | Compute unit limit too low or missing USDC ATA.                                                                          | The server pre-built transaction already includes a `SetComputeUnitLimit` instruction; if building manually, set at least **400,000 CU**. Also ensure the user has a USDC ATA. |
| Simulation failure (other)                     | Wrong signer or account mismatch                                                                                         | Obey `rules`; ensure `solanaAddress` matches the signing key.                                                                                                                  |

#### 4.2.2 Execute Offramp Transaction

After a successful quote, the partner (or user wallet) must **sign and submit** the Solana transaction. UR recommends using the **pre-built** `data.transaction` when it is present.

**Recommended path: pre-built transaction**

When `data.transaction` is non-empty:

1. Base64-decode to bytes and deserialize with `@solana/web3.js` `VersionedTransaction.deserialize`.
2. **Before signing**, run structural validation against the expected deposit layout (see **§4.2.3**), including `validateDepositViaAggregatorTx(tx, quote.programId)` when using the reference sketch.
3. **Recommended before signing:** refresh **`recentBlockhash`** via `connection.getLatestBlockhash` and rebuild the `VersionedTransaction` (same compiled instructions). Quote responses may sit for seconds before the user signs; a fresh blockhash improves landing rate and pairs with a current `lastValidBlockHeight` for confirmation.
4. Sign with the **user’s** Solana key (must match `solanaAddress` in the quote request).
5. `simulateTransaction` with `sigVerify: true` before broadcast.
6. `sendTransaction` to the correct RPC for the quoted `network`.
7. Poll confirmation using `blockhash` / `lastValidBlockHeight` that match the signed transaction (from step 3 if you refreshed; otherwise from the quote). If the blockhash expires before confirmation, obtain a **new quote** and retry.

If `data.transaction` is empty, assemble the deposit instruction manually using `programId`, accounts, `rules`, and fees—contact UR for bytecode-level docs if you are not using the pre-built path.

**Example (Node.js)**

Dependencies: `@solana/web3.js` (with `TransactionMessage.decompile` / `compileToV0Message`, as in recent releases) and a Partner OpenAPI client that implements request signing as in **§4.2.1**.

```javascript
import {
  Connection,
  VersionedTransaction,
  TransactionMessage,
} from '@solana/web3.js'
import { validateDepositViaAggregatorTx } from './solana-deposit-tx-validator.mjs' // see §4.2.3 below

// Use the correct RPC for your target network:
// Mainnet: 'https://api.mainnet-beta.solana.com' (or your preferred RPC provider)
// Devnet:  'https://api.devnet.solana.com'
const SOLANA_RPC_URL = 'https://api.mainnet-beta.solana.com'

async function depositWithPrebuiltTx({
  callOpenApi,
  solanaKeypair,
  quoteRequestBody,
}) {
  const connection = new Connection(SOLANA_RPC_URL, 'confirmed')

  const quoteResp = await callOpenApi('/v1/solana/deposit/quote', quoteRequestBody)
  const quote = quoteResp.body.data

  if (!quote?.transaction)
    throw new Error('Server did not return a pre-built transaction; build manually or retry later.')

  const tx = VersionedTransaction.deserialize(
    Buffer.from(quote.transaction, 'base64')
  )

  validateDepositViaAggregatorTx(tx, quote.programId)

  const { blockhash } = await connection.getLatestBlockhash('recent')

  tx.message.recentBlockhash = blockhash;

  tx.sign([solanaKeypair]);

  const sim = await connection.simulateTransaction(tx, { sigVerify: true })
  if (sim.value.err)
    throw new Error(`simulate failed: ${JSON.stringify(sim.value.err)}`)

  const signature = await connection.sendTransaction(tx, {
    maxRetries: 5,
    skipPreflight: false,
  })

  await connection.confirmTransaction(
    { signature, blockhash, lastValidBlockHeight },
    'confirmed'
  )

  return signature
}
```

Pass `quoteRequestBody` with the same shape as **§4.2.1** (including `urId`, `solanaAddress`, `usdcAmount`, `outputCurrency`, and optional `network` / `computeUnitPrice`). Use a **partner-signed** `callOpenApi` implementation consistent with your environment. Implement `validateDepositViaAggregatorTx` as in **§4.2.3** (or import an equivalent module).

#### 4.2.3 Security and Validation (Strongly Recommended)

Malicious browser extensions, compromised front-end bundles, or man-in-the-middle proxies could **swap** the Base64 transaction returned by `/v1/solana/deposit/quote` for a different `VersionedTransaction` before the user signs. To reduce that risk, **always validate** the instruction layout and program IDs **after** Base64 decode and **before** any signature.

**What a legitimate UR pre-built deposit transaction looks like**

For the current USDC-only path, UR expects a **`VersionedTransaction` (v0)** whose **compiled instructions** are **exactly three**:

| Index | Expected program                                                       | Role                                                            |
| ----- | ---------------------------------------------------------------------- | --------------------------------------------------------------- |
| `0`   | `ComputeBudget` (`ComputeBudgetProgram.programId`)                     | Typically `SetComputeUnitLimit`                                 |
| `1`   | `ComputeBudget`                                                        | Typically `SetComputeUnitPrice` (priority fee)                  |
| `2`   | UR deposit program (`data.programId` from the **same** quote response) | `deposit_via_aggregator` (Anchor discriminator + Borsh payload) |

Reject transactions with fewer than three instructions, more than three instructions, or any program id on the third instruction that does **not** match the `programId` returned alongside `transaction` in that quote.

**Address lookup tables (ALT)**

The reference `programIdForIx` helper below resolves each instruction’s program id from **`message.staticAccountKeys` only**. That matches **typical UR pre-built** `data.transaction` payloads from this endpoint, where each of the three instructions’ `programIdIndex` values point into `staticAccountKeys` (no extra lookup-table resolution needed).

If you deserialize a transaction where `programIdIndex >= staticAccountKeys.length`, the program id is supplied through one or more **address lookup tables**. In that case you must resolve the full account key list (for example via `VersionedTransaction` + loaded lookup tables, or APIs that hydrate `MessageV0` account keys) and compute the program id for each instruction from that resolved list—then compare instruction 2 to `quote.programId`. Until you extend the sketch that way, the validator may **throw** or **reject** a transaction that is valid on-chain.

**Program id by environment (reference)**

Always treat the **`programId` field in the quote response** as the source of truth. Published examples:

| Network             | `programId` (Base58)                           | USDC Mint                                      |
| ------------------- | ---------------------------------------------- | ---------------------------------------------- |
| Solana mainnet-beta | `DgiyAgiEJDt4hXw56mpKDd7mcYRYUaxrKg3ConyeupYX` | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| Solana devnet       | `FgWV99azAfJLSnSQprTKVyq7zCyLJEK4QiJ3ri3GPrAo` | `4ArjZDc2B1jLXtqpKCkPfXrkkfFoqsi19bxcDoTJmpiL` |

**Instruction data (third instruction)**

The third instruction’s `data` should decode as **`deposit_via_aggregator`**: 8-byte Anchor discriminator (first 8 bytes of `sha256("global:deposit_via_aggregator")`), followed by Borsh fields such as `inputAmount`, `minUsdcAmount`, `evmRecipient`, `evmOutputToken`, `nativeFee`, `swapData` length and payload, and `jupiterRemainingAccountsLen`. For the current USDC-only ruleset, `data.rules` from the quote may require empty swap/Jupiter tails—align validation with those flags when you decode.

**Reference validation sketch (Node.js)**

The following pattern matches production checks when program ids are indexed via **`staticAccountKeys`**: assert `VersionedTransaction`, resolve each instruction’s program id from `message.staticAccountKeys`, require instructions 0–1 to be Compute Budget and instruction 2 to equal `expectedProgramIdFromQuote`, then decode the third instruction’s data and ensure the discriminator matches `deposit_via_aggregator`. See **Address lookup tables (ALT)** above if you need full lookup resolution.

```javascript
import {
  VersionedTransaction,
  ComputeBudgetProgram,
  PublicKey,
} from '@solana/web3.js'

const DEPOSIT_VIA_AGGREGATOR_DISC = Buffer.from([
  0xc7, 0xb7, 0x9c, 0x2b, 0x4a, 0x6d, 0x26, 0x01,
]) // first 8 bytes of sha256("global:deposit_via_aggregator")

function programIdForIx(message, programIdIndex) {
  const keys = message.staticAccountKeys
  if (programIdIndex >= keys.length)
    throw new Error('programIdIndex requires ALT resolution; extend validator if you use lookups')
  return keys[programIdIndex].toBase58()
}

/**
 * @param {VersionedTransaction} tx
 * @param {string} expectedProgramIdFromQuote - data.programId from the same /v1/solana/deposit/quote response as data.transaction
 */
export function validateDepositViaAggregatorTx(tx, expectedProgramIdFromQuote) {
  if (!(tx instanceof VersionedTransaction))
    throw new Error('expected VersionedTransaction')

  const { message } = tx
  const ixs = message.compiledInstructions
  if (ixs.length !== 3)
    throw new Error(`expected exactly 3 compiled instructions, got ${ixs.length}`)

  const id0 = programIdForIx(message, ixs[0].programIdIndex)
  const id1 = programIdForIx(message, ixs[1].programIdIndex)
  const id2 = programIdForIx(message, ixs[2].programIdIndex)

  if (!ComputeBudgetProgram.programId.equals(new PublicKey(id0)))
    throw new Error(`instruction 0 must be ComputeBudget, got ${id0}`)
  if (!ComputeBudgetProgram.programId.equals(new PublicKey(id1)))
    throw new Error(`instruction 1 must be ComputeBudget, got ${id1}`)
  if (!new PublicKey(expectedProgramIdFromQuote).equals(new PublicKey(id2)))
    throw new Error(`instruction 2 programId mismatch: expected ${expectedProgramIdFromQuote}, got ${id2}`)

  const data = ixs[2].data
  if (data.length < 8 || !Buffer.from(data.slice(0, 8)).equals(DEPOSIT_VIA_AGGREGATOR_DISC))
    throw new Error('instruction 2 is not deposit_via_aggregator')

  return true
}
```

Call `validateDepositViaAggregatorTx(tx, quote.programId)` immediately after `VersionedTransaction.deserialize(...)`, then proceed to `tx.sign([...])`. You can extend the decoder to compare `inputAmount` / `minUsdcAmount` / `nativeFee` against the user’s intended quote parameters for defense in depth.

## 5. Onramp

This section documents Fiat-to-Crypto Onramp in **External Wallet Access Mode**.

### 5.1 Onramp API

#### 5.1.1 Get Onramp Limit

**1. Description** Returns user-specific onramp eligibility signals and amount caps.\
This endpoint does **not** return a single `isSupport` boolean. Integrators must evaluate:

* `regionLocked`
* `usdcDepegged`
* `livenessLocked`
* `maxAmounts`

**2. Request**

| Item            | Value                  | Note                      |
| --------------- | ---------------------- | ------------------------- |
| **HTTP Method** | `GET`                  |                           |
| **URI**         | `/api/v1/onramp-limit` |                           |
| **Auth Level**  | **Full Auth**          | Live account required     |
| **Headers**     | `Content-Type`         | Fixed: `application/json` |
|                 | `tokenId`              | User‘s URID               |
|                 | `network`              | Network Identifier        |
|                 | `sign/hash/deadline`   | Full Auth signature       |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "livenessLocked": false,
    "livenessLockMins": 0,
    "maxAmounts": {
      "USD": "50000",
      "EUR": "46500"
    },
    "usdcDepegged": false,
    "regionLocked": false
  },
  "timeNow": 1703123456789
}
```

**4. Result Fields**

| Field Name         | Type               | Description                                                     |
| ------------------ | ------------------ | --------------------------------------------------------------- |
| `livenessLocked`   | bool               | Whether liveness is currently locked for this user.             |
| `livenessLockMins` | int64              | Remaining lock duration in minutes (`0` if unlocked).           |
| `maxAmounts`       | map\[string]string | Max allowed onramp amount by fiat currency (no token decimals). |
| `usdcDepegged`     | bool               | Whether USDC depeg protection is active.                        |
| `regionLocked`     | bool               | Whether onramp is region-restricted for this user.              |

**5. Request Example**

```bash
curl -X GET 'https://api.ur.app/api/v1/onramp-limit' \
-H 'Content-Type: application/json' \
-H 'tokenId: 12345' \
-H 'network: 5000' \
-H 'hash: 0x...' \
-H 'sign: 0x...' \
-H 'deadline: 1703123456789'
```

**6. Error Codes**

| Code    | Description   | Reason                                    |
| ------- | ------------- | ----------------------------------------- |
| `10001` | Auth Failure  | Invalid authentication or signature.      |
| `10002` | Parse Error   | Invalid request format.                   |
| `10000` | Service Error | Internal failure when calculating limits. |

#### 5.1.2 Get Onramp Quote

**1. Description** Returns quote for both:

* `scene=onramp`: Fiat token -> destination token
* `scene=swap_retry`: retry swap using destination-chain USDC

The response includes `needLiveness`.\
Quote results are cached server-side and later validated by submit/retry endpoints.

**2. Request**

| Item            | Value                  | Note                      |
| --------------- | ---------------------- | ------------------------- |
| **HTTP Method** | `POST`                 |                           |
| **URI**         | `/api/v1/quote/onramp` |                           |
| **Auth Level**  | **Full Auth**          | Live account required     |
| **Headers**     | `Content-Type`         | Fixed: `application/json` |
|                 | `tokenId`              | User's URID               |
|                 | `network`              | Network Identifier        |
|                 | `sign/hash/deadline`   | Full Auth signature       |

**Request Parameters**

* You can get all the fiat token addresses from here: [token contract addresses](/developer-resources/smart-contracts.md#contract-addresses-1)
* The `amount` in the request body is the smallest unit, that means `10000` USD here equals 100 USD

```json
{
  "scene": "onramp",
  "srcChainId": "eip155:5000",
  "dstChainId": "eip155:8453",
  "fromToken": "FiatTokenAddress",
  "toToken": "0xDestinationTokenAddress",
  "amount": "100000000",
  "slippageBps": 50
}
```

**3. Response**

* The `*AmountOut` values in the response body is in the smallest unit. For example: `12345000` USDC equals 12.345 USDC.

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "quoteId": "onramp_direct_1703123000000_12345",
    "srcChainId": "eip155:5000",
    "dstChainId": "eip155:8453",
    "best": {
      "aggregator": "1inch",
      "to": "0xAggregatorAddress",
      "swapCalldata": "0x....",
      "expectedAmountOut": "1234500",
      "minAmountOut": "1228327",
      "slippageBps": 50,
      "deadline": 1703123600,
      "priceImpact": "0.12"
    },
    "allQuotes": [
      {
        "source": "1inch",
        "expectedAmountOut": "1234500",
        "priceImpact": "0.12"
      }
    ],
    "outputAmount": "1.2345",
    "exchangeRate": "0.0123",
    "displayRate": "0.0123",
    "displayUnit": 1,
    "needLiveness": true,
    "networkFee": "0.43",
    "processingFee": "0.50",
    "totalFee": "0.43",
    "warningMessage": ""
  },
  "timeNow": 1703123456789
}
```

* Key Fields

| Field Name          | Type   | Description                                                                                                                                                       |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quoteId`           | string | Quote identifier used by submit/retry endpoints.                                                                                                                  |
| `best.to`           | string | The value of `dstAggregator` in the Onramp request API                                                                                                            |
| `best.swapCalldata` | string | The value of `dstSwapCalldata` in the Onramp request API                                                                                                          |
| `outputAmount`      | string | The estmated human readable output amount                                                                                                                         |
| `exchangeRate`      | string | Includes the swap price impact and spreads                                                                                                                        |
| `displayRate`       | string | When the `displayUnit`==1, same as `exchangeRate`, but if the input currency is JPY, the `displayUnit` will be 100, and displayRate will be 100JPY -> 1 XXX token |
| `processingFee`     | string | Charged for normal users, pro users can ignore                                                                                                                    |
| `networkFee`        | string | Includes the estimated gas fee and crosschain fee                                                                                                                 |
| `needLiveness`      | bool   | If `true`, user must pass liveness before onramp submit.                                                                                                          |

**4. Request Example**

```bash
curl -X POST 'https://api.ur.app/api/v1/quote/onramp' \
-H 'Content-Type: application/json' \
-H 'tokenId: 12345' \
-H 'network: 5000' \
-H 'hash: 0x...' \
-H 'sign: 0x...' \
-H 'deadline: 1703123456789' \
-d '{
  "scene": "onramp",
  "srcChainId": "eip155:5000",
  "dstChainId": "eip155:8453",
  "fromToken": "0xFiatTokenAddress",
  "toToken": "0xDestinationTokenAddress",
  "amount": "100000000",
  "slippageBps": 50
}'
```

**5. Error Codes**

| Code    | Description     | Reason                                                          |
| ------- | --------------- | --------------------------------------------------------------- |
| `10001` | Auth Failure    | Invalid authentication or signature.                            |
| `10002` | Parse Error     | Malformed request body.                                         |
| `10009` | Invalid Param   | Unsupported scene/token/chain, empty fields, or invalid amount. |
| `10040` | Quote Try Again | Quote unavailable/expired; request a new quote.                 |
| `10000` | Service Error   | Internal quote computation failure.                             |

#### 5.1.3 Get Liveness Token (Onramp Compliance)

**1. Description** Creates a Sumsub liveness access token for onramp flow. Call this only when quote returns `needLiveness=true`.

**2. Request**

| Item            | Value                        | Note                      |
| --------------- | ---------------------------- | ------------------------- |
| **HTTP Method** | `GET`                        |                           |
| **URI**         | `/api/v2/get-liveness-token` |                           |
| **Auth Level**  | **Full Auth**                | Live account required     |
| **Headers**     | `Content-Type`               | Fixed: `application/json` |
|                 | `tokenId`                    | User's URID               |
|                 | `network`                    | Network Identifier        |
|                 | `sign/hash/deadline`         | Full Auth signature       |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "vendor": "sumsub",
    "access_token": "sumsub_access_token_xxx",
    "user_id": "sumsub_user_id_xxx"
  },
  "timeNow": 1703123456789
}
```

**4. Request Example**

```bash
curl -X GET 'https://api.ur.app/api/v2/get-liveness-token' \
-H 'Content-Type: application/json' \
-H 'tokenId: 12345' \
-H 'network: 5000' \
-H 'hash: 0x...' \
-H 'sign: 0x...' \
-H 'deadline: 1703123456789'
```

**5. Error Codes**

| Code    | Description   | Reason                                               |
| ------- | ------------- | ---------------------------------------------------- |
| `10001` | Auth Failure  | Invalid authentication or signature.                 |
| `10000` | Service Error | Liveness locked or upstream token generation failed. |

#### 5.1.4 Check Liveness Result

**1. Description** Polls latest liveness status for current user in onramp scenario.

**2. Request**

| Item            | Value                           | Note                      |
| --------------- | ------------------------------- | ------------------------- |
| **HTTP Method** | `GET`                           |                           |
| **URI**         | `/api/v2/check-liveness-result` |                           |
| **Auth Level**  | **Full Auth**                   | Live account required     |
| **Headers**     | `Content-Type`                  | Fixed: `application/json` |
|                 | `tokenId`                       | User's URID               |
|                 | `network`                       | Network Identifier        |
|                 | `sign/hash/deadline`            | Full Auth signature       |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "liveness_result": "pass",
    "checked_at": 1703123000,
    "expired_at": 1703727800,
    "liveness_fail_reason": "",
    "liveness_locked": false,
    "liveness_unlock_at": 0
  },
  "timeNow": 1703123456789
}
```

**4. Result Fields**

| Field Name             | Type   | Description                                                    |
| ---------------------- | ------ | -------------------------------------------------------------- |
| `liveness_result`      | string | Liveness status (`pass`, `pending`, `rejected`, etc.).         |
| `checked_at`           | int64  | Unix seconds when latest liveness check was processed.         |
| `expired_at`           | int64  | Unix seconds when current pass status expires (if applicable). |
| `liveness_fail_reason` | string | Failure reason if rejected.                                    |
| `liveness_locked`      | bool   | Lock status due to repeated failures.                          |
| `liveness_unlock_at`   | int64  | Unix seconds when lock ends (if locked).                       |

**5. Error Codes**

| Code    | Description              | Reason                               |
| ------- | ------------------------ | ------------------------------------ |
| `10001` | Auth Failure             | Invalid authentication or signature. |
| `10041` | Liveness Check Not Found | No liveness record yet.              |
| `10000` | Service Error            | Internal liveness query failure.     |

#### 5.1.5 Initiate Onramp With Permit

**1. Description** Submits onramp transaction with permit signature.\
Requires valid cached quote and (if required) passed liveness.

**2. Request**

| Item            | Value                        | Note                      |
| --------------- | ---------------------------- | ------------------------- |
| **HTTP Method** | `POST`                       |                           |
| **URI**         | `/api/v1/onramp-with-permit` |                           |
| **Auth Level**  | **Full Auth**                | Live account required     |
| **Headers**     | `Content-Type`               | Fixed: `application/json` |
|                 | `tokenId`                    | User's URID               |
|                 | `network`                    | Network Identifier        |
|                 | `sign/hash/deadline`         | Full Auth signature       |

**Request Parameters**

```json
{
  "quoteId": "onramp_direct_1703123000000_12345",
  "chainId": "eip155:5000",
  "tokenIn": "0xFiatTokenAddress",
  "amountIn": "100000000",
  "dstChainId": "eip155:8453",
  "dstAggregator": "0xAggregatorAddress",
  "dstTokenOut": "0xDestinationTokenAddress",
  "dstSwapCalldata": "0x....",
  "dstMinAmountOut": "1228327",
  "permitDeadline": 1703123900,
  "permitV": 28,
  "permitR": "0x....",
  "permitS": "0x...."
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "txHash": "0xabc123..."
  },
  "timeNow": 1703123456789
}
```

**4. Important Notes**

* Backend validates request fields against quote cache (`quoteId` binding).
* If quote expired or mismatched, submit is rejected.
* If eligibility says liveness is still required, submit is rejected.

**5. Error Codes**

| Code    | Description     | Reason                                                         |
| ------- | --------------- | -------------------------------------------------------------- |
| `10001` | Auth Failure    | Invalid authentication or signature.                           |
| `10002` | Parse Error     | Malformed request body.                                        |
| `10009` | Invalid Param   | Invalid amount/address/permit fields, or quote mismatch.       |
| `10040` | Quote Try Again | Quote expired; request a new quote.                            |
| `10000` | Service Error   | Eligibility check/permit validation/onchain submission failed. |

#### 5.1.6 Check Pending Retry

**1. Description** Returns one pending onramp retry candidate (if any).\
This endpoint is used for post-onramp retry handling when destination swap previously failed.

**2. Request**

| Item            | Value                          | Note                      |
| --------------- | ------------------------------ | ------------------------- |
| **HTTP Method** | `GET`                          |                           |
| **URI**         | `/api/v1/onramp/pending-retry` |                           |
| **Auth Level**  | **Full Auth**                  | Live account required     |
| **Headers**     | `Content-Type`                 | Fixed: `application/json` |
|                 | `tokenId`                      | User's URID               |
|                 | `network`                      | Network Identifier        |
|                 | `sign/hash/deadline`           | Full Auth signature       |

**3. Response**

If no retry is available:

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": null,
  "timeNow": 1703123456789
}
```

If retry is available:

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "originalTxHash": "0xoriginal...",
    "originalChainId": "eip155:5000",
    "originalToken": "0xFiatTokenAddress",
    "chainId": "eip155:8453",
    "fromToken": "0xUSDCAddressOnDstChain",
    "toToken": "0xDestinationTokenAddress",
    "amount": "99800000",
    "failedAt": 1703123000
  },
  "timeNow": 1703123456789
}
```

**4. Error Codes**

| Code    | Description   | Reason                               |
| ------- | ------------- | ------------------------------------ |
| `10001` | Auth Failure  | Invalid authentication or signature. |
| `10000` | Service Error | Internal retry query failure.        |

#### 5.1.7 Retry Swap With Permit

**1. Description** Executes destination-chain swap retry for a failed onramp record.

**2. Request**

| Item            | Value                             | Note                      |
| --------------- | --------------------------------- | ------------------------- |
| **HTTP Method** | `POST`                            |                           |
| **URI**         | `/api/v1/onramp-swap-with-permit` |                           |
| **Auth Level**  | **Full Auth**                     | Live account required     |
| **Headers**     | `Content-Type`                    | Fixed: `application/json` |
|                 | `tokenId`                         | User's URID               |
|                 | `network`                         | Network Identifier        |
|                 | `sign/hash/deadline`              | Full Auth signature       |

**Request Parameters**

```json
{
  "quoteId": "1inch_1703123000000_12345",
  "chainId": "eip155:8453",
  "originalTxHash": "0xoriginal...",
  "usdcAmount": "99800000",
  "tokenOut": "0xDestinationTokenAddress",
  "minAmountOut": "1228327",
  "aggregator": "0xAggregatorAddress",
  "swapCalldata": "0x....",
  "permitDeadline": 1703123900,
  "permitV": 28,
  "permitR": "0x....",
  "permitS": "0x...."
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "txHash": "0xretrytx..."
  },
  "timeNow": 1703123456789
}
```

**4. Important Notes**

* Retry call requires a valid retry quote from `scene=swap_retry`.
* Request fields must match quote cache and original retry record.
* Do not call this endpoint directly without pending-retry + quote preparation.

**5. Error Codes**

| Code    | Description     | Reason                                                                   |
| ------- | --------------- | ------------------------------------------------------------------------ |
| `10001` | Auth Failure    | Invalid authentication or signature.                                     |
| `10002` | Parse Error     | Malformed request body.                                                  |
| `10009` | Invalid Param   | Invalid amount/address/permit fields, quote mismatch, or state mismatch. |
| `10040` | Quote Try Again | Retry quote expired; request a new retry quote.                          |
| `10000` | Service Error   | Internal retry validation or onchain submit failure.                     |

#### 5.1.8 Cancel Retry

**1. Description** Cancels a pending retry (`can_retry`) for an onramp transaction.

**2. Request**

| Item            | Value                         | Note                      |
| --------------- | ----------------------------- | ------------------------- |
| **HTTP Method** | `POST`                        |                           |
| **URI**         | `/api/v1/onramp/retry/cancel` |                           |
| **Auth Level**  | **Full Auth**                 | Live account required     |
| **Headers**     | `Content-Type`                | Fixed: `application/json` |
|                 | `tokenId`                     | User's URID               |
|                 | `network`                     | Network Identifier        |
|                 | `sign/hash/deadline`          | Full Auth signature       |

**Request Parameters**

```json
{
  "originalTxHash": "0xoriginal..."
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": null,
  "timeNow": 1703123456789
}
```

**4. Important Notes**

* This is **not** the default path when no pending record exists.
* Use only when a retry-eligible transaction exists and user explicitly abandons retry.

**5. Error Codes**

| Code    | Description   | Reason                                             |
| ------- | ------------- | -------------------------------------------------- |
| `10001` | Auth Failure  | Invalid authentication or signature.               |
| `10002` | Parse Error   | Malformed request body.                            |
| `10009` | Invalid Param | Missing or invalid `originalTxHash`.               |
| `10000` | Service Error | Internal query/update failure during cancellation. |

### 5.2 End-to-End Flow

#### 5.2.1 Recommended Main Flow

1. Call `GET /api/v1/onramp-limit`.
2. Call `POST /api/v1/quote/onramp` with `scene=onramp`.
3. If quote returns `needLiveness=true`:

* Call `GET /api/v2/get-liveness-token`.
* Run Sumsub SDK in client.

  Example for Sumsub SDK calling:

  ```typescript
    const accessToken = await getAccessToken();
    const snsWebSdkInstance = snsWebSdk
      .init(accessToken, getAccessToken)
      .withConf({
        lang: "en",
        theme: "light",
      })
      .withOptions({ addViewportTag: false, adaptIframeHeight: true })
      .on("idCheck.onApplicantActionCompleted", (payload: any) => {
        if (payload?.answer === "GREEN") {
          // After successful verification, you can perform post-success actions here
        }
      })
      .build();
    snsWebSdkInstance.launch("#sumsub-websdk-container");
  ```
* After the user completes the above Sumsub process, step 2 (`POST /api/v1/quote/onramp`) can be called again. When `needLiveness = false`, the user may proceed with the transaction.

4. When liveness result is `pass` (or not required), call `POST /api/v1/onramp-with-permit`.

#### 5.2.2 Recommended Retry Flow

1. Periodically call `GET /api/v1/onramp/pending-retry`.
2. If response contains pending object:

* Call `POST /api/v1/quote/onramp` with `scene=swap_retry`.
* Call `POST /api/v1/onramp-swap-with-permit`.

3. If user decides not to retry, call `POST /api/v1/onramp/retry/cancel`.

### 5.3 Error Codes & Troubleshooting

#### Common Codes in Onramp

| Code    | Description           | Typical Meaning                                      |
| ------- | --------------------- | ---------------------------------------------------- |
| `10000` | DefError              | Generic backend/internal failure.                    |
| `10001` | AuthenticationFailure | Invalid token/signature/header auth data.            |
| `10002` | ParseRequestFailed    | Invalid request shape or JSON parse failure.         |
| `10009` | ParamInvalid          | Invalid parameter values or business-state mismatch. |
| `10040` | QuoteTryAgain         | Quote expired/unavailable; request a fresh quote.    |
| `10041` | LivenessCheckNotFound | No liveness record available yet.                    |

#### Troubleshooting Matrix

| Error Message / Signal                       | Reason                                                     | Solution                                                                          |
| -------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `needLiveness=true` in quote                 | User amount/eligibility requires liveness verification     | Complete liveness flow before submit.                                             |
| `Face verification required`                 | Submit attempted before passing required liveness          | Poll liveness result; submit only after `liveness_result=pass`.                   |
| `Quote expired. Please request a new quote.` | Quote cache missing/expired/mismatch                       | Re-request quote and submit with matching parameters.                             |
| Empty result from `pending-retry`            | No retry-eligible record currently (`can_retry` not found) | Continue periodic polling; do not call retry endpoint directly.                   |
| Retry submit rejected                        | Missing retry quote or quote/original record mismatch      | Re-run retry sequence: pending-retry -> quote(scene=swap\_retry) -> retry submit. |

## 6. Bank Transfer

This section covers payout transfer flows for existing external-wallet users. For recipient/contact listing, use the existing [Get User Profile](#id-3.1.1-get-user-profile) endpoint and its `contacts` field.

### 6.1 Create/Select Recipient

#### 6.1.1 Get Supported Banks

**1. Description** Returns supported banks and country metadata (including whether IBAN metadata is available).

**2. Request**

| Item            | Value           | Note |
| --------------- | --------------- | ---- |
| **HTTP Method** | `GET`           |      |
| **URI**         | `/api/v1/banks` |      |
| **Auth Level**  | **No Auth**     |      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": [
    {
      "key": "AT",
      "name": "Austria",
      "countryCode": {
        "iso2": "AT",
        "iso3": "AUT"
      },
      "ibanMetadata": {
        "placeholder": "AT00 0000 0000 0000 0000",
        "mask": "{AT}00 **** **** **** ****",
        "bankCode": {
          "length": 5,
          "startDigit": 4
        }
      }
    },
    {
      "key": "HK",
      "name": "Hong Kong",
      "countryCode": {
        "iso2": "HK",
        "iso3": "HKG"
      },
      "banks": [
        {
          "name": "Bank of China HK (中银香港)",
          "bankCode": "012",
          "bic": "BKCHHKHHXXX",
          "country": "HK",
          "accountMask": "\\012-000000000[000]",
          "accountPlaceholder": "012-000000000000",
          "accountNotice": ""
        },
        {
          "name": "Hang Seng Bank (恒生银行)",
          "bankCode": "024",
          "bic": "HASEHKHHXXX",
          "country": "HK",
          "accountMask": "\\024-000000000[000]",
          "accountPlaceholder": "024-000000000000",
          "accountNotice": ""
        }
      ]
    }
  ],
  "timeNow": 1771501763963
}
```

Sample is truncated for documentation; production response contains additional countries/banks in the same format.

#### 6.1.2 Get Bank by IBAN

**1. Description** Resolves bank details from an IBAN.

**2. Request**

| Item            | Value                         | Note |
| --------------- | ----------------------------- | ---- |
| **HTTP Method** | `GET`                         |      |
| **URI**         | `/api/v1/banks/iban/{ibanNo}` |      |
| **Auth Level**  | **No Auth**                   |      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "name": "Zürcher Kantonalbank",
    "bankCode": "00700",
    "bankCodes": ["00700", "00730", "00754", "00755", "30700"],
    "bic": "ZKBKCHZZXXX",
    "country": "CH",
    "accountMask": "CH00\\0\\07\\0\\0************",
    "accountPlaceholder": "CH0000700000000000000",
    "accountNotice": ""
  },
  "timeNow": 1771501763963
}
```

#### 6.1.3 Get Supported Recipient Countries and Cities

**1. Description** Returns supported recipient country/city combinations for transfer setup.

**2. Request**

| Item            | Value                    | Note |
| --------------- | ------------------------ | ---- |
| **HTTP Method** | `GET`                    |      |
| **URI**         | `/api/v1/country-cities` |      |
| **Auth Level**  | **No Auth**              |      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": [
    {
      "name": "Austria",
      "countryCode": {
        "iso2": "AT",
        "iso3": "AUT"
      },
      "cities": ["Vienna", "Graz", "Linz"],
      "zipCodeRegEx": "^\\d{4}$"
    },
    {
      "name": "Hong Kong",
      "countryCode": {
        "iso2": "HK",
        "iso3": "HKG"
      },
      "cities": ["Hong Kong"],
      "zipCodeRegEx": "^\\d{3}$"
    }
  ],
  "timeNow": 1771502369957
}
```

Sample is truncated for documentation; production response contains more countries/cities in the same format.

#### 6.1.4 Get Payment Purpose List

**1. Description** Retrieves supported payment purpose options used for compliance validation before transfer.

**2. Request**

| Item            | Value                      | Note |
| --------------- | -------------------------- | ---- |
| **HTTP Method** | `GET`                      |      |
| **URI**         | `/api/v1/payment-purposes` |      |
| **Auth Level**  | **No Auth**                |      |

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "purposes": [
      {
        "value": 0,
        "name": "Transfer to own account (other bank)"
      },
      {
        "value": 1,
        "name": "Purchase of goods"
      },
      {
        "value": 2,
        "name": "Payment for services (leisure, medical, travel, education, insurance, telecom, etc.)"
      },
      {
        "value": 3,
        "name": "Family support and inheritance"
      },
      {
        "value": 4,
        "name": "Charity donation"
      },
      {
        "value": 5,
        "name": "Salary, Benefits, Dividends"
      },
      {
        "value": 6,
        "name": "Real Estate and rent"
      },
      {
        "value": 7,
        "name": "Credit / debit card coverage"
      },
      {
        "value": 8,
        "name": "Investment, securities, trading"
      },
      {
        "value": 9,
        "name": "Currency exchange"
      },
      {
        "value": 10,
        "name": "Tax and governmental payments"
      },
      {
        "value": 11,
        "name": "Loan, Collateral"
      }
    ]
  },
  "timeNow": 1703123456789
}
```

#### 6.1.5 Verify Reference

**1. Description** Verifies user-entered payment reference text and returns `purposeId` and `ref`.

**2. Request**

| Item            | Value                      | Note                      |
| --------------- | -------------------------- | ------------------------- |
| **HTTP Method** | `POST`                     |                           |
| **URI**         | `/api/v1/verify-reference` |                           |
| **Auth Level**  | **Full Auth**              | Requires wallet signature |
| **Headers**     | `Content-Type`             | Fixed: `application/json` |
|                 | `tokenId`                  | User's URID               |
|                 | `network`                  | Network Identifier        |
|                 | `sign`                     | Wallet Signature          |
|                 | `hash`                     | Original request hash     |
|                 | `deadline`                 | Signature deadline        |

**Request Parameters**

```json
{
  "reference": "Invoice 2026-001"
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "clientPayoutRefParams": {
      "purposeId": 8,
      "refId": "REF-7A6C2A8E"
    }
  },
  "timeNow": 1703123456789
}
```

#### 6.1.6 Verify Contact (Bank Payment Request)

**1. Description** Validates final recipient and bank payload (`bankPaymentRequest`) and returns `contactId`, `purposeId`, and `refId`.

**2. Request**

| Item            | Value                    | Note                      |
| --------------- | ------------------------ | ------------------------- |
| **HTTP Method** | `POST`                   |                           |
| **URI**         | `/api/v1/verify-contact` |                           |
| **Auth Level**  | **Full Auth**            | Requires wallet signature |
| **Headers**     | `Content-Type`           | Fixed: `application/json` |
|                 | `tokenId`                | User's URID               |
|                 | `network`                | Network Identifier        |
|                 | `sign`                   | Wallet Signature          |
|                 | `hash`                   | Original request hash     |
|                 | `deadline`               | Signature deadline        |

**Request Parameters**

```json
{
  "account": "CH93 0076 2011 6238 5295 7",
  "bankName": "Hypothekarbank Lenzburg AG",
  "bic": "HYPCH22",
  "purpose": 1,
  "reference": "Invoice 2026-001",
  "creditor": {
    "name": "Alice Doe",
    "street": "Bahnhofstrasse 1",
    "city": "Zurich",
    "zip": "8001",
    "country": "CH"
  }
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "account": "CH93 0076 2011 6238 5295 7",
    "bankName": "Hypothekarbank Lenzburg AG",
    "bic": "HYPCH22",
    "purpose": 1,
    "creditor": {
      "name": "Alice Doe",
      "street": "Bahnhofstrasse 1",
      "city": "Zurich",
      "zip": "8001",
      "country": "CH"
    },
    "clientPayoutRefParams": {
      "contactId": "SP",
      "purposeId": 1,
      "refId": "REF-7A6C2A8E"
    }
  },
  "timeNow": 1703123456789
}
```

### 6.2 Permit & Transfer

#### 6.2.1 Get Fees

**1. Description** Returns transfer fee for a payout request.

**2. Request**

| Item            | Value                | Note                      |
| --------------- | -------------------- | ------------------------- |
| **HTTP Method** | `POST`               |                           |
| **URI**         | `/api/v1/banks/fees` |                           |
| **Auth Level**  | **Full Auth**        | Requires wallet signature |

**Request Parameters**

```json
{
  "amount": "1000",
  "tokenAddress": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007"
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "currency": "EUR",
    "fee": "0"
  },
  "timeNow": 1771832199786
}
```

#### 6.2.2 Create Payout Permit Request

**1. Description** Creates payout transfer request with user wallet signature (permit-style authorization) and submits on-chain payout.

**2. Request**

| Item            | Value                        | Note                      |
| --------------- | ---------------------------- | ------------------------- |
| **HTTP Method** | `POST`                       |                           |
| **URI**         | `/api/v1/payout-with-permit` |                           |
| **Auth Level**  | **Full Auth**                | Requires wallet signature |

**Request Parameters**

```json
{
  "amount": "25000",
  "permitAmount": "25000",
  "permitDeadline": 1703123456,
  "permitV": 27,
  "permitR": "0x...",
  "permitS": "0x...",
  "contactId": "EA-00017418",
  "tokenAddress": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007",
  "purposeId": "P001",
  "ref": "REF-7A6C2A8E",
  "metadata": {
    "bankAccountHolder": "Alice Doe",
    "bankName": "Hypothekarbank Lenzburg AG",
    "bankAccount": "CH9300762011623852957",
    "bankReference": "Invoice 2026-001"
  }
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "txHash": "0xabc123def456..."
  },
  "timeNow": 1703123456789
}
```

**4. Notes**

* Recipient name/address/reference fields should use Latin characters.
* Minimum input for USD payouts is typically 50 USD.
* Gas/network fee is deducted from the user's fiat transfer amount.

**6. Representative Error Conditions**

| Type                   | Description                                  |
| ---------------------- | -------------------------------------------- |
| Signature Error        | User signature verification failed.          |
| Monthly Limit Exceeded | Transfer exceeds monthly quota.              |
| Insufficient Balance   | Available fiat balance is insufficient.      |
| Minimum Amount Error   | Transfer amount below minimum allowed input. |

## 7. FX

This section describes the recommended FX execution flow in External Wallet Access Mode:

1. Call `POST /api/v1/quote/fx` to get the quoted `outputAmount` and `exchangeRate`.
2. Use the quote result to build `amountOutMinimum`.
3. Call `POST /api/v1/fx-exchange-with-permit` to execute the on-chain exchange.

### 7.1 FX API

#### 7.1.1 Get Output Amount and Exchange Rate

**1. Description**\
Returns quoted FX output amount (minimal unit) and exchange rate for a token pair.

**2. Request**

| Item            | Value              | Note                                                |
| --------------- | ------------------ | --------------------------------------------------- |
| **HTTP Method** | `POST`             |                                                     |
| **URI**         | `/api/v1/quote/fx` |                                                     |
| **Auth Level**  | **Basic Auth**     | Requires `tokenId` header                           |
| **Headers**     | `Content-Type`     | Fixed: `application/json`                           |
|                 | `tokenId`          | User's URID                                         |
|                 | `network`          | Network Identifier (`5000` Mainnet, `5003` Testnet) |

**Request Parameters**

```json
{
  "inputAmount": "50000",
  "inputToken": "0xdf79470986629ae4893BfCE0c6C0F4d085E99741",
  "outputToken": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007"
}
```

* You can get the fiat token addresses from here: [token contract addresses](/developer-resources/smart-contracts.md#contract-addresses-1)

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "",
  "result": {
    "inputAmount": "50000",
    "inputToken": "0xdf79470986629ae4893BfCE0c6C0F4d085E99741",
    "outputToken": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007",
    "outputAmount": "41869",
    "exchangeRate": "0.83"
  },
  "timeNow": 1772077859153
}
```

**4. Field Notes**

* `inputAmount` / `outputAmount`: minimal unit strings (do not treat as float).
* `exchangeRate`: `1 inputToken = x outputToken`, fixed 2 decimals, truncated down.
* Current quote mode is `exact_in` (input is fixed).

**5. Request Example**

```bash
curl -X POST "https://api.ur.app/api/v1/quote/fx" \
-H "Content-Type: application/json" \
-H "tokenId: 12345" \
-H "network: 5000" \
-d '{
  "inputAmount": "50000",
  "inputToken": "0xdf79470986629ae4893BfCE0c6C0F4d085E99741",
  "outputToken": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007"
}'
```

#### 7.1.2 Exchange with Permit

**1. Description**\
Executes FX on-chain using EIP-2612 style permit signature.

**2. Request**

| Item            | Value                             | Note                                    |
| --------------- | --------------------------------- | --------------------------------------- |
| **HTTP Method** | `POST`                            |                                         |
| **URI**         | `/api/v1/fx-exchange-with-permit` |                                         |
| **Auth Level**  | **Full Auth**                     | Requires signed headers + permit fields |
| **Headers**     | `tokenId`                         | User's URID                             |
|                 | `network`                         | Network Identifier                      |
|                 | `sign`                            | Wallet signature header                 |
|                 | `hash`                            | Request hash header                     |
|                 | `deadline`                        | Header signature deadline               |

**Request Parameters**

```json
{
  "userAddress": "0x1234567890abcdef1234567890abcdef12345678",
  "inputToken": "0xdf79470986629ae4893BfCE0c6C0F4d085E99741",
  "outputToken": "0x5E52c8993283023B83e87eF577f7f51Fa1c5B007",
  "inputAmount": "50000",
  "amountOutMinimum": "41869",
  "permitValue": "50000",
  "permitDeadline": 1772081459,
  "permitV": 27,
  "permitR": "0x...",
  "permitS": "0x..."
}
```

**3. Response**

```json
{
  "retCode": 0,
  "retMsg": "success",
  "result": {
    "txHash": "0xabc123def456..."
  },
  "timeNow": 1703123456789
}
```

**4. Parameter Notes**

* `inputAmount`: exact-in amount (minimal unit).
* `amountOutMinimum`: minimum acceptable output (minimal unit), usually derived from quote `outputAmount`.
* `permitValue`: must be `>= inputAmount`.
* `permitDeadline`: must be in the future.
* `permitR` / `permitS`: 32-byte hex strings (`0x` + 64 hex chars).

#### 7.1.3 Recommended End-to-End Flow

1. Call `POST /api/v1/quote/fx` with `inputAmount`, `inputToken`, `outputToken`.
2. Read `result.outputAmount` and `result.exchangeRate`.
3. Decide `amountOutMinimum` policy:
   * conservative: apply a safety factor on quote output;
   * strict: use quote `outputAmount` directly.
4. Build permit signature for `inputToken`.
5. Call `POST /api/v1/fx-exchange-with-permit`.
6. Track final transaction status via `txHash`.

#### 7.1.4 Common Error Codes (FX Related)

| Code    | Type            | Description                                             |
| ------- | --------------- | ------------------------------------------------------- |
| `10002` | Parse Error     | Missing/malformed parameters or headers                 |
| `10009` | Invalid Param   | Invalid token address, amount format, or token pair     |
| `10040` | Quote Try Again | Quote failed due to pricing/rpc conditions; retry quote |
| `10000` | System Error    | Permit verification or on-chain submission failed       |

## 8. OPEN API

This section details the UR-OPEN-API, designed for third-party integrations (B-side). For complete API documentation including authentication, endpoints, and data structures, please refer to [Delegated Contract Mode API Reference](/developer-resources/api-reference-delegated-contract-mode.md).

### 8.1 MINT

See [Mint URID](/developer-resources/openapis.md#mint-urid) in the Open API Reference.

### 8.2 Get User Profile

See [Fetch UR Account Information](/developer-resources/openapis.md#fetch-ur-account-information) in the Open API Reference.

### 8.3 Get User Balance

See [Fetch UR Account Balance](/developer-resources/openapis.md#fetch-ur-account-balance) in open API Reference.

### 8.4 Transaction Query

See [Fetch Transaction Details](/developer-resources/openapis.md#fetch-transaction-details) and [Fetch Transaction History](/developer-resources/openapis.md#fetch-transaction-history) in the Open API Reference.

### 8.5 Webhook Notifications

Webhook definitions are centralized in [Webhooks](/developer-resources/webhook.md).\
For off-ramp related events, see `transaction` and `allowance`.


---

# 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/developer-resources/api-reference-external-wallet-access-mode.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.
