Logo
Learn
  • Explore Course
Build
  • Explore Buildathon
  • Project Archive
Hack
Hack Coming Soon
  • Explore IRL Hackhouse
  • How to Qualify
  • Past Events

SettlX

SettlX is a Web3 payment platform for global merchants in volatile markets like Nigeria. It lets them accept USDC and receive instant NGN settlements at a locked rate, eliminating FX risk completely.

Videos

Description

SettlX — De-risked Merchant Settlements

Instant FX + Insurance for Web3 Payments | Built on Arbitrum Stylus (Rust)

SettlX is a Web3 payment settlement platform that lets global merchants accept stablecoin (USDC) payments and receive guaranteed local-currency (NGN) settlements at a locked exchange rate — eliminating FX volatility risk entirely. Smart contracts written in Rust using Arbitrum Stylus handle escrow, rate locking, and settlement on-chain.

Live Demo

Production: https://settl-x.vercel.app/

Network: Arbitrum Sepolia

Overview

A merchant in Lagos sells a product for $100 USDC. By the time they convert it, the FX rate has slipped — and they net only ₦138,000 instead of ₦150,000. Their margin is gone.

SettlX solves this.

When a $100 USDC payment arrives, SettlX instantly quotes the merchant a guaranteed fiat value. The merchant clicks "Lock Rate" — the exchange rate is cryptographically locked on-chain at that exact moment. The merchant receives exactly ₦150,000, regardless of what the market does next. The platform manages the FX exposure in the background.

The Problem

  • Crypto payment margins in emerging markets are razor-thin

  • FX volatility between payment creation and settlement can wipe out merchant profits

  • No existing solution offers guaranteed, locked-rate settlement at the point of sale

  • Merchants have no on-chain recourse if rates shift between payment and payout

How It Works

Payer → SettlX Contract → Merchant

  1. Payer approves USDC to the contract

  2. Payer calls payMerchant()

  3. USDC is held in escrow

  4. Merchant calls acceptPaymentWithRate()

  5. Rate is locked on-chain

  6. USDC is transferred to admin treasury

  7. Admin calls markAsPaid() after NGN transfer

  8. Payment status becomes Paid

Payment Lifecycle

Pending (0) — Payment created, awaiting merchant action
Accepted (1) — Rate locked, USDC transferred to admin
Rejected (2) — Merchant rejected, USDC refunded to payer
Paid (3) — Admin confirmed NGN bank transfer sent


Smart Contract Architecture

The contract is written in Rust using the Arbitrum Stylus SDK and compiled to WASM for on-chain execution. This provides significantly lower gas costs compared to equivalent Solidity contracts.

Core Smart Contract Capabilities

1. Create Payment (Escrow)

A payer sends USDC to the contract, which securely holds the funds in escrow until the merchant takes action.

2. Lock Exchange Rate

The merchant accepts the payment and locks the FX rate on-chain at that exact moment.
This guarantees the NGN amount they will receive, eliminating volatility risk.

3. Reject & Refund

If the merchant declines the transaction, funds are automatically refunded to the payer.

4. Confirm Settlement

After sending NGN to the merchant’s bank account, the admin confirms the payout on-chain, marking the payment as fully settled.

5. Merchant Bank Registration

Merchants register their bank details (stored as hashes for privacy) so off-chain NGN settlements can be executed securely.

Events

| Event                 | Parameters                                                                | Description                                                             |
| --------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `MerchantRegistered`  | `merchant (indexed)`, `bankName`, `accountName`, `accountNumber`          | Emitted on bank detail registration. Contains plaintext strings.        |
| `PaymentCreated`      | `id (indexed)`, `payer (indexed)`, `merchant (indexed)`, `amount`, `rfce` | Emitted when a payment is created. Contains plaintext `rfce` reference. |
| `PaymentAccepted`     | `id (indexed)`, `lockedRate`                                              | Emitted when merchant locks rate. `lockedRate` = NGN × 10^18.           |
| `PaymentRejected`     | `id (indexed)`                                                            | Emitted when merchant rejects payment.                                  |
| `PaymentMarkedAsPaid` | `id (indexed)`                                                            | Emitted when admin confirms NGN settlement.                             |

> **Important:** Because `rfce` and bank details are hashed on-chain, the plaintext values only exist in event logs. Frontend clients should index `PaymentCreated` and `MerchantRegistered` events to display human-readable references and bank info.

Getting Started

Contract:

Prerequisites

  • Rust (stable toolchain)

  • cargo-stylus CLI

  • Arbitrum Sepolia ETH for deployment gas

Installation

  1. Clone the repository

git clone https://github.com/Chigozie0706/SettlX
cd SettlX
  1. For contract deployment

cd contract-stylus
  • Install cargo-stylus:
    cargo install cargo-stylus

  • Add WASM target:
    rustup target add wasm32-unknown-unknown

  • Build for Arbitrum Sepolia

cargo stylus check --endpoint https://sepolia-rollup.arbitrum.io/rpc
  • Contract Deployment:

  1. Deploy to Arbitrum Sepolia:

cargo stylus deploy \
  --endpoint='https://sepolia-rollup.arbitrum.io/rpc' \
  --private-key="YOUR_PRIVATE_KEY"
  1. Initialize the contract:

cast send CONTRACT_ADDRESS \
  "init(address)" \
  USDC_TOKEN_ADDRESS \
  --rpc-url https://sepolia-rollup.arbitrum.io/rpc \
  --private-key YOUR_PRIVATE_KEY
  1. Export ABI:

    cargo stylus export-abi --json > settlX.json

Deployed Contracts (Testnet)

| Contract           | Network          | Address                                      |
| ------------------ | ---------------- | -------------------------------------------- |
| SettlX             | Arbitrum Sepolia | `0x4855dcefa1a1ecf8b2fbd7eae38b6f73a90f48d1` |
| USDC (Test)        | Arbitrum Sepolia | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` |
| Chainlink USDC/USD | Arbitrum Sepolia | `0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3` |

FRONTEND:

The frontend is built with Next.js 16, Privy (wallet authentication), Wagmi v2, and Viem.

cd frontend
cd settlX
pnpm install
pnpm run dev

Key Frontend Features

  • Transact page for payer approvals and payments

  • Merchant dashboard to view and lock FX rates

  • Admin dashboard for full settlement management

Plain text data such as rfce and bank details are recovered from event logs on the frontend.

Tech Stack

| Layer              | Technology                 |
| ------------------ | -------------------------- |
| Smart Contract     | Rust + Arbitrum Stylus SDK |
| Blockchain         | Arbitrum Sepolia (L2)      |
| Token Standard     | ERC-20 (USDC)              |
| Price Oracle       | Chainlink AggregatorV3     |
| Frontend Framework | Next.js 16 (App Router)    |
| Wallet Auth        | Privy                      |
| Web3 Client        | Wagmi v2 + Viem            |
| FX Rate API        | exchangerate.host          |

Why Arbitrum Stylus?

  • Lower gas costs

  • Fast finality

  • Familiar Rust tooling

  • Strong DeFi ecosystem

License

MIT OR Apache-2.0

Progress During Hackathon

<h1>SettlX — Progress Summary</h1><h2>What We're Building</h2><p><strong>SettlX</strong> is a cross-border payment settlement protocol built on Arbitrum using Stylus (Rust smart contracts). It solves a real problem for Nigerian merchants who receive USDC payments from international customers but need to settle in NGN — without trusting a centralized exchange or intermediary.</p><p>The core flow:</p><ol><li><p><strong>Payer</strong> sends USDC to a merchant via the SettlX contract — funds enter escrow</p></li><li><p><strong>Merchant</strong> accepts the payment and locks the current NGN/USDC exchange rate on-chain</p></li><li><p><strong>Admin</strong> transfers the NGN equivalent to the merchant's bank account, then marks the payment as Paid on-chain</p></li><li><p>If the merchant rejects, USDC is automatically refunded to the payer</p></li></ol><h2>Smart Contract (Stylus / Rust)</h2><p>Deployed on <strong>Arbitrum Sepolia</strong> at <code data-inline="true" spellcheck="false">0x4855dcefa1a1ecf8b2fbd7eae38b6f73a90f48d1</code></p><p><strong>What's complete:</strong></p><ul><li><p>Full payment lifecycle — <code data-inline="true" spellcheck="false">payMerchant</code>, <code data-inline="true" spellcheck="false">acceptPaymentWithRate</code>, <code data-inline="true" spellcheck="false">rejectPayment</code>, <code data-inline="true" spellcheck="false">markAsPaid</code></p></li><li><p>Merchant registration — <code data-inline="true" spellcheck="false">registerMerchantBankDetails</code>, <code data-inline="true" spellcheck="false">updateMerchantBankDetails</code></p></li><li><p>USDC escrow using ERC20 <code data-inline="true" spellcheck="false">transferFrom</code> / <code data-inline="true" spellcheck="false">transfer</code></p></li><li><p>Exchange rate locking — NGN/USDC rate is locked at acceptance time (scaled ×1e18), preventing rate slippage between acceptance and settlement</p></li><li><p>Custom error types for every failure case (<code data-inline="true" spellcheck="false">NotYourPayment</code>, <code data-inline="true" spellcheck="false">AlreadyProcessed</code>, <code data-inline="true" spellcheck="false">InvalidRate</code>, etc.)</p></li><li><p>Payment status machine: <code data-inline="true" spellcheck="false">Pending → Accepted → Paid</code> or <code data-inline="true" spellcheck="false">Pending → Rejected</code></p></li><li><p>Event emission for all state transitions — <code data-inline="true" spellcheck="false">PaymentCreated</code>, <code data-inline="true" spellcheck="false">PaymentAccepted</code>, <code data-inline="true" spellcheck="false">PaymentRejected</code>, <code data-inline="true" spellcheck="false">PaymentMarkedAsPaid</code>, <code data-inline="true" spellcheck="false">MerchantRegistered</code>, <code data-inline="true" spellcheck="false">MerchantUpdated</code></p></li><li><p>Bank details stored as <code data-inline="true" spellcheck="false">keccak256</code> hashes on-chain for privacy; plaintext recoverable only from events</p></li><li><p>Unlimited bank detail updates via <code data-inline="true" spellcheck="false">erase()</code> before <code data-inline="true" spellcheck="false">set()</code> — fixing a Stylus bytes32 overwrite limitation</p></li></ul><h2>Frontend (Next.js + Wagmi + Privy)</h2><p>Three separate dashboards, each fully functional:</p><p><strong>Merchant Dashboard</strong></p><ul><li><p>View all incoming payments with live NGN/USD exchange rate</p></li><li><p>Accept payments (locks rate on-chain) or reject (triggers USDC refund)</p></li><li><p>Register and update bank details (collapsible update form, pre-filled with current values)</p></li><li><p>Real-time event indexing for <code data-inline="true" spellcheck="false">MerchantRegistered</code> + <code data-inline="true" spellcheck="false">MerchantUpdated</code> — always shows latest bank details</p></li><li><p><code data-inline="true" spellcheck="false">rfce</code> (payment reference) recovered from <code data-inline="true" spellcheck="false">PaymentCreated</code> events and displayed as human-readable strings</p></li></ul><p><strong>Payer (Transact) Page</strong></p><ul><li><p>Send USDC to any registered merchant</p></li><li><p>Input payment reference (rfce), amount, and merchant address</p></li><li><p>USDC approval + payment in two steps</p></li></ul><p><strong>Admin Dashboard</strong></p><ul><li><p>View all payments across all merchants</p></li><li><p>"Mark as Paid" per payment with per-button processing state (Set-based locking — multiple buttons can process simultaneously without blocking each other)</p></li><li><p>Merchant bank details recovered from events and displayed alongside each payment</p></li></ul><p><strong>Shared technical details across all dashboards:</strong></p><ul><li><p><code data-inline="true" spellcheck="false">writeContractAsync</code> + <code data-inline="true" spellcheck="false">waitForTransactionReceipt</code> — toasts only fire after on-chain confirmation, not on wallet popup</p></li><li><p>No hardcoded gas parameters — MetaMask estimates dynamically (fixes "max fee less than base fee" errors)</p></li><li><p>Rich toast notifications for every action with context-aware loading → waiting → success/error states</p></li><li><p>All components that need inputs are inlined in JSX (not defined as sub-components) to prevent focus loss on keystrokes</p></li></ul><h2>Key Technical Decisions</h2><table style="min-width: 50px"><colgroup><col style="min-width: 25px"><col style="min-width: 25px"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Decision</p></th><th colspan="1" rowspan="1"><p>Why</p></th></tr><tr><td colspan="1" rowspan="1"><p>Stylus (Rust) over Solidity</p></td><td colspan="1" rowspan="1"><p>Lower gas costs, Rust's type safety, Arbitrum-native</p></td></tr><tr><td colspan="1" rowspan="1"><p>Rate locked at acceptance</p></td><td colspan="1" rowspan="1"><p>Protects merchant from slippage between acceptance and NGN transfer</p></td></tr><tr><td colspan="1" rowspan="1"><p>Bank details as hashes</p></td><td colspan="1" rowspan="1"><p>Privacy — account numbers not publicly readable on-chain</p></td></tr><tr><td colspan="1" rowspan="1"><p>Event sourcing for plaintext</p></td><td colspan="1" rowspan="1"><p>Hash stored on-chain; original string lives only in the event log</p></td></tr><tr><td colspan="1" rowspan="1"><p><code data-inline="true" spellcheck="false">erase()</code> before <code data-inline="true" spellcheck="false">set()</code> for updates</p></td><td colspan="1" rowspan="1"><p>Stylus bytes32 slots corrupt on 3rd+ overwrite without zeroing first</p></td></tr><tr><td colspan="1" rowspan="1"><p>Chainlink price feed (USDC/USD)</p></td><td colspan="1" rowspan="1"><p>Off-chain NGN/USD rate combined with on-chain USDC price for accurate NGN amounts</p></td></tr></tbody></table><h2>What's Working End-to-End</h2><ul><li><p> Contract deployed and verified on Arbitrum Sepolia</p></li><li><p> Merchant can register and update bank details</p></li><li><p> Payer can send USDC and create a payment</p></li><li><p> Merchant can accept (rate locks) or reject (refund fires)</p></li><li><p> Admin can mark payments as paid</p></li><li><p> All three dashboards reflect live on-chain state via event indexing</p></li><li><p> Exchange rate pulled live from Chainlink + external FX API</p></li></ul><h2>Stack</h2><ul><li><p><strong>Smart Contract</strong>: Rust + Arbitrum Stylus SDK</p></li><li><p><strong>Chain</strong>: Arbitrum Sepolia (testnet)</p></li><li><p><strong>Frontend</strong>: Next.js, TypeScript, TailwindCSS</p></li><li><p><strong>Wallet</strong>: Privy (embedded wallet + external wallet support)</p></li><li><p><strong>Chain interaction</strong>: Wagmi + Viem</p></li><li><p><strong>Price data</strong>: Chainlink USDC/USD feed + <a href="http://exchangerate.host">exchangerate.host</a> for NGN</p></li><li><p><strong>Token</strong>: USDC (6 decimals)</p></li></ul>

Tech Stack

NextWeb3RustNode

Fundraising Status

<p>None</p>

Team Leader
CChigozie Jacob
GitHub Link
github

GitHub

https://github.com/Chigozie0706/SettlX
Product Category
DeFi