Ego Blockchain Developer API
Ego exposes JSON-RPC 2.0 plus a few plain HTTP routes; there is no GraphQL. A web app connects users through the Ego Wallet extension, and the app's server reads the chain and submits signed transactions through its own Ego node.
window.ego.request() for connect, send, contract calls and signatures.POST http://127.0.0.1:47395/ on your own node.https://api.egoblockchain.com for blocks, prices and node lists.How the pieces connect
Your web page never holds keys: it asks the extension, and your server reads and writes the chain through its own node.
Connect a wallet
The Ego Wallet browser extension injects window.ego into every page (and window.ethereum when no other wallet took it). Every call is window.ego.request({ method, params }) and returns a Promise. The user approves each connect, send, call and signature in the extension's popup; the private key never reaches the page.
| Method | Params | Returns | Popup |
|---|---|---|---|
ego_requestAccountsalias eth_requestAccounts | none | ["egot1..."] | Yes, the first time per site |
ego_accountsaliases eth_accounts, ego_getAccounts | none | ["egot1..."], or [] if not connected or locked | No |
ego_chainIdalias eth_chainId | none | "0x1" (chain_id 1) | No |
ego_sendTransaction | [{ to, amount_egoc, memo? }] | tx hash "0x..." | Yes |
ego_callContract | [{ contractAddr, entrypoint, callArgs }], callArgs as hex | tx hash | Yes |
ego_signMessagealiases ego_sign, personal_sign | [message], text or 0x hex | Ed25519 signature, 128 hex characters | Yes |
Input rules the extension enforces: to must be an egot1... address, amount_egoc at least 0.000001, memo at most 256 characters, entrypoint matches [A-Za-z_][A-Za-z0-9_]{0,63}, callArgs is even-length hex. eth_sendTransaction also works and reads value in wei (1 EGOC = 1018).
const [address] = await window.ego.request({ method: 'ego_requestAccounts' });
const hash = await window.ego.request({
method: 'ego_sendTransaction',
params: [{ to: 'egot1...', amount_egoc: 5, memo: 'myapp:order:42' }],
});
Proving a user owns an address (sign-in)
Have the page sign a one-time challenge with ego_signMessage, then verify on your server. The wallet signs "\x19Ego Signed Message:\n" + byteLength + "\n" + message with Ed25519. The server needs the user's 32-byte public key: take it from any transaction they have sent (public_key_ed25519 in wallet.getTransactionHistory), then check that the key derives to the claimed address (see Signing).
ego_getPublicKey method would close it.Run your own node
The full API lives in every Ego Desktop node, so an app should run its own node on its server and call it from the backend. The same Linux build runs without a window when started with EGO_HEADLESS=1; it syncs the chain and serves the API like the desktop app.
| Setting | Default | What it does |
|---|---|---|
EGO_HEADLESS=1 | off | Full node with no window |
EGO_RPC_PORT | 47395 | Port of the JSON-RPC and HTTP API |
EGO_RPC_BIND | 127.0.0.1 | Address the API listens on. Keep it local and put a proxy in front, or set 0.0.0.0 to expose it |
EGO_DATA_DIR | ~/.local/share/EgoDesktop on Linux | Where the chain and wallet files live |
EGO_GATEWAY_PORT | 47398 | HTTPS gateway port for phones (separate, smaller method set) |
- Endpoint:
POST http://127.0.0.1:47395/withContent-Type: application/json. Batches (a JSON array of requests) work. - WebSocket:
ws://127.0.0.1:47395/ws, send{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}(ornewPendingTransactions) for live blocks and pending transactions. - CORS: a node only answers browser calls from localhost and extension origins. A page on your own domain cannot call a node directly; route it through your backend.
- The node API has no login or rate limit. If you expose it, put nginx (or similar) in front with limits.
- Every response follows JSON-RPC 2.0:
{"jsonrpc":"2.0","id":1,"result":...}or{"error":{"code":-32000,"message":"..."}}.
curl -s http://127.0.0.1:47395/ -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"wallet.getBalance","params":{"address":"egot1..."}}'
{"jsonrpc":"2.0","id":1,"result":{"uegoc":12500000,"egoc":12.5}}
JSON-RPC: wallet and transactions
Params are a JSON object (named fields). Amounts are integers in uEGOC: 1 EGOC = 1,000,000 uEGOC.
| Method | Params | Result |
|---|---|---|
wallet.getBalance | address | { uegoc, egoc } |
wallet.getNonce | address | { last_confirmed, next, fee_uegoc, chain_id, tx_version }. Use next, fee_uegoc, chain_id and tx_version to build the next transaction |
tx.submit | tx: a signed transaction object (see Signing) | { success: true, tx_hash }, or error -32000 (rejected), -32001 (mempool refused), -32602 (bad object) |
wallet.getTransactionHistory | address, limit (default 50) | array of transactions, newest first |
wallet.getTransaction | hash | one transaction, or null. block_height is set once it is in a block |
chain.getTxProof | txHash | Merkle inclusion proof, or null |
chain.verifyTxProof | the proof object | { valid } |
Confirming a payment
Poll wallet.getTransaction until block_height is set, then compare it with chain.getFinalizedHeight. A transaction at or below the finalized height is final; Ego uses Tendermint-style finality, so a finalized block is never reverted.
Linking a wallet to an account or order
No special API is needed. Either store the address the user connected with (after the sign-in check), or have them pay with a memo such as myapp:order:<id>. The memo is signed, so it cannot be changed in transit, and it is returned on every transaction (max 256 bytes).
JSON-RPC: chain, contracts, market, network
| Method | Params | Result |
|---|---|---|
chain.getFinalizedHeight | none | { height } |
chain.getBlocks | fromHeight, limit (default 50) | array of blocks |
chain.getBlockHeaders | fromHeight, limit (default 100) | array of headers |
chain.getNetworkStats | none | { blockCount, txCount, peerCount, finalizedHeight, egocPriceUsd, currentView } |
chain.getEgocPrice | none | { usd } |
contract.listDeployed | none | up to 500 { address, name, deployer, deployedAt, codeHash } |
contract.getState | contractAddr, prefix, key | { value }, hex or null |
contract.getActivity | contractAddr, limit (1 to 1,000) | recent calls and their results |
contract.query | contractAddr, entrypoint, argsHex, caller | { success, returnHex, error, ruUsed }. Runs the call read-only; nothing is written or paid |
market.params, market.offers, market.offer, market.trade, market.trades, market.profile, market.leaderboard | offers: asset, fiat, side, limit, cursor; offer and trade: id; trades: address, limit, cursor; profile: address; leaderboard: limit | escrow marketplace data |
shielded.state, shielded.leaves, shielded.check | state: address; leaves: from, limit; check: nullifiers, txs | shielded pool data |
p2p.getPeers, p2p.getCurrentView | none | peers the node knows, consensus view |
ego_getValidators, ego_getMempoolStats | none | block makers the node knows, mempool counts |
eth_chainId returns 0x577, eth_getBalance, eth_getTransactionByHash, eth_getTransactionReceipt and a few more). Several of them return placeholder values and eth_sendRawTransaction is not supported, so build on the native methods above.HTTP routes on a node
| Route | Returns |
|---|---|
GET /health | { status, block_height, chain_tip, finalized_height, tx_count } |
GET /chain/blocks?fromHeight=&limit= | blocks, newest 500 by default, max 1,000 |
GET /chain/transactions?fromHeight=&limit= | the transactions in those blocks |
GET /resolve/:name | the on-chain record of a hosted site |
GET /site/:name and /site/:name/*path | the files of a site hosted on Ego |
GET /site-status/:name | whether a hosted site is live |
GET /cid/:cid (also /file/:cid) | a public file by its content id |
GET /nodes | API addresses of nodes this node knows |
GET /hosting/nodes/:domain | nodes serving a hosted domain |
GET /ws | WebSocket, see Run your own node |
Signing a transaction
Users sign through the extension, so an app only builds transactions itself for its own server wallet (payouts, fees, automated deploys). Every transaction is Ed25519-signed over a fixed byte string, and the node rejects anything that differs by one byte.
- Keys: a 32-byte seed gives the Ed25519 key pair (
nacl.sign.keyPair.fromSeed(seed)). - Address:
blake2s256("ego/addr/v1" || chain_id as u32 little-endian || public key), take the first 20 bytes, prefix the byte0x20, encode bech32m with prefixegot(chain_id 1) orego(chain_id 2). - Call
wallet.getNonceand takenext,fee_uegoc,chain_idandtx_version(3 now; version 2 stops being accepted at block 40,000). - Build the version 3 signing bytes below.
timestampis Unix seconds and should be the current time, since old transactions expire from the mempool. - Sign them with Ed25519. The hash is
"0x" + hex(blake2s256(signing bytes)). - Send the object with
tx.submit.
"ego/tx/v3:" chain_id(u8) ":" from ":" to ":" amount(u64 LE) ":" nonce(u64 LE) ":"
timestamp(i64 LE) ":" fee_uegoc(u64 LE) ":" priority_fee_uegoc(u64 LE) ":"
len(tx_type)(u32 LE) tx_type ":" len(memo)(u32 LE) memo
import nacl from 'tweetnacl';
import { blake2s } from 'blakejs';
const enc = new TextEncoder();
const hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, '0')).join('');
const u64 = (n: bigint) => { const b = new Uint8Array(8); new DataView(b.buffer).setBigUint64(0, BigInt.asUintN(64, n), true); return b; };
const u32 = (n: number) => { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, n, true); return b; };
const cat = (...p: Uint8Array[]) => { const o = new Uint8Array(p.reduce((s, a) => s + a.length, 0)); let i = 0; for (const a of p) { o.set(a, i); i += a.length; } return o; };
export function signTransfer(seed: Uint8Array, from: string, to: string, amountUegoc: number,
nonceInfo: { next: number; fee_uegoc: number; chain_id: number }, memo = '') {
const c = enc.encode(':');
const type = enc.encode('transfer');
const m = enc.encode(memo);
const timestamp = Math.floor(Date.now() / 1000);
const bytes = cat(
enc.encode('ego/tx/v3:'), new Uint8Array([nonceInfo.chain_id]), c,
enc.encode(from), c, enc.encode(to), c,
u64(BigInt(amountUegoc)), c, u64(BigInt(nonceInfo.next)), c, u64(BigInt(timestamp)), c,
u64(BigInt(nonceInfo.fee_uegoc)), c, u64(0n), c,
u32(type.length), type, c, u32(m.length), m,
);
const kp = nacl.sign.keyPair.fromSeed(seed);
return {
hash: '0x' + hex(blake2s(bytes, undefined, 32)),
from, to, amount: amountUegoc, memo: memo || null, timestamp,
signature: hex(nacl.sign.detached(bytes, kp.secretKey)),
status: 'Pending', block_height: null, nonce: nonceInfo.next,
public_key_ed25519: hex(kp.publicKey),
tx_type: 'transfer', fee_uegoc: nonceInfo.fee_uegoc, priority_fee_uegoc: 0,
tx_version: 3, chain_id: nonceInfo.chain_id,
};
}
The Ego Wallet extension uses the same code; see extension/src/shared/crypto.ts in the Ego repository.
Deploying and calling contracts
Contracts are WebAssembly, written in Urego and compiled in the Ego Desktop dApp IDE (there is no compile endpoint on the node yet). Deploys and calls are ordinary signed transactions sent with tx.submit; every node runs them from finalized blocks.
| Field | Deploy | Call |
|---|---|---|
tx_type | deploy | call |
to and contract_addr | the new contract address | the contract address |
entrypoint | init | the function name |
call_args | init arguments, hex | arguments, hex |
wasm_code | the compiled module, hex, max 512 KB | empty |
amount | 0 | 0 |
memo (signed) | deploy:<code_hash>:<first 16 hex of blake3(call_args hex)> | call:<entrypoint>:<first 16 hex of blake3(call_args hex)> |
code_hash = hex(blake3(from + wasm_code_hex)), and the contract address is the first 20 bytes of that hash, hex. The same deployer and code always give the same address.- The memo is what binds
entrypoint,call_argsandwasm_codeto the signature; a transaction whose memo does not match is refused when it executes. - Try a call first with
contract.query(free, read-only), then send it. Afterwardscontract.getActivityshows whether it ran or why it was refused. - From a web page, the user's call goes through
ego_callContract, which builds and signs all of this in the extension. A browser deploy method does not exist yet; deploy from Ego Desktop or from your server with its own key.
Public endpoints
https://api.egoblockchain.com serves this page and the network's public read-only routes. Any website may call them (open CORS), which suits public stats, but they are not the source of truth: balances, nonces and submitting transactions go through a node. The same routes are also on https://rpc.egoblockchain.com.
| Route | Returns |
|---|---|
GET /health | server status |
GET /chain/blocks?limit= | recent blocks as nodes reported them; can lag the network |
GET /chain/transactions?limit= | recent transactions, same caveat |
GET /egoc | { symbol, usd, market_cap, supply, updated_at } |
GET /prices and /price/:symbol | USD prices the network uses for fees |
GET /nodes | nodes seen in the last 10 minutes |
GET /gateways | public HTTPS gateways (phones use these) |
GET /hosting/nodes/:domain | nodes serving a hosted site |
curl -s https://api.egoblockchain.com/egoc
Requests are rate limited per IP. Only GET is accepted here.
Limits to plan around
- No GraphQL. If your app wants one, put it in your backend on top of these methods.
- Network settings. Keep chain id and address prefix configurable: today chain_id 1 and
egot1...; mainnet uses chain_id 2 andego1.... - Detect the wallet by
window.ego.isEgoWallet, not by chain id: the extension reports0x1while a node's Ethereum layer reports0x577. - Filtered regions. The
egoblockchain.comnames are filtered in some countries (Iran today), so the public endpoints may not load from servers there. A node itself still joins through its built-in seed peers (v0.3.647 and later). - A node's API has no login or rate limit. Keep it on localhost or behind a proxy with limits.
- Not over the API yet: file storage uploads and site hosting (done in Ego Desktop), a compile endpoint, a browser deploy method, and an
ego_getPublicKeywallet method. Tell us which your app needs first on GitHub.