eth_sendRawTransaction
https://api.triport.io/v1/polygon/rpcBroadcasts a signed, RLP-encoded transaction to the Polygon mempool and returns its transaction hash.
eth_sendRawTransaction submits an already-signed transaction to Polygon
mainnet for inclusion in a block. You sign the transaction locally with your
private key, RLP-encode it, hex-encode the result, and pass that single
0x-prefixed hex string as the only parameter. Triport never sees or holds
your key — only the signed payload.
On success the method returns the transaction hash immediately. The hash is
returned as soon as the transaction is accepted into the mempool; it does
not mean the transaction has been mined. Polygon's Bor consensus produces
blocks roughly every ~2 seconds, so poll
eth_getTransactionReceipt — or
the transaction-by-hash lookups — with the returned hash to confirm inclusion
and read the final status.
This is the only state-changing method on the Polygon eth surface, and it
sits in its own polygon_send_tx rate-limit category with markedly lower
limits than read methods (3 rps on free vs. 15 rps for reads). Budget your
send rate accordingly and back off on -32003. The method is available from the
free tier upward — no paid tier is required to broadcast transactions.
Gotchas
- Use Polygon's chain ID. The transaction must be signed with EIP-155
replay protection using chain ID
0x89(137). A transaction signed for another chain (e.g. Ethereum mainnet,0x1) is rejected with-32000 invalid senderbefore it ever reaches the mempool, because the recovered signer does not match. - Replay protection is mandatory. Without EIP-155 binding to
0x89, a Polygon transaction could also be valid on other EVM chains — a replay-attack vector. Always sign with the correct chain ID. - Idempotent re-broadcast. Re-submitting the same signed transaction (same
hash) is safe. Depending on the upstream node it returns either the same hash
again or
-32000 already known. Treat both outcomes as success.
Parameters
A single positional parameter:
signedTransactionDatastring (0x-prefixed hex)requiredethers, web3.py, viem). Must carry an EIP-155 signature over chain ID 0x89 (137). Supports legacy and typed (EIP-1559 / EIP-2930) transactions.Response
Response fields
| Field | Type | Description |
|---|---|---|
jsonrpc | string | Always "2.0". |
id | number | string | Echoes the request id. |
result | string (0x-prefixed hex) | The 32-byte transaction hash. Use it to poll for the receipt and confirm inclusion. |
Errors
On failure the response carries an error object instead of result, with
extra context under error.data. See the shared
errors reference for the full envelope and the canonical
meaning of each platform code.
| Code | Meaning | When it happens |
|---|---|---|
-32000 | execution error (passthrough) | The network rejected the transaction — e.g. invalid sender (wrong chain ID / bad signature), nonce too low, insufficient funds, gas price too low, or already known. The message carries the node's reason. already known on a re-broadcast is harmless. |
-32001 | trial_expired | The free-trial period for this key has ended (HTTP 401). Upgrade to a paid tier to keep broadcasting. |
-32002 | tier_insufficient | The key's tier is below what the method requires. eth_sendRawTransaction is available from the free tier, so this appears only if the key's scopes are restricted. Carries data.required_tier / data.upgrade_url. |
-32003 | rate_limited | Sustained sends exceeded your tier's polygon_send_tx rps (HTTP 429). Honour Retry-After / error.data.retry_after_sec. Limits are low here: 3 rps free, 5 basic, 30 pro, 80 business. |
-32004 | subscription_expired | A previously active paid subscription has lapsed; renew to restore access. |
-32005 | unauthorized | Missing, invalid, or revoked API key. |
-32601 | method_unknown | The method name is misspelled or not part of the Polygon product. Carries data.chain and data.method. |
rate_limited (-32003)
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32003,
"message": "Rate limit exceeded: 3 RPS sustained on polygon_send_tx (free tier)",
"data": {
"current_tier": "free",
"category": "polygon_send_tx",
"limit_rps": 3,
"burst_capacity": 6,
"retry_after_sec": 1
}
}
}burst_capacity is always 2 × limit_rps. The response also carries
Retry-After and X-RateLimit-* headers — see
Errors. Rate limiting is enforced
per-second per tier with burst headroom; there is no daily quota.
Examples
JavaScript (fetch)
const signedTx = "0xf86c098504a817c8..."; // signed for chainId 0x89, produced by your signer
const res = await fetch("https://api.triport.io/v1/polygon/rpc", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TRIPORT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_sendRawTransaction",
params: [signedTx],
}),
});
const body = await res.json();
if (body.error) {
if (body.error.code === -32003) {
const wait = Number(res.headers.get("Retry-After") ?? body.error.data.retry_after_sec);
await new Promise((r) => setTimeout(r, wait * 1000)); // then retry
} else if (/already known/i.test(body.error.message)) {
console.log("already broadcast — safe to ignore");
} else {
throw new Error(body.error.message); // e.g. invalid sender, nonce too low
}
} else {
console.log("tx hash:", body.result);
}TypeScript SDK (@triport/sdk)
import { Triport, RateLimitedError } from "@triport/sdk";
import { Wallet, Transaction } from "ethers";
const triport = new Triport({ apiKey: process.env.TRIPORT_API_KEY! });
// Sign locally for Polygon (chainId 137 / 0x89)
const wallet = new Wallet(process.env.PRIVATE_KEY!);
const signedTx = await wallet.signTransaction({
chainId: 137,
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f06b8b",
value: 10n ** 18n,
nonce: 9,
gasLimit: 21000,
gasPrice: 20_000_000_000n,
});
try {
const txHash = await triport.polygon.rpc("eth_sendRawTransaction", [signedTx]);
console.log("tx hash:", txHash);
} catch (err) {
if (err instanceof RateLimitedError) {
await new Promise((r) => setTimeout(r, err.retryAfterSec * 1000)); // then retry
} else {
throw err;
}
}Python (triport-sdk)
import os, time
from triport import Triport
from triport.errors import RateLimitedError
from eth_account import Account
from eth_account.datastructures import SignedTransaction
triport = Triport(api_key=os.environ["TRIPORT_API_KEY"])
# Sign locally for Polygon (chainId 137 / 0x89)
signed: SignedTransaction = Account.sign_transaction(
{
"chainId": 137,
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f06b8b",
"value": 10**18,
"nonce": 9,
"gas": 21000,
"gasPrice": 20_000_000_000,
},
os.environ["PRIVATE_KEY"],
)
try:
tx_hash = triport.polygon.rpc(
"eth_sendRawTransaction",
[signed.rawTransaction.hex()],
)
print("tx hash:", tx_hash)
except RateLimitedError as e:
time.sleep(e.retry_after_sec) # then retryNotes
- The hash is not confirmation. A returned hash means accepted into the
mempool, not mined. Poll for the transaction receipt until one appears, then
check its
statusfield. Polygon's ~2-second block time means confirmation is usually quick, but reorgs near the chain head are possible — wait for a few confirmations before treating a transfer as final. - Sign for chain ID
0x89. Building and signing for the wrong chain ID is the most common cause of-32000 invalid sender. SetchainId: 137in your signer. - Idempotent re-broadcast. Re-submitting the same signed transaction usually
yields the same hash; some nodes return an
already knownexecution error (-32000) instead, which is harmless. - Public mempool exposure. Transactions broadcast here enter the public pending pool and are visible to other participants before inclusion, which exposes them to front-running. For latency-sensitive or MEV-sensitive sends, account for this when setting gas and slippage.
- Low send budget. The
polygon_send_txcategory caps sends well below read methods (3 rps free). For sustained high-throughput sending, upgrade to a tier with a higherpolygon_send_txrps limit. - Related: simulate a call first with
eth_call, size gas witheth_estimateGas, read fee data witheth_gasPrice/eth_feeHistory. See also Errors · Rate limits · Authentication.