Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ yarn-error.log*
.vscode

package-lock.json
yarn.lock

.yarn/

Expand Down
158 changes: 158 additions & 0 deletions DOC.md

Large diffs are not rendered by default.

Binary file added bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mantle-rwa-platform",
"private": true,
"packageManager": "yarn@1.22.22",
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
"type": "module",
"engines": {
"node": "22.x"
Expand All @@ -14,7 +14,7 @@
"build": "yarn workspaces foreach -A -pt run build",
"lint": "eslint .",
"type-check": "tsc --noEmit",
"generate:types": "yarn workspace @mantle/contracts generate:types",
"generate:types": "yarn workspaces foreach -A -pt --include '@contracts/*' run generate:types",
"sync-env": "cp .env packages/backend/.env && cp .env packages/contracts/.env"
},
"devDependencies": {
Expand All @@ -34,4 +34,4 @@
"dotenv": "^16.4.5",
"ed25519-hd-key": "^1.3.0"
}
}
}
9 changes: 9 additions & 0 deletions packages/arbitrum-contracts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Admin wallet private key for deployments
ADMIN_PRIVATE_KEY=your_private_key_here

# RPC URLs (optional - defaults are in hardhat.config.ts)
ARBITRUM_SEPOLIA_RPC=https://sepolia-rollup.arbitrum.io/rpc
ARBITRUM_MAINNET_RPC=https://arb1.arbitrum.io/rpc

# Etherscan API key for verification
ARBISCAN_API_KEY=your_arbiscan_api_key_here
29 changes: 29 additions & 0 deletions packages/arbitrum-contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
node_modules
.env
artifacts
cache
typechain-types
coverage
coverage.json
dist

# Hardhat files
cache
artifacts

# TypeChain
typechain
typechain-types

# solidity-coverage
coverage
coverage.json
.coverage_artifacts
.coverage_cache
.coverage_contracts

# Hardhat Ignition
ignition/deployments/chain-*

# VS Code
.vscode
135 changes: 135 additions & 0 deletions packages/arbitrum-contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Arbitrum Contracts

This package contains the Arbitrum-specific smart contracts for the Open Assets RWA platform, featuring stARB-based leverage functionality.

## Overview

The Arbitrum contracts package is a parallel implementation of the leverage system using **stARB** (Staked ARB) as collateral instead of mETH. The architecture mirrors the Mantle implementation exactly, with only token names and chain-specific configurations changed.

### Key Contracts

#### Arbitrum-Specific Contracts

- **MockStARB.sol** - Simulated liquid staked ARB token for demo/testing
- **MockArbitrumDEX.sol** - Mock AMM for stARB ↔ USDC swaps
- **ArbitrumSwapIntegration.sol** - Slippage-protected swap wrapper
- **StARBLeverageVault.sol** - Core leverage vault using stARB collateral

#### Shared Platform Contracts

- **SeniorPool.sol** - USDC lending pool (chain-agnostic)
- **AttestationRegistry.sol** - Asset attestation system
- **IdentityRegistry.sol** - On-chain KYC/identity management
- **TokenFactory.sol** - RWA token deployment
- **YieldVault.sol** - Yield distribution system
- **PrimaryMarket.sol** - Initial token offerings
- **SecondaryMarket.sol** - P2P trading
- **OAID.sol** - On-chain identity credentials

## Architecture

### stARB Leverage System Flow

1. **Collateral Deposit**: User deposits stARB (150% LTV requirement)
2. **Borrowing**: Vault borrows USDC from SeniorPool
3. **RWA Purchase**: USDC used to buy RWA tokens
4. **Yield Harvesting**: stARB appreciation is periodically harvested and swapped to USDC to pay loan interest
5. **Settlement**: Upon RWA asset maturity or liquidation, waterfall distribution occurs

### Health Monitoring

- **Liquidation Threshold**: 115% collateral ratio
- **Initial LTV**: 150% over-collateralization
- **Liquidation Fee**: 10% on excess after debt repayment

## Development

### Prerequisites

```bash
# Install dependencies
bun install
```

### Compile Contracts

```bash
bun run generate:types
```

### Deploy to Arbitrum Sepolia

```bash
# Set environment variables in .env
ADMIN_PRIVATE_KEY=your_private_key

# Deploy
bun hardhat run scripts/deploy/deploy_arbitrum.ts --network arbitrumSepolia
```

### Deploy to Arbitrum Mainnet

```bash
bun hardhat run scripts/deploy/deploy_arbitrum.ts --network arbitrum
```

## Network Configuration

### Arbitrum Sepolia Testnet

- **Chain ID**: 421614
- **RPC URL**: https://sepolia-rollup.arbitrum.io/rpc
- **Block Explorer**: https://sepolia.arbiscan.io

### Arbitrum One Mainnet

- **Chain ID**: 42161
- **RPC URL**: https://arb1.arbitrum.io/rpc
- **Block Explorer**: https://arbiscan.io

## Contract Addresses

After deployment, all contract addresses are saved to `deployed_contracts_arbitrum.json`.

## Key Differences from Mantle

| Aspect | Mantle | Arbitrum |
|--------|--------|----------|
| Collateral Token | mETH | stARB |
| Yield Source | Mantle staking rewards | ARB sequencer fee distribution |
| Expected APY | ~4-6% | ~8% |
| DEX Contract | MockFluxionDEX | MockArbitrumDEX |
| Integration | FluxionIntegration | ArbitrumSwapIntegration |
| Vault Contract | LeverageVault | StARBLeverageVault |

## Testing

The contracts use the same testing patterns as the Mantle implementation. Mock tokens allow easy testing without real asset acquisition.

### Test Flow

1. Mint MockStARB tokens
2. Create leverage position
3. Simulate price movements
4. Execute harvest
5. Test liquidation scenarios

## Security Considerations

- All price data comes from backend (off-chain oracle)
- 3% maximum slippage protection on swaps
- Emergency pause functionality on swap integrations
- Non-reentrant modifiers on all state-changing functions
- 150% over-collateralization requirement

## Integration with Backend

The backend's `BlockchainModule` conditionally loads Arbitrum providers when `NETWORK_TYPE=arbitrum`:

- `StArbPriceService` - Historical ARB/USD price tracking
- `ArbitrumDEXService` - DEX interaction layer
- `LeverageBlockchainService` - Position management (network-agnostic)

## License

MIT
77 changes: 77 additions & 0 deletions packages/arbitrum-contracts/contracts/core/AttestationRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

contract AttestationRegistry {
using ECDSA for bytes32;

struct AssetRecord {
bytes32 assetId;
bytes32 attestationHash;
bytes32 blobId;
address attestor;
uint48 timestamp;
bool revoked;
}

mapping(bytes32 => AssetRecord) public assets;
mapping(address => bool) public trustedAttestors;
address public owner;

event AssetRegistered(bytes32 indexed assetId, bytes32 blobId, bytes32 attestationHash, address attestor);
event AssetRevoked(bytes32 indexed assetId, string reason, uint256 timestamp);

modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}

constructor() {
owner = msg.sender;
trustedAttestors[msg.sender] = true;
}

function addTrustedAttestor(address attestor) external onlyOwner {
trustedAttestors[attestor] = true;
}

function registerAsset(
bytes32 assetId,
bytes32 attestationHash,
bytes32 blobId,
bytes calldata payload,
bytes calldata signature
) external {
require(assets[assetId].timestamp == 0, "Asset already registered");
require(keccak256(payload) == attestationHash, "Payload hash mismatch");

// Recover signer from hash
bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(attestationHash);
address recovered = ECDSA.recover(ethSignedHash, signature);

require(trustedAttestors[recovered], "Invalid attestor signature");

assets[assetId] = AssetRecord({
assetId: assetId,
attestationHash: attestationHash,
blobId: blobId,
attestor: recovered,
timestamp: uint48(block.timestamp),
revoked: false
});

emit AssetRegistered(assetId, blobId, attestationHash, recovered);
}

function revokeAsset(bytes32 assetId, string calldata reason) external onlyOwner {
require(assets[assetId].timestamp > 0, "Asset not found");
assets[assetId].revoked = true;
emit AssetRevoked(assetId, reason, block.timestamp);
}

function isAssetValid(bytes32 assetId) external view returns (bool) {
return assets[assetId].timestamp > 0 && !assets[assetId].revoked;
}
}
53 changes: 53 additions & 0 deletions packages/arbitrum-contracts/contracts/core/ComplianceModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./IdentityRegistry.sol";
import "./AttestationRegistry.sol";

contract ComplianceModule {
IdentityRegistry public identityRegistry;
AttestationRegistry public attestationRegistry;
bytes32 public assetId;
address public owner;

modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}

constructor(
address _identityRegistry,
address _attestationRegistry,
bytes32 _assetId
) {
owner = msg.sender;
identityRegistry = IdentityRegistry(_identityRegistry);
attestationRegistry = AttestationRegistry(_attestationRegistry);
assetId = _assetId;
}

function canTransfer(address from, address to, uint256 /* amount */) external view returns (bool) {
// 1. Check Sender KYC (unless minting/burning)
if (from != address(0)) {
if (!identityRegistry.isVerified(from)) return false;
}

// 2. Check Receiver KYC (unless burning)
if (to != address(0)) {
if (!identityRegistry.isVerified(to)) return false;
}

// 3. Check Asset Validity
if (!attestationRegistry.isAssetValid(assetId)) return false;

return true;
}

function setIdentityRegistry(address _identityRegistry) external onlyOwner {
identityRegistry = IdentityRegistry(_identityRegistry);
}

function setAttestationRegistry(address _attestationRegistry) external onlyOwner {
attestationRegistry = AttestationRegistry(_attestationRegistry);
}
}
Loading