overview
The first venue on the Arc testnet (5042002).
We found no DEX on the Arc testnet (chain 5042002) at the canonical Uniswap addresses. Nyx publishes the venue itself — the pools, the router, the quoter —
and puts a terminal in front of it. Nothing here is a price server: every on-chain number
on this site is read from a contract, or computed in your browser from those reads and from
Swap logs. The outside sources are Hyperliquid (reference markets on Trade, tagged ref) and Polymarket (Predict); both say so on screen.
the venue
Five contracts, one job each.
ArcPool is a constant-product pool shaped like a concentrated pool
(token0 / token1 / fee / liquidity /
slot0 / observe), without ranges, tick spacing or fee
growth. ArcRouterV2 executes the route, takes its fee before the check, and
reverts if less than the signed minimum arrives — a fee cap of 30
bps, at most 3 hops, and a referrer paid inside the same transaction. ArcQuoter gives the exact quote a broken leg
returns zero without dragging the others down. TestStock is a test
share, unbacked, with that warning in its own name and symbol.
The router and quoter speak the exact same selectors as a reference router used elsewhere in this project's stack — not for style, but so a contract already covered by 108 tests can point at Nyx's addresses without a single line changing.
Faucet is the fifth, and it exists because of a bug worth writing down:
the "Get 1,000 tUSDC" button used to call mint on the token itself, and
TestStock.mint requires msg.sender == minter. The minter is
the wallet that deployed the venue — so the button worked for exactly one address in
the world and reverted for every visitor, while a comment in the code
claimed anyone could mint. The minter is immutable and every pool
already holds the current tUSDC, so neither opening the mint nor swapping the cash token
was an option. The faucet holds a stack minted once and hands out a fixed ration with an
8-hour lock per address. It has no owner, mints nothing, and is not a distribution.
| Function | Selector |
|---|---|
swapExactIn(address,address,uint256,uint256,uint256,(uint8,address,bool,uint24)[]) | 0x05b094ac |
quoteMany((address,bool)[],uint256) | 0x02fc6ced |
the price
Price is the pool's reserves, not a quote of ours.
The terminal reads reserve0() and reserve1() from every pool on
each refresh, in small paced batches (the node rate-limits by request rate).
token0() and fee() never change, so they are read once per visit.
Price is the cash reserve divided by the share reserve, ordered by which token is
token0 — not by construction order. A pool that hasn't answered is marked
no read, a pool with a zeroed reserve is marked dead;
those two states are never merged into one, because "the node didn't answer" and
"the market has no liquidity" are different facts.
Every trade also writes a point into a 128-slot ring buffer, and the moving average used to mark positions interpolates between real points instead of extrapolating backward from the current price — the first version of that logic didn't, and a single transaction could move a thirty-minute average instantly. Two tests hold the line now: one proves the average does not yield to one transaction, the other proves it does yield once the price stays displaced for the full window. Both have to pass, because an oracle that never moves is as useless as one that moves too easily.
the volume
The volume on this chart is ours, not the market's.
The trading history on this testnet is generated by a bot we run
(bin/atividade.js). Every trade is a real, signed, auditable transaction
through the same ArcRouter a visitor would use, and none of it is market
demand. Test volume is not liquidity, and a number on this screen will not imply
otherwise.
The candle engine reads the pools' Swap events straight from the blocks
and was right from day one; what it was missing was something to read. The first six pools
had three trades in their entire life, so the chart drew nothing —
which was not a bug, it was the truth about a book nobody had traded.
The generator exists to give that engine a history, and it makes three promises worth stating because each one is checkable:
-
It goes through the router, never straight at the pool. Calling
ArcPool.swapdirectly would be cheaper and would emit the same event, but it would skiptransferFrom, the approval and the router's minimum check — the exact path a real user walks. -
Five separate wallets trade, not one. A single account trading with
itself is obvious in an explorer and never exercises the multi-owner approval path.
The five addresses are listed right here, precisely so anyone can tell our bot apart
from anything that isn't:
Bot wallet Address w1 0x102517f30c75f615e922c5bcc7bbd11ad7706fc2 w2 0x0e5c8b9cfccdccc7ba2b32468bfed252efa0570c w3 0x0a5d40709d15e0e9a9dc426816cdbca39762f554 w4 0x5a8cdede2dbc02dec895aac9c9b72e35693a1061 w5 0x7fd4eca4cb7b7bf36233144cc94ac0944f0ce496 -
Price moves but cannot run away. A mean-reverting random walk keeps
every pool within ±15% of an anchor. That band is not cosmetic: these pools are the
oracle the perpetuals engine reads (
slot0/observe), so a runaway price here would break the other product. Each trade's effect is simulated with the pool's own formula before anything is signed; if it would leave the band the direction flips, then the size is halved, and if it still doesn't fit the trade is skipped.
Running it for real requires an explicit environment variable. Without it the script reads the chain, simulates against the real reserves, prints what it would do, and signs nothing.
live reference prices
Most charts on Trade are someone else's market.
ref is not a Nyx market. Its price, 24h change,
volume, open interest, funding, order book and trades come from
Hyperliquid's public API
— crypto and memecoins from its main perpetuals, stocks, indices, commodities and FX from the
trade.xyz (HIP-3) markets. Nyx has no pool or order book for them, so the ticket says
trading on Nyx is not open for this market yet and sends no order, real or simulated.
-
Only markets tagged
on Arcare tradable. They are the pools on Arc testnet described above, with candles from theirSwaplogs. Where the same ticker also has a reference (NVDA, TSLA, AAPL…), the chart opens on the live reference and a switch shows the Arc pool. They are two different markets with two different prices and they do not track each other. -
How it streams. On load the terminal makes one
metaAndAssetCtxscall per market group, straight from your browser toapi.hyperliquid.xyz— no server of ours in between, and nothing is sent to the Arc node. Then one WebSocket carries every mid price, plus the candle, book, trades and stats of the market you have open. The last candle builds live. - When the feed drops, the screen says so. The chart and the list show reference feed disconnected, reconnecting, prices turn grey and carry their age, and the terminal falls back to re-reading over REST every 15 seconds. A price that was not read is a dash, never a zero. On a return visit the list can open with the last read saved in your browser — greyed out and labelled with its age until the live read replaces it.
-
Which markets. The list is generated by
bin/catalogo.jsfrom Variational's public market list, kept where Hyperliquid lists the same asset, plus a short hand-picked list of memecoins. Prices quoted per thousand units on Hyperliquid (kPEPE, kBONK…) are shown per single unit. A few markets have no full name in any source we could check; those show their Hyperliquid identifier instead of a guessed name.
the quote
The pool is what answers how much comes out.
Price is a ratio; a quote is how much you actually receive for a specific size, after
the fee and the impact your own order causes on that reserve. ArcQuoter
executes the real trade inside a read call and returns the simulated result — what
shows up in the terminal's ticket is that number, not a formula fitted on the page.
A pool that can't absorb the size reports it: it returns less than what was requested, and the terminal treats that as "no route for this size" rather than estimating a worse fill.
the execution
The contract checks what arrived.
Between the quote and the confirmation, the price can move. The router turns that risk into a guarantee: it compares the balance that actually landed in your wallet against the minimum you signed (default tolerance 1%), and reverts the whole transaction if it falls short. There is no silent partial fill.
- No Nyx contract holds your funds — the swap happens inside a single transaction.
- Token approval is requested for the order's value, not for an unlimited amount.
- You sign in your own browser; the transaction goes from your wallet straight to the Arc node — there is no proxy in this app, because the Arc node returns CORS headers correctly.
Verified end to end: the calldata the terminal builds is byte-identical to a transaction already executed successfully on the chain, 714 characters compared word by word. The referral variant was checked the same way — the calldata this page builds was simulated against the deployed contract and came back with the amount, not a revert. What was not exercised by an automated check is the click inside the wallet extension itself — that depends on a human confirming.
fees
A cap that binds, and a charge that is on.
ArcRouterV2 has a hard-coded fee ceiling — it cannot be raised past it, not
even by whoever deployed it — and the fee it takes today is 10 bps of what the
route pays out. It is deliberately a third of the ceiling: the pool already
charges 30 bps per hop, and charging the maximum on day one would leave no room to move
without deploying yet another contract.
| Parameter | Value |
|---|---|
| Router fee cap | 30 bps |
| Router fee charged today | 10 bps |
| Pool fee (goes to LPs, not the platform) | 30 bps per hop |
| Referral cut | 30% of the fee, not of the trade |
The referral split happens inside the swap transaction itself — no ledger, no IOU, no balance a referrer has to come back and withdraw. A link is a wallet address; whoever opens it and later trades pays that address its cut in the same transaction, in whatever token the trade pays out. The cut is a slice of the fee, never of the trade: two people trading the same size receive the same amount, link or no link. See Referrals.
The old router (0xe1c9…ac4a) is still on chain, unchanged and still charging
zero — contracts cannot be altered, and it is the address an already-audited consumer
points at through selector 0x05b094ac. The new one answers that same selector
with the same shape, which is why the terminal could move without breaking anything.
architecture
Two layers, not three.
There's no backend between you and the chain. Reads (price, reserves, quote) and writes (approval, swap) both go straight from your browser to the Arc node — the only server in this stack serves the static files you're reading right now.
Browser (vanilla JS, no framework)
│ reads: eth_call batched via JSON-RPC
│ writes: signed by your wallet (EIP-1193), sent straight to the node
▼
Arc testnet RPC ── ArcPool / ArcRouterV2 / ArcQuoter · IndiceNyx / PerpVaultV2 / PerpMarketV2
This is why the app works with no wallet connected too: every read call needs no signature, so price and quotes load before you ever click "Connect wallet." It's also why there's no API key, no rate-limited proxy of ours to go down — the Arc node returns CORS headers correctly, which the chain this project started on does not.
leverage
Perpetual futures, paid from a published index.
IndiceNyx on chain and the contract itself caps it at 1% per
update and 10% of total movement in any rolling 24 hours — those caps bind
the key, and they bind us too. The vault, not another trader, pays your profit, and our own junior
tranche absorbs the first loss before any outside LP. The operator address is printed on the
Leverage tab, read live from the contract.
Spot was the first leg; leverage is the second. One market is live on the Arc testnet,
NVDA, with one PerpVaultV2 and one PerpMarketV2 behind it and a
single IndiceNyx feeding the price. The design deliberately avoids inventing
infrastructure Arc doesn't have: no off-chain matching engine, no separate virtual price curve to
keep honest. Only one asset is live because of keeper gas, not because of the contracts: the index
has to be republished every few minutes for opening to stay available, and each extra market
multiplies that bill.
| Contract | Role |
|---|---|
IndiceNyx | The price feed. An operator key publishes one price per asset; the contract refuses any jump above 1%, and refuses a day whose jumps add up past 10%. It records the round and the timestamp of every update, which is what the market uses to settle at the next round and to know how stale the price is. |
PerpVaultV2 | Counterparty vault for one market. It holds two balances: a junior tranche we funded with 20,000 tUSDC that takes the first loss, and a senior tranche for outside LPs. Payouts come out of junior until it is empty. The junior cannot leave while any position, unsettled exit or unpaid credit still exists. |
PerpMarketV2 | Position book for one asset — isolated margin, funding, health, liquidation. One position per address per market, by design. Entry and exit both settle at the index round after the transaction, so seeing a price update in the mempool is worth nothing. |
IArcOracle | Minimal read interface into the existing ArcPool. It is now only a sanity brake on opening — the index has to be within 3% of the pool 30-minute average — and never touches a payout. |
Margin is isolated, not cross: losing a position never pulls margin from another one.
Funding has no perp curve to converge toward the spot price — there is none — so it
exists only to discourage one-sided positioning, distributed with an accumulator index
(indiceLong / indiceShort), so no loop over open positions and no
gas that grows with the number of traders.
abrir and fechar both take a price limit and a deadline, and the
limit is enforced by the contract, not by the app. Closing with a limit of
0 is always available: it takes whatever the reference price is and is
never gated by the operator, the pool or a read, because an exit that can be locked is not an exit.
Closing pays you the value at the worst the next index round could be, and the remainder — at most
about 2% of notional — is settled by assentar(id), which anyone can call.
public stats
A JSON file anyone can regenerate.
stats.json is a snapshot,
not a live feed. It is written by bin/stats.js, a read-only Node script with no key
that signs nothing: it calls the same contracts this site calls, with the same code (it imports
app/src), so a number in the file and the same number on screen come out of one function.
Published snapshot: reading stats.json…
- Per pool: price, reserves, pool fee, trades and tUSDC volume for 24h, 7d and all time, last trade, and the average buy fill at 100 / 1,000 / 10,000 tUSDC.
- Totals: trades, volume, liquidity and TVL across the pools, plus routed trades split into our wallets (the five bots, the deployer and one test contract of ours) and everyone else.
- Perp: vault balances and open interest per market, read live, for whatever markets are actually wired up — one today, NVDA. The file carries the index caps next to the numbers so a reader knows who sets the price these vaults settle against.
- Honesty fields: head block and its time,
complete(false when a log window did not answer, which makes every count a floor),readFailures(anything that did not read isnulland named there, never 0), and a count of poolSwapevents whose sender is not a Nyx router (swaps no one can be credited for).
Testnet, test money. The volume in it is our own bot's, the same volume described above.
addresses
Published on the Arc testnet, 2026-09-07.
Chain 5042002 · testnet.arcscan.app
| Core | Address |
|---|---|
| ArcRouterV2 (in use, 10 bps fee, referral split) | 0x42c0751668511a4b586489fd8da754e19dbc5ed0 |
| ArcRouter (v1, no fee, still live) | 0xe1c9780ae6df124a2e229310cc123a8dc520ac4a |
| ArcQuoter | 0xb4f14009549cc822be9e3a7de950c61d9d72c459 |
| tUSDC (test cash) | 0x1c89af2bd394594f73fa6047f4b9305bb7a4c56c |
| Faucet (1,000 tUSDC / 8h) | 0x598daa33d0098f8c5de56ab037f367ed9a77ea8e |
| Share | Token | Pool |
|---|
| Perp market (v2, 2026-09-15) | PerpVaultV2 | PerpMarketV2 | IndiceNyx |
|---|
limits
What is not ready yet.
Plain record, no polish:
- Leverage — in development. The deployed contracts have a known vulnerability (see leverage above), and their vaults are empty, so the tab reads the markets and lets existing positions close, but no position can be opened. The design covers only six of the twenty-one assets, with market orders and a price limit, one position per address per market, no cross-margin, no stop or take-profit orders, and no keeper of ours running liquidations.
- Predict — still not a Nyx product. There is no prediction-market contract, no oracle and no date for one. The tab is a read-only window into Polymarket: their markets, their prices, their order book, read live from their public API. Trading, custody and resolution all happen at Polymarket, off the Arc chain, so the tab links out instead of offering a ticket.
- Referrals — the split is live in
ArcRouterV2and paid inside the swap. What is not on chain is the attribution: who referred whom lives in the visitor's own browser, so clearing site data clears the tag, and a different browser is a different visitor. There is no account here to remember it for you. - Volume and price history are generated by our own bot — real, auditable transactions, but not market demand. See the volume above.
- 24h change stays blank until a pool actually has 24h of history — it is never faked from the opening price.
- You pay in the chain's test dollar (tUSDC), not in the Arc's native USDC — wrapping the native gas token for trading isn't implemented.