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:
- Half becomes treasury. Reddit stock the contract holds and cannot release. There is no function that would.
-
Half becomes a burn budget. Its only permitted destination is the curve:
the agent spends it buying
$RDDTR, and every token it buys goes to0x…dEaDin the same transaction.
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 tax | 9900 bps decaying to zero over 3 seconds |
| fee recipient timelock | 259,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:
treasuryRDDT: nowhere. No exit exists.burnBudgetRDDT: into the curve, in exchange for $RDDTR.- $RDDTR: to the burn address, in the same transaction it was bought in.
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:
-
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. -
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. -
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(). -
Rebuild
data/moves.jsonfrom the treasury's ownHarvestedandBoughtAndBurnedlogs, 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:
| File | Size | Where |
|---|---|---|
| hero.webp | 1200×1484 · 123 KB | the agent at its desk, cut out on transparency, no frame and no halo, straight on the page |
| logo.png | 256² · alpha | the nav mark, at 32px in the header |
| avatar.png | 1000² | profile picture, full bleed so a circular crop never shows a corner of nothing |
| banner-x.png | 1500×500 | X header. Character on the right so the profile avatar cannot cover it |
| og.png | 1200×630 | link 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
-
Pons could un-approve RDDT.
approvedPairTokensis owner-controlled. It would not touch an already-launched curve, but it would stop anyone from launching another one quoted the same way. - Graduation. The curve graduates at 4.2 ETH of the quote asset. What happens to the fee stream after graduation is Pons' behaviour, not this contract's, and it should be re-read against the factory before anyone relies on it.
-
The agent can stop. It is one process with one key. That is why
harvest()is permissionless: the vault does not depend on it being alive. - Nothing here is a claim on Reddit. RDDT is a tokenized share contract on a chain, subject to whoever administers that contract, which has pause, oracle and multiplier admin functions. The vault holds a token, with all of that token's risk.
- A mistaken transfer is unrecoverable. Deliberately. See above.
Addresses
| What | Address |
|---|---|
| PonsV2LaunchFactory | 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e |
| V2FeeEscrow | 0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e |
| RDDT (Robinhood Token) | 0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C |
| AgentTreasury | not deployed yet |
| $RDDTR | not launched yet |
| Curve | not launched yet |
| Agent key | not running yet |
Chain id 4663 · RPC https://rpc.mainnet.chain.robinhood.com ·
explorer robinhoodchain.blockscout.com