Opening launches on Ripples
Opening launches on Ripples
Documentation
Every chain Ripples runs on has its own programs, calls and settlement asset. Start with the one you are building on. If you are here to launch something rather than integrate, the creator guide is the shorter road.
Every address
Listed in one place
What a launch freezes lists every address we deployed or hold. One you were sent as ours that is not on it did not come from us.
Every key
Its limits in writing
The same list states both funding targets, the fees, what a creator earns, the pool lock, and what each of our keys can and cannot do.
Feeds and indexers
One generated document
Integrate carries every address, event topic and call, plus the market feed that answers DEX Screener and GeckoTerminal for both chains.
For creators
You need a browser wallet, a name, a ticker, and enough of the chain's own asset to pay for the launch. Any launch that creates a token asks for a picture and a description as well, on either chain. On Robinhood the token keeps them: the image link, the description and your links are written into the token when it is created, and nothing can change them afterwards. On Solana the token points at a details file instead. An NFT-only launch on Robinhood asks for neither: it carries its own artwork, and a collection that adds artwork later points at a details file you publish yourself, which is covered under Preparing artwork. Everything else is decided in the launch form, which adds up everything the transaction will spend and names each part before your wallet is asked to approve it.
Optional creator fees
A creator can hand their whole future trading-fee income to the people holding the token. The decision is permanent on every chain: once fees are routed, no one can reverse it or point it somewhere else, so treat it as final before you sign. Where holder rewards are active on chain, the wallet currently receiving creator fees routes 100% of future creator trading fees to a distributor built for that launch. Start it under Advanced while launching, or open Holder fee sharing on an existing token launch in Account. On Solana one confirmed request creates the distributor and routes the fees. On the contract chains it takes two: preparing the distributor changes nothing on its own, and the second transaction is what moves the fees.
On a three-chain launch, the option in the form covers the Robinhood and Arc markets. The Solana market takes a second confirmation: its control appears on the launch page once that market opens, and the creator signs there.
This is separate from pricing a market in a stock token or recording a StockLink. NFT sale proceeds and creator balances earned before routing remain separate. Rewards contain actual earned assets: the paired asset, the launch token, or both. Eligible balances are weighted by how long they were held during each period. There is no fixed yield; a period with no distributable fees pays nothing. On Solana, pre-graduation creator trading fees and post-graduation fees actually collected from the locked pool use the same recorded creator share. The pool's fee rate and collected amounts differ from curve trading fees.
Ripples reads public holding history and publishes a funded allocation for each period. What Ripples can and cannot do with it is set out in the Terms.
Once a period is published, Ripples sends each holder's share to their wallet. Ripples pays the network fee and, on Solana, the rent of any account the payment needs. Nothing is required from the holder. A share too small to be worth its network fee is left in Account to claim.
Account shows each published period and both amounts in each asset's own units, and a period that has not arrived can be claimed there. Ripples checks the claim against the chain before a wallet is asked to sign, and refuses one that has already been paid. Both assets are claimed together; an issuer hold on a required transfer leaves the claim unspent until it lifts. A claim you make yourself pays network fees and any new account rent in the network's own asset. A display multiplier changes a wallet's display, not the amount transferred.
Public allocations are available from GET /v1/holder-rewards with rail, account and an optional cursor. Full epoch proofs use GET /v1/holder-rewards/epochs/{distributor}/{epoch}?rail=.... Rewards are offered only where the chain's own factory or program supports them. A setting on this site cannot turn them on.
Launch types
A token launch creates one fixed supply of a token and a market where anyone can buy and sell it. An NFT collection launch creates a drop people buy from directly. A combined launch creates both and connects them, which is the launch type Ripples was built for.
In a combined launch, part of every NFT payment goes into the token's market instead of to you, and the wallet that bought the NFT is recorded for a share of the token supply. That share is not paid out immediately: it waits for the market to reach its funding target, then releases gradually. The rest of each payment is yours, and you take it out from your account page whenever you want.
The launch form offers three settings for that. They differ in how much of each NFT payment the market takes, how much of the token supply the buyers are reserved, and how long they wait for it:
A launch that carries its collection across chains offers Spark and Wave. Tide reserves more of the token supply than a collection shared between chains can.
Every collection keeps its own copy of those four numbers from the moment it is created, so they describe the launch you make today rather than a rule the network applies to everyone. They cannot be changed afterwards.
Yes. Token, NFT collection, and combined launches are all open to any wallet on both chains.
Pick the chain first, then the launch type. You can change either while you are still filling the form. Once the launch is created, neither moves.
On Solana, your wallet asks once to sign in to Ripples, then once to create the launch; a combined launch adds a second transaction for the collection, and trading opens when both are confirmed. Only transactions cost SOL. On Robinhood, your wallet asks once to let the factory spend the ETH launch fee, then once to create the launch. A combined launch there is one transaction.
Launch types
An agent launch is a combined launch that the agent opens for itself. A contract is created for it and opens the launch in the same transaction, which makes that contract the creator of record: a 70% share of every trade fee is paid into it and becomes the agent's working capital rather than your income.
From there the agent works its own launch. It can buy its own token on its own market, sell it back inside a ceiling you set, mint from its own collection, pay addresses you named, and write to its public run log. It cannot do anything else. There is no owner, no setter and no withdrawal, so the limits you set are the limits for the life of the launch, and you cannot raise them afterwards any more than anyone else can.
You set seven things: the wallet that works the agent, the runway that goes into the treasury at launch, what it can spend in a day, what it can spend in one transaction, how much of its own token it can sell in a day, how many pieces it can mint in a day, and the addresses it can pay. The daily spending limit covers everything together, so buying, minting and paying all draw on the same budget. A sell limit of zero means the agent can never sell its own token, which is what the form opens on.
An agent launch takes the same two wallet signatures any other launch on Robinhood takes: one spending permission for the ETH, then the launch itself. The contract, its opening funds and the launch all land in that one transaction, so there is no state where the agent holds money and has no market.
Two things follow from the contract holding everything. Pieces the agent mints from its own collection stay with it, because it can pay published addresses and cannot transfer an NFT. And the artwork settings stay yours: you point the collection at its pictures and freeze them through the agent, the same way you would on any other launch.
Launch types
Give one coin a token on each chain with the same name, ticker and supply, and one image and description shown on both markets. In the launch form, choose a pair of chains, then choose how the two markets open.
Fill first creates a contract on each chain that holds an opening buy anyone can add to. For the first 30 minutes any wallet can join with up to a quarter of a side's target. After that, a side takes only what it still needs. Once both sides are full, Ripples opens the Robinhood market and then the Solana market, and each side's target becomes its market's first buy. That buy pays no opening tax, the tax on buys in a new market's first seconds.
Everyone on a side pays the same average price and gets tokens in proportion to what they put in. If more came in than the side needed, every share is scaled down by the same fraction and the rest comes back with the tokens.
Open now launches both markets from your own two wallets, one after the other, with an equal opening buy on each. Launch the second within 10 minutes of the first to pair them as one Crosscurrent; after that, the first stays a launch of its own.
You need a Solana wallet and a Robinhood wallet connected at once, a name, a ticker and an image. Your share of the trade fee on each chain is paid to the wallet you created that side with.
A Fill first deadline is at least 31 minutes and at most 24 hours after you create it. Each side's opening buy is capped at 40% of that side's funding target, so both markets open as live markets below their targets. An opening buy at the cap holds about 52% of each token's supply, and the review shows the exact share before you create.
A Crosscurrent's two funding targets are set to the same value when it is created. Every other launch keeps its own chain's target.
Choose Crosscurrent with a collection to open a token on each chain with a collection. Publish the artwork before you create: it is fixed, and minting opens when the markets open. Until a market reaches its funding target, part of every mint on its chain goes into that market. People who mint before then share part of that chain's token, released from when the market reaches its target: with Spark, 2% over 90 days, starting 14 days later; with Wave, 5% over a year, starting 30 days later. Tokens from a mint stay with the wallet that minted, and a Crosscurrent's collection cannot take a later drop.
Both tokens open with a supply of 1,065,000,000 at the same price, about three times a Crosscurrent's without a collection. When a market reaches its funding target, tokens nobody bought are burned, and the share for people who mint is kept only where someone minted. So the Robinhood token can end with up to 2.9% more supply than the Solana token with Spark, or 7.2% with Wave.
Launching from a post
Tag @ripplesdotrun in a post that says launch, give it a ticker and attach a picture. That is the whole form. A token and a collection are created on Robinhood, the picture becomes the artwork, and a reply under your post carries the links. Everything else below is optional and can be said in any order, in the words you would use anyway. A post that only talks about a launch, with no ticker in it, is left alone.
The launch is made by Ripples and held for you until you claim it. Claiming is one signature on the claim page the thread links to, and it hands you the creator fee and piece #1. A post that says for @someone launches it for them instead, and it is their claim to make.
$KOI. Up to ten letters and digits with at least one letter, so a ticker may begin with a digit. Digits alone are read as a number rather than a ticker, which is how a price written in the post stays a price. The first ticker in the post is the launch's own.for @alice. The reply is addressed to them and the claim is theirs.33 pieces, and equally 7 editions, 12 nfts, 5 items, 9 pcs or x1000. Any number from one upwards. Say nothing and it is 100. A bare number is left in the name, so 50 Cent is a name and not a size.paired with GLD, or pair with, pair it with, pair it up with, with or without a dollar sign. The market then prices, trades and charges its fees in that stock instead of in ETH. It has to be a stock Ripples prices launches against; a ticker outside that set is answered with the reason.2% creator fee or creator fee of 2%. Anywhere from 1% to 10%, charged on every trade on top of the 1% everyone pays. Say nothing and it is 2%.half of that to holders, or all, everything, or a figure like 30% of that to holders. Anywhere from 1% to 100% of your own fee, paid to holders in the stock the launch is paired with. It needs a pairing, because that is what it is paid in.hey @ripplesdotrun launch $JPMEME JPEG Morgan for @alice, 33 pieces,
paired with GLD, 2% creator fee, with half of that going to holdersThat makes a 33-piece collection called JPEG Morgan, trading against gold, charging 2% to the creator on every trade, half of which goes back to whoever is holding, paid in gold. Alice claims it.
A launch that promised a share gets two contracts of its own at the moment it is made. One divides every creator fee at the share the post named and sends the creator's part straight on. The other holds what is owed to holders and pays it out. The share is set when the launch is made and there is no function anywhere for changing it afterwards.
What each wallet is owed is worked out from how long it held rather than from what it held at one moment, so buying just before a payout earns close to nothing. Periods close about once a day. Nothing expires, and what is owed can be taken in Account under paired stock rewards, or by anyone pushing it on a holder's behalf.
A post that cannot be launched gets a reply saying which part to change: a missing picture, a missing or over-long ticker, a stock nobody here prices against, a fee or a share outside its range, a share with no pairing to be paid in, or a collection of no pieces. Ten launches a day per account, for yourself or for someone else; the reply says when the next one is due.
Cost
The launch fee is set on chain and read when the form opens, so the form shows you the fee that will actually be charged rather than one written down here. Below it, “Total at approval” adds up everything else the transaction will spend, with the parts named underneath, and the form refuses to submit if your wallet cannot cover it. On Robinhood that total is the ETH side alone, because the network fee comes out of a separate ETH balance; the line says so.
The metadata charge goes to Metaplex rather than to Ripples, and only a launch that creates a token pays it. Most of the account deposit stays recoverable on chain rather than being spent.
Your market's trade fee is split: part of it reaches you on every trade from the first one, and the rest goes to Ripples. The exact split is shown in the form before you launch. At the target the market closes and its liquidity moves into a locked Raydium pool, and the same share of the fees that position collects is yours. A market keeps the schedule it launched under, so an older market can carry a different fee, split, or target.
Two balances matter here and they are not interchangeable. Launches, mints and trades are charged in ETH; every transaction also costs a network fee in ETH. A wallet holding only ETH cannot send anything. You take a 30% share of the trade fee from your market's first trade and the treasury takes the rest, before the target and after it. A market on Solana pays its creator from its first trade as well, and holds a further slice of the same fee in the program's buyback vault for $RIPPLES, the Ripples protocol token. A market keeps the schedule it launched under, so an older market can carry a different fee, split, or target.
A market can be priced in another approved asset instead, on either chain, and the form lists the assets it may name. Today that list is ETH and RIPPLES, plus the tokenised stocks the registry approves, on Robinhood and SOL on Solana. What a launch freezes carries the current list. That choice is fixed when the launch is created. The launch fee stays in ETH on Robinhood whatever the market is priced in, so a creator who picks a different asset approves two spending permissions rather than one, each captioned for what it covers. The funding target and the opening reserve then come from that asset's own row, in that asset's own decimal places, and the two figures above describe the default asset alone. An asset whose issuer can pause it, or block an address, stops every buy, sell, mint and move into the pool on a market priced in it while the hold is on. Neither control is Ripples' and neither can be lifted here.
Artwork
An NFT points at a small text file that names it and links its image. Somebody has to publish those files. You have three choices, and the form asks which one you want while you set the collection up.
The launch's own picture follows what you are launching rather than which chain you are on. Whenever the launch creates a token, the form takes the image, the description and the links from you and Ripples publishes them and serves them at a public address; the field says so where you choose the file. An NFT-only launch on Robinhood publishes none of that through Ripples. A collection there that adds its artwork later carries one details link, a public JSON file you host that names the launch and points at its picture, and the form asks for that link in place of an image; the form shows the whole file as an example you can copy. A collection with its artwork prepared up front points straight at the files you published. The three routes below are offered on both chains.
Choose the image files and Ripples publishes them, one numbered file per NFT, then points the collection at them. This is the shortest path and the one to take if you already have the pieces. Large collections upload in batches; the form reports progress and tells you what is left if a batch fails.
If you already host your artwork, give the form the folder link instead. The folder needs one file per NFT numbered from the first, with the images beside them, served over HTTPS from an address that will still answer in a year. Nothing on the chain copies your files, so a folder that disappears takes the artwork with it.
A collection can open for sale before the pieces exist. Each NFT starts with the image from your launch, and once it is bought, an image service draws its final artwork. The service runs on your own provider key, so your provider bills you for the images and Ripples adds nothing on top of what they charge.
Pick Grok, OpenAI, or an HTTPS endpoint you run yourself. Alongside the key you save a prompt every piece follows and the name and description each piece carries; write {id} where the piece number belongs. Before you save, Check key asks your provider whether the key works and can draw images. Nothing is charged for that, because no image is made. Draw a preview makes one real image on your saved key at your provider's price, up to 5 per collection.
Set this up in the form as you launch, or later from the collection page. Only the wallet that created the collection can change it, and Ripples encrypts the key and uses it for that collection alone. Until a key is saved nothing is drawn, and the collection page says so to you and to anyone else reading it.
Whichever you pick, keep the square version of your picture sharp: a marketplace uses it as the collection's avatar and crops anything that is not square.
You can freeze the artwork links later. That settles the address every NFT reads its details from and gives up your own ability to move it. Pieces hosted with Ripples can no longer be replaced or removed by you, and Ripples can remove hosted pieces that break its terms. A link you host elsewhere stays under your control. The files behind it can still change.
Liquidity
Your market has a funding target. While it is below that target, buyers trade against the market itself and the price rises as tokens are sold. What the market holds moves both ways: a buy adds to it and a sell takes from it, so the figure on the market page falls as well as rises. Anyone can settle a market that reaches its target. Ripples runs a bot that does it automatically, with no more power than any other wallet.
What settling does depends on the chain. On Solana the market is a contract of its own until then, and settling puts its reserve and its remaining inventory into a public Raydium pool and locks the position, so the liquidity cannot be pulled out afterwards. The funding target includes the cost of creating that pool, which is why the target you see is a little above the market threshold, and trading pauses for a moment while the pool is prepared.
On Robinhood a launch is a public Uniswap v4 pool from its first block, so there is nowhere for it to move. Settling retires the position that priced the filling market and opens the permanent one in the same pool, in one transaction, under the lock the launch declared at create. Whoever triggers it pays the network fee.
Liquidity going live also starts the clock on the tokens a combined launch reserves for its buyers. Nothing releases before it. After it, each buyer's share waits out the cliff, then releases gradually until it is fully available. Buyers claim from the token page or from their account page, and the reserved tokens stay with the wallet that bought the NFT even if the NFT itself has been sold on.
Liquidity going live does not change what the market pays you. On Solana your share of the trade fee is paid from the first trade, and after the target the same share of what the locked pool collects is yours. On Robinhood the trade fee is split from the first trade too, and the target changes nothing about it. The pool there charges no fee of its own, so that split is the whole of what a finished market pays you. A market keeps the schedule it launched under, so an older market can carry a different fee, split, or target.
Your NFT proceeds are separate from all of this. They do not wait for the target and they do not stop at it. As soon as people buy, the amount is yours, and your account page lists it beside a settle control.
After you launch
Two links matter. The collection page is where people buy an NFT. The market page is where the token trades. Both work without a wallet connected, so anyone can read the price, the supply and how much of the target the market holds before deciding anything.
Say these four things plainly, because a buyer cannot work them out from a picture:
Do not promise a price, a return, or that anyone will be able to sell. You control none of those. What you can promise is what the contracts do, and every figure on both pages is read from the chain, so anyone can check you.
After the market reaches its target, the pages link to the pool. That is a good moment to post again: the liquidity is locked by then, and it is the first thing a careful buyer looks for.
Collections on Robinhood use the standard ERC-721 and ERC-7572 interfaces, so marketplaces that index this chain can list them. Check OpenSea for the current state of that support.
Both chains
Every number below is set when a launch is created and enforced by the program on Solana or the contracts on Robinhood. None of it is a policy. Where Ripples holds a key, the line says exactly what that key can and cannot do.
Addresses
Every address Ripples deployed or holds, in full. Ripples announces one of its own here and nowhere else, so an address you were sent as ours that is not on this list did not come from us. A launch made on Ripples has its own address, which its own page shows. A trade on Robinhood also settles through Uniswap's pool manager and its universal router, which Ripples did not deploy. The Robinhood reference below prints the router's address, and the graduation hook above answers the pool manager's. Finishing a market that has reached its target takes no key of ours, so no wallet is listed for it: anyone can make that call.
Integrate carries the same addresses in machine-readable form, with the event topics and the calls that read a market.
Approved assets
A launch settles in one asset, chosen when it is created and frozen with everything else in this section. The owner keeps a list of the assets a new launch may name. That list is a list of assets: it says nothing about who may trade, hold or see a market, and Ripples builds no eligibility or jurisdiction gate anywhere. The rows below are copied from the deployment record rather than read from a chain, so the registry named first is the authority and each row carries the block it was approved in.
A launch that names no asset takes the chain's own and the figures the form fills in, which are the two funding targets below. A launch that names another asset must declare that asset's row as the registry holds it, and the factory refuses the create otherwise. Those figures are not restated here: the form reads the row when you pick the asset, and it will not let a launch declare a row it could not read. Two rows are never comparable as plain numbers either, since each is in its own asset's decimal places.
The stock link registry beside it records an association and nothing more. It stores an address, verifies nothing about the issuer or the instrument, moves no money, and cannot be changed or removed once written. An association and a settlement asset are separate choices: a launch can carry one, the other, both, or neither.
Combined launch
A combined launch is one token and one NFT collection that need each other. The share of a mint that buys the token market while it is filling, the slice of supply those minters vest, and the vest schedule are written into the launch and cannot be redirected after create, by the creator or by Ripples.
Market
Below the target, buyers trade against the market itself. At the target the position goes under the lock the launch declared at create, at the price the market last traded at. On Solana that means the reserve and the remaining inventory seed a public Raydium pool. On Robinhood the launch is already a public Uniswap v4 pool, so settling retires the position that priced the filling market and opens the permanent one in the same pool. The figures below are what the launch form sets today. A market created earlier keeps the target, the fee and the shares it was created under, and its own page states them. Anyone can settle a finished market. Ripples runs a bot that does it automatically, with no more power than any other wallet.
totalBurned() counts only tokens retired through the burner, including its buybacks. These are different totals. Transfers to the burn address do not reduce the token's totalSupply()Creator earnings
A creator is paid from mints, from trades, or from both. Trading pays from the launch's first trade on either chain, and reaching the target does not stop it. The rows below are the whole answer. Each launch fixes its own figures at create, so an older launch carries what it was created with and its own page shows it.
Keys
Ripples never receives a wallet key and never signs a mint. The only launches and trades it signs are Fill first openings: when a side is full, the Crosscurrent opening key below creates that side's market and makes its opening buy with the money its contributors put in. Every key it does hold is listed here, with what that key can do and what it cannot.
Reading the owner from Robinhood.
Today's limits
These are the limits of what is deployed today, stated so nobody reads a locker and finds a promise this section did not make.
The instruction and contract names behind all of this are in the two chain references below. The genesis launches are on the genesis page.
Chain one · Solana Mainnet
The browser uses a checked-in description of the Ripples program, its Anchor interface definition (IDL), to build transactions with Solana's @solana/kit library. Compatible wallets use Solana Wallet Standard to review and sign those transactions. The public API indexes program accounts, publishes launch images and details for the launch form, and stores signed NFT reveal settings. A separate Virtuals agent integration supports one dedicated agent wallet. It can read, nothing more. Agent transactions, paid offerings, and Mainnet access are off.
Deployment
QuoteAsset account per mint, written by the config authority. A launch records the mint, its token program, and its decimals when it is created, so read the launch rather than a listNEXT_PUBLIC_LAUNCHPAD_CHAIN=solana
NEXT_PUBLIC_LAUNCHPAD_CLUSTER=mainnet-beta
NEXT_PUBLIC_LAUNCHPAD_PROGRAM_ID=RippcKjgg9pkjHY7RKp6R1K8jPGwq2BRSJcgbErufjF
NEXT_PUBLIC_LAUNCHPAD_API_BASE=https://api.ripples.run
NEXT_PUBLIC_ALLOW_MAINNET=1The browser reads Solana Mainnet through the registry relay at https://api.ripples.run/v1/rpc, which holds the provider keys, and confirms each transaction by polling the same relay. The page opens no websocket to a Solana node. Without an API base, a local build reads the cluster's public endpoint, api.mainnet-beta.solana.com.
NEXT_PUBLIC_LAUNCHPAD_RPC_URL names an endpoint for the page to read first, with the relay behind it. Every NEXT_PUBLIC_ value is readable in the page, so the build refuses an endpoint that carries a key in its query string, its path, or a user name and password. The relay takes one JSON-RPC call per request, allows the read methods a page needs plus sendTransaction, and answers getProgramAccounts for the Ripples programs only. NEXT_PUBLIC_LAUNCHPAD_RPC_FALLBACK_URL names a different second endpoint; set it to off to read from the first alone.
Transactions
The connected wallet pays the SOL cost. Before asking for a signature, the app checks the amount being transferred, the minimum SOL needed to keep new accounts active (rent exemption), and a network-fee buffer. The @solana/kit planner fetches a recent blockhash, simulates the transaction, and adds a compute-unit safety margin. After signing, the app shows the transaction signature and checks its status until it confirms or its lastValidBlockHeight expires.
const message = await client.planTransaction(instructions);
const transaction = await signTransactionMessageWithSigners(message);
const signature = getSignatureFromTransaction(transaction);
await sendWithoutConfirming(transaction, {
commitment: "confirmed",
preflightCommitment: "confirmed",
skipPreflight: false,
});
// Poll signature status and block height using
// message.lifetimeConstraint.lastValidBlockHeight.Before the Raydium pool is created, token prices are quoted in SOL. A purchase creates the buyer's standard token holding account (associated token account) only when one does not already exist. Launch tokens use nine decimal places, and the wallet signs the complete trade.
Agent integration
Virtuals' Agent Commerce Protocol (ACP) lets agents buy and sell services. The private Ripples service connects one registered Virtuals agent and one dedicated Solana wallet to the selected network, the configured Ripples program, and the committed Anchor IDL. It accepts only a defined list of Ripples actions. It rejects arbitrary transfers, instructions for other programs, Virtuals token-launch tools, general-purpose trading, and program-authority or maintenance actions.
Before sending a transaction, the service confirms that both Solana endpoints are on the expected network. It decodes each instruction from the Anchor IDL and checks the program address, account order, read/write permissions, required signer, current Ripples account state, and spending limits for one action and one day. Each request receives a stable request fingerprint, a digest used to recognize the same request, and a Solana memo. If the result is unclear, the service pauses and checks the existing transaction instead of sending it again.
The optional seller listing (ACP offering) is a fixed-price USDC service named ripples-action-v1. After a buyer funds a job, the service reads and checks the latest request again immediately before execution. The agent wallet pays any SOL sent into Ripples, Solana network fees, and new-account funding. Virtuals sponsorship does not pay for Ripples program transactions.
The integration is read-only. Agent transactions, the paid seller listing, and Mainnet access are off. This section will say so when that changes.
Your own agent
To create or join a Crosscurrent on both chains from your own agent, read Run a Crosscurrent from an agent.
An agent that already holds a wallet does not go through the Virtuals service above. It reads launches from the public API, reads chain state through the registry relay or a node of its own, builds the same instructions the browser builds, and signs them with its own key. That wallet pays every network fee and every trade. Nothing on this page signs, pays, or approves on its behalf.
GET https://api.ripples.run/v1/launches lists Solana Mainnet token launches, newest first, as count, nextOffset and a launches array. Each row carries launch (the market address), mint, creator, name, symbol, and collection for a combined launch. /v1/collections does the same for NFT collections, and /v1/launches/<address> returns one launch with its current market state. The list answers Solana Mainnet by default and Robinhood for ?chain=robinhood, and the single-launch route reads whichever address form it was given, so it answers on either chain. /v1/collections indexes Solana Mainnet alone. Robinhood launches can also be read straight from the factories, as the board does.
const base = process.env.RIPPLES_API ?? "https://api.ripples.run";
const page = await fetch(`${base}/v1/launches?offset=0`).then((r) => r.json());
for (const row of page.launches) {
// row.launch, row.mint, row.creator, row.name, row.symbol, row.collection
}
// An index with nothing in it is a valid answer, so read the first row only if there is one.
const [first] = page.launches;
const one = first && (await fetch(`${base}/v1/launches/${first.launch}`).then((r) => r.json()));Each chain is counted on its own, and either answers count: 0 until the first launch on it exists, so write the loop to survive an empty page. limit defaults to 50 and caps at 200 on /v1/launches, and is 25 either way on /v1/collections; offset counts rows from the newest. Reads are limited to 120 a minute per client address, and every answer carries what is left in x-ratelimit-remaining.
Point RIPPLES_API at your own build's API when you want to run the same calls against a test network.
For chain state, POST https://api.ripples.run/v1/rpc relays one JSON-RPC call per request to the Solana Mainnet endpoints the site reads through. It accepts the read methods a client needs (getAccountInfo, getMultipleAccounts, getLatestBlockhash, simulateTransaction, getSignatureStatuses and the rest) plus sendTransaction. getProgramAccounts is answered for the Ripples program only, and a batched request is refused. The relay is limited to 600 calls a minute per client address, so an agent that polls harder than that should bring its own endpoint.
On Solana Mainnet, build the buy from the published IDL, solana/idl/ripples.json, with the program address from the table above. Generating a client from it with Codama gives the same getBuyInstructionAsync the browser uses. The launch account names the mint, the vault, and the address the launch pays its creator at, the settings account names the treasury, and the price is a constant-product curve on the launch's virtual reserves, so the floor can be sized before anything is signed. The buyback vault is a program address the client derives; every other account is on one of those two records.
import {
appendTransactionMessageInstructions, createKeyPairSignerFromBytes, createSolanaRpc,
createTransactionMessage, getBase64EncodedWireTransaction, pipe,
setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
} from "@solana/kit";
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
import { fetchConfig, fetchLaunch, findConfigPda, getBuyInstructionAsync } from "./generated/ripples";
const rpc = createSolanaRpc("https://api.ripples.run/v1/rpc");
const agent = await createKeyPairSignerFromBytes(agentSecretKey);
const launch = await fetchLaunch(rpc, launchAddress);
const [configAddress] = await findConfigPda();
const settings = await fetchConfig(rpc, configAddress);
const [buyerToken] = await findAssociatedTokenPda({
owner: agent.address,
mint: launch.data.mint,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});
// The platform's fee and the creator's own charge come off the same base, then the
// constant-product output. In the first 3 seconds the opening tax comes off
// before both, so a floor sized here fails until that window closes.
const quoteIn = 100_000_000n;
const fee = (quoteIn * BigInt(launch.data.tradeFeeBps)) / 10_000n;
const creatorTax = (quoteIn * BigInt(launch.data.creatorTaxBps)) / 10_000n;
const net = quoteIn - fee - creatorTax;
const expected = (launch.data.virtualToken * net) / (launch.data.virtualQuote + net);
const minTokensOut = (expected * 9_900n) / 10_000n;
// Where this launch pays its creator. The account is pinned to that address, so it
// cannot be derived, and a launch created before the field existed carries the default
// address there and is paid at launch.creator instead.
const creatorPayout =
launch.data.creatorPayout === "11111111111111111111111111111111"
? launch.data.creator
: launch.data.creatorPayout;
const buy = await getBuyInstructionAsync({
launch: launchAddress,
mint: launch.data.mint,
tokenVault: launch.data.tokenVault,
buyerToken,
buyer: agent,
treasury: settings.data.treasury,
creatorPayout,
quoteIn,
minTokensOut,
});
const { value: latest } = await rpc.getLatestBlockhash().send();
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayerSigner(agent, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(latest, m),
(m) => appendTransactionMessageInstructions([buy], m),
);
const signed = await signTransactionMessageWithSigners(message);
const signature = await rpc
.sendTransaction(getBase64EncodedWireTransaction(signed), { encoding: "base64" })
.send();On Robinhood a market is a Uniswap v4 pool, so the agent prices it from the hook with viem and swaps through the Universal Router, using the addresses from the Robinhood deployment table. The router spends through Permit2, so the quote token is approved to Permit2 and Permit2 to the router. The network fee is paid in ETH from the same wallet.
import { createPublicClient, createWalletClient, http, parseAbi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const chain = {
id: 4663,
name: "Robinhood",
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
};
const account = privateKeyToAccount(agentPrivateKey);
const client = createPublicClient({ chain, transport: http() });
const wallet = createWalletClient({ account, chain, transport: http() });
const QUOTE = parseAbi(["function approve(address spender,uint256 value) returns (bool)"]);
const PERMIT2_ABI = parseAbi([
"function approve(address token,address spender,uint160 amount,uint48 expiration)",
]);
const ROUTER = parseAbi([
"function execute(bytes commands,bytes[] inputs,uint256 deadline) payable",
]);
// Price the market off LaunchLens.launchesOf and take the hook's cut off the output
// before signing a floor. The Robinhood "Token trading" section shows that
// arithmetic in full, and where the lens is deployed.
const quoteIn = 10n ** 16n;
const amountOutMinimum = floorFor(quoteIn);
const expiry = Math.floor(Date.now() / 1000) + 3_600;
await wallet.writeContract({ address: quoteToken, abi: QUOTE, functionName: "approve", args: [permit2, quoteIn] });
await wallet.writeContract({ address: permit2, abi: PERMIT2_ABI, functionName: "approve", args: [quoteToken, universalRouter, quoteIn, expiry] });
const hash = await wallet.writeContract({
address: universalRouter,
abi: ROUTER,
functionName: "execute",
args: [
"0x10",
[encodeV4Swap({ poolKey, zeroForOne: quoteIsCurrency0, amountIn: quoteIn, amountOutMinimum })],
BigInt(expiry),
],
});
await client.waitForTransactionReceipt({ hash });Token launch
In the standard web flow, the creator supplies a name, ticker, description, image, and optional Website, X, Telegram, and custom links. Ripples publishes them together, then fills the instruction's uri field automatically. Developers building their own interface may provide a compatible public metadata URI directly.
The createLaunch instruction creates three core records: the market state (launch program-derived address, or PDA), the standard Solana token (SPL Token mint), and the trading inventory controlled by Ripples (program vault). The full token supply is issued once, then the powers to mint or freeze more tokens are removed. Metaplex Token Metadata stores the token name, symbol, and URI.
import { getCreateLaunchInstructionAsync } from "./generated/ripples";
const instruction = await getCreateLaunchInstructionAsync({
launch,
tokenVault,
creatorToken,
creator: walletSigner,
treasury,
args: {
nonce,
name: "Ripple",
symbol: "RPL",
uri: "ipfs://…",
curveSupply: 800_000_000n * 1_000_000_000n,
lpSupply: 219_000_000n * 1_000_000_000n,
virtualQuote: 9_600_000_000n,
virtualToken: 1_073_000_000n * 1_000_000_000n,
graduationQuote: 24_000_000_000n,
lpUnlockAt: 9_223_372_036_854_775_807n,
linked: false,
nftAllocationBps: 0,
mintToCurveBps: 0,
vestingCliff: 0n,
vestingDuration: 0n,
initialBuy: 0n,
minTokensOut: 0n,
snipeExemptions: [],
creatorTaxBps: 0, // the creator's own charge, 0 to 1000 bps
},
});Token-only launches open with a virtual quote reserve of 0.4 times their funding target and cannot accept NFT contributions. Linked launches use 1.25 times the same target and retain the larger liquidity allocation needed for NFT-funded graduation. For an approved token quote, read its current QuoteAsset row: the stored reserve is the linked reserve. The standalone reserve is two fifths of its target, available only when both ratios are exactly representable in raw quote units. The program checks these terms at creation. Later approval changes apply to future launches; they do not change an existing market's reserves or target.
The market's fee is not an argument. The program writes 1% into every launch it creates, so a caller cannot open a market on other terms. creatorTaxBps is the creator fee on top of it, paid to the creator in full and capped by the program. It sits last in the arguments rather than in the slot the fee left, so a client built against the older list fails to decode instead of sending a tax nobody asked for.
The move to a Raydium liquidity pool becomes available when the actual SOL reserve covers the funding target (graduationQuote) plus the saved setup cost (graduationCostLamports). An optional first purchase (initialBuy) completes in the same transaction as creation.
A token linked to an NFT collection begins in the waiting state (LinkPending). Trading stays disabled until createLinkedCollection succeeds. If that second transaction never completes, the creator can call cancelPendingLaunch.
NFT collections
OpenSea reads Metaplex Core NFTs, so an NFT minted on Mainnet has a page at opensea.io/assets/solana/<NFT address>, linked from its card on the collection page. Nothing minted on a test network is listed there, so a test build leaves that link off. The collection itself has no separate OpenSea page; any of its NFTs is the way in.
createCollection creates the Ripples collection state and its Metaplex Core collection. mintAsset creates one NFT per transaction, records the SOL payment, updates the buyer's mint limit, and records any contribution to the token market of a combined launch for the original minter.
| Instruction | Authority | Purpose |
|---|---|---|
mintAsset | Minter | Pay SOL and create one NFT. |
revealAsset | Reveal authority | Replace a live placeholder exactly once. |
setBaseUri | Creator | Move the link a prepared collection reads its artwork from, until it is locked. |
setPlaceholderUri | Creator | Change the live placeholder before freezing. |
freezeMetadata | Creator | Permanently disable URI controls. |
creatorWithdraw | Creator | Withdraw only attributed creator proceeds. |
SOL sent directly to the collection's program-derived address (PDA) is not counted as an NFT payment. A separate recovery instruction, callable by anyone, can send only that extra SOL to the recorded creator.
Stock association
A token launch, an NFT collection, or a combined launch may optionally create an immutable StockLink sidecar. It records the subject, exact mint address, and SPL Token or Token-2022 program. Creation verifies that the mint account exists, is initialized, and is owned by the recorded token program. The sidecar leaves its subject account and launch economics unchanged.
| Launch type | StockLink subject |
|---|---|
| Token launch | Launch PDA |
| NFT collection | Collection PDA |
| Combined launch | Launch PDA |
The program verifies the mint-account relationship, not the issuer or what the mint tracks. The first-party Mainnet picker resolves tokenized tracker mints from the issuer catalog. If that catalog is unavailable, link creation is disabled and the bundled snapshot remains explicitly unverified. Public pages repeat the exact mint lookup and required Token-2022 program check before calling a link an issuer-catalog match. Direct program callers can link another valid SPL Token or Token-2022 mint, but the first-party UI labels it unverified.
A stock link records a reference. It moves no assets and creates no entitlement. The link does not purchase or swap for the tracker token, pair it with the launch, take custody, reward holders, confer a dividend entitlement, or distribute any asset. It changes no economics: a market that records one keeps the asset, the target, the fee and the vesting terms it was created with, and the sidecar can be read without touching any of them. The first-party picker is disabled on test networks.
A launch settles in the asset it was created against, which is SOL unless the creator named an approved mint at create. That choice is made in the same form and is otherwise unrelated to a StockLink: a launch may reference a tracker and settle in SOL, settle in an approved mint and reference nothing, do both, or do neither. Recording an association never moves the asset a trade settles in, and pricing a market in a mint records no association with its issuer. The market page says which of the two, if either, a launch carries.
Access to a referenced tokenized tracker, or to an asset a launch settles in, depends on the issuer's terms and the user's jurisdiction. That is a disclosure about the asset and its issuer. Ripples builds no eligibility or jurisdiction gate: nothing in the program, the site or this reference checks where a reader is or who they are, and the approved list is a list of assets. The issuer's own pause and account freeze are the only holds, they are read rather than applied, and a market priced in a paused mint stops until the issuer lifts it.
Token trading
// Amounts are base units: the quote's own smallest unit, and 9-decimal SPL units.
// A launch quoted in SOL takes lamports here; one quoted in a mint takes that mint's.
// creatorPayout is where this launch pays its creator, pinned by the launch record:
// launch.creatorPayout, or launch.creator where that field is the default address.
await getBuyInstructionAsync({
launch,
mint,
tokenVault,
buyerToken,
buyer: walletSigner,
treasury,
creatorPayout,
quoteIn: 100_000_000n,
minTokensOut,
});
await getSellInstructionAsync({
launch,
launchAuthority,
mint,
tokenVault,
sellerToken,
seller: walletSigner,
treasury,
creatorPayout,
tokensIn,
minQuoteOut,
});A launch fixes tradeFeeBps when it is created and keeps it for the life of the market, so read it from the launch account rather than assuming a rate. It fixes quoteMint, quoteTokenProgram and quoteDecimals the same way, so read the asset off the launch too. A launch created before per-launch assets existed reports its quote as absent, which means SOL. The web form creates Solana markets at 1%, split as fixed shares of the fee itself: 0.7% of volume to the creator, 0.24% set aside for the $RIPPLES buyback, and the remainder to the protocol. The creator's share is paid from the launch's first trade, and after graduation collectLockedFees divides what the locked pool position collects on the same recorded share. Read that share as creatorFeeShareBps on the launch account: a market created under an earlier schedule reports its own. A launch can also fix a creator fee,creatorTaxBps, which comes off the same amount the fee does, on a buy and on a sell, and reaches the creator whole with no share to the protocol. Read it off the launch and size the floor after it, as the agent buy above does. During the first 3 seconds, non-exempt buyers also pay an opening tax that falls from 99% to zero. That tax buys no tokens. It is held by the launch as snipeTaxCollected and seeded into the pool at graduation, and it is deliberately outside realQuote(), so it does not count toward the funding target. The signer always supplies a price limit: minTokensOut for purchases and minQuoteOut for sales.
A market pays its creator to creatorPayout on the launch account. It starts as the creator, and only the address named there can move it, with setCreatorPayout, which takes effect on the next trade. Income that address cannot receive directly waits in escrow rather than becoming protocol revenue: claimEscrow pays out one balance in one asset, and claimNativeEscrow pays out SOL. Both are permissionless and neither can redirect the payment. A SOL claim too small to lift the recipient over its own rent floor is refused, because the runtime would reap the balance it landed in.
Combined launches
Before the Raydium pool is created, the configured share of each NFT payment moves into the token market reserve without changing its virtual pricing reserves. A minter's share is fixed when the launch graduates: it is pro-rata to what their mints routed into the market. After the pool is complete, claimVested releases tokens according to the configured waiting period and vesting schedule. finalizeBeneficiary closes a minter out once they have claimed in full, which is what lets the last of the rounding dust be accounted for. Selling or transferring the NFT does not transfer this token claim.
Raydium liquidity pool (CPMM)
beginGraduation pauses token trading and records the amounts that will start the liquidity pool.createCpmmPool prepares the required SOL and creates the launch-bound Raydium constant-product pool (CPMM). The pool pairs the launch token against the launch's own quote mint, under that mint's own token program. A mint carrying extensions Raydium screens needs its SupportMintAssociated account passed as a remaining account, and a mint with no such account cannot enter a pool.lockLp secures the liquidity-provider position. Permanent locks use Raydium Burn & Earn.finalizeGraduation marks the move complete after the program verifies where the liquidity position is held.Before the pool is created, the launch creator or current Ripples program authority can call resumeTrading to clear the prepared amounts and reopen token trading. This recovery path closes once a pool position has been recorded.
A time-locked liquidity-provider position (LP position) remains in a program-owned account until lpUnlockAt. Permanent locks use collectLockedFees, which sweeps both sides of the pool into the launch's own accounts and divides what arrived on the launch's creatorFeeShareBps: the creator's share goes to creatorPayout and the remainder to the treasury. It pays the same creatorPayout a trade pays, so moving that address moves both. Time-locked positions use withdrawUnlockedLp only after the lock expires.
Public registry API
This API supports the public web app. The routes below index the Solana chain; /v1/launches also answers Robinhood for ?chain=robinhood, and /v1/launches/<address> answers on either chain. The private Virtuals agent integration is a separate service; this API never receives its wallet credentials or transaction requests.
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/launches | Indexed launch PDAs and current public state. |
| GET | /v1/collections | Indexed collection PDAs and supply. |
| POST | /v1/auth/solana/challenge | Create a one-time message for a wallet to sign. |
| POST | /v1/auth/solana/verify | Verify the Ed25519 wallet signature and issue a private session token. |
| POST | /v1/launch-media | Validate and publish a launch image and its public token or collection details. Requires a Solana wallet session. |
| POST | /v1/collections/:collection/config/challenge | Bind provider config to creator and collection. |
| PUT | /v1/collections/:collection/config | Save the freshly signed provider config. |
/v1/launch-media accepts name, symbol, description, and an image object containing a MIME type and base64 data. Optional socials fields cover Website, X, and Telegram; customLinks accepts up to five labeled HTTPS links. Images may be PNG, JPEG, GIF, WebP, or AVIF and may not exceed 1 MB. The service verifies the actual file type, stores the image and JSON at public links addressed by their own content, and returns metadataUri and imageUrl. The same address answers with the same bytes for as long as it is hosted, and Ripples can remove content that breaks its terms. The browser uses metadataUri automatically; creators using the standard form never need to enter or host it themselves.
Addresses are base58 and case-sensitive. Sign the exact UTF-8 challenge bytes and submit the raw 64-byte Ed25519 signature encoded as base58. This proves wallet control without a password or a Solana transaction. Each challenge works once and expires.
Chain two · Robinhood
Ripples is a set of public contracts, and their source is published at github.com/ripples-dot-run/contracts under MIT. Two factories deploy every launch, so a token, its market, its collection, and its vesting contract all come from code you can read before you use it. The browser calls them with viem, and any wallet that supports Robinhood can review and sign those calls. Nothing in the middle can move your funds: launches and collections are read straight from the factories, and the trade floor you see is the floor your wallet signs.
Deployment
approvedQuote(address) and quoteEconomics(address), paged by quoteCount() and quotes(offset, limit)linkOf(curve). It stores an address and verifies nothing about the issuer or the instrumentcontractURI.A market is read and traded through the contracts of the group it was created under; its page names them.
NEXT_PUBLIC_LAUNCHPAD_CHAIN=rh-mainnet
NEXT_PUBLIC_LAUNCHPAD_RH_RPC_URL=https://rpc.mainnet.chain.robinhood.comThe endpoint is optional and defaults to the address above. Set it only to front the public endpoint with your own origin. A build whose chain and endpoint name different networks is refused at startup rather than left reading one network while labeling itself the other.
A build that points at a live network also needs NEXT_PUBLIC_ALLOW_MAINNET=1, which stops a mistyped environment from aiming a development build at real funds.
Transactions
Every charge is pulled by allowance, and the approval names the launch's own quote rather than the factory's default: a buy or a mint on a market priced in an approved asset is approved against that asset, read with QUOTE() off the market or the collection. A trade is the one thing that does not spend a plain allowance: it goes through a router that spends through Permit2, so a first trade in an asset approves the token to Permit2 once and then Permit2 to the router for an amount and a window. The second grant names whichever router is placing the trade, and a permission granted to one router does not cover a trade through the other. The launch fee is the exception and stays in ETH, which feeToken() answers, so creating a launch priced in another asset takes two approvals: one for the fee and one for the opening buy. Launch fees, buys, and mints each need an approve before the call that spends it, and none of them take a native payment. The app plans the pair and runs it through one eth_simulateV1 before the wallet is asked for anything, so a closed mint window, a wallet cap, a sold-out drop, or a moved price surfaces while nothing has been signed and no allowance is standing. Where the node cannot simulate, the request proceeds without one.
Gas is estimated with a margin and priced from eth_feeHistory, falling back to eth_gasPrice on a node that does not answer it. The app records the transaction hash and account nonce before sending, so a reload during a pending transaction reconciles it instead of signing a second one.
The swap parameters are the one place this network departs from current v4-periphery, and it fails quietly. Its SWAP_EXACT_IN_SINGLE struct carries an extra head word between amountOutMinimum and the offset to hookData. Encode the documented five-field struct and the router reads the hook-data offset out of that unused slot. Where the pool's first currency is the chain's own asset the misread lands on a zero word and the swap goes through. Where it is an ERC-20 the same slot holds a token address, the calldata bounds check fails, and the call reverts with no return data at all, before the router reaches the PoolManager. Every market priced in ETH is ERC-20 quoted, so encodeV4Swap writes the sixth field.
// One read prices the market: config, position, state, raise, price, opening tax.
// launchesOf lives on the lens; the hook has no such function and reverts.
const [view] = await client.readContract({
address: launchLens,
abi: LAUNCH_LENS_ABI,
functionName: "launchesOf",
args: [[poolId]],
});
// Which side the hook charges depends on the hook the pool is keyed to, so read
// view.config.hook before sizing the floor.
//
// On QuoteFeeHook every leg comes off the WETH side whichever side the swap
// specifies: the opening tax first, then the trade fee and the creator's own
// charge on what is left. An exact-input buy is charged on its input and the
// curve sees the net, so the floor is the constant-product move on that net.
const taxQ = (quoteIn * view.snipeTaxBps) / 10_000n;
const feeQ = ((quoteIn - taxQ) * view.config.tradeFeeBps) / 10_000n;
const creatorQ = ((quoteIn - taxQ) * view.config.creatorTaxBps) / 10_000n;
const netOnQuoteFee = tokensOut(view, quoteIn - taxQ - feeQ - creatorQ);
// On LaunchHook the cut comes out of the swap's unspecified side, which on an
// exact-input buy is the launch token: the opening tax first, then the trade fee
// and the creator's own charge on what is left.
const gross = tokensOut(view, quoteIn);
const tax = (gross * view.snipeTaxBps) / 10_000n;
const fee = ((gross - tax) * view.config.tradeFeeBps) / 10_000n;
const creatorTax = ((gross - tax) * view.config.creatorTaxBps) / 10_000n;
const net = gross - tax - fee - creatorTax;
// Size the floor on what reaches the buyer, at the tolerance the app signs on
// both chains. quoteFeeHook is the address the deployment table above lists.
const isQuoteFeeHook = (hook) => hook.toLowerCase() === quoteFeeHook.toLowerCase();
const reaches = isQuoteFeeHook(view.config.hook) ? netOnQuoteFee : net;
const amountOutMinimum = (reaches * 9_900n) / 10_000n;
// V4_SWAP is the router's one command; the actions inside it are
// SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL. The deadline is a wall-clock
// second: the router compares it to block.timestamp.
const { request } = await client.simulateContract({
account: buyer,
address: universalRouter,
abi: UNIVERSAL_ROUTER_ABI,
functionName: "execute",
args: [
"0x10",
[encodeV4Swap({ poolKey, zeroForOne: quoteIsCurrency0, amountIn: quoteIn, amountOutMinimum })],
BigInt(Math.floor(Date.now() / 1000) + 600),
],
});
const hash = await wallet.writeContract(request);
await client.waitForTransactionReceipt({ hash });Token launch
createLaunch deploys the ERC-20 and the launch's locker, opens the pool and seeds the curve position, in one transaction. The full supply is minted once at creation and there is no minting role afterwards. createLinkedLaunch adds the collection and the vesting contract to the same transaction, so a combined launch is one transaction and leaves no half-created state to repair. Both calls take the launch fee from the caller's allowance, so the wallet approves that spend before either one.
Both calls take an optional opening buy. The factory routes it through the launch's own LPLocker, which it registers as exempt, so only the trade fee applies to that buy. Three writes fill the exempt set: the creator at registration, every address passed in snipeExempt, and any address the fee recipient is later moved to with setCreatorFeeRecipient. The set is narrower than it reads. The hook matches its exempt set against the address that called the PoolManager, and every ordinary trade reaches the PoolManager through a router, so a creator buying later pays the opening tax like any other buyer. An address passed in snipeExempt is only ever exempt for a buy it sends to the PoolManager itself.
The two calls are two launch shapes. A standalone launch opens against a smaller pricing reserve, because its raise can only arrive one trade at a time and every trade moves the price. A combined launch opens against a larger one, because an NFT collection can route a whole raise in without the price moving at all. Both reach the same target, and the registry row carries a reserve for each shape.
const params = {
name: "Ripple",
symbol: "RPL",
curveSupply: 800_000_000n * 10n ** 18n,
lpTokenSupply: 219_000_000n * 10n ** 18n,
vQuoteInit: 288_000_000_000_000_000n,
vTokenInit: 1_073_000_000n * 10n ** 18n,
graduationQuote: 720_000_000_000_000_000n,
lpUnlockAt: 0n,
// On quoteFeeFactory the charge below is the whole of what trading pays the
// creator: that factory stamps no creator share into the trade fee.
creatorTaxBps: 0n, // the creator's own charge, 0 to 1000 bps
// The asset this market settles in. The zero address means the factory's default.
// vQuoteInit and graduationQuote above are in THIS asset's own decimals and must
// equal the row in the registry that belongs to the factory you create on, so
// read them from quoteFeeRegistry.quoteEconomics(quote) rather than typing them.
// The two factories keep different rows for the same asset and each refuses the
// other's.
quote: approvedQuote,
};
// The launch fee is charged in feeToken(), so it is approved, not sent as value. A
// launch priced in another approved asset approves that asset for the opening buy as
// well, which is two permissions rather than one.
await wallet.writeContract({
address: feeToken,
abi: QUOTE_ABI,
functionName: "approve",
args: [quoteFeeFactory, launchFee],
});
const { request } = await client.simulateContract({
account: creator,
address: quoteFeeFactory,
abi: TOKEN_FACTORY_ABI,
functionName: "createLaunch",
args: [params],
});The trade fee is not a launch parameter. Every market this factory opens charges TRADE_FEE_BPS, and the pool fee and tick spacing are the hook's own constants. The reserves above are the standalone pair. createLinkedLaunch declares the linked pair instead, and the factory refuses either shape declared against the other's reserve.
An lpUnlockAt of zero is the factory's way of saying the liquidity never unlocks. The factory rejects a configuration whose supply cannot seed its own graduation, whose seed price falls outside its band, or whose creator tax exceeds MAX_CREATOR_TAX_BPS, so an unlaunchable market never reaches the chain.
NFT collections
Collections on Robinhood use the standard ERC-721 and ERC-7572 interfaces, with ERC-2981 royalties, so marketplaces that index this chain can list them. Check OpenSea for the current state of that support. The collection page and every NFT row here link to the marketplace address for the collection where the chain has one.
A collection deployed by the factories above answers contractURI() (ERC-7572). The factory points it at https://api.ripples.run/v1/collection-assets/<collection address>/contract.json at creation, and that document carries the collection's name, a square logo, its page on this site, and the royalty percentage and payout address read from the contract, so a marketplace page fills itself in without anyone claiming it. The description a marketplace shows comes from the artwork the creator published rather than from this document. A collection reads the origin once and keeps it, so a drop created before its factory published one answers nothing here; its collection page says which of the two it is. A creator hosting their own document can repoint it with setContractURI at any time, including after the artwork is frozen.
createCollection deploys a collection at a fixed price in the collection's own quote, which is ETH unless the creator named another approved asset at create. Naming an asset makes that asset's registry row bind: the price has to clear the row's floor, minPriceQuote, and an asset the registry has not approved is refused. A collection that names none takes the factory's default and is held to no row. The other half of the price rule, that a mint's routed share must not round to nothing at the asset's decimals, belongs where both numbers exist: createLinkedLaunch checks it at create and Collection721.linkToCurve checks it again when the two are joined. A combined launch shares one asset across the collection and the curve, so the routed share of a mint reaches the market in the same asset the minter paid in; linkToCurve reverts QuoteMismatch rather than joining two that disagree. A pregen collection serves artwork from baseURI immediately. A live collection serves placeholderURI until the platform signer writes each final URI once. mint creates up to twenty NFTs per transaction, inside the collection's per-wallet cap, and routes the linked share of the payment into the token market in the same call.
| Call | Authority | Purpose |
|---|---|---|
mint(qty, minRoutedTotal) | Minter | Pay the collection's quote and create the NFTs, with a floor on the routed share. |
setTokenURI | Platform signer | Replace a live placeholder exactly once. |
setBaseURI | Creator | Move the link a prepared collection reads its artwork from, until it is frozen. |
setPlaceholderURI | Creator | Change the live placeholder before it is frozen. |
setContractURI | Creator | Repoint the collection document marketplaces read. Outside the freeze, which covers the artwork links. |
freezeMetadata | Creator | Permanently stop the artwork links from moving. |
creatorWithdraw | Creator | Withdraw only attributed creator proceeds. |
Quote tokens sent to the collection outside a mint are not counted as payment. recoverUnattributed, callable by anyone, sends only that surplus to the recorded creator.
Token trading
A Robinhood market is an ordinary Uniswap v4 pool with the Ripples hook on it. A buy or a sell is a v4 swap through the Universal Router at 0x8876789976dEcBfCbBbe364623C63652db8C0904, spending through Permit2 at 0x000000000022D473030F116dDEE9F6B43aC78BA3, so any aggregator that already routes v4 can trade a Ripples market without integrating anything of ours. That path takes any order the market can fill whole, which is every ordinary size. The address the app calls a market is the launch's LPLocker. It holds the position and answers the launch's own facts, and it takes no orders.
An order larger than what the market has left to sell needs the other path. A launch's liquidity is one position over one bounded price range, and LaunchHook refuses any swap that would end outside it. The Universal Router places its swap with a wide price limit, so an oversized buy walks past the range end, meets that refusal and reverts whole. Ripples' own router at 0x22DB9B2c6C6DB56ABC3fc196E4bC1FBb9c4338a6 places the same swap with the range end as the price limit instead. The pool fills to the end of the curve and stops, and the quote it never reached is never pulled out of the wallet. That is the path the app trades Robinhood markets through, and the one to use for the last buy before a market reaches its target. A market keyed to QuoteFeeHook takes the same call at 0x0e558e1e65BD1850730A3B4a89654c066fC035B7: a v4 pool's hook is part of its key, so a router built against one hook refuses every pool on the other, and the market's page names the one to use.
swapExactIn(key, zeroForOne, amountIn, minAmountOut, recipient, deadline) returns (spent, amountOut) and spends through the same Permit2 allowance the Universal Router path uses, granted to this router rather than to that one. spent is what the pool consumed and is at most amountIn. minAmountOut is a price rather than a quantity: the router holds spent * minAmountOut <= amountIn * amountOut, which on an order that spends in full is exactly amountOut >= minAmountOut and on one that fills partly asks the same rate of the part that filled. So a floor signed against the whole order keeps its meaning whichever way the order lands.
Read the outcome from Filled(poolId, sender, recipient, zeroForOne, amountIn, spent, amountOut). spent below amountIn is a partial fill, and the difference is money that stayed with the sender. The PoolManager's own Swap reports the part that filled and says nothing about the part that did not, so amountIn beside spent is the only place a partial fill is readable at all. CurveEndReached is the revert on a market with nothing left to sell.
The pool key is LaunchHook.poolKeyFor(token, quote), and its id is the same hash the PoolManager takes, so a market can be addressed before anything has been read from it. poolIdOfToken(token) goes the other way.
Everything a price needs comes from one read: LaunchLens.launchesOf(bytes32[] poolIds), at the market reader in the deployment table above. The hook carries no launchesOf of its own, and a call sent there reverts. Each LaunchView carries the config, the seed plan, the live state, the raise so far, the pool price, and the opening tax in force at that block. Read the fee as config.tradeFeeBps, the target as config.graduationQuote, the asset as config.quote and the opening tax as snipeTaxBps. The tax decays from config.launchedAt, which is when trading opened rather than when the launch was registered. The quote's decimal places come from the quote token itself: no contract in a launch republishes them, and a target read at the wrong decimals is out by orders of magnitude. The lens holds no state and no permission, and everything it returns it reads back out of the hook's own views and the PoolManager, so a caller who does not trust it can reproduce every field from those two addresses.
Price a market from the position and the pool price, never from the pool's active liquidity, which reads zero while the curve position is intact. Read view.config.hook before sizing a floor: which side of the swap the hook charges is a property of the hook, and the two below differ. Reconstruct a fill from the PoolManager's Swap together with TradeFeeCharged, CreatorTaxCharged and SnipeTaxCharged: Swap alone overstates what the trader received by everything the hook took.
The web form creates Robinhood markets at 1%, which is the whole fee, and a market keeps the fee, the share and the hook it was registered with. For the first 3 seconds after trading opens, a buy pays an opening tax on top that starts at 99% and falls to zero; the tax is charged first, then the trade fee and the launch's own config.creatorTaxBps on what is left. The creator's tax is credited to the creator whole and reaches no split, so a market that declared one takes three legs out of a buy rather than two.
Markets created by QuoteFeeFactory 0x66112C2c987eD7A366715bD28CCc0DF629813206 are keyed to QuoteFeeHook 0x6172fD469E900F74eD04b1763daB4D2c2BFB6AcC, which takes every leg out of the ETH side of the swap, in beforeSwap where that side is specified and in afterSwap where it is not, so a buy and a sell both pay in ETH and the opening tax comes out of the quote a buyer pays rather than the tokens they receive. The hook credits the fee and the tax to QuoteFeeRouter 0xC8842e8196E96Cef0988Cffb144722fDe8a8a684, which sends 70% to the buyback burner and 30% to the treasury (BURN_SHARE_BPS, a constant). config.creatorFeeBps is 0 on every launch this factory creates. A market created in the Founders Window is the exception, by design: its factory (0x1C4032F557621D3b77E11748706BEfd26F3b6ef4) stamps 30% into config.creatorFeeBps, paid to the creator from the first trade for the life of the market, and its own router (0x8d8DcC0024B178cccDA2602654d2F269998dBB9e) sends everything it receives to the buyback burner, so the burn keeps 70% of the fee and the treasury takes nothing on those markets. Launch on Ripples during the Founders Window and reach a $30K market cap within seven days. We'll cover your DEX Screener Enhanced Token Info. Available to the first 10 qualifying projects, alongside the existing Founders Window benefits. config.creatorTaxBps is charged in ETH on the same base and credited to the creator in full.
Markets created by TokenLaunchFactory 0xE89C1b33B6C2505414667bE7E5084774a72150e1, including $RIPPLES and $KOI, are keyed to LaunchHook 0xaF59944A7d03B914567cb0272b7E588A7aE7AAC4, which takes its cut out of the swap's unspecified side, which on an exact-input buy is the launch token, so compute the floor you sign on the net rather than on what the pool pays out. It splits the fee in afterSwap from the launch's first trade, on config.creatorFeeBps, and credits both sides to its pull-escrow ledger in the currency the fee landed in: a buy's fee is in the launch token and a sell's is in the quote. Read the share off the launch rather than assuming it. On an ordinary buy the taxed amount is in the launch token and the hook credits it to BURN_SINK, and the quote the buyer paid stays in the market.
Nothing leaves the hook during a swap. What it owes an address is owed(token, account), and claim(token) takes it. claimFor(token, account) is the same payment made by anyone, and it never pays anyone but account. A creator sends that income somewhere else with setCreatorFeeRecipient(poolId, recipient), callable by the address receiving it today, which starts as the creator.
| Call | Purpose |
|---|---|
LaunchLens.launchesOf(poolIds) | Config, position, state, raise, price, and opening tax, per pool id. |
LaunchHook.snipeTaxBps(poolId) | The opening tax in force at this block, in basis points. |
LaunchHook.snipeExempt(poolId, account) | Whether an address pays no opening tax. Matched against the caller of the PoolManager, so a router hides the trader behind it. |
LPLocker.QUOTE() · LPLocker.TOKEN() | The two assets this market pairs. |
LPLocker.realQuote() · GRADUATION_QUOTE() | The raise so far and the raise that graduates it, in the quote's own units. |
LPLocker.graduated() | Whether the raise has closed. |
LaunchHook.owed(token, account) | What the hook still owes an address in one asset, in that asset's own units. |
Markets created before this generation are bonding curves rather than pools. Those carry buy, buyFor, sell, sellWithPermit, quoteBuyFor, TRADE_FEE_BPS, QUOTE_DECIMALS() and LAUNCH_START. None of those selectors exists on a market the current factory creates, and calling one reverts. Call poolId() on the market address to tell the two apart: a pool-backed market answers it, a curve does not.
Combined launches
While the curve is open, mintToCurveBps of each NFT payment moves into the curve reserve and is recorded against the minter in AllocationVesting. The routed quote counts toward the target without changing the virtual reserves that set the price.
A minter's share is fixed when the launch graduates and the reserved slice is delivered: allocationOf(minter) is pro-rata to what they routed in. claimable(minter) reports what has vested, and claim() releases it under the launch's cliff and duration. The claim belongs to the wallet that minted; selling or transferring the NFT does not move it. finalizeBeneficiary(minter) closes a beneficiary out once they have claimed in full, so burnSurplus can burn the rounding dust left above the reserved balance.
Agent launches
AgentTreasuryFactory.createAndLaunch(charter, tp, np, lp, d, runway) deploys an AgentTreasury, moves runway into it from the caller, and has it call TokenLaunchFactory.createLinkedLaunch with the same tuples a combined launch takes. The treasury is therefore the launch's creator, and the creator's share of every trade fee is credited to it. Approve runway of RUNWAY_ASSET() to the factory first; it is the launch factory's fee token, and a launch settling in anything else is refused.
The charter is (address operator, uint128 dailySpend, uint128 perCallSpend, uint128 dailySell, uint32 dailyMint, address[] payees), fixed in the constructor. The three quote figures are in RUNWAY_ASSET and dailySell is in the launch token. perCallSpend has to be non-zero and at most dailySpend; at most eight payees, each once.
The operator key calls buy(amountIn, minAmountOut, deadline), sell(amountIn, minAmountOut, deadline), mint(quantity, minRoutedTotal), pay(to, amount) and note(digest, uri). Trading goes through this launch's own pool; the treasury reads the key off its locker, so there is no venue argument. Quote leaving the contract is metered against dailySpend and perCallSpend together across buys, mints and payouts; selling is metered on the order rather than the fill. The window is the UTC day. remainingToday() answers what is left of each ceiling.
claimFees() and claimVested() are permissionless and can only pay the treasury: the first pulls the creator's share out of the hook, the locker and the collection, the second the slice the treasury's own mints earned. The creator keeps setCollectionBaseURI, setCollectionPlaceholderURI and freezeCollectionMetadata, and has no other call. There is no owner, no pause and no withdrawal.
Every call emits: Launched, Bought, Sold, Minted, Paid, FeesClaimed, VestedClaimed and Noted. AgentTreasuryFactory.isFromFactory(address) is what tells a treasury this factory deployed from any other contract answering the same reads.
Uniswap v4 liquidity pool
LaunchHook.graduationReady(poolId) turns true once LPLocker.realQuote() clears GRADUATION_QUOTE() and the opening tax window has closed. From there LPLocker.settleGraduation() is callable by anyone and runs once. It closes the raise on the hook, retires the curve position whole, opens a full-range position out of the proceeds and anything a linked collection routed in, hands a linked launch its reserved slice, and burns whatever supply is left. Swaps are refused between the close and the permanent position, which is why it is one transaction rather than two. Quote beyond what the token inventory can pair goes to the treasury rather than moving the opening price.
There is no graduate() on a market address. The hook's graduate(poolId) takes only the launch's own locker, so settleGraduation() is the call to make: it is permissionless, anyone can finish a market that has reached its target, and it closes the raise and settles the position in one transaction or reverts whole.
The locker holds the position under UNLOCK_AT. A permanent lock never reaches unlock. The pool charges no fee of its own, so collectFees normally collects nothing. It stays because the position is real, and a pool that ever earned a fee would have no other way to divide it: the call divides the position's fees on the share the factory stamped into the locker: nothing to the creator on a locker from QuoteFeeFactory 0x66112C2c987eD7A366715bD28CCc0DF629813206, which sends the position's fees to the treasury the factory recorded, and a 70% share to the creator on one from TokenLaunchFactory 0xE89C1b33B6C2505414667bE7E5084774a72150e1, with the rest to the treasury. What a finished market pays its creator is the trade fee the hook credits, taken with LaunchHook.claim. A time-locked position can be unwound after its timestamp, with a floor on both amounts.
Reading the board
Every launch is read from the factory that created it, and two token launch factories have made markets on this chain, so a complete board pages both and asks each one isFromFactory in turn. launchCount() and launches(offset, limit) page the token launches; collectionCount() and collections(offset, limit) page the drops. isFromFactory(address) is the check that an address came from Ripples rather than from a lookalike deployment: pass the launch's locker on the token rail, the collection on the NFT rail. Multicall3 sits at its canonical address on Robinhood, so a page of launches reads in one request.
const count = await client.readContract({
address: tokenLaunchFactory,
abi: TOKEN_FACTORY_ABI,
functionName: "launchCount",
});
const page = await client.readContract({
address: tokenLaunchFactory,
abi: TOKEN_FACTORY_ABI,
functionName: "launches",
args: [0n, count],
});
// page: { token, locker, hook, creator }[]A token answers for itself. name() and symbol() are the ERC-20 pair; logo() and description() return strings; and socials() returns five links as twitter, telegram, discord, website and farcaster, any of which may be empty. All seven are written in the token's constructor and have no setter, so what you read once is what the launch froze. Nothing about a launch depends on an index this app controls.
Crosscurrents
With Fill first, each side of a Crosscurrent is held in an escrow: a contract on its own chain that holds the side's opening buy until the side opens or refunds, and pays out to its market, to the wallets that paid in, and to the creator (the launch deposit, and on Robinhood the creator's trade fees). On Solana the upgrade authority listed below can replace the program and change this. The earlier dual_fill program listed below still runs the Crosscurrents created before the current one.
Both escrows store the same dualFillKey, the sha256 of the bytes ripples-dual-fill-v1, a zero byte and the canonical JSON of the record's core. The core names both creators, both factories or programs, the identity and terms hashes, the deadline and the nonce, so anyone holding the record recomputes the key and finds both escrows from it. The record id is the base64url of the key's first 16 bytes.
A creator calls createFill on the factory with the launch parameters, the dualFillKey, the target, the deadline, the public window's end, the open-alone choice and a fee budget. Contributors call deposit, or depositEth where the factory wraps ETH, and withdraw while the side is filling. The opener, a key Ripples holds that the factory and each fill's terms name as KEEPER, calls open once the side is full, or abort to return a side that must open with the other to its contributors at once. Anyone calls claimFor and refundFor on anyone's behalf, returnFeeBudget once a side is refundable, and claimFees and handOverFees after it opens. Events: FillCreated, Deposited, Withdrawn, Filled, Cancelled, Aborted, Opened, Claimed, Refunded, FeeBudgetReturned, CreatorFeesForwarded and FeesHandedOver.
A creator calls create_fill, which moves the launch reserve into the vault. Contributors call contribute and withdraw; the creator can cancel a filling side before its deadline. The fill's opener, the Ripples key each fill names, calls open or abort. Anyone pays for claim, refund, return_reserve and close_fill. close_fill never closes the fill account itself, so no fill can be created twice at one address. Events: FillCreated, Contributed, Withdrawn, FillFull, FillCancelled, FillAborted, FillOpened, Claimed, Refunded, ReserveReturned and FillClosed.
The opener is the Crosscurrent opening key, a key Ripples holds: KEEPER on the factory and opener on each Solana fill. It can open a full side, abort a side that must open with the other into refunds, return launch deposits and forward creator fees. It cannot move contributions anywhere else or open a side that is not full.
A Crosscurrent with a collection is created through LinkedDualFillFactory.createFill, which takes the launch, collection and coupling parameters beside the fill's own terms and stores keccak256(abi.encode(tp, np, lp)) as PARAMS_HASH. open(tp, np, lp) creates the linked launch, its collection and the minters' vesting in one transaction, freezes the collection's metadata, and emits Opened then CollectionOpened. collection() and vesting() read zero until then. Every other call and event is DualFill's, with the same selectors. The fill stays the collection's owner and royalty receiver, and claimFees forwards what the collection pays it to the creator.
On Solana, create_linked_fill writes the fill with the Linked profile and a LinkedTerms account at the seeds linked and the fill. open_linked(collection_nonce) creates the launch, its collection and the opening buy in one instruction, and forward_income sends collection revenue and royalties that reached the fill's vault on to the creator. Events: LinkedFillCreated, LinkedOpened and IncomeForwarded.
Agents
An agent can create a Crosscurrent, join one, and claim or take back its money without a browser. It signs with its own keys, a Robinhood Chain wallet and a Solana keypair, and pays every network fee itself. Ripples never signs for it or holds its keys.
Every answer starts with statusLabel and summary. Quote them instead of describing status yourself.
import { readFile } from "node:fs/promises";
import { createDualFillClient, solanaSignerFromFile } from "@ripplesdotrun/agent-sdk";
import { privateKeyToAccount } from "viem/accounts";
const client = createDualFillClient({
network: "testnet",
evm: privateKeyToAccount(process.env.RIPPLES_PRIVATE_KEY),
solana: await solanaSignerFromFile(process.env.RIPPLES_SOLANA_KEYPAIR),
apiBase: process.env.RIPPLES_API_BASE,
site: process.env.RIPPLES_SITE,
});
const created = await client.create({
identity: {
name: "Example",
symbol: "EXMPL",
description: "What this coin is for.",
image: { bytes: await readFile("coin.png"), mime: "image/png" },
},
openingBuy: "0.01",
deadline: { minutes: 360 },
ifOneSideFills: "refund-both",
});
console.log(created.statusLabel, created.summary, created.url);{
"mcpServers": {
"ripples": {
"command": "npx",
"args": ["-y", "@ripplesdotrun/mcp"],
"env": {
"RIPPLES_NETWORK": "robinhood-testnet",
"RIPPLES_PRIVATE_KEY": "…",
"RIPPLES_SOLANA_KEYPAIR": "/path/to/keypair.json",
"RIPPLES_API_BASE": "…",
"RIPPLES_SITE": "…"
}
}
}
}Launch types
A bridged Crosscurrent is one token supply with an address on Solana, Robinhood and Arc and a balance a holder moves between them. One chain is its home: that chain mints the whole supply, carries the linked NFT collection, and holds the tokens backing the other two. The other two venues start at zero and receive their share over the bridge. Each venue then opens its own market with its own opening buy, in its own settlement asset, sized to the same dollar value at the exchange rates in the quote.
Creators start one at /launch/bridged. The rest of this section is the API that page is built on. Paths are relative to https://api.ripples.run/v1/bridged-crosscurrents unless they are written out in full, and every amount is a decimal string of whole units at its own asset's decimals.
GET /status answers without a credential. creationEnabled says whether new bridged launches can be created right now, and reason names the condition that closed them when they cannot. window.modes lists the two ways a launch can open, window.default is "launch-now", and window.maxSeconds is 24 hours, the furthest ahead a deadline may sit. window.publicSeconds belongs to the backing window and is explained with it under reserving. release carries the contracts this API is configured for, and releaseHash names that set in one value, so a client can tell when it changes.
GET /quotes lists the assets a Robinhood market can be funded in. Each row carries address, chainId, symbol, decimals, uiMultiplier (how many underlying shares one token represents, as a decimal number) and isDefault. The chain's own asset is the default row and carries no expiresAt: its reference price is held to a maximum age, and POST /quote refuses one that has gone stale. A stock row carries an expiresAt and stops being offered once it passes. holderRewards says which of the two EVM markets can route creator fees to token holders. Arc and Solana each settle in one asset, so neither has a list.
POST /quote prices one launch and creates nothing. It takes no credential. The body is judged on its own fields before any chain is read, so a field that is missing or malformed comes back as a 400 that names it, whatever the chains are doing.
origin"robinhood" or "arc".targetUsdE8"100000000".creatorTaxBpscollection.piecescollection.priceQuotecollection.mintersShareBpscollection.mintToCurveBpscollection.vestCliffcollection.vestDurationcollection.royaltyBpscollection.perWalletCap0 sets no cap.globalSupplySDquotes.robinhoodGET /quotes. Left out, the chain's own asset is used.holderRewardsrobinhood and arc, each true or false, naming the markets whose creator fees go to token holders. The Solana market is not part of this choice: it is routed after it opens, on a confirmation from the creator's Solana wallet.pricedpriced block from an earlier answer, so the launch is priced at the rates you reviewed. Rates that have moved too far are refused with a 422.The answer carries terms (every figure the three markets open on), priced (the reference rates it used), releaseHash and expiresAt. Quote again once expiresAt passes.
POST /prepare writes the reservation. It needs a bearer token from the creator's Robinhood wallet, and the collection artwork has to be uploaded and sealed first. Send the same body you quoted, plus releaseHash, terms and priced from the quote, a 32-byte nonce, a solanaNonce, creators naming the EVM and Solana wallets, the token identity, collection.artworkKey, and how the token opens.
mode"launch-now", the default, or "backing-window". With launch now the creator's opening buy goes in as each venue's fill is created, nobody else can join, and the token opens on all three chains as soon as the last fill is in. Send no publicUntil, or 0.deadlineSending the same body twice returns the one reservation it already made. A body whose terms or priced differ from what the API prices now is refused with a 409, so nothing is reserved on figures you did not see. The answer is the record: the plan both wallets sign, the three markets' local terms, and the messages to sign for each venue.
mode: "backing-window" lets anyone join each opening buy before the token opens, capped per wallet, and the markets wait for the window to end. It takes the two times below in place of the default deadline.
publicUntilwindow.publicSeconds on GET /status reports.deadlinepublicUntil and at most 24 hours ahead. Every venue's opening buy has to be full by then.GET /{assetId} returns one launch as the API holds it, including the latest receipts for the current attempt. Older receipts page through GET /{assetId}/attempts/{attemptId}/evidence?limit=&offset=, in pages of 1 to 100 within the first 10,000. Signatures a creator replayed on chain are shown to that creator and committed to by hash for everyone else.
The record also carries displayImageUrl: the picture Ripples shows for the launch, or null when it is still the committed one. The image sealed into the token on each chain cannot change; the picture on Ripples can. POST /{assetId}/display takes imageUrl, an image published through /v1/launch-media, and signature, either creator wallet signing the text Ripples bridged display image, the asset ID and the address, one per line. The latest accepted picture is the one shown, and every listing carries it.
POST /{assetId}/transfers/quote prices one transfer from source to destination for amountLD in the source's own decimals, and returns the fee the bridge charges with a quoteHash over it. POST /{assetId}/transfers/evidence records the receipt for a send that already happened, and GET /{assetId}/transfers/{guid} answers with that receipt and whether the destination has credited it yet. A transfer stuck undelivered can be pushed again with POST /{assetId}/transfers/{guid}/retry, which builds a transaction the owner signs. The balance leaves the source before it arrives on the destination, so a transfer in flight is on neither side.
GET /v1/bridged-crosscurrents lists the launches on the configured release, newest first, as total, limit, offset and rows. limit is 1 to 100, offset counts rows within the first 10,000, and creator narrows the list to one EVM wallet. Each row carries the launch's name, symbol, home chain, current state and every address it has recorded on a chain, which is enough to link to it without reading the record. GET /v1/bridged-crosscurrents/by-address/{network}/{address} goes the other way, from any of those addresses back to the launch. network is eip155:4663, eip155:5042 or solana:mainnet-beta.