What the API provides
The API supports native and approved ERC-20 launch pairs and fixed, DAO, or delegated strategy control. A launch supports up to eight strategy legs; integrations read the live limit from config.limits.strategyLegLimit, and the contracts and schema enforce a hard maximum of 16. The API validates the configuration and returns the exact coordinator transaction. The machine-readable contract is available at /api/public/v1/openapi.json.
Tokens launch on Flap (venue: "flap", a tax token with configurable tax destinations), and strategies trade on Aster. The top level of GET /config describes the Flap launch; config.venues.flap mirrors it.
Your application reads live configuration, prepares a launch, signs locally, broadcasts directly or through the optional relay, and polls by launch ID. A private key is never sent to Jibe.
For launched tokens, it also reads canonical vault state and prepares delegated or DAO transactions for local signing. Fixed vaults remain read-only through the public API.
BNB Smart Chain
Jibe runs on BNB Smart Chain. Every endpoint accepts a chainId query parameter and every launch draft carries a chainId field; both default to 56 when omitted, and any other id returns 400 UNSUPPORTED_CHAIN. config.chain echoes the chain the API answered for.
The native launch quote is BNB and the settlement stablecoin is USDT; both use 18 decimals. Market IDs are opaque integers from /markets and name Aster perpetual markets. The explorer is bscscan.com, and a public RPC is https://bsc-dataseed.bnbchain.org.
Install and connect
These Node.js examples use viem. Use a managed signer or connected wallet in production; an environment key is shown only to keep the server-side example compact.
npm install viem # Keep private keys out of source control. CREATOR_PRIVATE_KEY=0x... # 56 = BNB Smart Chain. Every read and draft below is scoped to this chain. CHAIN_ID=56 # Any BNB Smart Chain RPC. RPC_URL=https://bsc-dataseed.bnbchain.org IMAGE_URI=ipfs://... METADATA_URI=ipfs://... METADATA_HASH=0x...
Shared setup and live catalogs
Source the current coordinator, quote assets and pair IDs, limits, and Aster market IDs from the API. Do not hardcode a deployment address.
import {
createPublicClient,
createWalletClient,
decodeFunctionData,
defineChain,
http,
isHex,
keccak256,
parseUnits,
size,
toHex,
zeroAddress,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
const API_BASE = "https://www.jibe.trade/api/public/v1";
const CHAIN_ID = Number(process.env.CHAIN_ID ?? 56);
const rpcUrl = process.env.RPC_URL;
const privateKey = process.env.CREATOR_PRIVATE_KEY;
if (!rpcUrl || !privateKey || !isHex(privateKey) || size(privateKey) !== 32) {
throw new Error("Missing RPC URL or valid 32-byte signer");
}
const account = privateKeyToAccount(privateKey);
// Every endpoint is scoped by chainId (56 when omitted). Sending it on every
// call keeps reads, drafts, and vault actions on one chain.
async function api(path, init) {
const url = new URL(`${API_BASE}${path}`);
url.searchParams.set("chainId", String(CHAIN_ID));
const response = await fetch(url, init);
const payload = response.headers.get("content-type")?.includes("application/json")
? await response.json()
: null;
if (!response.ok) {
if (response.status === 429) {
throw new Error(
`Rate limited; retry after ${response.headers.get("retry-after") ?? "a short delay"} seconds`,
);
}
throw new Error(
payload?.error
? `${payload.error.code}: ${payload.error.message}`
: `API request failed with status ${response.status}`,
);
}
if (!payload || !("data" in payload)) throw new Error("Invalid API response");
return payload.data;
}
const [config, marketCatalog] = await Promise.all([
api("/config"),
api("/markets"),
]);
if (config.chain.id !== CHAIN_ID) throw new Error("Config is for another chain");
if (!config.capability.canPrepare) {
throw new Error(config.capability.blocker ?? "Launching is unavailable");
}
// config.chain describes the chain the API answered for: id, name, native
// currency (BNB), and explorer. Build the viem chain from it instead of
// hardcoding one.
const chain = defineChain({
id: config.chain.id,
name: config.chain.name,
nativeCurrency: config.chain.nativeCurrency,
rpcUrls: { default: { http: [rpcUrl] } },
blockExplorers: {
default: { name: "Explorer", url: config.chain.explorerUrl },
},
});
const wallet = createWalletClient({ account, chain, transport: http(rpcUrl) });
const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
const strategyMarkets = marketCatalog.strategyMarkets.filter(
(market) => market.verified,
);
const nativeQuote = marketCatalog.quoteAssets.find(
(quote) => quote.selectable && quote.address.toLowerCase() === zeroAddress,
);
if (!nativeQuote || strategyMarkets.length === 0) {
throw new Error("No selectable native quote or strategy market");
}Flap launch
This complete fixed-strategy Flap example uses BNB as the pair, one perpetual market, no opening purchase, 30-day anti-farmer protection, and the website’s other default launch values.
const primaryMarket = strategyMarkets[0];
const nativeFixedLaunch = {
// The chain the draft targets; must match the chain /config and /markets
// were read from. Defaults to 56 (BNB Smart Chain) when omitted.
chainId: CHAIN_ID,
// Wallet that must sign the prepared launch transaction.
creator: account.address,
// Token identity. Public launches require canonical ipfs:// URIs.
name: "Example Jibe",
symbol: "EXPERP",
description: "A token with a managed perpetual strategy.",
imageUri: process.env.IMAGE_URI,
metadataUri: process.env.METADATA_URI,
metadataHash: process.env.METADATA_HASH,
website: "",
twitter: "",
telegram: "",
github: "",
youtube: "",
debox: "",
venue: "flap",
// Read launchable quote details from /markets; never hardcode decimals.
quoteAsset: nativeQuote.address,
quoteSymbol: nativeQuote.symbol,
quoteDecimals: nativeQuote.decimals,
// Maximum opening purchase in raw quote units. "0" disables it.
creatorBuyMode: "quote",
initialBuyAmount: "0",
// Trading tax in basis points: 100 BPS = 1%.
buyTaxBps: 100,
sellTaxBps: 100,
// Creator weights over the post-protocol remainder must total 10,000 BPS.
vaultBps: 10000,
dividendBps: 0,
deflationBps: 0,
lpBps: 0,
// Seconds. The website default is 30 days.
antiFarmerDurationSeconds: 30 * 86400,
// Zero allocates all returned USDT classified as strategy profit to the
// buyback bucket. A keeper later executes the purchase and dead-address transfer.
profitDividendBps: 0,
// Quote dividends use the selected launch-pair asset.
dividendMode: "quote",
dividendAsset: nativeQuote.address,
// Fixed mode has no DAO vote or delegated manager.
strategyMode: "fixed",
strategyLegs: [{
// IDs and limits come from /markets.
marketId: primaryMarket.marketId,
marketLabel: primaryMarket.label,
direction: "long",
// 10,000 BPS = 100% of strategy equity.
equityAllocationBps: 10000,
// 20,000 BPS = 2x, capped by the live market limit.
requestedLeverageBps: Math.min(
20000,
primaryMarket.maximumLeverageBps,
),
}],
};
const prepared = await api("/launches/prepare", {
method: "POST",
headers: {
"content-type": "application/json",
"idempotency-key": crypto.randomUUID(),
},
body: JSON.stringify({ launch: nativeFixedLaunch }),
});
console.log(prepared.launchId);
console.log(prepared.predictedToken);
console.log(prepared.launchTransaction);The response includes launchId, predictedToken, expiresAt, launchTransaction, and approvalTransaction. Native launches return a null approval.
Flap launch with an ERC-20 pair and multiple markets
When /markets advertises a selectable nonzero quote asset, this Flap variant expresses the opening purchase in raw token units and allocates the strategy across three verified markets.
const erc20Quote = marketCatalog.quoteAssets.find(
(quote) => quote.selectable && quote.address.toLowerCase() !== zeroAddress,
);
const [first, second, third] = strategyMarkets;
if (
!erc20Quote ||
config.limits.strategyLegLimit < 3 ||
!first ||
!second ||
!third
) {
throw new Error("This example requires an ERC-20 quote and three markets");
}
const erc20MultiMarketLaunch = {
...nativeFixedLaunch,
name: "Multi Market Index",
symbol: "MMARK",
// Switch the launch pair from BNB to a selectable ERC-20.
quoteAsset: erc20Quote.address,
quoteSymbol: erc20Quote.symbol,
quoteDecimals: erc20Quote.decimals,
// Convert a 100-token opening purchase to raw ERC-20 units.
initialBuyAmount: parseUnits("100", erc20Quote.decimals).toString(),
dividendAsset: erc20Quote.address,
// Distinct markets whose allocations total 10,000 BPS.
strategyLegs: [
{
marketId: first.marketId,
marketLabel: first.label,
direction: "long",
equityAllocationBps: 4000,
requestedLeverageBps: Math.min(20000, first.maximumLeverageBps),
},
{
marketId: second.marketId,
marketLabel: second.label,
direction: "short",
equityAllocationBps: 3500,
requestedLeverageBps: Math.min(15000, second.maximumLeverageBps),
},
{
marketId: third.marketId,
marketLabel: third.label,
direction: "long",
equityAllocationBps: 2500,
requestedLeverageBps: Math.min(15000, third.maximumLeverageBps),
},
],
};
const preparedErc20 = await api("/launches/prepare", {
method: "POST",
headers: {
"content-type": "application/json",
"idempotency-key": crypto.randomUUID(),
},
body: JSON.stringify({ launch: erc20MultiMarketLaunch }),
});DAO and delegated launches
DAO mode requires all four governance settings. Delegated mode requires a nonzero manager address, which becomes the delegated master. The master and its appointed delegates can replace the portfolio; only the master can manage delegates and transfer master authority. Fixed mode requires neither DAO settings nor a manager.
// DAO: token stakers propose and vote on replacement portfolios.
const daoLaunch = {
...erc20MultiMarketLaunch,
name: "Holder Governed Markets",
symbol: "HGOV",
strategyMode: "dao",
governanceSettings: {
// Duration is canonical seconds (12 hours here); the launch UI accepts
// minute, hour, or day input.
votingPeriodSeconds: 43200,
// A proposer needs 0.001% of the snapshotted token totalSupply().
proposalThresholdPpm: 10,
// At least 10% of the snapshotted token totalSupply() must participate.
quorumPpm: 100000,
// At least 60% of Support-plus-Oppose votes must be Support.
approvalBps: 6000,
},
};
// Delegated: the nonzero manager becomes the master authority.
const delegatedLaunch = {
...nativeFixedLaunch,
name: "Delegated Strategy",
symbol: "DSTRAT",
strategyMode: "delegated",
// The master can update the strategy, appoint multiple delegates, and transfer
// master authority. Appointed delegates can also update the portfolio.
strategyManager: "0x1111111111111111111111111111111111111111",
};
// Prepare either object using:
// body: JSON.stringify({ launch: daoLaunch })
// body: JSON.stringify({ launch: delegatedLaunch })Read and prepare vault actions
Read canonical contracts, the current profile version, cooldown, limits, roles, staking state, proposal state, and one optional actor at a single observed block. The actor must be the wallet that signs the prepared transaction.
const token = "0x..."; // Canonical launched token address.
const vaultState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
if (vaultState.actor?.address.toLowerCase() !== account.address.toLowerCase()) {
throw new Error("actor must equal the signing wallet");
}
console.log(vaultState.mode, vaultState.profile, vaultState.portfolio);
console.log(vaultState.delegated, vaultState.dao, vaultState.actor);
// Reads remain available even when action preparation is unavailable.
if (!vaultState.capability.canPrepareActions) {
console.log(vaultState.capability.actionBlocker);
}Preparation is stateless and advisory. It exposes only user-authorized actions, derives the target, calldata, recipients, fees, and current versions server-side, and never receives a private key. The contract revalidates permissions and state when mined. Fixed mode remains read-only for public users.
async function prepareVaultAction(token, input) {
if (input.actor.toLowerCase() !== account.address.toLowerCase()) {
throw new Error("actor must equal the signing wallet");
}
const currentConfig = await api("/config");
if (!currentConfig.capability.vaultManagement.canPrepareActions) {
throw new Error(
currentConfig.capability.vaultManagement.blocker?.message ??
"Vault management is unavailable",
);
}
return api(`/tokens/${token}/vault/actions/prepare`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input),
});
}
async function broadcastPreparedAction(preparedAction) {
// The API returns target, calldata, chain, and value. Never replace them.
// Preparation is stateless/advisory; the contract revalidates current state.
const { transaction } = preparedAction;
const hash = await wallet.sendTransaction({
account,
chain,
to: transaction.to,
data: transaction.data,
value: BigInt(transaction.value),
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") throw new Error("Vault action reverted");
return { hash, receipt };
}
// No private key, signed transaction, target, or calldata is sent to the API.
// Only the locally controlled account signs and broadcasts.
const preparedAction = await prepareVaultAction(token, {
action: "claim_rewards",
actor: account.address,
});
await broadcastPreparedAction(preparedAction);Delegated vault management
Source market IDs and leverage ceilings from /markets, and source the live leg limit, authorization, cooldown, and profile version from the vault response. Do not submit a target, calldata, or expected version. Preparation takes a fresh vault snapshot and checks each selected market’s enabled state and leverage at the returned observedBlock.
const delegatedState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
if (delegatedState.mode !== "delegated") throw new Error("Not delegated");
if (!delegatedState.actor?.authorized) throw new Error("Not authorized");
// Source IDs and leverage limits from /markets and leg capacity from vault state.
const market = marketCatalog.strategyMarkets[0];
if (!market || (delegatedState.limits?.strategyLegLimit ?? 0) < 1) {
throw new Error("No supported strategy market");
}
const replacement = await prepareVaultAction(token, {
action: "replace_portfolio",
actor: account.address,
active: true,
portfolio: [{
marketId: market.marketId,
direction: "long",
equityAllocationBps: 10000,
requestedLeverageBps: Math.min(20000, market.maximumLeverageBps),
}],
});
// The API reads and binds the current profile version; clients do not supply it.
if (replacement.expectedProfileVersion !== delegatedState.profile?.version) {
throw new Error("State changed; request a fresh preparation");
}
await broadcastPreparedAction(replacement);The master manages delegates, and only the current master can transfer master authority.
const delegate = "0x...";
const secondDelegate = "0x...";
const nextMaster = "0x...";
// The API checks the current actor role before every stateless preparation.
for (const input of [
{
action: "add_delegates",
actor: account.address,
delegates: [delegate, secondDelegate],
},
{ action: "remove_delegate", actor: account.address, delegate },
{ action: "clear_delegates", actor: account.address },
]) {
await broadcastPreparedAction(await prepareVaultAction(token, input));
}
// Only the current delegated master may transfer master authority.
await broadcastPreparedAction(await prepareVaultAction(token, {
action: "transfer_manager",
actor: account.address,
newManager: nextMaster,
}));DAO staking and rewards
Prepare and mine the canonical ERC-20 approval before preparing the stake. Refresh actor state between steps so allowance, position, and claimable rewards are current. Stake amounts are capped at uint160.max by the position storage type.
const amount = "1000000000000000000"; // Raw token units chosen by the user.
const daoState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
if (daoState.mode !== "dao" || !daoState.contracts.stakeVault) {
throw new Error("Not a DAO vault");
}
// Approval target and amount are encoded server-side from canonical state.
const approval = await prepareVaultAction(token, {
action: "approve_stake",
actor: account.address,
amount,
});
await broadcastPreparedAction(approval);
// Refresh state after approval; preparation checks the observed allowance.
const stake = await prepareVaultAction(token, {
action: "stake",
actor: account.address,
amount,
});
await broadcastPreparedAction(stake);let latestDaoState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
const claimableRewards = BigInt(
latestDaoState.actor?.stake?.claimableRewards ?? "0",
);
// Claim is a separate action. Simulate before broadcasting because pending
// reward sources can change between reads.
if (claimableRewards > 0n) {
const claim = await prepareVaultAction(token, {
action: "claim_rewards",
actor: account.address,
});
await publicClient.call({
account: account.address,
to: claim.transaction.to,
data: claim.transaction.data,
value: BigInt(claim.transaction.value),
});
await broadcastPreparedAction(claim);
}
latestDaoState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
const withdrawAmount = latestDaoState.actor?.stake?.amount;
if (!withdrawAmount || withdrawAmount === "0") throw new Error("No stake");
// withdraw() also attempts to deliver accrued rewards. Do not automatically
// send a second claim afterward; a zero-value claim reverts with InvalidAmount.
await broadcastPreparedAction(await prepareVaultAction(token, {
action: "withdraw_stake",
actor: account.address,
amount: withdrawAmount,
}));DAO proposals and voting
Proposal preparation binds the current profile version, canonical authority hub, current proposal fee, market constraints, token, and trimmed proposal title/body. The wallet submits one transaction; title and body are authenticated by that transaction and published in the proposal event.
const current = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
const proposalMarket = marketCatalog.strategyMarkets[0];
const title = "Increase primary market exposure";
const body =
"Increase primary market exposure while retaining the configured safeguards.";
if (
current.mode !== "dao" ||
!proposalMarket ||
(current.limits?.strategyLegLimit ?? 0) < 1
) throw new Error("DAO proposal is unavailable");
const proposal = await prepareVaultAction(token, {
action: "propose_portfolio",
actor: account.address,
active: true,
title,
body,
portfolio: [{
marketId: proposalMarket.marketId,
direction: "long",
equityAllocationBps: 10000,
requestedLeverageBps: Math.min(
20000,
proposalMarket.maximumLeverageBps,
),
}],
});
// Current version and proposal fee are derived from the same API state.
if (
proposal.expectedProfileVersion !== current.profile?.version ||
proposal.transaction.value !== current.dao?.proposalFeeWei
) throw new Error("Proposal state changed; prepare again");
await broadcastPreparedAction(proposal);Voting and execution use one of the active proposal IDs returned by fresh vault state. Anyone may request preparation, but the signing actor and contracts must satisfy the live rules. Voting power is the actor's current stake, so newly staked tokens can vote and a vote may be submitted again after adding stake. Support, Oppose, and Abstain are available.
const votingState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
const activeProposalId = votingState.dao?.activeProposals.find(
(proposal) => proposal.state === 1,
)?.id;
if (!activeProposalId) throw new Error("No active proposal");
if (BigInt(votingState.actor?.stake?.votes ?? "0") === 0n) {
throw new Error("No current voting weight");
}
await broadcastPreparedAction(await prepareVaultAction(token, {
action: "cast_vote",
actor: account.address,
proposalId: activeProposalId,
choice: "support",
}));// Refresh state after voting closes.
const executableState = await api(
`/tokens/${token}/vault?actor=${account.address}`,
);
const active = executableState.dao?.activeProposals.find(
(proposal) =>
proposal.state === 3 &&
proposal.isSelectedWinner &&
BigInt(executableState.observedBlock.timestamp) >=
BigInt(proposal.executeAfter),
);
if (!active) throw new Error("No selected proposal is ready for execution");
const execution = await prepareVaultAction(token, {
action: "execute_proposal",
actor: account.address,
proposalId: active.id,
});
// Simulate against current state in case conditions changed after the API read.
await publicClient.call({
account: account.address,
to: execution.transaction.to,
data: execution.transaction.data,
value: BigInt(execution.transaction.value),
});
await broadcastPreparedAction(execution);Proposal state 3 means voting succeeded, not that execution is immediately available. Execution requires the proposal's snapshotted executeAfter timestamp and isSelectedWinner. The winner is the earliest-ending successful proposal for the expected profile version, using proposal nonce as the tie-breaker. Simulate immediately before signing and treat the contract result as authoritative.
Metadata upload
You may pin content yourself and provide canonical ipfs:// URIs plus the bytes32 hash of the final metadata JSON. The optional convenience endpoint validates and pins content; it needs only an Idempotency-Key. A short-lived creator signature may be added to bind the upload to your wallet:
Artwork may be PNG, JPEG, WebP, GIF, or SVG, up to 3 MiB. Its declared MIME type must match the uploaded bytes.
import { readFile } from "node:fs/promises";
import { keccak256, toHex } from "viem";
function canonicalJson(value) {
if (Array.isArray(value)) {
return `[${value.map(canonicalJson).join(",")}]`;
}
if (value && typeof value === "object") {
return `{${Object.entries(value)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
.join(",")}}`;
}
return JSON.stringify(value);
}
const metadata = {
name: "Example Jibe",
symbol: "EXPERP",
description: "A token with a managed perpetual strategy.",
links: {
website: "", twitter: "", telegram: "",
github: "", youtube: "", debox: "",
},
};
const imageBytes = new Uint8Array(await readFile("./token.png"));
const idempotencyKey = crypto.randomUUID();
// Optional: bind the upload to your wallet. Skip the signature and the three
// x-wallet-*/x-signature-* headers below to upload with the key alone.
const expiresAt = new Date(Date.now() + 4 * 60_000).toISOString();
const imageHash = keccak256(toHex(imageBytes));
const metadataInputHash = keccak256(
toHex(new TextEncoder().encode(canonicalJson(metadata))),
);
const signature = await account.signTypedData({
domain: {
name: "Jibe Public API",
version: "1",
chainId: CHAIN_ID,
},
types: {
MetadataUpload: [
{ name: "wallet", type: "address" },
{ name: "chainId", type: "uint256" },
{ name: "audience", type: "string" },
{ name: "expiresAt", type: "uint64" },
{ name: "imageHash", type: "bytes32" },
{ name: "metadataHash", type: "bytes32" },
{ name: "nonceHash", type: "bytes32" },
],
},
primaryType: "MetadataUpload",
message: {
wallet: account.address,
chainId: BigInt(CHAIN_ID),
audience: API_BASE,
expiresAt: BigInt(Math.floor(new Date(expiresAt).getTime() / 1000)),
imageHash,
metadataHash: metadataInputHash,
nonceHash: keccak256(toHex(idempotencyKey)),
},
});
const form = new FormData();
form.set("image", new File([imageBytes], "token.png", { type: "image/png" }));
form.set("metadata", JSON.stringify(metadata));
const response = await fetch(`${API_BASE}/metadata`, {
method: "POST",
headers: {
"idempotency-key": idempotencyKey,
"x-chain-id": String(CHAIN_ID),
"x-wallet-address": account.address,
"x-wallet-signature": signature,
"x-signature-expires-at": expiresAt,
},
body: form,
});
const payload = await response.json();
if (!response.ok) throw new Error(payload.error?.message);
const { imageUri, metadataUri, metadataHash } = payload.data;The optional authorization hash covers the sorted input and image bytes. The returned metadataHash covers the final pinned metadata, including its image URI; use that returned value when preparing.
Leave links.website empty and the pinned metadata may link to the token's Jibe page instead. A website you supply is never replaced.
Approve and broadcast
Execute an ERC-20 approval when one is returned, wait for its successful receipt, then submit the exact launch transaction. Never replace the supplied spender or approval amount. The spender is the coordinator, config.coordinator.
async function executePreparedLaunch(preparation) {
const latest = await api("/config");
if (!latest.capability.canLaunch) {
throw new Error(latest.capability.blocker ?? "Launching is unavailable");
}
// ERC-20 creator purchases return an exact approval.
if (preparation.approvalTransaction) {
const approval = preparation.approvalTransaction;
const approvalHash = await wallet.sendTransaction({
account,
chain,
to: approval.to,
data: approval.data,
value: BigInt(approval.value),
});
const receipt = await publicClient.waitForTransactionReceipt({
hash: approvalHash,
});
if (receipt.status !== "success") throw new Error("Approval reverted");
}
const transaction = preparation.launchTransaction;
const launchHash = await wallet.sendTransaction({
account,
chain,
to: transaction.to,
data: transaction.data,
value: BigInt(transaction.value),
});
const launchReceipt = await publicClient.waitForTransactionReceipt({
hash: launchHash,
timeout: 10 * 60_000,
});
if (launchReceipt.status !== "success") {
throw new Error("Launch transaction reverted");
}
// The launch is complete. The token lives at preparation.predictedToken.
console.log("Launch succeeded:", preparation.predictedToken, launchHash);
return launchHash;
}
await executePreparedLaunch(prepared);Optional signed relay
Local-account scripts may sign the launch and ask Jibe to relay the bytes. The relay validates signer, chain, target, calldata, value, gas, and fees and does not pay gas. Send any ERC-20 approval directly first.
const transaction = prepared.launchTransaction;
const latest = await api("/config");
if (!latest.capability.canLaunch || !latest.capability.canRelay) {
throw new Error(latest.capability.blocker ?? "Relay is unavailable");
}
// Populate nonce, gas, and EIP-1559 fees before signing raw bytes.
const transactionRequest = await wallet.prepareTransactionRequest({
account,
chain,
to: transaction.to,
data: transaction.data,
value: BigInt(transaction.value),
});
const serializedTransaction = await wallet.signTransaction(transactionRequest);
const relay = await api("/launches/submit", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
launchId: prepared.launchId,
serializedTransaction,
}),
});
console.log(relay.transactionHash, relay.relayState);Track status
A successful transaction receipt completes the launch. The token address is predictedToken from the preparation. Poll by launch ID if you also want to know when the indexer has the token page and API data ready; the status response then carries token and its path. The factory indexer connects the preparation to canonical launch events by predicted address.
// Optional: wait until the token page and API data are ready.
async function waitForIndexedLaunch(launchId) {
for (;;) {
const status = await api(`/launches/${launchId}`);
console.log(status.state);
if (status.state === "indexed" || status.state === "finalized") {
return status;
}
if (["failed", "expired", "reorged"].includes(status.state)) {
throw new Error(status.message ?? status.state);
}
await new Promise((resolve) =>
setTimeout(resolve, status.retryAfterMs ?? 2000),
);
}
}
const launch = await waitForIndexedLaunch(prepared.launchId);
console.log(launch.token, launch.path);Possible states include prepared, submitted, relayed, indexed, and finalized. Direct broadcast can remain prepared until indexing. indexed means the launch is canonical in the read models; finalized adds only deeper block depth and is not required for anything. Terminal outcomes include failed, expired, and reorged.
ABIs and contract addresses
Curated, user-facing ABIs are available at /api/public/v1/abi/coordinator.json, /api/public/v1/abi/strategy-authority-hub.json, /api/public/v1/abi/stake-vault.json, and /api/public/v1/abi/strategy-vault.json. The Stake Vault ABI includes permissionless reward synchronization, and the Strategy Vault ABI includes permissionless revenue synchronization plus taxEconomicsV1, the authoritative per-launch selected and gross tax allocation. The fixed revenue constants are legacy fallbacks, not the V1 token’s effective protocol split.
Take the coordinator from /config and a token’s vault, router, custody, authority hub, configuration, and stake vault from /tokens/{address}/vault. The shared contracts are also listed under Contracts. Prepare launches through the API rather than encoding them yourself.
const coordinatorAbi = await api("/abi/coordinator.json");
const decoded = decodeFunctionData({
abi: coordinatorAbi,
data: prepared.launchTransaction.data,
});
if (decoded.functionName !== "launch") {
throw new Error("Unexpected prepared coordinator call");
}
console.log(
`${config.chain.explorerUrl}/address/${config.coordinator}?tab=contract`,
);Field units and constraints
| Fields | Rule | Website default |
|---|---|---|
chainId | 56 (BNB Smart Chain). The prepared transaction carries it. Any other value is rejected. | 56 |
initialBuyAmount | Unsigned decimal string in raw quote units. BNB uses 18 decimals; ERC-20 uses the advertised quote decimals. | 0 |
venue | flap. Drafts take buyTaxBps, sellTaxBps, the four tax destinations, antiFarmerDurationSeconds, profitDividendBps, dividendMode, dividendAsset, and github/youtube/debox links. | flap |
buyTaxBps, sellTaxBps | Basis points where 100 is 1%. Each side must stay within config.limits.minimumTaxBps and maximumTaxBps (currently 100–1,000). | 100 / 100 |
| Tax destinations | vaultBps + dividendBps + deflationBps + lpBps must equal 10,000. These are creator weights over the creator-distributable remainder, and vaultBps must be at least config.limits.minimumVaultBps. | 10000 / 0 / 0 / 0 |
protocolTaxBps, creatorDistributionBps | Read these live values from /config. The protocol share is reserved from all distributable token tax after the venue’s upstream fee, not only from the perpetual allocation. Preparation folds that share into the effective gross vault destination. | 2500 / 7500 |
equityAllocationBps | Market allocations may total up to 10,000. Any remainder stays as reserve, and each market may appear once. | One market, long, 10000 |
requestedLeverageBps | 10,000 is 1× and 20,000 is 2×. Stay at or below each market’s live maximumLeverageBps. | 20000 (2×) |
antiFarmerDurationSeconds | Whole seconds from zero through 365 days. | 2592000 (30 days) |
profitDividendBps | Share of the returned USDT classified as strategy profit and allocated to the dividend bucket; the remainder enters the buyback bucket for later execution. | 0 (all buyback) |
| DAO settings | votingPeriodSeconds is 1 minute–30 days. proposalThresholdPpm and quorumPpm are 1–1,000,000 PPM of the token’s snapshotted totalSupply(). approvalBps is 5,001–10,000 BPS of Support-plus-Oppose votes. | 43200 seconds / 10 PPM / 100000 PPM / 5500 BPS |
| DAO vote choice | The API accepts support, oppose, or abstain; the ABI enum values are 0, 1, and 2. All three count toward quorum. Abstain is excluded from the approval denominator. | support |
Retries, expiry, and limits
- Reuse the same
Idempotency-Keyand identical body after a network failure. Reusing it with a different body returns409. - Request a new preparation after expiry so runtime checks, predicted address, and calldata are current.
- Respect
RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset, andRetry-After. - A same-nonce replacement must be a verified fee bump of the identical launch transaction.
- Use current values from
/configand/markets; preparation rejects stale choices.
Catalog reads
Use GET /tokens for cursor pagination and GET /tokens/{address} for token details. Unsearched catalog and detail reads, configuration, markets, OpenAPI, and ABI responses are shared-cacheable. Actor-free vault summaries use an eight-second CDN cache; actor-specific vault state, search results, launch status, and mutations are not cached.
For the product model and launch settings, see Launching a token.

