> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runners.family/llms.txt
> Use this file to discover all available pages before exploring further.

# Launch a coin

> Upload metadata, sign, submit and confirm.

### One call

`POST /api/launch/create` publishes the coin's image and details and returns its launch transaction, already signed by RUNNERS: by the launchpad signer and by the new mint, whose key RUNNERS generates and does not keep. Sign it as the creator and send it to any Solana RPC. RUNNERS finds the launch on chain by its pool address; there is nothing to submit.

```json theme={null}
{
  "owner": "<creator wallet public key>",
  "name": "Example Coin",
  "symbol": "COIN",
  "description": "",
  "website": "", "x": "", "telegram": "",
  "image": "<base64 image bytes>",
  "imageType": "image/png",
  "quoteMint": "So11111111111111111111111111111111111111112",
  "mode": "standard",
  "firstBuy": "0"
}
```

Only `owner`, `name`, `symbol`, `image` and `imageType` are required. `quoteMint` defaults to SOL, and `mode` to `standard` for SOL and `rewards` otherwise. `firstBuy` is in quote-token atoms; optional `priority` and `slippageBps` work as in prepare. Name, symbol, description, links and image follow the rules in Upload metadata. A description that looks like HTML can be refused by the CDN.

The response holds the transaction and the coin's addresses:

```json theme={null}
{
  "transaction": "<base64 wire, signed by everyone except the creator>",
  "mint": "<coin mint>",
  "pool": "<pool address>",
  "config": "<config address>",
  "quoteMint": "So11111111111111111111111111111111111111112",
  "mode": "standard",
  "blockhash": "<blockhash>",
  "lastValidBlockHeight": "<height>",
  "metadataUri": "<metadata URI>",
  "costs": {"configRent":"0","poolRent":"...","totalRent":"...","firstBuyRent":"0","wrappedSolRent":"0"},
  "firstBuyQuote": null
}
```

Sign and send it before the blockhash expires, about a minute. Add the creator's signature to the returned bytes; RUNNERS' signatures stay in place:

```js theme={null}
// @solana/web3.js
const tx = VersionedTransaction.deserialize(Buffer.from(transaction, 'base64'));
tx.sign([creator]);
await connection.sendRawTransaction(tx.serialize());
```

With Python `solders`, sign the message and keep the other signatures: `VersionedTransaction.populate(tx.message, [creator.sign_message(to_bytes_versioned(tx.message)), *tx.signatures[1:]])`. `VersionedTransaction(tx.message, [creator])` fails because it expects a keypair for every signer.

Optionally poll `POST /api/launch/confirm` with the mint and your transaction's signature. A pair token without a launch configuration for its current price band returns `config_required`: retry later, or launch step by step below.

### Before you start

Use a funded Solana wallet. Generate and retain separate mint and config keypairs locally; retries reuse them. Send JSON and omit the HTTP `Origin` header.

* `mode: "standard"` pairs with SOL (`So11111111111111111111111111111111111111112`). `"rewards"` accepts a supported quote token.
* Amounts are decimal strings in integer base units. `firstBuy: "0"` skips the initial buy; `"100000000"` is 0.1 SOL. Other quote tokens use their on-chain decimals. Rent and network fees use lamports.
* `GET /api/metadata` returns the authorization challenge. Use its `appOrigin`, `deploymentId` and Unix-second `issuedAt`; timestamps are accepted from 300 seconds before to 30 seconds after server time.
* Wallet message signatures for the upload and the preparation are optional: the launch transactions your wallet signs are what authorize the coin. A signature that is sent must be valid.

### Upload metadata

Upload image bytes; caller-provided image or metadata URLs are not accepted. If your image is already hosted, download it locally and upload those bytes. RUNNERS publishes the launch metadata.

Build `metadata` with keys in this exact order, including empty fields:

```json theme={null}
{"name":"Example Coin","symbol":"COIN","description":"","socials":{"x":"","telegram":"","website":""}}
```

Trim name, symbol and description before computing the digest. Name: 1–32 UTF-8 bytes, no control characters. Symbol: `^[A-Z0-9]{1,10}$`. Description: at most 280 characters. Social links: empty or an HTTP(S) URL with a dotted hostname, no credentials and at most 2048 characters. Normalize nonempty links with `new URL(link).href`, adding `https://` if no scheme was supplied.

Images must be PNG, JPEG, GIF or WebP, at most 5 MiB and 4096 × 4096 pixels. Compute lowercase hexadecimal SHA-256 hashes:

```text theme={null}
imageHash = sha256(imageBytes)
digest = sha256(utf8(JSON.stringify([
  "RUNNERS metadata content v1", metadata, imageType, imageHash
])))
```

To sign the upload (optional), sign this exact UTF-8 message with ed25519. Join lines with `\n`, with no trailing newline:

```text theme={null}
RUNNERS launch metadata v1
Origin: <appOrigin>
Deployment: <deploymentId>
Wallet: <owner public key>
Content: <digest>
Issued: <issuedAt>
Authorize publishing this coin's image and metadata. No transaction or token transfer.
```

Build the authorization, adding `"signatureBase64":"<message signature>"` only if you signed:

```json theme={null}
{"owner":"<wallet public key>","deploymentId":"<deploymentId>","issuedAt":1791200000,"metadata":{"name":"Example Coin","symbol":"COIN","description":"","socials":{"x":"","telegram":"","website":""}}}
```

Send `POST /api/metadata` with that JSON base64-encoded as `authorizationBase64`:

```json theme={null}
{
  "authorizationBase64": "<base64 of the authorization JSON>",
  "image": {"type":"image/png","base64":"<image bytes>"}
}
```

Send both parts base64-encoded: the CDN in front of RUNNERS refuses request bodies that look like HTML, such as a description containing `<a href=`, or raw image bytes that happen to match, with its own HTML `403` page. A plain `authorization` object, or multipart fields `image` (file) and `authorization` (JSON string), are still accepted but can be refused that way. Save the returned `id` and `namespace`. Identical uploads return the same record; unused uploads expire after 24 hours.

### Prepare

Optional cost check:

```http theme={null}
GET /api/launch/estimate?quote=So11111111111111111111111111111111111111112&mode=standard
```

Read `costs`, `economics`, `transactionCount` and `validUntil`. Network and priority fees are extra. For an initial buy, add the estimate's `firstBuyRent` to `totalRent`; prepare's `totalRent` already includes it. `wrappedSolRent` is temporary and refunded in the launch transaction. Prepare returns exact costs for your metadata; an estimate uses maximum name/ticker lengths.

Construct the request in exactly this property order:

```json theme={null}
{"deploymentId":"<deploymentId>","owner":"<wallet public key>","baseMint":"<mint public key>","quoteMint":"So11111111111111111111111111111111111111112","config":"<config public key>","mode":"standard","metadataId":"<upload id>","metadataNamespace":"<upload namespace>","firstBuy":"0","issuedAt":1791200000}
```

`owner`, `baseMint`, `quoteMint` and `config` must all differ. Refresh the challenge if its timestamp expired. To sign the preparation (optional), sign these UTF-8 lines, joined with `\n` and no trailing newline:

```text theme={null}
RUNNERS launch preparation v2
Origin: <appOrigin>
<JSON.stringify(request), no spaces>
Authorize preparing this coin's launch. Your wallet will approve the launch transactions separately.
```

Send `POST /api/launch/prepare` with those request fields, plus `signatureBase64` if you signed. Optional unsigned `options` fields are `priority` (`normal`, `fast` or `turbo`; default `fast`), `slippageBps` (0–5000; default 0) and `buildTransactions` (default `true`). A zero-slippage first buy executes inside pool creation. Setting `buildTransactions: false` returns no transactions to sign.

The response includes unsigned `transactions`, their `signers`, `costs`, `economics`, `config`, `pool` and `validUntil`:

| `configSource` | Transactions |
| - | - |
| `shared` | Sign `transactions.pool`; `transactions.config` is `null`. Use the returned `config`, which can differ from your config key. |
| `creator` | Sign `transactions.config` with the owner and config keys, then `transactions.pool` with every listed signer. |

Inspect the transaction instructions, fee payer, metadata, first-buy amount and launch terms before signing. Shared configurations can be verified against a simulation of the expected config instruction; their `configCreationSignature` alone is not verification.

### Sign and submit

Sign every returned transaction with its listed keys, without changing its message:

```js theme={null}
// @solana/web3.js; requiredKeypairs match this transaction's signers.
const tx = Transaction.from(Buffer.from(transactionBase64, 'base64'));
tx.partialSign(...requiredKeypairs);
const signedWire = tx.serialize().toString('base64');
```

`transactions.pool` arrives with one signature already in place: RUNNERS' launchpad signer `RUNXxXY4j5LnT4DAZm4w3Vg6sB8bpFxSxUg1rwqLkHx`, a read-only signer that no instruction uses. Trading terminals attribute the coin to RUNNERS by it. It is not in `signers`: add your signatures to the returned bytes with `partialSign` as shown. web3.js `sign()` resets existing signatures, and rebuilding the transaction from its message drops this one, so the transaction can no longer be completed.

If you build the pool transaction yourself (`buildTransactions: false`), add the launchpad signer as a read-only signer after the creator and mint, used by no instruction. Sign with the mint, then, before the creator signs, send `POST /api/launch/tag` with `{"deploymentId","baseMint","owner","transactionBase64"}`. The response `{"signer","signatureBase64"}` is the launchpad's signature for that exact message; add it, then sign with the creator. RUNNERS signs only a pool transaction that matches your prepared draft. An untagged pool transaction is still accepted.

Persist the prepared response, signed wires and signatures before sending. Submit before `validUntil` (at most 120 seconds after prepare) and before blockhash expiry.

`POST /api/launch/submit`:

```json theme={null}
{
  "deploymentId":"<deploymentId>",
  "owner":"<wallet public key>",
  "baseMint":"<mint public key>",
  "pool":"<pool from prepare>",
  "config":"<config from prepare>",
  "configTransaction":null,
  "poolTransaction":{
    "signature":"<base58 fee-payer signature>",
    "transactionBase64":"<signed wire in canonical base64>",
    "blockhash":"<from prepare>",
    "lastValidBlockHeight":"<from prepare>"
  },
  "relay":true
}
```

For `configSource: "creator"`, supply `configTransaction` in the same shape as `poolTransaction`. With `relay: true`, RUNNERS saves and verifies the wires, then broadcasts config first and pool after config confirmation. Otherwise broadcast in that order yourself. Repeating the same signed submission is idempotent; a successful save is not confirmation.

### Confirm and retry

`POST /api/launch/confirm`:

```json theme={null}
{"deploymentId":"<deploymentId>","baseMint":"<mint public key>","signature":"<pool transaction signature>"}
```

| `state` | Action |
| - | - |
| `unknown`, `config_landed` | Poll every few seconds. The launch is unresolved. |
| `confirmed` | Save the mint and receipt. Stop retrying. |
| `failed`, `expired` | Prepare again only if `retry` is `pool` or `both`. |

On an offered retry, reuse the original mint, config key, metadata and quote, with a fresh authorization and freshly signed prepared transactions. `pool` rebuilds the pool transaction; `both` rebuilds both. A landed creator config fixes the price band; a later move of more than one band makes the launch `not_retryable`. Start a new mint/config only when recovery requires it.

After a timeout, check the existing launch before replacing anything. `unknown` does not mean failed. See [Errors and limits](https://docs.runners.family/guides/errors.md) for HTTP retries.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.