New: the Free plan. Check FirstLight's timing against your own feed
All docs pages

FirstLight Submit

FirstLight Submit is Robinhood Chain transaction submission at one URL: it sends your signed transactions to the sequencer. It takes the standard JSON-RPC call eth_sendRawTransaction. You change one URL in your client.

Plan

Submit is part of the Ultra plan. Your FirstLight contact enables submit on your key and tells you its limits. Ask for a key on Telegram.

Endpoint

https://submit.first-light.stream/{KEY}

{KEY} is the key that you hold for the FirstLight streams. You can also send the key in a header, with the path /:

POST https://submit.first-light.stream/
Authorization: Bearer {KEY}

Submit runs in AWS Ohio (us-east-2), in the sequencer's AWS zone. It speaks HTTP/1.1 with keep-alive. It does not speak HTTP/2.

The plain URL

A client inside AWS us-east-2 can ask its FirstLight contact for a plain http:// URL. It takes the same key and gives the same answers, without the cost of TLS.

On the plain URL your key and your transactions travel in cleartext. Anyone on the path can read your key and use it until we revoke it. https:// stays the default.

The calls

MethodAnswer
eth_sendRawTransactionOne call, or a JSON-RPC batch of calls.
eth_chainId0x1237 (chain ID 4663). Submit answers it directly, and it sends nothing to the sequencer.
Any other methodHTTP 200 with the JSON-RPC error -32601, "the method <name> does not exist/is not available".

Read your nonce, gas and receipts from the chain's public RPC. Sign the transaction, then send the signed bytes to Submit. Submit does not check a nonce or a signature. The sequencer checks them.

The answers

The default answer

By default you get the answer for your transaction verbatim: the transaction hash, or the error. This answer comes after the sequencer has sequenced the transaction.

The fast answer

Send the header Rhc-Answer: hash, or ask your FirstLight contact to set the fast answer on your key. Submit then answers the keccak-256 hash of your transaction as soon as it has sent the transaction. The response header Rhc-Written-Us gives the time from your request to that send, in microseconds. With the fast answer, an error from the sequencer does not reach you. Read the receipt to know the result.

Limits

Each key has four limits: a rate per second, a burst, a number of transactions in flight and a number of connections. The defaults are 5 per second, a burst of 20, 10 in flight and 4 connections. Your FirstLight contact tells you the limits of your key and can raise them.

  • A batch of n calls takes n from the rate. Submit admits a batch whole or refuses it whole, so a refusal never leaves a gap in your nonces.
  • A transaction stays in flight until Submit has the sequencer's answer, also after a fast answer.
  • A request body larger than 384 KiB gets HTTP 413.
  • A signed transaction larger than 128 KiB (131,072 bytes) is refused with the JSON-RPC error -32602.

Send the key in the path or in the Authorization header, not in both. A request with a key in the path and an Authorization header gets 404.

Errors

AnswerMeaningConnection
429, Retry-After: 1, JSON-RPC -32005 "limit exceeded: rate"Over the rate or the burst.Stays open
429, -32005 "limit exceeded: inflight"Too many transactions in flight.Stays open
429, -32005 "limit exceeded: blocked"The sequencer refused more than half of your last 64 transactions. The block lasts 60 s.Stays open
429, -32005 "limit exceeded: connections"Too many connections for your key.Closed after this answer
503, -32005 "no warm connection"Submit had no ready connection to the sequencer for your transaction. Retry after a short wait.Stays open
503, -32005 "submission is off"We stopped submission. Retry after a short wait.Stays open
404, no bodyThe path is not a key.Closed
401, no bodyThe key is unknown or expired.Closed
403, no bodyThe key does not have submit, or a second key was sent on the same connection.Closed
413The request body is larger than 384 KiB.

Keep your connection warm

A new connection costs two round trips (TCP and TLS 1.3). A warm connection costs neither.

  • Submit never closes an idle connection. Each answer carries Keep-Alive: timeout=600 and Connection: keep-alive. Submit closes a connection that takes no byte of an answer for 30 s.
  • Node's undici uses that hint, up to its own maximum of 600 s. Go net/http and reqwest close an idle connection after 90 s by default. Set their idle timeout higher, as the examples below do.
  • Python requests never closes an idle connection of a Session. Send through one Session.
  • When you are idle for minutes, send eth_chainId. It keeps the connection open and sends nothing to the sequencer.
  • HTTP/1.1 holds one request in flight per connection. For a burst, open connections up to your connection limit and keep them open. Above that limit, send batches or use the fast answer.

Examples

In these examples, URL is https://submit.first-light.stream/{KEY} and raw is the signed transaction as 0x hex.

curl

curl -sS -D - "$URL" -H 'content-type: application/json' \
    --data '{"jsonrpc":"2.0","id":1,"method":"eth_sendRawTransaction","params":["0x02f8..."]}'
# Add -H 'Rhc-Answer: hash' for the fast answer.

Node.js with undici

import { Agent, request } from "undici";
const agent = new Agent({ connections: 4, keepAliveTimeout: 600_000, keepAliveMaxTimeout: 600_000 });
const { statusCode, body } = await request(URL, {
  method: "POST", dispatcher: agent, headers: { "content-type": "application/json" },
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_sendRawTransaction", params: [raw] }),
});
const answer = await body.json();

ethers v6

import { JsonRpcProvider } from "ethers";
const submit = new JsonRpcProvider(URL, undefined, { staticNetwork: true, batchMaxCount: 1 });
const hash = await submit.send("eth_sendRawTransaction", [raw]);

Do not use broadcastTransaction here. It also calls eth_blockNumber, which Submit does not serve. By default ethers groups the calls of 10 ms into one batch, and Submit admits or refuses a batch whole. batchMaxCount: 1 sends each call alone.

viem

import { createWalletClient, http } from "viem";
const submit = createWalletClient({ transport: http(URL) });
const hash = await submit.sendRawTransaction({ serializedTransaction: raw });

Gas on Robinhood Chain

The intrinsic gas of a transfer changes with the cost of L1 data. The sequencer refuses a gas limit of 21,000 as "intrinsic gas too low". Set the gas limit at or above eth_estimateGas from the public RPC.

"Already known" and nonce order

  • The sequencer can answer "already known" or "nonce too low" for a transaction that it has accepted. You pay gas once. With the default answer, you get the answer for the accepted transaction.
  • Two transactions with dependent nonces, sent within about a millisecond, can reach the sequencer out of order. The sequencer then refuses the later nonce. Send a dependent transaction after the answer to the one before it, or be ready to send it again.

What we see

Per key, we see the transactions accepted, the transactions that landed on the chain, the transactions limited or refused and why, the sequencer's errors, and the time from your request to the first send. We do not record your key or your URL. We name a key by a short ID.

Need a key? Ask on TelegramMessage @idatsy, direct to the operator. Start with a free key for the headers stream.