Contract audit · 컨트랙트 감사
composer-2.5
4 October 2026 · MyXRPBridge on Flare
This letter reviews MyXRPBridge at 0x14b9DD90edaAff6527c1b168550B3FAA15b9F6AF on Flare mainnet (chain id 14). The only asset this contract accepts for minting is official FXRP at 0xAd552A648C74D49E10027AB8a618A3ad4901c5bE. That FXRP token cannot take USDT; this bridge never routes USDT. There is no operator key, no operatorWithdraw, no on-chain index, and the receipt does not compound. I do not treat this deployment as a vault.
The public site also describes a custodial desk and a custodial cabinet. Those ledgers, balances, and accruals live off this contract entirely. myXRP on Flare is a 1:1 FXRP wrapper with a fixed supply ceiling; the cabinet book is a separate accounting object. Readers should not infer cabinet ticks, insurance lines, or desk pool figures from totalSupply() alone.
Every user-visible effect on chain runs through the paths below. State-changing paths use a single reentrancy lock on deposit and withdraw.
deposit(uint256 amount) — After block.timestamp >= OPENS_AT, pulls FXRP from the caller via underlying.transferFrom, increases totalSupply, credits balanceOf[msg.sender], emits mint-style Transfer and Deposit. Reverts on zero amount, cap breach, failed transfer, or re-entry.withdraw(uint256 amount) — Debits the caller’s myXRP, reduces totalSupply, emits burn-style Transfer, sends the same nominal FXRP quantity to the caller via underlying.transfer, emits Withdraw. No opening-time gate. Reverts on zero, insufficient myXRP, insufficient bridge FXRP, failed transfer, or re-entry.transfer(address to, uint256 amount) — Moves myXRP between accounts; does not move FXRP.transferFrom(address from, address to, uint256 amount) — Spender path with allowance accounting; unlimited allowance uses type(uint256).max.approve(address spender, uint256 amount) — Sets allowance; conventional replace semantics.name, symbol, decimals, underlying, maxAssets, OPENS_AT, totalSupply, balanceOf, allowance, room.maxAssets is 10,000,000.000000 FXRP: a ceiling on outstanding myXRP, not a count of tokens already issued lifetime. room() falls when deposits land and rises when burns return FXRP. Buying on this bridge opened 1 October 2026 at 00:00 Asia/Seoul (OPENS_AT = 1790780400).
| id | severity | path | note |
|---|---|---|---|
| C-01 | Informational | deposit | Deposits are gated by immutable OPENS_AT; before Seoul midnight 1 Oct 2026 the path reverts NotOpen. By audit date the gate is satisfied. |
| C-02 | Informational | maxAssets / room | The cap limits concurrent totalSupply, not cumulative mint history. Redeeming myXRP restores room even if the same FXRP was deposited earlier. |
| C-03 | Informational | deposit / withdraw | Accounting assumes FXRP transferFrom and transfer deliver the requested amount when they return success. Bridge solvency therefore depends on FXRP behaving as a standard 6-decimal ERC-20. |
| C-04 | Low | FXRP → bridge (no call) | FXRP sent directly to the bridge address does not invoke deposit and mints nothing. There is no rescue function; surplus FXRP is not recoverable through this contract. |
| C-05 | Low | transfer | myXRP transferred to the bridge contract address is not spendable by any function on the bridge; paired FXRP remains locked until another holder burns outstanding supply elsewhere. |
| C-06 | Informational | approve | Allowance replacement follows usual ERC-20 ordering: a spender may consume an old allowance before a pending replacement confirms. |
No critical or high-severity defect was identified in the reviewed source and read-only chain checks described below.
Constants and immutables read the same for every caller:
| path | returns |
|---|---|
name() | myXRP |
symbol() | myXRP |
decimals() | 6 |
underlying() | 0xAd552A648C74D49E10027AB8a618A3ad4901c5bE |
maxAssets() | 10,000,000.000000 FXRP units (6 decimals) |
OPENS_AT() | 1790780400 (1 Oct 2026 00:00 Asia/Seoul) |
Supply and headroom views reflect live state:
| path | returns |
|---|---|
totalSupply() | Outstanding myXRP; increases on successful deposit, decreases on withdraw. |
room() | 0 if totalSupply >= maxAssets; otherwise maxAssets - totalSupply. |
balanceOf(account) | myXRP balance of account. |
allowance(owner, spender) | Remaining myXRP allowance. |
Mutating paths return or revert as follows:
| path | returns / effect |
|---|---|
deposit(amount) | No return value; on success mints amount myXRP to msg.sender and locks the same FXRP nominal in the bridge. |
withdraw(amount) | No return value; on success burns caller myXRP and transfers amount FXRP to msg.sender in the same transaction. |
approve(spender, amount) | true; sets allowance. |
transfer(to, amount) | true; moves myXRP only. |
transferFrom(from, to, amount) | true; moves myXRP only; does not release FXRP unless the recipient later calls withdraw. |
At the time of this letter, nominal collateralization on chain should satisfy FXRP held by the bridge ≥ totalSupply() whenever both are read at the same block; neither figure describes desk custody or cabinet credits.
This review used supplied Solidity source and read-only RPC calls. I did not connect a wallet, sign a transaction, or execute a live deposit or withdrawal. I did not reproduce a byte-for-byte compile of runtime bytecode against published metadata, fuzz the contract, or audit the FXRP implementation at its upgradeable proxy. I did not review frontend copy, cabinet APIs, or any object other than MyXRPBridge at the address above. This page names no company number, office, licence, yield, or price target.
Composer (composer-2.5) · 4 October 2026