Working with the ELA Main Chain
The ELA main chain is a UTXO-based blockchain written in Go. It uses BPoS consensus combined with AuxPoW (merged mining with Bitcoin). Transactions are structurally similar to Bitcoin but with critical differences.
Key Differences from Bitcoin
| Aspect | Bitcoin | ELA Main Chain |
|---|---|---|
| Curve | secp256k1 | NIST P-256 (secp256r1) |
| Address prefix | 1, 3, bc1 | E (standard), 8 (multi-sig), X (cross-chain), i (DID) |
| Transaction types | ~5 | 40+ (voting, DID, Council proposals, etc.) |
| Consensus | PoW | BPoS + AuxPoW (merged mining) |
| Block time | ~10 minutes | ~2 minutes |
| Scripting | Bitcoin Script | Subset of Bitcoin Script |
SDK Installation
# From npm (if published)
npm install @elastosfoundation/wallet-js-sdk
# Or from GitHub
git clone https://github.com/elastos/Elastos.ELA.Wallet.JS.SDK.git
cd Elastos.ELA.Wallet.JS.SDK
npm install && npm run build
Generating Addresses
import { Mnemonic, HDKey, Address, SignType } from "@elastosfoundation/wallet-js-sdk";
const mnemonic = Mnemonic.generate("english");
const seed = Mnemonic.toSeed(mnemonic, "");
const masterKey = HDKey.fromMasterSeed(seed, HDKey.CURVE_P256);
const accountKey = masterKey.deriveWithPath("m/44'/0'/0'");
const pubKey = accountKey.deriveWithIndex(0).getPublicKeyBytes();
const address = Address.newFromPubKey(pubKey, Address.PREFIX_STANDARD);
console.log("Address:", address.toString()); // Starts with 'E'
console.log("Public Key:", Buffer.from(pubKey).toString("hex"));
Address Version Bytes
// Standard address (version byte 0x21)
const standardAddr = Address.newFromPubKey(pubKey, 0x21);
// Multi-sig address (version byte 0xc7)
const multiSigAddr = Address.newFromMultiSigPubKeys(
3, // required signatures
[pubKey1, pubKey2, pubKey3, pubKey4, pubKey5],
0xc7
);
// Cross-chain address (version byte 0x67)
const crossChainAddr = Address.newFromPubKey(pubKey, 0x67);
Understanding UTXO Transactions
Unlike Ethereum's account model, ELA uses UTXOs. To send ELA, you:
- Query unspent outputs (UTXOs) belonging to your address
- Select UTXOs that sum to at least the amount you want to send
- Create a transaction with those UTXOs as inputs
- Add outputs: one for the recipient, one for change back to yourself
- Sign each input with your P-256 private key
- Broadcast the signed transaction
RPC Endpoints
The ELA mainchain has two types of API:
- JSON-RPC (for node operations):
https://api.elastos.io/ela - REST Explorer API:
https://blockchain.elastos.io/api/v1/
Querying UTXOs
async function getUTXOs(address: string): Promise<UTXO[]> {
const response = await fetch("https://api.elastos.io/ela", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "listunspent",
params: [{ addresses: [address] }],
}),
});
const data = await response.json();
return data.result;
}
interface UTXO {
txid: string;
vout: number;
address: string;
amount: string;
confirmations: number;
}
Creating and Signing a Transaction
import {
Transaction,
TransactionInput,
TransactionOutput,
SignType,
} from "@elastosfoundation/wallet-js-sdk";
async function createTransferTransaction(
fromPrivKey: Buffer,
fromPubKey: Buffer,
fromAddress: string,
toAddress: string,
amountSela: bigint
): Promise<string> {
const utxos = await getUTXOs(fromAddress);
const feeSela = 10000n; // 0.0001 ELA
const targetSela = amountSela + feeSela;
let totalInput = 0n;
const selectedUTXOs: UTXO[] = [];
for (const utxo of utxos) {
selectedUTXOs.push(utxo);
totalInput += BigInt(Math.round(parseFloat(utxo.amount) * 1e8));
if (totalInput >= targetSela) break;
}
if (totalInput < targetSela) {
throw new Error(`Insufficient funds: have ${totalInput} sela, need ${targetSela} sela`);
}
const tx = new Transaction();
tx.txType = 0x02; // TransferAsset
for (const utxo of selectedUTXOs) {
const input = new TransactionInput();
input.txid = utxo.txid;
input.vout = utxo.vout;
input.sequence = 0xfffffffe;
tx.addInput(input);
}
const recipientOutput = new TransactionOutput();
recipientOutput.address = toAddress;
recipientOutput.amount = amountSela;
recipientOutput.assetId = Transaction.ELA_ASSET_ID;
tx.addOutput(recipientOutput);
const changeSela = totalInput - amountSela - feeSela;
if (changeSela > 0n) {
const changeOutput = new TransactionOutput();
changeOutput.address = fromAddress;
changeOutput.amount = changeSela;
changeOutput.assetId = Transaction.ELA_ASSET_ID;
tx.addOutput(changeOutput);
}
for (let i = 0; i < tx.inputs.length; i++) {
tx.signInput(i, fromPrivKey, fromPubKey, SignType.SIGN_TYPE_P256);
}
return tx.serialize().toString("hex");
}
Broadcasting a Transaction
async function broadcastTransaction(rawTx: string): Promise<string> {
const response = await fetch("https://api.elastos.io/ela", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "sendrawtransaction",
params: [rawTx],
}),
});
const data = await response.json();
if (data.error) {
throw new Error(`Broadcast failed: ${data.error.message}`);
}
return data.result;
}
Common Transaction Types
For quick reference, here are the types you'll use most often from the SDK:
| Code | Name | Purpose |
|---|---|---|
0x02 | TransferAsset | Standard ELA transfer |
0x08 | TransferCrossChainAsset | Cross-chain deposit (main to ESC/EID) |
0x09 | RegisterProducer | Register a BPoS validator |
0x21 | RegisterCR | Register as Council candidate |
0x25 | CRCProposal | Submit a governance proposal |
0x62 | ExchangeVotes | Convert ELA to stake votes |
0x63 | Voting | Cast BPoS votes with lock time |
For the complete list of all 40+ transaction types with correct hex codes, see the Transaction Types reference.
Querying the Main Chain
async function getBlockCount(): Promise<number> {
const res = await rpcCall("getblockcount", []);
return res.result;
}
async function getTransaction(txid: string): Promise<any> {
const res = await rpcCall("getrawtransaction", [txid, true]);
return res.result;
}
async function getBalance(address: string): Promise<string> {
const utxos = await getUTXOs(address);
let total = 0;
for (const utxo of utxos) {
total += parseFloat(utxo.amount);
}
return total.toFixed(8);
}
async function rpcCall(method: string, params: unknown[]): Promise<any> {
const response = await fetch("https://api.elastos.io/ela", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
});
return response.json();
}