Search by name, SuiNS, address, digest, object ID, coin type or checkpoint.

API

Sending transactions

Send a signed transaction and get its final effects, events and balance changes in the same call, as soon as validators certify it. No polling, no second request.

Overview

You build and sign the transaction yourself, with any SDK or wallet. tide takes the signed bytes, hands them from its own full node straight to Sui validators and answers once a quorum of validators has certified the effects. Certified effects are final: the result can no longer change. tide does not wait for the transaction to land in a checkpoint, which usually follows within a second.

  • Transactions that use only your own objects (transfers, splits, merges, most wallet actions) take the fast path and need no consensus round. Transactions with shared objects (DEX swaps, most DeFi) go through consensus.
  • A transaction that runs and fails, for example on a Move abort, is still final and still costs gas. It comes back as a normal answer with status failure and the reason.
  • A transaction the network refuses before running it (a bad signature, too little gas, an object another transaction already used) comes back as an error with a reason you can act on. Nothing changes on chain and no gas is charged.
  • Sending the same bytes again is always safe. It returns the same result and never runs twice. See Resending is safe.

Quickstart

Get a key on API keys (the Free plan works). This example uses the Sui TypeScript SDK to build and sign, and tide to send.

TypeScript
import { SuiGrpcClient } from "@mysten/sui/grpc";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { Transaction } from "@mysten/sui/transactions";

const sui = new SuiGrpcClient({ network: "mainnet", baseUrl: "https://fullnode.mainnet.sui.io:443" });
const keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_PRIVATE_KEY!);

// 1. Build and sign locally. Your key never leaves your machine.
const tx = new Transaction();
tx.setSender(keypair.toSuiAddress());
const [coin] = tx.splitCoins(tx.gas, [1_000_000]); // 0.001 SUI
tx.transferObjects([coin], "0xRECIPIENT_ADDRESS");
const { bytes, signature } = await tx.sign({ client: sui, signer: keypair });

// 2. Send through tide. The answer arrives once the effects are final.
const res = await fetch("https://api.tide.ad/v0/transactions/send", {
  method: "POST",
  headers: { "content-type": "application/json", "x-api-key": "YOUR_API_KEY" },
  body: JSON.stringify({ txBytes: bytes, signatures: [signature] }),
}).then((r) => r.json());

if (res.error) throw new Error(`${res.error.code}: ${res.error.message}`);
console.log(res.digest, res.status, `final in ${res.timing.finalMs} ms`);
for (const b of res.balanceChanges) console.log(b.address, b.coinType, b.amount);

With curl, given base64 bytes and a signature:

curl
curl -X POST "https://api.tide.ad/v0/transactions/send" \
  -H "x-api-key: YOUR_API_KEY" -H "content-type: application/json" \
  -d '{"txBytes":"AAACAAgA...","signatures":["AKe6ytTI..."]}'

Existing JSON-RPC clients

sui_executeTransactionBlock on https://api.tide.ad/ takes the usual params (bytes, signatures, options, request type) and answers with the usual SuiTransactionBlockResponse, so a JSON-RPC client can send through tide unchanged. It always waits for certified effects; the request type is accepted and confirmedLocalExecution is false. showInput returns the sender, gas data and signatures only; use showRawInput for the full transaction.

TypeScript
import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";

const tide = new SuiJsonRpcClient({ url: "https://api.tide.ad/?api-key=YOUR_API_KEY", network: "mainnet" });

// bytes and signature from tx.sign(...) above
const result = await tide.executeTransactionBlock({
  transactionBlock: bytes,
  signature,
  options: { showEffects: true, showEvents: true, showObjectChanges: true, showBalanceChanges: true },
});
console.log(result.digest, result.effects?.status.status);
curl
curl -X POST "https://api.tide.ad/?api-key=YOUR_API_KEY" -H "content-type: application/json" -d '{
  "jsonrpc": "2.0", "id": 1, "method": "sui_executeTransactionBlock",
  "params": ["AAACAAgA...", ["AKe6ytTI..."], { "showEffects": true, "showEvents": true }, "WaitForLocalExecution"]
}'

Request

POST /v0/transactions/send with a JSON body:

FieldTypeWhat
txBytesstringThe transaction the wallet signed: base64 BCS TransactionData. This is the bytes value that tx.sign() and wallets' signTransaction return.
signaturesstring[]Base64 serialized signatures, one per signer: the sender's, plus the sponsor's when someone else pays gas. A multisig, zkLogin or passkey signature counts as one. A single string is accepted too.

Response

FieldWhat
digestThe transaction digest.
statussuccess or failure. Both are final.
errornull on success. On failure: message, command (the index of the command that failed) and, for a Move abort, abort with package, module, function, instruction, code and constant (the error constant's name when the module has one).
finalAlways true: a quorum of validators certified these effects.
sender, kindThe sender, and owned (only the sender's own objects) or shared (uses shared objects).
checkpointnull in the answer: the transaction is final before it is in a checkpoint. A second later, getTransaction or POST /v0/transactions has the checkpoint and the parsed transaction.
gascomputationCost, storageCost, storageRebate, nonRefundableStorageFee and totalMist (computation plus storage minus rebate), in MIST as strings.
balanceChanges{ address, coinType, amount } for every coin balance that changed; amount is signed, in the coin's smallest unit.
objectscreated, mutated, unwrapped, deleted, wrapped and published, each with objectId, version, digest, owner and type where they apply.
events{ type, packageId, module, sender, json, bcs } in the order the transaction emitted them.
effectsdigest, epoch, lamportVersion, eventsDigest, dependencies and bcs (base64 TransactionEffects, for SDKs that parse effects themselves).
resenttrue when tide already had this digest from an earlier send and answered with that result.
timing.finalMsMilliseconds from tide receiving the request to final effects.

Owners use the Sui JSON form: { AddressOwner }, { ObjectOwner }, { Shared: { initial_shared_version } }, "Immutable" or { ConsensusAddressOwner: { start_version, owner } }. Trim the answer with fields= like any call, for example ?fields=digest,status,error,timing.

Resending is safe

A Sui transaction is identified by its digest, and the network runs a digest at most once. tide keys every send by digest too: a second send of the same bytes while the first is in flight waits for it, and a send of bytes that already landed returns the same effects with resent: true. So when a request times out or a connection drops, send the same bytes again. Do not rebuild the transaction to retry: a rebuilt transaction has a new digest.

If no final effects arrive within 30 seconds the answer is upstream_busy with the digest. The transaction may still land: send the same bytes again, or look it up by digest with getTransaction.

Errors

Errors use the usual shape (see Errors). A refusal before execution is rejected (HTTP 422, JSON-RPC -32002) with data.digest, data.reason and data.nodeMessage, the network's own words.

reasonWhat to do
SIGNATURE_INVALIDA signature does not verify for these bytes. Sign the exact bytes you send, with the sender's key, and add the sponsor's signature when someone else pays gas.
GAS_BALANCE_TOO_LOWThe gas coins hold less SUI than the budget. Use more gas coins or lower the budget.
GAS_BUDGET_TOO_LOWRaise the budget to at least the minimum and sign again.
GAS_BUDGET_TOO_HIGHThe budget is above 50 SUI. Lower it and sign again.
GAS_PRICE_TOO_LOWSet at least this epoch's reference gas price and sign again.
OBJECT_LOCKEDAnother transaction already uses one of these object versions. Wait for it to finish, then rebuild with the new versions.
OBJECT_VERSION_UNAVAILABLEAn input object changed since you built the transaction. Rebuild with current versions and sign again.
OBJECT_NOT_FOUNDAn input object does not exist (wrong id, deleted or wrapped).
EXPIREDThe transaction's expiration has passed. Build it again.
ADDRESS_DENIEDAn address is on the deny list of a regulated coin the transaction uses.
TOO_LARGEThe transaction is over a protocol size limit. Split it.
REJECTEDAnything else; nodeMessage says what.

invalid_params (400) means the body itself is wrong: bytes that are not base64 TransactionData, or no signatures. upstream_busy (503) with reason OVERLOADED means validators were busy with the objects the transaction uses; send the same bytes again after retry-after.

Credits and rate limits

A send costs 5 credits; an answer from an earlier send of the same digest costs 1. Refusals and errors cost nothing. Sends have their own rate class, at your plan's heavy rate, and their own pool on tide's side, so a burst of sends never waits behind other calls.

PlanSends per secondSends a month in plan credits
No key2 (shared with all calls)200 a day per IP
Free51,000,000
Developer256,000,000
Business10050,000,000
Professional250120,000,000

See Plans and pricing.

Methods

MethodWhat it doesCredits
tx.sendPOST /v0/transactions/sendSend a signed transaction and get its final effects, events and balance changes.5
sui_executeTransactionBlockJSON-RPCExecute a signed transaction (Sui JSON-RPC compatible) and answer with final effects.5