Skip documentation navigation

Index Programmable Custom on Robinhood

Use the chain-bound Router and finalized feed to identify Programmable Custom launches on Robinhood Chain. Resolve public self-serve activation live and report fee behavior separately.

Resolve activation from the live authority

This contract covers public self-serve Robinhood Custom launches. Resolve current availability from the live discovery and readiness authorities before every create or ingestion session. Documentation and a reachable route do not prove that production writes are active.

Chain
Robinhood Chain Mainnet · 4663 · eip155:4663
Target launch path
Public self-serve, with separate controller-wallet review
Public activation authority
Live discovery customLaunchApi.versions.v4 plus the chain-bound readiness response
Live create gate
Require versions.v4.publicWrites: true, publicAuthorization: true and releaseReady: true from the live discovery document
Indexer read path
Require a schema-valid feed response with quality.status: ready; verify finality and authoritative exact-source eligibility independently for every item
Public-item authority
The live finalized feed only, followed by the Router, finality and exact-source verification steps in this guide
Release binding
Require the source commit, source tree, policy and deployment evidence returned by the live readiness authority
Required fee policy
Resolve the complete current value from live discovery at customLaunchApi.versions.v4.platformFeePolicy
Fee behavior claim
false
Fixture feed example
0 eligible V3-finalized, authoritatively source-verified candidates / 0 published / 0 quarantined. This is a schema-valid parser vector, not a production observation. Always fetch the live feed.

Use one chain-bound identity and label

Require the server-authored platformId: programmable and category: custom, then independently verify their Router launch kind 1 binding. Names, symbols, factories and hook addresses are not provenance by themselves.

Platform ID
programmable
Category
custom
Public label
Programmable Custom
Launch kind
LaunchKindV1.CustomGraph = 1
Durable key
(eip155:4663, Router address, onchain.routerLaunchId)

Keep the API request UUID in launchId separate from the onchain routerLaunchId. Normalize token and contract addresses only inside the eip155:4663 namespace. Never derive platform or category from client-controlled project metadata, and never merge an address with the same bytes on another chain.

Bind to Launch Stamp Router V1

Robinhood provenance uses the Router's embedded launch-stamp registry. There is no separate authoritative Custom Registry for this generation.

Generation
programmable-launch-stamp-router-v1
Provenance start block
50469365
Router runtime Keccak-256
0x1dbbdaaad901ea3c6134dca0d4872a4789b3c071bf8ccfb44edd65d26d817388
Profile-normalized ABI SHA-256 (jq -cS plus LF)
0xab25262ce1cb907eba1cb820492754c0cd5d7278eb5fd6a024ba24c767323ac0
Hosted ABI byte SHA-256
bb4e728e9f9c850eb01f928e8a798ac206a82e241a8d93b3b3c686635c88ed86

Download the complete Router ABI and hash its exact served bytes before decoding. The hosted-file digest covers exact bytes. The profile digest hashes one compact jq -cS serialization plus its trailing LF; the two digests are intentionally not interchangeable. The fixture records both. At onchain.l2Inclusion.blockNumber, also match the Router runtime and immutable CHAIN_ID, permit authority, Graph Factory and PoolManager getters to the live capabilities document. Confirm the L2 receipt block hash and event positions first. Do not use the deprecated flat checkpoint projection for these chain-4663 reads.

Discover all Router events, then verify getters

Accept logs only from the exact Router address at or after block 50469365. The three stamp events are discovery candidates, not final provenance by themselves.

Robinhood Launch Stamp Router event topics
EventFull signaturetopic0Indexed inputsRole
ProgrammableLaunchStampedV1ProgrammableLaunchStampedV1(bytes32,address,address,address,bytes32,bytes32)0x6cf479a102f1eebc9244f48f8d68f6aa52b4c5a4516318df58ba46614a5b14f2launchId, token, hookDiscovery
ProgrammableLaunchRouteStampedV1ProgrammableLaunchRouteStampedV1(bytes32,uint8,bytes32,bytes32,bytes32)0x45e7cc355b63ca67d6278a0d8d23470ce2a0741a9c60283d7dee712df7a877a5launchId, kind, routePayloadHashDiscovery
ProgrammableComponentStampedV1ProgrammableComponentStampedV1(bytes32,address,uint8,bytes32)0x8147265e7396d6400cee8d049456a1f7438fdfbe2a7c81c976d51ba67e52ff4blaunchId, component, kindDiscovery
EIP712DomainChangedEIP712DomainChanged()0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31noneNot a launch signal
  1. Correlate all stamp events by the indexed launch ID.
  2. Resolve launchStamp(bytes32) and the token, pool or component lookup at the verified onchain.l2Inclusion.blockNumber.
  3. Require launch kind 1, exact token, hook, PoolManager, pool ID, route launcher, component proofs and stamp commitments.
  4. Treat Graph Factory logs as execution diagnostics only. They do not replace the Router stamp or its registry reads.

Use the public finalized feed for discovery

The feed is unauthenticated, newest first and bounded. Status and request-history routes require a Bearer API key and are not terminal discovery feeds.

  1. Before generating types or validating data, hash the exact hosted OpenAPI bytes and require equality with the top-level openApiSha256 in a ready response. A missing or mismatched digest is UNAVAILABLE.
  2. Fetch ?limit=25. Do not cache beyond the response's HTTP policy without preserving an explicit observed time.
  3. Require chainId: "4663" and caip2: "eip155:4663" on every page and item.
  4. Process the page, then pass an unchanged non-null nextCursor as the next cursor value.
  5. Stop and return UNAVAILABLE when the endpoint request fails. A malformed eligible V3 candidate must fail the whole request; do not accept a row-wise quarantine or partial success.
  6. Treat sourceRowCount, publishedRowCount and quarantinedRowCount as global finalized-dataset totals, not page lengths. A successful response must be ready, with source equal to published and quarantined equal to zero. The current launches.length may be smaller than published but must never be larger.

Keep request status separate from finality

Request history has a lifecycle status. A finalized-feed item does not. Its route and schema establish the feed class; require onchain.terminal: true and onchain.checkpointType: ethereum_finalized, plus non-null V3 l2Inclusion, l1Posting and l1FinalizedCheckpoint evidence on the item.

Robinhood Custom request status handling
StatusTerminal listing rule
receivedDo not publish as a finalized terminal listing
validatingDo not publish as a finalized terminal listing
action_requiredDo not publish as a finalized terminal listing
authorizedDo not publish as a finalized terminal listing
awaiting_wallet_signatureDo not publish as a finalized terminal listing
wallet_action_requiredDo not publish as a finalized terminal listing
submittedDo not publish as a finalized terminal listing
sequencer_soft_confirmedDo not publish as a finalized terminal listing
ethereum_postedDo not publish as a finalized terminal listing
finalizedLifecycle terminal; correlate with a terminal ethereum_finalized feed item
failedDo not publish as a finalized terminal listing
  • sequencer_soft_confirmed remains reversible.
  • ethereum_posted proves posting, not Ethereum finality.
  • Use onchain.l2Inclusion for the exact chain-4663 transaction, block and Router event positions. Replay and match the receipt before reading the Router at that block; the route-event log index must precede the launch-event log index in that receipt. Require the top-level onchain.transactionHash to equal the nested L2 transaction hash.
  • Use onchain.l1Posting only for the Ethereum batch-posting event and onchain.l1FinalizedCheckpoint only for the common finalized checkpoint. Require rollup 0x23A19d23e89166adedbDcB432518AB01e4272D94, SequencerInbox 0xBd0D173EEb87D57A09521c24388a12789F33ba96, and chain eip155:1. Its ordered provider readbacks are drpc / drpc.org then quicknode / quicknode.com; bind them to the chain deployment's Ethereum-finality evidence and keep all L1 coordinates separate from the L2 receipt.
  • The flat onchain.blockNumber, blockHash and logIndex fields are a deprecated stage projection. They are not a transaction locator; at the finalized stage the log index belongs to l1Posting, not the finalized-checkpoint block. Never combine them with the top-level L2 transaction hash.
  • If any nested coordinate is missing or disagrees with its provider readback, finality or provenance is INDETERMINATE on the affected axis. Historical V2 evidence remains private authenticated history and is never a finalized public-feed candidate. Only a separate, fully revalidated canonical V3-finalized projection may qualify on its own evidence.
  • Require sourceVerification.status: exact_match and an exact_match status with its complete authoritative binding on every component. A provider-only Sourcify observation is not publication authority; queued, retrying and needs-attention states remain private authenticated history.
  • For direct log indexing, retain an overlap, compare canonical block hashes and rewind to the last common finalized checkpoint after a reorg.
  • For the public feed, remove a cached item if a later complete fetch no longer contains it inside the replay window. Never silently keep an orphaned observation as finalized.

Return explicit, independent result axes

Provenance, authoritative source verification, security, finality, market support and fee behavior are separate facts. Feed availability and write activation are separate again. One axis must never fill or erase another.

STAMPED
Provenance only: every Router binding, canonical read and event correlation succeeds at the V3 l2Inclusion receipt block.
NOT_STAMPED
A canonical Router lookup succeeds and returns zero. A direct deployment outside this Router has no Programmable launch stamp.
UNAVAILABLE
Use on the affected availability, write-activation or fee axis. It does not turn an independently proven stamp into unavailable provenance. An explicit false create gate is INACTIVE, not UNAVAILABLE.
INDETERMINATE
Use on provenance or finality when its runtime, ABI, provider, required V3 L2/L1 coordinate or finality evidence is missing or disagrees. Do not use it as a synonym for NOT_STAMPED.

An existing finalized STAMPED launch remains indexable when public writes are inactive or unavailable and when fee behavior is UNAVAILABLE.

Provenance, source and finality

Publish Programmable Custom only after Router provenance, authoritative exact-source binding and finality pass. These remain separate results; source verification cannot replace Router provenance or finality, and none is a safety endorsement.

Security and market support

A stamp is not an audit, endorsement, liquidity check or trading guarantee. Derive pool activity, liquidity, swap support and token risk independently.

Platform fee

Do not hardcode a rate or recipient from this page or the fixture. Resolve the current global requirement from live discovery at customLaunchApi.versions.v4.platformFeePolicy and preserve its rate, percentage, recipient, scope and enforcement fields together. Report actual fee behavior as UNAVAILABLE unless separately proven: basis, currency, accounting, rounding, accrual and claim mechanics may remain unknown, while canonical onchain enforcement and revenue are not guaranteed. Do not copy the global policy onto a finalized feed row: per-launch applicability remains UNVERIFIED unless the backend publishes an explicit launch binding. Do not infer that a direct Router transaction or any path outside the V4 API carries the policy. Fee-path absence is not a write-activation blocker.

Start with the finalized feed

These Node.js examples perform no write, wallet action, trade or fee claim. Set ROBINHOOD_RPC_URL to a trusted chain-4663 provider, ETHEREUM_DRPC_RPC_URL and ETHEREUM_QUICKNODE_RPC_URL to the independently configured chain-1 providers before running the TypeScript example.

cURL

curl --fail --silent --show-error \
  'https://api.programmable.market/v4/chains/4663/finalized-custom-launches?limit=25'

TypeScript enrichment loop (complete verifier required)

This short loop deliberately imports an OpenAPI-generated response parser and a separate verifyCompleteRouterBinding implementation. It is not a standalone Router verifier. Never emit STAMPED after checking only the launch kind: the imported verifier must replay the exact V3 l2Inclusion receipt, then satisfy every L2 block, runtime, immutable, event, lookup, proof and commitment check listed in the code before it returns. It must also keep l1Posting and l1FinalizedCheckpoint distinct and ignore the deprecated flat trio as a transaction locator.

import { createHash } from "node:crypto";
import { createPublicClient, http, type Abi } from "viem";

import {
  parseFinalizedPage,
} from "./generated/custom-launch-v4";
import { index } from "./index";
import {
  verifyCompleteRouterBinding,
} from "./verify-complete-router-binding";

/*
 * This is the pagination and enrichment loop, not a shortened Router verifier.
 * Generate parseFinalizedPage from the complete V4 OpenAPI schemas.
 * verifyCompleteRouterBinding must throw unless it independently verifies:
 * - RPC chain 4663 and the receipt identified by onchain.l2Inclusion;
 * - exact L2 transaction/block/hash and launch/route event-log positions;
 * - two independent chain-1 RPC readbacks of the distinct onchain.l1Posting
 *   event and onchain.l1FinalizedCheckpoint block/hash;
 *   deprecated flat blockNumber/blockHash/logIndex are a stage projection,
 *   not a transaction locator, and MUST NOT replace the nested coordinates;
 * - Router runtime plus CHAIN_ID, Graph Factory, permit authority and
 *   PoolManager address/runtime immutable getters at the L2 receipt block;
 * - the exact receipt and contiguous Component -> Route -> Launch event group;
 * - every launchStamp field, token/pool/component lookup, stampProof and
 *   component runtime, plus event and top-level/onchain commitment bindings.
 * Never replace that function with a kind-only launchStamp read.
 */
const origin = "https://api.programmable.market";
const readinessResponse = await fetch("https://api.programmable.market/v4/chains/4663/readiness", {
  headers: { accept: "application/json" },
});
if (!readinessResponse.ok) throw new Error("UNAVAILABLE: release readiness");
const readiness = await readinessResponse.json() as {
  status?: string;
  openApiSha256?: string;
};
if (readiness.status !== "ready") {
  throw new Error("UNAVAILABLE: release readiness is not ready");
}

const openApiResponse = await fetch(new URL("/openapi/custom-launch-v4.json", "https://programmable.market"));
if (!openApiResponse.ok) throw new Error("UNAVAILABLE: V4 OpenAPI");
const openApiBytes = new Uint8Array(await openApiResponse.arrayBuffer());
const openApiSha256 = "sha256:" + createHash("sha256").update(openApiBytes).digest("hex");
if (readiness.openApiSha256 !== openApiSha256) {
  throw new Error("UNAVAILABLE: V4 OpenAPI/readiness digest mismatch");
}

const abiUrl = new URL("/contracts/robinhood/ProgrammableLaunchStampRouterV1.abi.json", "https://programmable.market");
const abiResponse = await fetch(abiUrl);
if (!abiResponse.ok) throw new Error("UNAVAILABLE: Router ABI");
const abiText = await abiResponse.text();
const abiSha256 = createHash("sha256").update(abiText).digest("hex");
if (abiSha256 !== "bb4e728e9f9c850eb01f928e8a798ac206a82e241a8d93b3b3c686635c88ed86") {
  throw new Error("INDETERMINATE: Router ABI hash mismatch");
}

const routerAbi: Abi = JSON.parse(abiText);
const robinhoodClient = createPublicClient({
  transport: http(process.env.ROBINHOOD_RPC_URL),
});
const ethereumClients = [
  {
    providerId: "drpc",
    trustDomain: "drpc.org",
    client: createPublicClient({ transport: http(process.env.ETHEREUM_DRPC_RPC_URL) }),
  },
  {
    providerId: "quicknode",
    trustDomain: "quicknode.com",
    client: createPublicClient({ transport: http(process.env.ETHEREUM_QUICKNODE_RPC_URL) }),
  },
];
if (await robinhoodClient.getChainId() !== 4663) {
  throw new Error("INDETERMINATE: RPC is not chain 4663");
}
if ((await Promise.all(ethereumClients.map(({ client }) => client.getChainId())))
  .some((chainId) => chainId !== 1)) {
  throw new Error("INDETERMINATE: finality RPC is not chain 1");
}

let cursor: string | null = null;
const seenCursors = new Set<string>();
do {
  const url = new URL("/v4/chains/4663/finalized-custom-launches", origin);
  url.searchParams.set("limit", "25");
  if (cursor) {
    if (seenCursors.has(cursor)) {
      throw new Error("INDETERMINATE: repeated pagination cursor");
    }
    seenCursors.add(cursor);
    url.searchParams.set("cursor", cursor);
  }

  const response = await fetch(url, { headers: { accept: "application/json" } });
  if (!response.ok) {
    throw new Error("UNAVAILABLE: finalized feed HTTP " + response.status);
  }

  const page = parseFinalizedPage(await response.json());
  if (page.chainId !== "4663" || page.caip2 !== "eip155:4663") {
    throw new Error("INDETERMINATE: chain identity mismatch");
  }
  const { sourceRowCount, publishedRowCount, quarantinedRowCount } = page.quality;
  if (
    sourceRowCount !== publishedRowCount ||
    quarantinedRowCount !== 0 ||
    page.launches.length > publishedRowCount ||
    page.quality.status !== "ready"
  ) {
    throw new Error("INDETERMINATE: inconsistent global feed-quality totals");
  }

  for (const launch of page.launches) {
    if (
      launch.platformId !== "programmable" ||
      launch.category !== "custom" ||
      launch.onchain.terminal !== true ||
      launch.onchain.checkpointType !== "ethereum_finalized" ||
      launch.onchain.schemaVersion !==
        "programmable.custom-launch-onchain-evidence.v3" ||
      launch.onchain.l2Inclusion.schemaVersion !==
        "programmable.custom-launch-l2-inclusion.v1" ||
      launch.onchain.l2Inclusion.chainId !== "4663" ||
      launch.onchain.l2Inclusion.caip2 !== "eip155:4663" ||
      launch.onchain.l2Inclusion.receiptStatus !== "success" ||
      launch.onchain.l1Posting === null ||
      launch.onchain.l1FinalizedCheckpoint === null ||
      launch.onchain.l1Posting.schemaVersion !==
        "programmable.custom-launch-l1-posting.v1" ||
      launch.onchain.l1Posting.chainId !== "1" ||
      launch.onchain.l1Posting.caip2 !== "eip155:1" ||
      launch.onchain.l1Posting.rollup !==
        "0x23A19d23e89166adedbDcB432518AB01e4272D94" ||
      launch.onchain.l1Posting.sequencerInbox !==
        "0xBd0D173EEb87D57A09521c24388a12789F33ba96" ||
      launch.onchain.l1FinalizedCheckpoint.schemaVersion !==
        "programmable.custom-launch-l1-finalized-checkpoint.v1" ||
      launch.onchain.l1FinalizedCheckpoint.chainId !== "1" ||
      launch.onchain.l1FinalizedCheckpoint.caip2 !== "eip155:1" ||
      launch.onchain.l1FinalizedCheckpoint.consensusCheckpointTag !== "finalized" ||
      launch.sourceVerification.status !== "exact_match" ||
      launch.onchain.router !== "0x34965F2A2ee9254522232C32F02056E92BE0C98a"
    ) {
      throw new Error("INDETERMINATE: incomplete identity, finality or Router binding");
    }

    const verified = await verifyCompleteRouterBinding({
      robinhoodClient,
      ethereumClients,
      routerAbi,
      launch,
      expected: {
        chainId: 4663,
        caip2: "eip155:4663",
        startBlock: 50_469_365n,
        router: "0x34965F2A2ee9254522232C32F02056E92BE0C98a",
        routerRuntimeCodeHash:
          "0x1dbbdaaad901ea3c6134dca0d4872a4789b3c071bf8ccfb44edd65d26d817388",
        graphFactory: "0x0B6b3F40f84Df25D3bd69238f937096177DD09Bd",
        graphFactoryRuntimeCodeHash:
          "0xd23692fae59331592048e71a96d4963e170ee56e449683dc9f7fa3f9470018b8",
        permitAuthority: "0xeD617CE7f82e2AB589aDeFFD319D1D872Bc8De06",
        permitAuthorityRuntimeCodeHash:
          "0xd7d408ebcd99b2b70be43e20253d6d92a8ea8fab29bd3be7f55b10032331fb4c",
        poolManager: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
        poolManagerRuntimeCodeHash:
          "0xbd3881180b547f5fe817545743cfb4343e96b1bc6640dcd70c106b0066e95626",
        l1Rollup: "0x23A19d23e89166adedbDcB432518AB01e4272D94",
        l1SequencerInbox: "0xBd0D173EEb87D57A09521c24388a12789F33ba96",
        launchKind: 1,
      },
    });

    index({
      platformId: launch.platformId,
      category: launch.category,
      label: "Programmable Custom",
      chain: "eip155:4663",
      routerLaunchId: launch.onchain.routerLaunchId,
      tokenAddress: verified.token,
      tokenMetadata: launch.projectMetadata.token,
      provenance: "STAMPED",
      finality: "FINALIZED",
      feedAvailability: "AVAILABLE",
      security: "UNVERIFIED",
      marketSupport: "UNVERIFIED",
      feePolicyApplicability: "UNVERIFIED",
      feeBehavior: "UNAVAILABLE",
    });
  }

  cursor = page.nextCursor;
} while (cursor);

Send a reproducible integration report

Include the chain, endpoint, UTC observation time, response status, Router, l2Inclusion, l1Posting, l1FinalizedCheckpoint, provider source and the public error code. Never send an API key, signed transaction or private request body.