How Reddit Trader works

Every claim on this page is checkable against Robinhood Chain. Where something is not deployed yet, it says so rather than describing it in the present tense.

What this is

Reddit Trader is a trading agent with a treasury and exactly two moves. Its token, $RDDTR, launches on Pons v2 with its bonding curve quoted in RDDT, the tokenized Reddit share that exists natively on Robinhood Chain. Because the curve settles in RDDT, the creator fee Pons charges on every buy and sell is already a Reddit share by the time it is credited.

The agent splits that fee, immutably, on a ratio fixed at deploy:

So trading volume does two things at once: it accumulates an asset that can never be sold, and it removes supply from the asset it can. There is no model with an opinion, no signal, no leverage and no discretion. Those are the parts of a "trading agent" that are usually either fake or dangerous.

The chain and the venue

Everything runs on Robinhood Chain, chain id 4663, an Ethereum L2 whose distinguishing feature is that Robinhood's tokenized equities live on it natively. The launchpad is Pons v2, whose factory (PonsV2LaunchFactory) is verified on Blockscout and open to anyone: canLaunch(address) returns true for every address tested, including unused ones, and neither whitelistedLaunchers nor launchForwarder gates a direct call. That is what makes this launch possible without asking Pons for anything.

The chain's RPC answers access-control-allow-origin: *, so every number on this site is read by your own browser, straight from a node. There is no backend in front of it that could be lying to you.

The quote asset, and how to tell it from a fake

Searching the explorer for RDDT returns dozens of contracts. All but one are memecoin copycats, and the tell is a supply of exactly 1,000,000,000. The real tokenized share is:

Address 0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C
Name Reddit • Robinhood Token
Contract BeaconProxy → implementation Stock at 0xb35490d6…64C5aE2
Deployer 0x4783C67b63dE2B358Ac5951a7D41F47A38F3C046
Decimals 18

The deployer-plus-implementation pair is the reliable test: every genuine Robinhood equity on this chain is a beacon proxy onto the same Stock implementation, deployed by the same address. A copycat can spoof the name and the symbol; it cannot spoof that.

The one thing that had to be checked before any of this was worth building. Pons keeps an allowlist of quote assets, approvedPairTokens(address), and an unapproved pair reverts PairTokenNotApproved() (0x49285dfb) at launch time. Worse, previewLaunchEconomics does not enforce it: an unapproved token returns the same default hash as a junk address instead of reverting, so the launch looks fine right up until it fails. RDDT is approved, and a full launchToken simulated against the live factory with pairToken = RDDT returns a real token and curve address. Sixteen equities are approved; several obvious ones are not.

The launch

One call does everything. launchToken takes the metadata, a config id and the quote asset, and returns the token and its curve:

launchToken(
  (string name, string symbol, string logo, string description,
   (string twitter, string telegram, string discord, string website, string farcaster) socials,
   address creatorFeeRecipient, uint16 creatorTaxBps, bool buybackEnabled,
   bytes32 expectedEconomics, bytes32 salt) params,
  uint256 launchConfigId,   // 0, the only config that exists
  address pairToken         // RDDT, not address(0)
) payable returns (address token, address curve)

Read live from the factory:

launchFee()0.0005 ETH, sent as msg.value
maxCreatorTaxBps()1000, so the creator fee caps at 10%
getLaunchConfig(0)supply 1e9, curve fee 100 bps, graduation 4.2 ETH
snipe tax9900 bps decaying to zero over 3 seconds
fee recipient timelock259,200 s, three days to change it

Two details are easy to get wrong. expectedEconomics is mandatory and must equal previewLaunchEconomics(configId, pairToken) read in the same call path. It is the contract's guard against terms shifting between quote and execution, and a stale value reverts. And MAX_LOGO_LENGTH is 512 characters, so the logo field holds a URL; an inlined data URI is around 32,000 characters and reverts MetadataTooLong() (0x85b8e2f4).

creatorFeeRecipient is set to the vault at launch. It is a single address, not a list, so a multi-way split would need a splitter contract in that slot.

How the fees actually arrive

This is the part that decides the whole architecture. Pons does not push fees. They are credited inside V2FeeEscrow and the recipient has to pull them: balanceOfToken(recipient, token) to read, claimToken(token) to collect. A plain wallet set as creatorFeeRecipient would sit there forever while its shares piled up in a contract it never called.

And because the escrow pays in the curve's quote asset, a curve quoted in RDDT credits the recipient in RDDT. No swap, no router, no oracle, no slippage, no sandwich. This matters more than it sounds: an agent that wanted to hold Reddit stock the ordinary way would need a venue for it, and it would have to trust a price. Here the exchange rate is whatever the market paid on the curve, and the settlement asset is the target asset.

One trap worth writing down: an empty escrow reverts its own NoBalance() (0xc2caa2a6) before any check in the calling contract runs. So a "nothing to do" harvest fails with the escrow's error, never the vault's.

The treasury

AgentTreasury is about two hundred lines and most of its value is in what it does not contain. There is no owner, no rescue, no withdraw, no delegatecall and no selfdestruct. Its complete list of state-changing functions is four, and tools/compile.mjs refuses to emit an artifact if a fifth ever appears, or if any of them grows a parameter that lets a caller name a destination.

Where value can go, exhaustively:

There is no path, for anybody including the deployer, that moves a token to a wallet. The promise is enforced by the absence of the function rather than by a policy, which is the only kind of promise you can check by reading. The cost is real, and worth saying plainly: a token sent here by mistake is gone.

function buybackAndBurn(uint256 amountIn, uint256 minOut) external returns (uint256 burnedOut) {
    if (address(curve) == address(0)) revert NotWired();
    uint256 budget = burnBudget;
    if (budget == 0) revert NothingToSpend();
    if (amountIn == 0 || amountIn > budget) amountIn = budget;   // cannot reach the treasury

    share.approve(address(curve), amountIn);                      // exactly this much, never more
    uint256 before = token.balanceOf(address(this));
    curve.buy(amountIn, minOut, address(this));
    burnedOut = token.balanceOf(address(this)) - before;
    if (burnedOut == 0) revert BoughtNothing();
    share.approve(address(curve), 0);

    token.transfer(BURN, burnedOut);                              // the only destination
}

Both moves are permissionless. The agent runs them on a loop, but the agent is not privileged. If it stops, anyone can keep the treasury current. The agent is the thing that bothers, not the thing that is trusted. The one thing a caller does control is minOut, and both the ops console and the loop compute it from the curve's own getReserves() in the same call path: a buyback sent with minOut = 0 is an open invitation to sandwich it.

There is one admin function, and it disables itself. wire() sets the token and the curve, once, and only the deploying wallet can call it. It has to exist: the curve does not exist until the launch, and the launch needs the treasury's address as creatorFeeRecipient. Each needs the other's address first, and CREATE2 does not break the cycle because the curve's own init code embeds the fee recipient. So the treasury is deployed first and wired second. It refuses any curve that does not both issue that token and quote in RDDT, and after its one successful call it reverts AlreadyWired() forever.

The agent loop

The loop lives in agent/agent.mjs and runs off-chain with one hot key. What it does, in order:

  1. Read the treasury's state(), one call for all seven numbers. Free, keyless, and the only thing that decides whether there is anything to do.
  2. If the escrow credit clears the threshold, simulate and send harvest(). A simulation that reverts is a transaction that would have burned gas for nothing.
  3. Re-read the budget, which the harvest just changed, and if it clears the threshold read the curve's reserves, compute a slippage floor from them, and send buybackAndBurn().
  4. Rebuild data/moves.json from the treasury's own Harvested and BoughtAndBurned logs, not from what the loop thinks it did, and redeploy the site only if the file changed.

The hot key holds gas and nothing else. It is not the treasury's owner, because the treasury has no owner; the worst a compromised agent key can do is call two permissionless functions that anyone could call anyway, or waste its own gas.

The agent does not claim to predict anything. There is no sentiment model, no Reddit scraping, no price forecast. If that ever gets built it will be described here in the past tense, after it exists, with the transactions to show for it.

The agent's portrait

The character in the hero is a still 3D render, deliberately. An earlier build put a rigged GLB in a live WebGL scene and it was worse in every way that matters: a generated mesh is lumpy where a generated render is clean, it cost 1.4 MB and a WebGL context to look worse than a 123 KB image, and it moved for no reason. The still won on looks and on weight, so the whole 3D runtime came out.

Cutting a render out cleanly is its own problem. A background remover returns a usable mask and a bad edge. It keeps a rim of the backdrop, which reads as a white outline on a dark page, and deleting those bright pixels ruins the silhouette instead. tools/matte.py treats the two separately: the mask is rebuilt (median, threshold, one pixel in, sub-pixel feather) and the fringe is replaced rather than deleted, by pushing interior colour outward past the silhouette. One case a brightness threshold cannot solve at all: a bright sliver of backdrop against dark wood, where the character's own head is brighter still. That one is handled by local contrast instead, a pixel goes only if it is much brighter than its own neighbourhood.

Every piece of art on the site is the same character, generated with Higgsfield (Nano Banana Pro, 2K) and cut down with sips:

FileSizeWhere
hero.webp1200×1484 · 123 KBthe agent at its desk, cut out on transparency, no frame and no halo, straight on the page
logo.png256² · alphathe nav mark, at 32px in the header
avatar.png1000²profile picture, full bleed so a circular crop never shows a corner of nothing
banner-x.png1500×500X header. Character on the right so the profile avatar cannot cover it
og.png1200×630link previews

The one live thing in the hero is the strip along the bottom of the portrait. assets/desk.js reads the treasury and writes the agent's state into it: watching, collecting when escrow has a credit waiting, or offline when no vault is deployed. It is a chain read, not a decoration: it cannot say the agent is working when it is not.

The whole page is three static files plus two small scripts. No framework, no bundler, no CDN at runtime, no WebGL.

What can break

Addresses

WhatAddress
PonsV2LaunchFactory0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e
V2FeeEscrow0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e
RDDT (Robinhood Token)0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C
AgentTreasurynot deployed yet
$RDDTRnot launched yet
Curvenot launched yet
Agent keynot running yet

Chain id 4663 · RPC https://rpc.mainnet.chain.robinhood.com · explorer robinhoodchain.blockscout.com