On this page

Launching on Pons

Create a token on Robinhood through Pons V2. Use your source Gateway on Ethereum, BNB Chain, Arbitrum or Base for cross-chain creation, or call the Robinhood composer directly for a local launch. Neither flow uses Omniassets contracts. The examples below use Base. See current deployments and ABIs.

Prepare metadata

Send multipart form data to POST https://api.omnisea.io/omniapps/uploads. Required fields: image, name, symbol. Optional fields: description, twitter, website. Convert PNG, JPG, JPEG or WebP images to WebP before uploading and enforce the 5 MB limit after conversion. The response includes imageURI and metadataURI; you may also host valid HTTPS or IPFS URIs yourself.

Name and symbol allow 64 and 16 UTF-8 bytes. Description allows 1,024 bytes, each social field 256 bytes, and each URI 512 bytes. Use byte lengths, not JavaScript string lengths.

Quote and submit with ethers v6

Install ethers. This browser example assumes a connected EIP-1193 wallet at window.ethereum. Replace the metadata URIs with your uploaded values. The signer needs source native currency for the quote and transaction gas.

import { BrowserProvider, Contract } from "ethers";

const API = "https://api.omnisea.io";
const WEB = "https://www.omnisea.io";
const BASE_GATEWAY = "0xf8F68CeC81BEBa31fEb01EE80ae5bC0d7FFE6EB4";
const PONS_COMPOSER = "0xE4818ED8b7829E02f35415D85BE7944D3F517fBA";
const sourceChainId = Number(8453); // Set 4663 for local creation.
const local = sourceChainId === 4663;
await window.ethereum.request({
  method: "wallet_switchEthereumChain",
  params: [{ chainId: `0x${sourceChainId.toString(16)}` }],
});
const provider = new BrowserProvider(window.ethereum);
if (Number((await provider.getNetwork()).chainId) !== sourceChainId) throw Error("Wrong chain");
const signer = await provider.getSigner();
const account = await signer.getAddress();
const input = {
  sourceChainId, initiator: account,
  name: "TEST", symbol: "TEST",
  imageURI: "https://your-host.example/token.webp",
  metadataURI: "https://your-host.example/token.json",
  description: "", twitter: "", website: "",
  creatorFeeRecipient: account, refundRecipient: account,
};
const response = await fetch(`${API}/omniapps/pons/quote`, {
  method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify(input),
});
if (!response.ok) throw Error(await response.text());
const q = await response.json();
const expected = local ? PONS_COMPOSER : BASE_GATEWAY;
if (q.sourceChainId !== sourceChainId || q.mode !== (local ? "local" : "cross-chain") ||
    q.gateway.toLowerCase() !== expected.toLowerCase() || q.expiresAt <= Date.now()) throw Error("Unexpected or expired quote");
const t = q.request.token;
if (t.name !== input.name || t.symbol !== input.symbol || t.logo !== input.imageURI ||
    t.description !== input.description || q.request.metadataURI !== input.metadataURI ||
    t.creatorFeeRecipient.toLowerCase() !== account.toLowerCase() ||
    q.request.refundRecipient.toLowerCase() !== account.toLowerCase() ||
    Number(t.creatorTaxBps) !== 0 || t.buybackEnabled ||
    t.socials.twitter !== input.twitter || t.socials.website !== input.website ||
    t.socials.telegram !== "" || t.socials.discord !== "" || t.socials.farcaster !== "" ||
    BigInt(q.request.configId) !== 0n) throw Error("Request changed");
const abiResponse = await fetch(`${WEB}/docs/abi/${local ? "omnipad-v2-pons" : "omnipad-v2-gateway"}.json`);
if (!abiResponse.ok) throw Error("ABI unavailable");
const entry = new Contract(expected, await abiResponse.json(), signer);
const request = {
  ...q.request,
  configId: BigInt(q.request.configId),
  maxPonsFee: BigInt(q.request.maxPonsFee),
  maxProtocolFee: BigInt(q.request.maxProtocolFee),
  deadline: BigInt(q.request.deadline),
};
// Show value and both fee caps to the user before requesting a signature.
const value = BigInt(q.value);
const method = local ? entry.launchLocal : entry.launch;
if (local) {
  if (value !== request.maxPonsFee + request.maxProtocolFee) throw Error("Wrong local value");
} else {
  const fee = await entry.quoteLaunch(request);
  if (fee.nativeFee !== value || fee.lzTokenFee !== 0n) throw Error("Re-quote the launch");
}
await method.staticCall(request, { value });
const receipt = await (await method(request, { value })).wait();
if (!receipt || receipt.status !== 1) throw Error("Launch failed");
console.log(receipt.hash);

Handle unknown wallet chains with a trusted wallet_addEthereumChain configuration. Never retry wallet rejection automatically. Download and pin ABIs locally in production. Add your own maximum payment and deadline policy before signing.

The response field gateway means the transaction target: it is the composer in local mode. Call launchLocal, not launch, on that address. API quotes expire after 60 seconds; the request has a separate 30-minute on-chain deadline. Local creation completes atomically; cross-chain creation needs destination confirmation.

Request structure and direct contract use

The downloaded ABI contains the complete tuple. LaunchRequest contains:

FieldTypeMeaning
tokentupleToken fields below
metadataURIstringHTTPS or IPFS metadata location
configIduint256API uses Pons configuration 0
maxPonsFeeuint128Destination Pons fee cap
maxProtocolFeeuint128Destination Omnisea fee cap
deadlineuint64Unix seconds
refundRecipientaddressAuthorized claimant on Robinhood

token contains name, symbol, logo, description, socials, creatorFeeRecipient, creatorTaxBps, buybackEnabled, expectedEconomics and salt. The socials tuple is ordered twitter, telegram, discord, website, farcaster; all are strings. Economics and salt are bytes32, tax is uint16, and buyback is bool.

Without the API, read launchFee(), previewLaunchEconomics(configId, address(0)), and canLaunch(composer) from Pons; read protocolFee() from the composer. Supply a fresh random salt, the returned economics commitment, current fee caps and a future deadline. Use creatorTaxBps = 0 and buybackEnabled = false: other values are rejected. No initial purchase is supported. Set logo to the image URI.

On Base, quote with quoteLaunch(request) and send its exact nativeFee to launch(request). On Robinhood, send exactly maxPonsFee + maxProtocolFee to launchLocal(request). No ERC-20 approval is required. The composer scopes the salt to source EID, initiator and request ID before calling Pons.

Pons records the composer as its on-chain caller/deployer. creatorFeeRecipient assigns the fee recipient, not necessarily every platform-specific ownership right. Both recipient wallets must be controlled on Robinhood; smart-account addresses can differ across chains.

Completion and refunds

After receipt confirmation, call POST /omniapps/requests/track with {chainId, transactionHash}, then read GET /omniapps/requests/{requestId} with backoff. Open /en/omnipad/4663/{tokenAddress} after successful creation. ponsUrl is an optional external link.

Local creation failures revert the transaction. Cross-chain expired requests or exceeded fee caps become terminal failures with destination credit. External execution errors remain retryable, not automatically terminal refundable failures. See API and tracking before implementing recovery.

Introducing Omnipad-Launch tokens between chains