Chameleon
Start
OverviewHow it worksQuickstart
Concepts
ConceptsPrivacy model
Reference
The routerJavaScript engineContracts and addresses
Practice
Real examplesSelling and exitingSecurity and limitsFAQChangelog
Robinhood Chain · 4663

The router

ChameleonRouter is one contract of about 100 lines. It holds no funds between calls, has no owner, charges no fee and cannot be upgraded.

Constructor

constructor(address announcer_)

The announcer is the ERC-5564 singleton, 0x55649E01B5Df198D18D95b5cc5051630cfD45564 on Robinhood Chain. It is immutable.

buy

function buy(
    address curve,
    uint256 minTokensOut,
    address stealth,
    bytes calldata ephemeralPubKey,
    bytes1 viewTag,
    uint256 gasDust
) external payable returns (uint256 tokensOut)

msg.value is the amount spent on the curve plus gasDust.

ParameterMeaning
curveA Pons curve. Take it from the factory's TokenLaunched event.
minTokensOutThe least that must land on the stealth address, or the call reverts.
stealthThe one-time address derived from the receiver's meta-address.
ephemeralPubKey33-byte compressed public key of the sender's one-time key.
viewTagFirst byte of the hashed shared secret.
gasDustETH forwarded to stealth so it can pay for its own transactions. Must be below msg.value.

What it does, in order

  1. Locks against re-entry, checks the inputs.
  2. Reads the curve's token with curve.token() and the stealth address's balance.
  3. Calls curve.buy{value: quoteIn}(quoteIn, minTokensOut, stealth).
  4. Measures the balance change. Reverts if it is zero or below minTokensOut.
  5. Sends gasDust to stealth.
  6. Calls announcer.announce(1, stealth, ephemeralPubKey, metadata).
  7. Returns any ETH the curve handed back to the caller.

Errors

ErrorCause
Reentrancy()A nested call into buy.
BadStealth()stealth is the zero address.
BadEphemeralKey()The key is not 33 bytes.
DustTooLarge()gasDust is not below msg.value.
NothingToBuy()The stealth address received no tokens.
BelowMinimum(got, wanted)Slippage: fewer tokens than minTokensOut.
DustTransferFailed()The stealth address rejected the gas.
RefundFailed()The caller could not receive a refund.

Invariants

  • The router's ETH and token balances are zero after every call.
  • The buyer the curve records is always the router.
  • No function can be called by an owner, because there is no owner.

Tested against the real chain

The 11 tests in contracts/test/ChameleonRouter.t.sol run on a fork of Robinhood Chain against the real Pons curve and the real announcer:

TestWhat it proves
test_tokensLandOnStealthNotOnCallerThe tokens arrive on the stealth address, none on the caller or router.
test_gasDustReachesStealthAndRouterKeepsNothingThe dust arrives and the router ends at zero.
test_announcementMatchesErc5564LayoutScheme 1, correct indexed fields, 57-byte metadata with view tag, selector, token and amount.
test_curveSeesTheRouterAsBuyerNeverTheUserCurveBuy.buyer is the router.
test_stealthCanSellOnItsOwnUsingTheDustThe stealth address approves and sells with only the dust it received.
test_revertsBelowMinimumSlippage protection works.
test_revertsOnBadEphemeralKeyInput checks.
test_revertsOnZeroStealthInput checks.
test_revertsWhenDustSwallowsEverythingInput checks.
test_manyBuysManyStealthAddressesRouterStaysEmptyFive buys to five addresses leave the router at zero.
test_gasUsedForOneBuyAbout 188,000 gas on the fork.