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.v4plus the chain-bound readiness response - Live create gate
- Require
versions.v4.publicWrites: true,publicAuthorization: trueandreleaseReady: truefrom 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
0eligible V3-finalized, authoritatively source-verified candidates /0published /0quarantined. 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 and registry
- 0x34965F2A2ee9254522232C32F02056E92BE0C98a — opens website in a new tab
- 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.
| Event | Full signature | topic0 | Indexed inputs | Role |
|---|---|---|---|---|
| ProgrammableLaunchStampedV1 | ProgrammableLaunchStampedV1(bytes32,address,address,address,bytes32,bytes32) | 0x6cf479a102f1eebc9244f48f8d68f6aa52b4c5a4516318df58ba46614a5b14f2 | launchId, token, hook | Discovery |
| ProgrammableLaunchRouteStampedV1 | ProgrammableLaunchRouteStampedV1(bytes32,uint8,bytes32,bytes32,bytes32) | 0x45e7cc355b63ca67d6278a0d8d23470ce2a0741a9c60283d7dee712df7a877a5 | launchId, kind, routePayloadHash | Discovery |
| ProgrammableComponentStampedV1 | ProgrammableComponentStampedV1(bytes32,address,uint8,bytes32) | 0x8147265e7396d6400cee8d049456a1f7438fdfbe2a7c81c976d51ba67e52ff4b | launchId, component, kind | Discovery |
| EIP712DomainChanged | EIP712DomainChanged() | 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31 | none | Not a launch signal |
- Correlate all stamp events by the indexed launch ID.
- Resolve
launchStamp(bytes32)and the token, pool or component lookup at the verifiedonchain.l2Inclusion.blockNumber. - Require launch kind
1, exact token, hook, PoolManager, pool ID, route launcher, component proofs and stamp commitments. - 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.
- Public release and activation discoveryResolve the current V4 write and authorization gates before create.
- Capabilities and chain bindingsResolve routes, contracts, profile and current safety flags.
- Runtime release identityCheck exact backend source, tree, policy and finality composition.
- Finalized Custom launch feedIngest only terminal
ethereum_finalizeditems. - Robinhood V4 OpenAPIHash the exact bytes, require equality with readiness
openApiSha256, then validate the full response. - Download the terminal integration fixtureExact bindings, topics, lifecycle, live-authority paths and a schema-valid empty-feed vector for fail-closed tests. It carries no production status or current fee values.
- Before generating types or validating data, hash the exact hosted OpenAPI bytes and require equality with the top-level
openApiSha256in areadyresponse. A missing or mismatched digest isUNAVAILABLE. - Fetch
?limit=25. Do not cache beyond the response's HTTP policy without preserving an explicit observed time. - Require
chainId: "4663"andcaip2: "eip155:4663"on every page and item. - Process the page, then pass an unchanged non-null
nextCursoras the nextcursorvalue. - Stop and return
UNAVAILABLEwhen the endpoint request fails. A malformed eligible V3 candidate must fail the whole request; do not accept a row-wise quarantine or partial success. - Treat
sourceRowCount,publishedRowCountandquarantinedRowCountas global finalized-dataset totals, not page lengths. A successful response must beready, with source equal to published and quarantined equal to zero. The currentlaunches.lengthmay 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.
| Status | Terminal listing rule |
|---|---|
received | Do not publish as a finalized terminal listing |
validating | Do not publish as a finalized terminal listing |
action_required | Do not publish as a finalized terminal listing |
authorized | Do not publish as a finalized terminal listing |
awaiting_wallet_signature | Do not publish as a finalized terminal listing |
wallet_action_required | Do not publish as a finalized terminal listing |
submitted | Do not publish as a finalized terminal listing |
sequencer_soft_confirmed | Do not publish as a finalized terminal listing |
ethereum_posted | Do not publish as a finalized terminal listing |
finalized | Lifecycle terminal; correlate with a terminal ethereum_finalized feed item |
failed | Do not publish as a finalized terminal listing |
sequencer_soft_confirmedremains reversible.ethereum_postedproves posting, not Ethereum finality.- Use
onchain.l2Inclusionfor 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-levelonchain.transactionHashto equal the nested L2 transaction hash. - Use
onchain.l1Postingonly for the Ethereum batch-posting event andonchain.l1FinalizedCheckpointonly for the common finalized checkpoint. Require rollup0x23A19d23e89166adedbDcB432518AB01e4272D94, SequencerInbox0xBd0D173EEb87D57A09521c24388a12789F33ba96, and chaineip155:1. Its ordered provider readbacks aredrpc / drpc.orgthenquicknode / 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,blockHashandlogIndexfields are a deprecated stage projection. They are not a transaction locator; at the finalized stage the log index belongs tol1Posting, 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
INDETERMINATEon 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_matchand anexact_matchstatus 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
l2Inclusionreceipt 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, notUNAVAILABLE. 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.
- Report a documentation or integration issueUse a public issue only for non-sensitive evidence.
- Contact Programmable supportShare the minimum public coordinates needed to reproduce the problem.

