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

How it works

Four actors take part: the receiver (you), the sender (the wallet that pays), the router (our contract) and the Pons curve. The announcer is a shared ERC-5564 contract that carries a small public note for each payment.

The flow

  1. The receiver creates a spending key and a viewing key, and publishes the meta-address built from their public halves.
  2. The sender draws a fresh random key r and computes a shared secret with the viewing public key. From it they derive a one-time address A that only the receiver can control.
  3. The sender calls router.buy(...) with A as the destination. The router buys from the Pons curve with A as the recipient, sends A a little gas, and calls the announcer.
  4. The announcer emits an Announcement event holding A, the public half of r, and a one-byte view tag.
  5. The receiver scans announcements, uses the view tag to skip the ones that are not theirs, and recognises the ones that are.
  6. The receiver derives the private key of A and can sell or move the tokens.

Why Pons makes this easy

Pons curves expose buy(uint256 quoteIn, uint256 minOut, address recipient). The recipient argument is free: the tokens land wherever you point it. Chameleon simply points it at a stealth address. The router measures the recipient's balance before and after, so it can check the minimum and announce the exact amount.

The derivation

With K_spend and K_view the receiver's public keys, G the curve generator and r the sender's one-time key:

R       = r * G                         published in the announcement
S       = r * K_view  (= k_view * R)    both sides compute the same point
h       = keccak256(x coordinate of S)
tag     = first byte of h               the view tag
K_stealth = K_spend + h * G
address = last 20 bytes of keccak256(K_stealth, uncompressed, without the prefix)
k_stealth = k_spend + h  (mod n)        only the receiver can compute this

The Try it page and the explorer on the home page run exactly this, with the real bytes.

The view tag

Scanning every announcement with full elliptic-curve maths would be slow. The view tag is the first byte of h, published in the metadata. A scanner computes h with its viewing key and compares one byte. For announcements that are not theirs, 255 out of 256 are rejected right there.

The announcement

The router calls the standard announcer with scheme 1 (secp256k1 with view tags). The metadata follows the ERC-5564 layout for a token transfer, so generic scanners understand it:

BytesContent
0view tag
1 to 40xa9059cbb, the ERC-20 transfer selector
5 to 24the token address
25 to 56the amount of tokens received

Gas for the new address

A fresh address has no ETH, so it could not sell. The router forwards a small gasDust with the buy. On this chain gas is around 0.02 gwei, so 0.00002 ETH covers many transactions. See Selling and exiting.

What the chain sees

CurveBuy      buyer = router     recipient = A
Announcement  caller = router    stealth address = A    view tag = 0x..

Your meta-address and your main wallet appear in neither event.