Opening launches on Ripples
Opening launches on Ripples
Integration
There are two ways in and they answer the same markets. Read the chain: on Robinhood a Ripples launch is a Uniswap v4 pool from its first block, on the same pool manager as every other v4 pool, so anything already watching that contract sees a launch the moment it opens. Or read our feed, which answers the DEX Screener and GeckoTerminal specifications for both chains from the same store our own pages read.
Everything below is generated from the deployment record and the compiled contracts. The same document as JSON is at /integrate/manifest.json.
The feed
DEX Screener Adapter Specs v1.1 and GeckoTerminal Integration API Standards v0.1, answered by one body. The routes are open: no key and no origin header.
| Network | Base URL | Venue key |
|---|---|---|
| solana | https://api.ripples.run/v1/dex/solana | ripples |
| robinhood | https://api.ripples.run/v1/dex/robinhood | ripples |
| Endpoint | Query | Answers |
|---|---|---|
/latest-block | none | { block } |
/asset | id | { asset } |
/pair | id | { pair } |
/events | fromBlock, toBlock | { events } |
fromBlock=10&toBlock=15 covers blocks 10 through 15.400 invalid_request with a message naming the cap. Set your chunk size at or below it./latest-block is the indexer's own cursor, never the chain. The head is the lowest indexed cursor across every source writing the chain, and /events stops at it, so a block is never served twice and never skipped. Stop at the head and you will never meet the cut.Four calls. The head first, then a window of trades, then the market and its two assets. Every URL below is the real one for the Robinhood feed.
# Ask how far the feed has indexed. Never read past this block. https://api.ripples.run/v1/dex/robinhood/latest-block # block.blockNumber is the head. Everything below stops there. # Read a window of trades. Both bounds are inclusive. https://api.ripples.run/v1/dex/robinhood/events?fromBlock=57699981&toBlock=57700000 # Each event carries pairId, the legs, priceNative and reserves. # Look up the market an event names. Its record is fixed at creation, so read it once. https://api.ripples.run/v1/dex/robinhood/pair?id=0x…poolId # asset0Id is the launch token and asset1Id is what it is priced in. # Look up each side of the market, for decimals and supply. https://api.ripples.run/v1/dex/robinhood/asset?id=0x…token # decimals is always present. Supplies are exact decimal strings.
A market that has reached its target keeps answering on /pair. On Robinhood it keeps trading in the same pool under permanent liquidity, so its trades keep arriving on /events. On Solana the trading moves to Raydium CPMM, and the feed carries nothing further for it.
Addresses
One hook per chain, and it is the only thing separating a Ripples pool from any other Uniswap v4 pool. Filter on the hooks field of the pool manager's Initialize and you have every Ripples market on the chain.
Every Ripples pool carries the same key: fee = 0 and tickSpacing = 200, on every chain and for every launch. Both are constants of the hook, so a deployment cannot change them and they are safe to hard code. The pool itself charges nothing; the hook charges everything.
| Contract | Address |
|---|---|
| Uniswap v4 pool manager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
| Launch hook | 0xaF59944A7d03B914567cb0272b7E588A7aE7AAC4 |
| Launch lens | 0x5E16EA0A8910053c333383CF8eb13369eFb5c0b8 |
| Token launch factory | 0xE89C1b33B6C2505414667bE7E5084774a72150e1 |
| Collection factory | 0x5136aBf2F4059E390459a7dc9FAD4b0859834894 |
| Read-only RPC | https://rpc.mainnet.chain.robinhood.com |
Pool key: fee = 0, tickSpacing = 200, hooks = 0xaF59944A7d03B914567cb0272b7E588A7aE7AAC4. The two currencies are sorted numeric ascending, and fixed for the pool's life; both currencies are ERC20s, because a launch settles in an approved quote asset and never in native ETH, which is what both feed standards require of an asset identifier. The pool id is keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks)).
The hook's permissions are the low fourteen bits of its own address, 0x2AC4: beforeInitialize beforeAddLiquidity beforeRemoveLiquidity beforeSwap afterSwap afterSwapReturnsDelta
| Contract | Address |
|---|---|
| Uniswap v4 pool manager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
| Launch hook | 0xaF59944A7d03B914567cb0272b7E588A7aE7AAC4 |
| Launch lens | 0x2BD4aB15858d35cedF837f236C51cD3901Eed372 |
| Token launch factory | 0x0338754d997967402896c9D45dFd5352E68606ba |
| Collection factory | 0x3A632Ce9B54C499abD837c1809a5369f0A224cDc |
| Read-only RPC | https://rpc.testnet.chain.robinhood.com |
Pool key: fee = 0, tickSpacing = 200, hooks = 0xaF59944A7d03B914567cb0272b7E588A7aE7AAC4. The two currencies are sorted numeric ascending, and fixed for the pool's life; both currencies are ERC20s, because a launch settles in an approved quote asset and never in native ETH, which is what both feed standards require of an asset identifier. The pool id is keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks)).
The hook's permissions are the low fourteen bits of its own address, 0x2AC4: beforeInitialize beforeAddLiquidity beforeRemoveLiquidity beforeSwap afterSwap afterSwapReturnsDelta
Events
Price and volume come from the pool manager, not from us. The hook's own events say what the pool manager cannot: which pool is a Ripples launch, what the hook took out of a swap and in which currency, and where the launch is in its life.
| Event | topic0 | Indexed |
|---|---|---|
Initialize(bytes32,address,address,uint24,int24,address,uint160,int24) | 0xdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d6438 | id, currency0, currency1 |
ModifyLiquidity(bytes32,address,int24,int24,int256,bytes32) | 0xf208f4912782fd25c7f114ca3723a2d5dd6f3bcc3ac8db5af63baa85f711d5ec | id, sender |
Swap(bytes32,address,int128,int128,uint160,uint128,int24,uint24) | 0x40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f | id, sender |
The hook takes its fee inside the swap, so the pool manager's Swap amounts overstate what the trader received by exactly the fee it charged. The fee lands in the swap's unspecified currency: on an exact-input buy that is the launch token, on an exact-input sell it is the asset the market is priced in, and on an exact-output swap it is whatever the swapper pays in, charged on top of the amount they named. Take the currency from the event rather than from the direction. Read TradeFeeCharged, CreatorTaxCharged and, inside the opening window, SnipeTaxCharged from the same transaction, and subtract all of them. Each carries the amount and the currency rather than a rate, precisely so a reader never has to reconstruct the schedule. None of them moves a token to any outside address: they are claims held inside the pool manager until they are claimed.
TradeFeeCharged is emitted once per payee, so one trade lands two of them whenever the launch pays a creator as well as the treasury, and the fee is their sum. CreatorTaxCharged is a different charge from a different rate: the trade fee is split with the treasury and the creator's tax is not, and a trader on a launch that sets one pays both.
| Event | topic0 | Indexed |
|---|---|---|
Claimed(address,address,uint256) | 0xf7a40077ff7a04c7e61f6f26fb13774259ddf1b6bce9ecf26a8276cdd3992683 | token, account |
ContributionRecorded(bytes32,address,uint256,uint256) | 0x3b848fcbc1d1d0f19a4a9f30e3990d2ca028a1972bf282879a6fde3e4b28dcba | poolId, from |
CreatorFeeRecipientSet(bytes32,address,address) | 0xe98b751fcb6b01f7da48fff9556bd647d42ca4105dc6220341437f98853c6090 | poolId, previous, recipient |
CreatorTaxCharged(bytes32,address,address,uint256) | 0x2876f994f3ad04672af9d383779387ed0d3bac89871b923c9f9eef41cd6db318 | poolId, currency, recipient |
Credited(address,address,uint256) | 0x4e45da441832cf53bdaa69235704fc0575e68210f459ee1562911024b12967d5 | token, account |
Graduated(bytes32,uint256,uint256) | 0xc1eaf6441a62895ddec3612cee606bd971cf6b1b38dd700f103e7b6b94167d0c | poolId |
GraduationRaiseSettled(bytes32,uint256) | 0xd83e1ca0da2f9ec27f30bfc1a0e691a966ad11db495309f9ba21e5c51bf3ad83 | poolId |
GraduationSettled(bytes32,uint128) | 0x0ee67cdb506e34995a59d35b93c370999743c9c5f4237b1b7f61fa3629cae904 | poolId |
LaunchRegistered(bytes32,address,address,address,address,uint256,uint160,int24,int24,uint128) | 0xc072459bb9c7ab9120cc03eec0a7110f7a20df41ccb3610b0c54814b3aaff318 | poolId, token, quote |
LaunchSeeded(bytes32,uint128,int24,int24) | 0x56a662ac2d06fd82f68115bc8aae74e0d9a11dc119aed160f41a5d4def5a5445 | poolId |
LaunchpadSet(address,bool) | 0x6bce717262ad8d508f67dc3376d3a257125260f8a426f2afef7296415f64850a | launchpad |
OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | previousOwner, newOwner |
OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | previousOwner, newOwner |
SnipeTaxCharged(bytes32,address,address,uint256,uint256) | 0x5b35e3889fff69d0365744fa6d7a696f04c94a77c6200b004fda6320ea34606e | poolId, currency, recipient |
TradeFeeCharged(bytes32,address,address,uint256) | 0x2285eb140ea82ca28170f503bd29870d3ff46c2cea01b0d3bf42deed0d8da500 | poolId, currency, recipient |
Filter on the emitting address as well as the topic. Two factories are deployed on every chain, they are rotated separately, and 10 of their administrative events are byte-identical signatures. A reader keying on the topic alone attributes one rail's administration to the other.
| Signature | topic0 | Emitted by |
|---|---|---|
AssetOriginSet(string) | 0x73b3415953bb883c64e683859b3e778e2ffbee212a395f92fe8b7a2784381407 | TokenLaunchFactory, LaunchpadFactory |
FeePassCollectionSet(address) | 0x23631d31dc72ddf493ae1a8e4578e58a5b375a1c6a5daae27c73c01278c072d2 | TokenLaunchFactory, LaunchpadFactory |
FeePassConsumed(uint256,address) | 0x2575cb7be131797b2b269f251e7bcc8713b9c113ab30a4df1c90f384365def77 | TokenLaunchFactory, LaunchpadFactory |
FeesWithdrawn(address,uint256) | 0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a | TokenLaunchFactory, LaunchpadFactory |
LaunchFeeSet(uint256) | 0x9c14cfb9531fb93979e22b461eaa41216ae6de79984d69ee4b411726defedeba | TokenLaunchFactory, LaunchpadFactory |
OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | TokenLaunchFactory, LaunchpadFactory |
OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | TokenLaunchFactory, LaunchpadFactory |
PlatformSignerSet(address) | 0xf1589df78975fe5494fa74969b25e6930dd0785c88cce9fb712c07e39aceedd9 | TokenLaunchFactory, LaunchpadFactory |
QuoteRegistrySet(address) | 0xc70b5b06e2432adbb407847681d30654be86cd6573a21454d58858ff7ec05db5 | TokenLaunchFactory, LaunchpadFactory |
TreasurySet(address) | 0x3c864541ef71378c6229510ed90f376565ee42d9c5e0904a984a9e863e6db44f | TokenLaunchFactory, LaunchpadFactory |
| Event | topic0 | Indexed |
|---|---|---|
AssetOriginSet(string) | 0x73b3415953bb883c64e683859b3e778e2ffbee212a395f92fe8b7a2784381407 | none |
FeePassCollectionSet(address) | 0x23631d31dc72ddf493ae1a8e4578e58a5b375a1c6a5daae27c73c01278c072d2 | collection |
FeePassConsumed(uint256,address) | 0x2575cb7be131797b2b269f251e7bcc8713b9c113ab30a4df1c90f384365def77 | passTokenId, creator |
FeesWithdrawn(address,uint256) | 0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a | to |
LaunchCreated(address,address,bytes32,address,address,(string,string,uint256,uint256,uint256,uint256,uint256,uint64,uint96,address,string,string,string,string,string,string,string)) | 0xe6b9a7f89a54ef27b5f1a729370db18a53da3bcedf25a9cce395d3a7151c9d15 | creator, token, poolId |
LaunchFeeSet(uint256) | 0x9c14cfb9531fb93979e22b461eaa41216ae6de79984d69ee4b411726defedeba | none |
LaunchHookSet(address) | 0x4cce86045ee56e14b861f315f9bbfd9d15354e2b7546aa36b0b6c0d637caaa22 | hook |
LinkedLaunchCreated(address,address,address,address,address) | 0x12279582cbf91e478c8256e79366d189bc65274e22711012d134561b98fcdb79 | creator, token |
NftProtocolFeeSet(uint96) | 0xac2a075822a6a2a357f96fc654b9ebe49d686520b38aacb2d6f0c378d7deab1b | none |
OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | previousOwner, newOwner |
OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | previousOwner, newOwner |
PlatformSignerSet(address) | 0xf1589df78975fe5494fa74969b25e6930dd0785c88cce9fb712c07e39aceedd9 | signer |
QuoteRegistrySet(address) | 0xc70b5b06e2432adbb407847681d30654be86cd6573a21454d58858ff7ec05db5 | registry |
TokenWithdrawn(address,address,uint256) | 0x8210728e7c071f615b840ee026032693858fbcd5e5359e67e438c890f59e5620 | token, to |
TreasurySet(address) | 0x3c864541ef71378c6229510ed90f376565ee42d9c5e0904a984a9e863e6db44f | treasury |
WaveCreated(address,address,address,uint256,uint96) | 0x9f1af47c78a2baeaaca67ab23f33a8b692892145caf22a42ec2c21f3550182ee | creator, locker |
| Event | topic0 | Indexed |
|---|---|---|
AssetOriginSet(string) | 0x73b3415953bb883c64e683859b3e778e2ffbee212a395f92fe8b7a2784381407 | none |
CollectionCreated(address,address,(string,string,uint256,uint256,uint256,uint64,uint64,uint8,string,string,uint96,address)) | 0x97cb7b021a2c09bd07f0519545e6cb005ae3c7bd3dc6d434a64c0308f7f833b2 | creator, collection |
DefaultProtocolFeeSet(uint96) | 0x95465226ac2117c565282226e964ccd12d2c9aa47cc47869f27d19ef1de07c33 | none |
FeePassCollectionSet(address) | 0x23631d31dc72ddf493ae1a8e4578e58a5b375a1c6a5daae27c73c01278c072d2 | collection |
FeePassConsumed(uint256,address) | 0x2575cb7be131797b2b269f251e7bcc8713b9c113ab30a4df1c90f384365def77 | passTokenId, creator |
FeesWithdrawn(address,uint256) | 0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a | to |
LaunchFeeSet(uint256) | 0x9c14cfb9531fb93979e22b461eaa41216ae6de79984d69ee4b411726defedeba | none |
OwnershipTransferStarted(address,address) | 0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700 | previousOwner, newOwner |
OwnershipTransferred(address,address) | 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0 | previousOwner, newOwner |
PlatformSignerSet(address) | 0xf1589df78975fe5494fa74969b25e6930dd0785c88cce9fb712c07e39aceedd9 | signer |
QuoteRegistrySet(address) | 0xc70b5b06e2432adbb407847681d30654be86cd6573a21454d58858ff7ec05db5 | registry |
TokenFeesWithdrawn(address,address,uint256) | 0x6241d2d4228642a412963629be2a38b8fea3c68fc02692659f2afb536393e3af | token, to |
TreasurySet(address) | 0x3c864541ef71378c6229510ed90f376565ee42d9c5e0904a984a9e863e6db44f | treasury |
Reads
Two calls list every launch a factory has created, a third says whether an address came from it, and the hook answers everything about the market behind a pool id. launchesOf takes a batch and returns zeroes for an id it does not know rather than reverting, so one bad id does not lose the other answers.
| Call | Selector | Returns |
|---|---|---|
launchCount() | 0x27cca59f | uint256 |
launches(uint256,uint256) | 0x90166851 | (address,address,address,address)[] |
isFromFactory(address) | 0x2e2ecd8d | bool |
| Call | Selector | Returns |
|---|---|---|
collectionCount() | 0xd57f966b | uint256 |
collections(uint256,uint256) | 0x7697c512 | address[] |
isFromFactory(address) | 0x2e2ecd8d | bool |
| Call | Selector | Returns |
|---|---|---|
poolKeyFor(address,address) | 0x8f15ae9b | (address,address,uint24,int24,address) |
poolIdOfToken(address) | 0x65d7b483 | bytes32 |
configOf(bytes32) | 0x6ef61607 | (address,address,address,address,address,address,address,uint96,uint96,uint96,uint64,uint64,uint64,uint96,address,uint256) |
stateOf(bytes32) | 0x64482ac4 | (uint8,uint128,uint256,uint256) |
planOf(bytes32) | 0xff798040 | (uint160,int24,int24,uint128,uint256) |
realQuote(bytes32) | 0x05ca6842 | uint256 |
graduationQuote(bytes32) | 0x4ce1c4a2 | uint256 |
graduationReady(bytes32) | 0x5315769b | bool |
snipeTaxBps(bytes32) | 0x7da3dafe | uint256 |
launchesOf is answered by a separate contract, listed per chain above where one is deployed. It holds no state, no money, and no permission: every field it returns it reads back out of the five hook views above and the pool manager, so a reader who does not want to trust it can reproduce the answer from those two addresses. Every chain the record publishes carries one. A chain listed without a lens has no batch read: enumerate its launches through the factory calls above and read each market from the hook views.
| Call | Selector | Returns |
|---|---|---|
HOOK() | 0xa54eb242 | address |
POOL_MANAGER() | 0x62308e85 | address |
launchesOf(bytes32[]) | 0x52c17af4 | (bytes32,(address,address,address,address,address,address,address,uint96,uint96,uint96,uint64,uint64,uint64,uint96,address,uint256),(uint160,int24,int24,uint128,uint256),(uint8,uint128,uint256,uint256),uint256,uint160,uint256)[] |
A Ripples market is an ordinary Uniswap v4 pool, so any router that speaks v4 trades one. There is one size that needs ours. A launch's liquidity is a single position over a single bounded price range, and the hook refuses any swap that would end outside it, so a router that places its swap with a wide price limit walks a buy larger than the market has left straight into that refusal. The order reverts whole rather than taking what was there.
LaunchRouter at 0x22DB9B2c6C6DB56ABC3fc196E4bC1FBb9c4338a6 takes that order. swapExactIn(key, zeroForOne, amountIn, minAmountOut, recipient, deadline) places the swap with the range end as its price limit, so the pool fills to the end of the curve and stops, and returns (spent, amountOut). It pulls only spent from the caller through Permit2, so the part of the order the market could not take never moves. minAmountOut is a price rather than a quantity: the router holds spent * minAmountOut <= amountIn * amountOut, which is amountOut >= minAmountOut on an order that spends in full and the same rate on the part of one that does not.
Read the fill from Filled(poolId, sender, recipient, zeroForOne, amountIn, spent, amountOut). spent below amountIn is a partial fill and the market is at the end of its curve. The pool manager's Swap reports the part that filled and says nothing about the part that did not, so a reader that wants the difference has to take it from here. CurveEndReached is the revert on a market with nothing left to sell, and it is the answer to "this launch is ready to graduate" rather than a fault.
Solana
A Solana launch is a program-owned account with virtual reserves rather than a pool, and a Raydium CPMM pool exists only once the launch reaches its target. Before that there is no pool to read: take the market from the feed above, or decode the launch account with the interface definition below.
| What | Value |
|---|---|
| Program | RippcKjgg9pkjHY7RKp6R1K8jPGwq2BRSJcgbErufjF |
| Interface definition | solana/idl/ripples.json at ff94c30263eb, version 0.1.0 |
| Instructions | 46 |
| Events | AssetMinted, AssetRevealed, AuthorityAccepted, AuthorityProposed, BeneficiaryFinalized, Bought, BuybackSwept, CollectionCreated, CollectionLinked, ConfigUpdated, ContributionRecorded, CreatorPayoutSet, EscrowClaimed, ExternalLaunchControllerSet, ExternalLaunchCreated, GraduationBegun, GraduationCancelled, GraduationFinalized, HolderRewardClaimed, HolderRewardEpochPublished, HolderRewardsInitialized, LaunchCreated, LiquidityLocked, LockedFeesCollected, MetadataUpdated, PoolCreated, QuoteAssetSet, RevenueWithdrawn, Sold, UnattributedRecovered, VestingClaimed |
Crosscurrent
A Crosscurrent opens a token on Solana and on Robinhood Chain from one saved record. Each side's opening buy fills in a contract on its own chain, and nothing moves between the chains. Anyone holding the record can recompute its key and find both contracts from chain state alone. On Solana that contract is an account of dual_fill, the Crosscurrent program, and on Robinhood Chain it is a contract a Crosscurrent factory creates.
| Contract or program | Mainnet (4663, mainnet-beta) | Test networks (46630, devnet) |
|---|---|---|
| Token launch factory | 0xE89C1b33B6C2505414667bE7E5084774a72150e1 | 0x0338754d997967402896c9D45dFd5352E68606ba |
| Factory for Crosscurrents | 0xc6ca96a76D51BE7C2092e0aDe08052ab7De08A87 | 0xAF790e6AfE2981af31593cBF531a67C284841C65 |
| Factory for Crosscurrents with a collection | 0xffcD39866B78be6a4f8B7Ff39b980bD5aa528Ce9 | 0xe74cf6C2419353f477643612B4fbf3D2692D9a6F |
| Factory for Crosscurrents run by an agent | 0xCdCA84dF5BcdAeb0fb0Fd2BAbF2887F7B387c08D | 0x0560333e52D843Ad8DCf0E02a6f5c0f2f60744FA |
| Keeper that opens filled sides | 0xB8F0B600D2b6b60364337742efA6592EF7E2d971 | 0xaB8C25d68B728f7665d6D6627D8017B0A2422C25 |
| Launch program | RippcKjgg9pkjHY7RKp6R1K8jPGwq2BRSJcgbErufjF | RippcKjgg9pkjHY7RKp6R1K8jPGwq2BRSJcgbErufjF |
| dual_fill program | 98pdDLeZS9qJxz8e5TubnNyVh4YT5rsA2Vm8X7NbpNRT | 98pdDLeZS9qJxz8e5TubnNyVh4YT5rsA2Vm8X7NbpNRT |
| dual_fill upgrade authority | Bz3ovxtMncmF48SYam8hQpB3qB4CGeTV4HEY1E3HRHup | GL6b9QJAimMYaHf68q1BGUy3xcm61RoM8J8ZxupperEe |
| dual_fill IDL | dual_fill | dual_fill |
| Earlier dual_fill programs | Cros7xBrWN2ty4BZs1fmugFvsXTQKweBqFEqcCtTWRVq (dual_fill.Cros7xBrWN2ty4BZs1fmugFvsXTQKweBqFEqcCtTWRVq), DuaLwpVYtkyuRLPFDF9BcJx2YiDCXQ3n5PsQ4C8en1wj (dual_fill.mainnet-beta) | Cros7xBrWN2ty4BZs1fmugFvsXTQKweBqFEqcCtTWRVq (dual_fill.Cros7xBrWN2ty4BZs1fmugFvsXTQKweBqFEqcCtTWRVq), DuaLwpVYtkyuRLPFDF9BcJx2YiDCXQ3n5PsQ4C8en1wj (dual_fill.devnet) |
| agent_vault program | R467NctqAyhF21QV2BAVRUW8D9iT6bXuWAUYxJWmKb9 | R467NctqAyhF21QV2BAVRUW8D9iT6bXuWAUYxJWmKb9 |
| agent_vault upgrade authority | Bz3ovxtMncmF48SYam8hQpB3qB4CGeTV4HEY1E3HRHup | GL6b9QJAimMYaHf68q1BGUy3xcm61RoM8J8ZxupperEe |
| agent_vault IDL | agent_vault | agent_vault |
A program with an upgrade authority can still have its code replaced by that key. The manifest carries each program's binary and IDL sha256 from its deployment record, and the IDL row names the file under Interfaces that describes the program on that cluster. An earlier dual_fill program keeps the Crosscurrents it opened, and each is listed with the IDL it runs.
dualFillKey = sha256(utf8(domain) || 0x00 || utf8(canonicalJson(core))), where the domain is ripples-dual-fill-v1 and canonical JSON has keys sorted, integers above 2^53 as decimal strings, no whitespace. The id in a Crosscurrent's URL is base64url(dualFillKey[0..16]), no padding, 22 characters. The core is the record both chains commit to, and @ripplesdotrun/agent-sdk recomputes the key from it before it signs.
core {"deadline":1790000000,"dualFillFactory":"0x00000000000000000000000000000000000071f1","dualFillProgram":"EmuBkApiTEXxPG9JrsZ6xeHdVvP7tYpRQVemrjRdz8Wu","evmChainId":46630,"identitySha256":"311a1911fcf4321fda99a8280f03d5e81df594a7e162f599e8d438e5aad1dc16","mode":"fill-first","nonce":"12345678901234567890","openAlone":false,"publicUntil":1789980000,"rhCreator":"0xd065914767ba406e17ff69dfa63d979eb3d7c0d9","rhTokenFactory":"0x0338754d997967402896c9d45dfd5352e68606ba","solanaCluster":"devnet","solanaCreator":"257dxMGYQNp7bb2BMXhDhqfj4fZkGAN2bkuM8cdHSpoU","solanaProgram":"RippcKjgg9pkjHY7RKp6R1K8jPGwq2BRSJcgbErufjF","termsSha256":"c1d53b286cd40bcf5d9e396a740890774bcd504714ccd70fda5a17a023cb5867","v":1}
key 0x7d706417a3d20dcafcc371f8f131876442711f88975490baefb4b3c38a3e9d78
id fXBkF6PSDcr8w3H48TGHZAV20 has none of these optional core keys, so its key hashes the core as printed:
agent: present only when an agent is the creator.collectionSha256: present only for a Crosscurrent with a collection.fillOf(rhCreator, dualFillKey) on the factory the core names in dualFillFactory. For an agent side, first read treasuryOf(rhCreator, dualFillKey) on agent.robinhood.factory: the treasury is the fill's creator.["fill", solanaCreator, dualFillKey] under dualFillProgram. For an agent side, derive the agent from solanaCreator under agent.solana.program, then its vault, and use the vault as the fill's creator.| Account | Program | Seeds | Bytes | Filter offsets |
|---|---|---|---|---|
| fill | dual_fill | ["fill", creator, dualFillKey] | 611 | none |
| vault | dual_fill | ["vault", fill] | system account | none |
| receipt | dual_fill | ["receipt", fill, contributor] | 81 | fill at 8, contributor at 40 |
| linkedTerms | dual_fill | ["linked", fill] | 497 | none |
| agent | agent_vault | ["agent", sponsor, dualFillKey] | 537 | sponsor at 8 |
| agentVault | agent_vault | ["vault", agent] | system account | none |
Filter on the fills a factory created as well as the topic. A fill emits the same events whichever factory made it, and 11 signatures are shared this way. Take fill addresses from each factory's FillCreated, whose signatures differ, and treasury addresses from AgentDualFillCreated.
| Event | topic0 | Indexed |
|---|---|---|
FillCreated(address,address,bytes32,uint256,uint64,uint64,bool,uint256,bytes32,(string,string,uint256,uint256,uint256,uint256,uint256,uint64,uint96,address,string,string,string,string,string,string,string)) | 0x00a4d22a91b6b8283c2b127728ec8deeda1a3ae5cb1c462f802b4cf6f0733ce4 | fill, creator, dualFillKey |
| Event | topic0 | Indexed |
|---|---|---|
Aborted(address) | 0x13c3922f06c44c3cac6a2c721f8ec8db793360b0180d5e560ca2028de2037eaa | opener |
Cancelled() | 0x63b958841f79ab97cb5456da181454b9932c0e15a3b17f1cbd27e2a8bc610437 | none |
Claimed(address,uint256,uint256) | 0x987d620f307ff6b94d58743cb7a7509f24071586a77759b77c2d4e29f75a2f9a | account |
CreatorFeesForwarded(uint256,uint256) | 0xaa330b55610f73593b7470d1932f2523ccc17286e023c6ec532d595cc6a2acd5 | none |
Deposited(address,uint256,uint256,bool) | 0x65e6f89b6907d6277741ee4ccbc4ae260163a17f16bbb55b5344dff064867c40 | account |
FeeBudgetReturned(uint256) | 0xc721e23deda263c544f0b77b4fbc995baa8253a1443e8c5b4b64b1d7568596a6 | none |
FeesHandedOver(address) | 0xbb1a6597846990220fd35033701ecd7153257e500949af585ec78e5e27cfebe3 | creator |
Filled(uint256) | 0xd14df08fabc3175c71c9c6b212bd0fd290d0e78a5f809c5f28deb5ba6f029b91 | none |
Opened(address,address,bytes32,address,uint256,uint256,uint256) | 0xf849d2586cabd9983f79f1db336a8684e1489b070cefc38d37db4d37858eff0e | token, locker, poolId |
Refunded(address,uint256) | 0xd7dee2702d63ad89917b6a4da9981c90c4d24f8c2bdfd64c604ecae57d8d0651 | account |
Withdrawn(address,uint256,uint256) | 0x92ccf450a286a957af52509bc1c9939d1a6a481783e142e41e2499f0bb66ebc6 | account |
| Event | topic0 | Indexed |
|---|---|---|
FillCreated(address,address,bytes32,uint256,uint64,uint64,bool,uint256,bytes32,(string,string,uint256,uint256,uint256,uint256,uint256,uint64,uint96,address,string,string,string,string,string,string,string),(string,string,uint256,uint256,uint256,uint64,uint64,uint8,string,string,uint96,address),(uint96,uint96,uint64,uint64,bool,bool)) | 0x3f5bfa35fd1339f5fb34156fdedbd6866ad2474437afb17b271e113136c28ccf | fill, creator, dualFillKey |
| Event | topic0 | Indexed |
|---|---|---|
Aborted(address) | 0x13c3922f06c44c3cac6a2c721f8ec8db793360b0180d5e560ca2028de2037eaa | opener |
AssetForwarded(address,uint256) | 0x05c47bff5ba0a5fce90c1fa237cb294e2be5093759e575a4c5f46b1e60201875 | asset |
Cancelled() | 0x63b958841f79ab97cb5456da181454b9932c0e15a3b17f1cbd27e2a8bc610437 | none |
Claimed(address,uint256,uint256) | 0x987d620f307ff6b94d58743cb7a7509f24071586a77759b77c2d4e29f75a2f9a | account |
CollectionOpened(address,address) | 0x2b23f7c6fc26148cee31ca8edf5b8e65c5d38af84c2e194d0366d330db459a7c | collection, vesting |
CreatorFeesForwarded(uint256,uint256) | 0xaa330b55610f73593b7470d1932f2523ccc17286e023c6ec532d595cc6a2acd5 | none |
Deposited(address,uint256,uint256,bool) | 0x65e6f89b6907d6277741ee4ccbc4ae260163a17f16bbb55b5344dff064867c40 | account |
FeeBudgetReturned(uint256) | 0xc721e23deda263c544f0b77b4fbc995baa8253a1443e8c5b4b64b1d7568596a6 | none |
FeesHandedOver(address) | 0xbb1a6597846990220fd35033701ecd7153257e500949af585ec78e5e27cfebe3 | creator |
Filled(uint256) | 0xd14df08fabc3175c71c9c6b212bd0fd290d0e78a5f809c5f28deb5ba6f029b91 | none |
Opened(address,address,bytes32,address,uint256,uint256,uint256) | 0xf849d2586cabd9983f79f1db336a8684e1489b070cefc38d37db4d37858eff0e | token, locker, poolId |
Refunded(address,uint256) | 0xd7dee2702d63ad89917b6a4da9981c90c4d24f8c2bdfd64c604ecae57d8d0651 | account |
Withdrawn(address,uint256,uint256) | 0x92ccf450a286a957af52509bc1c9939d1a6a481783e142e41e2499f0bb66ebc6 | account |
| Event | topic0 | Indexed |
|---|---|---|
AgentDualFillCreated(address,address,address,address,bytes32,uint256) | 0xebe6fc7218fc60683b8bbe69d8e4e6b14dcd8ce8b6c1379426f7fc07e0425bd9 | creator, treasury, operator |
| Event | topic0 | Indexed |
|---|---|---|
BondReturned(address,uint256) | 0x9347cad0e396dd77ea84d092d0e565cf227a1672e5981022e22c07d8d09e12bc | to |
Bought(uint256,uint256) | 0x3ccb2ab6980b218b1dd4974b07365cd90a191e170c611da46262fecc208bd661 | none |
Bound(address,address) | 0x0d128562eaa47ab89086803e64a0f96847c0ed3cc63c26251f29ba1aede09d4e | token, locker |
FeesClaimed(uint256,uint256) | 0xa1f87f32d0f17fab0242ca800d736293de8988c14b27747e218cf13d5c249f53 | none |
FillCancelled(address) | 0x5924e8f5c663cbf9d94e93dabad9ac7323f82cfe85069dc40c4ac75df47cf2a4 | fill |
FillStarted(address,bytes32,uint256) | 0x033bda7bf11fdf18549a1f433edc81b9d5822672d5b69dc6e6c4e057a4251640 | fill, dualFillKey |
Noted(bytes32,string) | 0xd4d08a94163d1e1ffbf270c31a57b5f1502d91abcd6ac037e0b62cc5af7e4aef | digest |
OperatorRetired() | 0xa68770f5e7c6c8b6662c89b74137de3ebddabd56f9d487ce9d8ea60e0bab4a8a | none |
Paid(address,uint256) | 0x737c69225d647e5994eab1a6c301bf6d9232beb2759ae1e27a8966b4732bc489 | to |
RunwayReturned(address,uint256) | 0x796a3a10564d81bea438b8ea0d5c4f97140ef425a506ccfc6366d57b3a46e93d | to |
Sold(uint256,uint256) | 0x7ea63cabf285309a7f2750def3e745dd81fc85e39926c5b47f0bd45938e1a412 | none |
| Call | Selector | Returns |
|---|---|---|
fillCount() | 0x6051e4dc | uint256 |
fills(uint256,uint256) | 0x7dca72b7 | address[] |
fillOf(address,bytes32) | 0x08d2ffc7 | address |
isFill(address) | 0x3b52525a | bool |
| Call | Selector | Returns |
|---|---|---|
DUAL_FILL_KEY() | 0x0e8ceacc | bytes32 |
CREATOR() | 0xe4fbb609 | address |
PARAMS_HASH() | 0x1411a9ef | bytes32 |
terms() | 0xd5025625 | (address,bytes32,uint256,uint64,uint64,bool,uint256,bytes32,address,address,address,bool,address) |
snapshot() | 0x9711715a | (uint8,uint256,uint256,uint256,uint64,address,address,bytes32,uint256,uint256,uint256,bool,bool,bool) |
status() | 0x200d2ed2 | uint8 |
depositOf(address) | 0x23e3fbd5 | uint256 |
claimable(address) | 0x402914f5 | uint256, uint256 |
refundable() | 0xbf89662d | bool |
token() | 0xfc0c546a | address |
poolId() | 0x3e0dc34e | bytes32 |
openedAt() | 0x38930203 | uint64 |
| Call | Selector | Returns |
|---|---|---|
fillCount() | 0x6051e4dc | uint256 |
fills(uint256,uint256) | 0x7dca72b7 | address[] |
fillOf(address,bytes32) | 0x08d2ffc7 | address |
isFill(address) | 0x3b52525a | bool |
curveSupplyFor(uint96) | 0xdb7bc409 | uint256 |
| Call | Selector | Returns |
|---|---|---|
collection() | 0x7de1e536 | address |
vesting() | 0x44c63eec | address |
| Call | Selector | Returns |
|---|---|---|
treasuryOf(address,bytes32) | 0x9edb32bd | address |
treasuryCount() | 0xc0d10bd3 | uint256 |
treasuries(uint256,uint256) | 0x36169483 | address[] |
isFromFactory(address) | 0x2e2ecd8d | bool |
| Call | Selector | Returns |
|---|---|---|
CREATOR() | 0xe4fbb609 | address |
OPERATOR() | 0x983d2737 | address |
fill() | 0xd9c55ce1 | address |
charter() | 0xc1aadb4d | address, uint128, uint128, uint128, uint32, address[] |
remainingToday() | 0xfc3c5515 | uint256, uint256, uint256 |
boughtTokens() | 0x582c6d70 | uint256 |
sellableTokens() | 0x808bcddc | uint256 |
payees() | 0x4e8086aa | address[] |
retired() | 0x2eb38ae0 | bool |
| Interface | Kind | sha256 |
|---|---|---|
| DualFillFactory | ABI | 03fa98cbb68a540f6b08740101167f7a5b385363da58a8168922a100c0fc7eb8 |
| DualFill | ABI | ca0604843ff21e99784f513b9ef0dcbd175d75849fa5d293f7b40bc209b92aa2 |
| LinkedDualFillFactory | ABI | 9b4e277866be8461d24bf6c5fd3ae9acb2667568ac6511234b076dea77df033d |
| LinkedDualFill | ABI | 9d7b309dcb49b08d1fb35a465ee0bd2cdc0a1d900f1c66e1149be20917237f58 |
| DualFillAgentFactory | ABI | 9e3aace11bec64624f45c4fdb3da4a32daa773f34afe97b24981a60722500427 |
| DualFillAgentTreasury | ABI | 14f28dc37c34ea5921b41c69116cd02386437a98beb883a9dc822bd46ef34ed9 |
| dual_fill | Anchor IDL | 43cb738a99fe6d3ea47c7af9e480b92bdd4c23d3e304dc7391373a8022accf03 |
| dual_fill.Cros7xBrWN2ty4BZs1fmugFvsXTQKweBqFEqcCtTWRVq | Anchor IDL | ce5b0e9ce9cfd9f3e8b4526a3c02be864ffca58fcc185d489540e4373ebeb6b7 |
| dual_fill.mainnet-beta | Anchor IDL | 957d2184ac7e0c1ef4c6f7978b821e697c470c74ed59d096efeec54faa8aa5a0 |
| dual_fill.devnet | Anchor IDL | deeee916af52b731a8241b87e77664d20349f8f71ef6dc0c0d87fe9be669b436 |
| agent_vault | Anchor IDL | e1f879aea10b13c3c8af7ea6cf248bf02e1a37439d1ee1ab0ade157fb93bcb36 |
Every path below is on https://api.ripples.run. A route that needs a session takes one from a Robinhood wallet: POST /v1/auth/evm/challenge returns a message to sign, and POST /v1/auth/evm/verify exchanges the signature for a session.
| Route | Session | Answers |
|---|---|---|
GET /v1/dual-fills | None | Crosscurrents on the board, newest first, or every Crosscurrent of one creator. |
POST /v1/dual-fills | Wallet | Save a Crosscurrent before either chain sees a transaction, and receive everything both chains commit to. |
GET /v1/dual-fills/terms | None | Today's terms for a token, combined or agent Crosscurrent, the contracts they use and the shapes offered. |
GET /v1/dual-fills/by-address/:address | None | The Crosscurrent that recorded an address, with the side it belongs to and its role there. |
POST /v1/dual-fills/artwork | Wallet | Start a collection's artwork draft, which takes pieces for 24 hours. |
PUT /v1/dual-fills/artwork/:draft/pieces/:tokenId | Wallet | Upload one piece of a draft's artwork. |
POST /v1/dual-fills/artwork/:draft/seal | Wallet | Publish a draft as content-addressed folders, one for each chain its pieces mint on. |
GET /v1/dual-fills/:id | None | One Crosscurrent as both chains hold it now. |
DELETE /v1/dual-fills/:id | Wallet | Stop setting up a Crosscurrent before either side exists. |
POST /v1/dual-fills/:id/legs | None | Add a side once its transaction has confirmed; the API checks it against chain state. |
POST /v1/dual-fills/:id/reprice | Wallet | Price an Open now Crosscurrent's Solana side again before it is created. |
Every answer about a Crosscurrent starts with statusLabel and summary, the words ripples.run shows.
The manifest
Every address, topic, selector and rule on this page is read from one generated file, written from our deployment record and the compiled contracts. Nothing on this page is typed by hand, and a redeploy that moves an address moves the page.
The addresses on this page are the current generation. An earlier token launch factory still holds the launches it made, and those markets still trade, so it still answers launchCount and launches for them. An indexer covering every Ripples market on Robinhood reads both factories, and both are named in the address list. Every market either one created shares the same launch hook and the same pool key, so a reader filtering on the hook sees all of them without knowing which factory made which.
The same document as JSON is at /integrate/manifest.json. If an address here ever disagrees with the chain, the chain is right and we want to hear about it: the support page reaches us.