# Smart Contracts

## Source Code & Audits

* **Contract Source Code**: <https://github.com/ur-app/ur-contracts>
* **Audit Reports**: <https://github.com/ur-app/ur-contracts/tree/main/Audits>

## Environment

Most of UR's core functionalities, including account management and card operations, are deployed and executed exclusively on the **Mantle Network**. Interactions with other chains (such as Ethereum, Arbitrum, Base, and Monad) are only required for specific features like **Deposit** and **Cash to Crypto**.

For deployment, we recommend using [QuickNode](https://www.quicknode.com/) or [Alchemy](https://www.alchemy.com/) as your RPC provider.

### Mantle Mainnet

* **Chain ID**: 5000
* **RPC Endpoint**: <https://rpc.mantle.xyz>

### Mantle Sepolia Testnet

* **Chain ID**: 5003
* **RPC Endpoint**: <https://rpc.sepolia.mantle.xyz>

### Chain Explorers

| Network      | Mainnet                   | Testnet                           |
| ------------ | ------------------------- | --------------------------------- |
| Mantle       | <https://mantlescan.xyz/> | <https://sepolia.mantlescan.xyz/> |
| Ethereum     | <https://etherscan.io/>   | <https://sepolia.etherscan.io/>   |
| Arbitrum One | <https://arbiscan.io/>    | <https://sepolia.arbiscan.io/>    |
| Base         | <https://basescan.org/>   | <https://sepolia.basescan.org/>   |
| BSC          | <https://bscscan.com/>    | <https://testnet.bscscan.com/>    |
| Monad        | <https://monadscan.com/>  | <https://testnet.monadscan.com/>  |

## Functions

### Account

Our account model is based on the ERC721 NFT standard; each user has a unique UR ID (Token ID) that identifies and manages their assets and permissions on the UR platform.\
The user's KYC status is recorded in the contract's `status` mapping, with each status corresponding to a numeric value as follows:

#### **Contract Addresses**

| Network | Address                                                                                                                           |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Mainnet | [`0x4a05148119683E0A41b52fb973EEF0EE81536c47`](https://mantlescan.xyz/address/0x4a05148119683E0A41b52fb973EEF0EE81536c47)         |
| Testnet | [`0xfE6fB4aE524c8f032E14691C3B2465cc5bcB9677`](https://sepolia.mantlescan.xyz/address/0xfE6fB4aE524c8f032E14691C3B2465cc5bcB9677) |

#### **Methods**

| Function Name         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ownerOf`             | Query EVM wallet address by URID.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `tokenOfOwnerByIndex` | Query URID by EVM wallet address. This ERC721Enumerable method returns the token ID owned by an address at a given index. Since each user should hold one Account NFT, use `index = 0` to get the user's URID.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `status`              | <p>Status: KYC passed, status is <code>Live</code>. Functions for <code>non-Live</code> statuses are restricted.<br><br><code>SoftBlocked, // 1</code><br><code>Tourist, // 2</code><br><code>Blocked, // 3</code><br><code>Closed, // 4</code><br><code>Live // 5</code></p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `limit`               | <p>Query user monthly transaction limit by URID.<br>All outgoing Fiat transactions count towards this limit.<br>For KYC-verified users, the default monthly limit is <strong>100,000 CHF</strong>.<br><br>Returns <code>usedLimit</code> (uint256), <code>clientLimit</code> (uint256), <code>startLimitDate</code> (uint256).<br><br>Users receive a spending limit that refreshes every 30 days.<br><br>- <strong>usedLimit</strong> (<code>uint256</code>): Amount of the limit already used in the <em>current</em> 30-day cycle. This value is reset every 30 days.<br>- <strong>clientLimit</strong> (<code>uint256</code>): Total allowed limit for the current 30-day period.<br>- <strong>startLimitDate</strong> (<code>uint256</code>): Timestamp of the last limit reset (start time of the current 30-day cycle).</p> |

#### **ABI**

[Fiat24Account.sol](https://github.com/ur-app/ur-contracts/blob/main/src/Fiat24Account.sol)

#### Example: Get URID by Wallet Address (TypeScript + viem)

```typescript
import { createPublicClient, http, parseAbi } from 'viem'
import { mantle } from 'viem/chains'

const client = createPublicClient({
  chain: mantle,
  transport: http('https://rpc.mantle.xyz')
})

const abi = parseAbi([
  'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)'
])

const accountContractAddress = '0x4a05148119683E0A41b52fb973EEF0EE81536c47'
const userAddress = '0x...' // Replace with the user's wallet address

async function getUrid() {
  try {
    const tokenId = await client.readContract({
      address: accountContractAddress,
      abi: abi,
      functionName: 'tokenOfOwnerByIndex',
      args: [userAddress, 0n]
    })

    console.log(`URID: ${tokenId.toString()}`)
  } catch (error) {
    console.error('Error fetching URID:', error)
  }
}

getUrid()
```

### Fiat Balance

#### **Contract Addresses**

| Currency | Mainnet                                                                                                                   | Testnet                                                                                                                           |
| -------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| USD      | [`0xD598839598bBF508b97697b7D9e80054D4bcaaCC`](https://mantlescan.xyz/address/0xD598839598bBF508b97697b7D9e80054D4bcaaCC) | [`0xdf79470986629ae4893BfCE0c6C0F4d085E99741`](https://sepolia.mantlescan.xyz/address/0xdf79470986629ae4893BfCE0c6C0F4d085E99741) |
| EUR      | [`0x0578be9C858e6562dd8cd11a738b89Ca48194dA5`](https://mantlescan.xyz/address/0x0578be9C858e6562dd8cd11a738b89Ca48194dA5) | [`0x5E52c8993283023B83e87eF577f7f51Fa1c5B00`](https://sepolia.mantlescan.xyz/address/0x5E52c8993283023B83e87eF577f7f51Fa1c5B007)  |
| CHF      | [`0x53587A05ccDdCE555C2Cd7cE4C9c5Bc3D912E2f3`](https://mantlescan.xyz/address/0x53587A05ccDdCE555C2Cd7cE4C9c5Bc3D912E2f3) | [`0x52837070C96C6D5E23ed90a43479c0237c41864c`](https://sepolia.mantlescan.xyz/address/0x52837070C96C6D5E23ed90a43479c0237c41864c) |
| CNH      | [`0xa0af0C397CB0A52F5E8Bc7BB89068dDDfaE9F211`](https://mantlescan.xyz/address/0xa0af0C397CB0A52F5E8Bc7BB89068dDDfaE9F211) | [`0x4AfbC767e6d310296657b7759a5F2c303F26B327`](https://sepolia.mantlescan.xyz/address/0x4AfbC767e6d310296657b7759a5F2c303F26B327) |
| SGD      | [`0x8F7F92F2A0247cc8660C4C4EF69582Bc6849B4d9`](https://mantlescan.xyz/address/0x8F7F92F2A0247cc8660C4C4EF69582Bc6849B4d9) | [`0x2FEb2d95ce8eC88931c4031cdd2875C90EDC87FE`](https://sepolia.mantlescan.xyz/address/0x2FEb2d95ce8eC88931c4031cdd2875C90EDC87FE) |
| JPY      | [`0x3bC9fC0460cAC2DdD352848ECc0BFe204c220717`](https://mantlescan.xyz/address/0x3bC9fC0460cAC2DdD352848ECc0BFe204c220717) | [`0x8af3be43607cb1e57b6e37fda99b6b988c5b48f0`](https://sepolia.mantlescan.xyz/address/0x8af3be43607cb1e57b6e37fda99b6b988c5b48f0) |
| HKD      | [`0x64266a15432004708e5fCA0239f664d069853374`](https://mantlescan.xyz/address/0x64266a15432004708e5fCA0239f664d069853374) | [`0x30c94bCF88c5c8f3Ff08F38242a38C656Bb28a6d`](https://sepolia.mantlescan.xyz/address/0x30c94bCF88c5c8f3Ff08F38242a38C656Bb28a6d) |

#### **Methods**

| Method      | Description                                                                                                                                                                                                     |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `balanceOf` | Query a user's fiat token balance by their address.                                                                                                                                                             |
| `decimals`  | Precision of the token. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`).                                                             |
| `allowance` | This function can be used to query the amount a user has approved for a given spender. Generally, for an unlimited approval, we call the approve method with value set to the maximum of uint256, i.e. 2^256-1. |

#### **ABI**

[Fiat24Token.sol](https://github.com/ur-app/ur-contracts/blob/main/src/Fiat24Token.sol)

#### Example: Get Balance (TypeScript + viem)

```typescript
import { createPublicClient, http, parseAbi, formatUnits } from 'viem'
import { mantle } from 'viem/chains'

// 1. Create a client connected to Mantle Mainnet
const client = createPublicClient({
  chain: mantle,
  transport: http('https://rpc.mantle.xyz')
})

// 2. Define the ABI for balanceOf and decimals
const abi = parseAbi([
  'function balanceOf(address owner) view returns (uint256)',
  'function decimals() view returns (uint8)'
])

// 3. Contract and User addresses
const eurContractAddress = '0x0578be9C858e6562dd8cd11a738b89Ca48194dA5' // EUR on Mantle Mainnet
const userAddress = '0x...' // Replace with the user's wallet address

async function getBalance() {
  try {
    // 4. Read contract data
    const [balance, decimals] = await Promise.all([
      client.readContract({
        address: eurContractAddress,
        abi: abi,
        functionName: 'balanceOf',
        args: [userAddress]
      }),
      client.readContract({
        address: eurContractAddress,
        abi: abi,
        functionName: 'decimals'
      })
    ])

    // 5. Format and display the balance
    console.log(`Raw Balance: ${balance}`)
    console.log(`Decimals: ${decimals}`)
    console.log(`Formatted Balance: ${formatUnits(balance, decimals)} EUR`)
    
  } catch (error) {
    console.error('Error fetching balance:', error)
  }
}

getBalance()
```

### Money Transfer

#### **Contract Addresses**

**Contract refers to the** [**currency contracts**](#contract-address-1)**.**

#### **Methods**

> **Note:** Only users in **Live** status (`status == 5`) are allowed to transfer their fiat tokens out in any form.

| Method                | Description                                                                                                                                                                                                                           |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transfer`            | Transfer fiat token to any EVM address.                                                                                                                                                                                               |
| `transferByAccountId` | Transfer fiat tokens to the target user by their URID.                                                                                                                                                                                |
| `clientPayout`        | <p>User to transfer their currency out via bank transfer to a UR-supported bank account.<br>Before the user calls this contract function, a payout account (contactId) must be created through UR's API (The API is coming soon).</p> |

#### Example: Transfer Fiat (TypeScript + viem)

```typescript
import { createWalletClient, http, parseAbi, parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mantle } from 'viem/chains'

// 1. Setup Wallet Client (Required for write operations)
// ⚠️ NEVER hardcode private keys in production code
const account = privateKeyToAccount('0x...') 
const client = createWalletClient({
  account,
  chain: mantle,
  transport: http('https://rpc.mantle.xyz')
})

// 2. Define ABI for transfer
const abi = parseAbi([
  'function transfer(address to, uint256 amount) returns (bool)'
])

// 3. Configuration
const eurContractAddress = '0x0578be9C858e6562dd8cd11a738b89Ca48194dA5' // EUR Contract
const recipientAddress = '0x...' // Receiver's address
const amountToSend = '10' // Amount in EUR

async function transferFiat() {
  try {
    // 4. Send Transaction
    // Note: Ensure the sender has enough balance and ETH(MNT) for gas
    const hash = await client.writeContract({
      address: eurContractAddress,
      abi: abi,
      functionName: 'transfer',
      args: [
        recipientAddress, 
        parseUnits(amountToSend, 2) // Assuming 2 decimals for EUR
      ]
    })

    console.log(`Transaction sent! Hash: ${hash}`)
  } catch (error) {
    console.error('Transfer failed:', error)
  }
}

transferFiat()
```

### Money Exchange

Also known as foreign-exchange conversion, used for exchanging between fiat currencies.

#### **Contract Addresses**

| Network                | Address                                                                                                                           |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Mantle Mainnet         | [`0x9F88e04D129d4a4247F009833ba0Bd5D8F6A2146`](https://mantlescan.xyz/address/0x9F88e04D129d4a4247F009833ba0Bd5D8F6A2146)         |
| Mantle Sepolia Testnet | [`0x2C2E6BC745e583629a4157c2D8a9234d59F4067e`](https://sepolia.mantlescan.xyz/address/0x2C2E6BC745e583629a4157c2D8a9234d59F4067e) |

#### **Methods**

> **Note:** Only users in **Live** status (`status = 5`) are allowed to do money exchange.

| Method                 | Description                                                             |
| ---------------------- | ----------------------------------------------------------------------- |
| `moneyExchangeExactIn` | Exchange between 2 currencies                                           |
| `getExchangeRate`      | Query the exchange rate between two specified currency token addresses. |

#### **ABI**

[Fiat24CryptoRelay.sol](https://github.com/ur-app/ur-contracts/blob/main/src/Fiat24CryptoRelay.sol)

#### Example: Money Exchange (TypeScript + viem)

This process involves two steps:

1. Query the current exchange rate using `getExchangeRate`.
2. Execute the exchange using `moneyExchangeExactin`.

```typescript
import { createPublicClient, createWalletClient, http, parseAbi, parseUnits, formatUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mantle } from 'viem/chains'

// 1. Setup Clients
const account = privateKeyToAccount('0x...')
const publicClient = createPublicClient({
  chain: mantle,
  transport: http('https://rpc.mantle.xyz')
})
const walletClient = createWalletClient({
  account,
  chain: mantle,
  transport: http('https://rpc.mantle.xyz')
})

// 2. Define ABI
const abi = parseAbi([
  'function getExchangeRate(address srcToken, address destToken) view returns (uint256)',
  'function moneyExchangeExactin(address srcToken, address destToken, uint256 srcAmount, uint256 minDestAmount) returns (uint256)'
])

// 3. Configuration
const exchangeContractAddress = '0x9F88e04D129d4a4247F009833ba0Bd5D8F6A2146'
const eurAddress = '0x0578be9C858e6562dd8cd11a738b89Ca48194dA5' // Source: EUR
const usdAddress = '0xD598839598bBF508b97697b7D9e80054D4bcaaCC' // Destination: USD
const amountInEur = '100' // Exchange 100 EUR

async function exchangeCurrency() {
  try {
    // 4. Step 1: Get Exchange Rate
    const rate = await publicClient.readContract({
      address: exchangeContractAddress,
      abi: abi,
      functionName: 'getExchangeRate',
      args: [eurAddress, usdAddress]
    })
    console.log(`Current Rate: ${formatUnits(rate, 18)}`) // Rate has 18 decimals

    // 5. Calculate Minimum Destination Amount (Slippage Protection)
    // Example: Allow 1% slippage
    const srcAmount = parseUnits(amountInEur, 2) // EUR has 2 decimals
    const expectedDestAmount = (srcAmount * rate) / parseUnits('1', 18)
    const minDestAmount = (expectedDestAmount * 99n) / 100n 

    // 6. Step 2: Execute Exchange
    const hash = await walletClient.writeContract({
      address: exchangeContractAddress,
      abi: abi,
      functionName: 'moneyExchangeExactin',
      args: [eurAddress, usdAddress, srcAmount, minDestAmount]
    })

    console.log(`Exchange submitted! Hash: ${hash}`)
  } catch (error) {
    console.error('Exchange failed:', error)
  }
}

exchangeCurrency()
```

### Deposit (Off-ramp)

#### **Contract Addresses**

| Network      | Mainnet                                                                                                                                   | Testnet                                                                                                                                            |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Mantle       | [`0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083`](https://mantlescan.xyz/address/0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083)                 | [`0xd6d4C6eB84697cB9bf0045e88b8f3A0bD42D3d66`](https://sepolia.mantlescan.xyz/address/0xd6d4C6eB84697cB9bf0045e88b8f3A0bD42D3d66)                  |
| Arbitrum     | [`0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083`](https://arbiscan.io/address/0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083#readProxyContract)  | [`0xCa8eFFac628001B86e068b8367174F91E7E88357`](https://sepolia.arbiscan.io/address/0xCa8eFFac628001B86e068b8367174F91E7E88357)                     |
| Ethereum     | [`0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083`](https://etherscan.io/address/0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083#readProxyContract) | [`0xF67b89d376D3aAA872E151B4526271D9394aE192`](https://sepolia.etherscan.io/address/0xF67b89d376D3aAA872E151B4526271D9394aE192)                    |
| Base Network | [`0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083`](https://basescan.org/address/0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083)                   | [`0xd6d4C6eB84697cB9bf0045e88b8f3A0bD42D3d66`](https://sepolia.basescan.org/address/0xd6d4C6eB84697cB9bf0045e88b8f3A0bD42D3d66#writeProxyContract) |
| Monad        | [`0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083`](https://monadscan.com/address/0xd08B421A33F9b09A59E2ebf72afEF2365ce5b083)                  | [`0x886DCF3BCb4fe8b2b07366C9F9aEbD3e471E8abA`](https://testnet.monadscan.com/address/0x886DCF3BCb4fe8b2b07366C9F9aEbD3e471E8abA)                   |

#### **Methods**

> Note: Only users with status **Live (5)** or **SoftBlocked (1)** can perform deposit(off-ramp) transactions.

| Method                      | Description                                                                                                                                                                                                                                                               |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `depositTokenViaUsdc`       | The user deposits USDC into the contract and receives fiat currency in their UR account.                                                                                                                                                                                  |
| `depositTokenViaAggregator` | <p>Supports users depositing a specific crypto asset and receiving fiat currency in their UR account.<br><br><em>The prerequisite is that the frontend must integrate a DEX/aggregator API(API is coming soon) and construct the Crypto → USDC swap as calldata.</em></p> |

#### **ABI**

[Fiat24CryptoDeposit.sol](https://github.com/ur-app/ur-contracts/blob/main/src/Fiat24CryptoDeposit.sol)

### Onramp

#### **Contract Addresses**

| Network                    | Mainnet                                                                                                                   | Testnet |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------- |
| Mantle                     | [`0x2460634bf887A0F4E885278B93E10f91D48a5a8c`](https://mantlescan.xyz/address/0x2460634bf887A0F4E885278B93E10f91D48a5a8c) |         |
| Arbitrum/Base/Ethereum/BSC | `0xAACe017F0a6Bb9890E449d5b27fbcA9C440b81e9`                                                                              |         |

#### **Methods**

#### **ABI**

[BufferPool.sol](https://github.com/ur-app/ur-contracts/blob/main/src/BufferPool.sol)

### Card

#### **Contract Addresses**

**Contract refers to the** [**currency contracts**](#contract-address-1)**.**

**Spender Contract (Card Authorization):**

| Network | Address                                                                                                                           |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Mainnet | [`0xb9d38DDE25f67D57af5b91C254F869F90d483d05`](https://mantlescan.xyz/address/0xb9d38DDE25f67D57af5b91C254F869F90d483d05)         |
| Testnet | [`0x25d66C564532258eD9cdBB6215E260AFf41d8bae`](https://sepolia.mantlescan.xyz/address/0x25d66C564532258eD9cdBB6215E260AFf41d8bae) |

#### Methods

To use the UR debit card, users must authorize the **Spender Contract** to spend their **Fiat** or **USDe** balance via `approve` or `permit` methods.

| Method               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approve` / `permit` | <p>Authorize the <strong>Spender Contract</strong> to spend funds.<br><br>The debit card payments are booked by the Spender contract via the <code>authorize()</code> function. Therefore, the card owner needs to approve a certain limit to the Spender contract for future card spendings.<br><br><strong>Supported Assets:</strong><br>- <strong>Fiat Tokens:</strong> Users must approve the corresponding Fiat token contract.<br>- <strong>USDe:</strong> Users can also authorize USDe directly for consumption.<br><br><strong>Usage Notes:</strong><br>- <strong>Enable Authorization:</strong> It is common practice to approve the maximum <code>uint256</code> value (<code>2^256 - 1</code>) to ensure uninterrupted service.<br>- <strong>Revoke Authorization:</strong> To cancel the authorization, call the <code>approve</code> method with an amount of <code>0</code>.</p> |

#### **ABI**

[Fiat24Token.sol](https://github.com/ur-app/ur-contracts/blob/main/src/Fiat24Token.sol)

#### Card Transaction Flow

![Card Transaction Flow](/files/DKunAsOuWU3WqoAKXg6c)


---

# 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/smart-contracts.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.
