Common Pitfalls
Elastos spans multiple chains with different curves, address formats, and fee models. These are the mistakes developers hit most often — organized by chain so you can jump to what matters.
- Main Chain
- Smart Chain
- Cross-Chain
The P-256 / secp256k1 Confusion
Problem: You generate a key pair using a standard Bitcoin/Ethereum library and try to sign a main chain transaction. The signature is invalid.
Root cause: The ELA main chain uses NIST P-256 (secp256r1). Every standard crypto library in the Bitcoin/Ethereum ecosystem defaults to secp256k1.
Solution: Use the Elastos Wallet JS SDK for main chain operations. For ESC/EID, standard Ethereum libraries work fine.
// This uses secp256k1 internally
import { Wallet } from "ethers";
const wallet = Wallet.createRandom();
// This wallet CANNOT sign main chain transactions
import { HDKey } from "@elastosfoundation/wallet-js-sdk";
const masterKey = HDKey.fromMasterSeed(seed, HDKey.CURVE_P256);
Using Ethereum Address for Main Chain
Problem: You send ELA to a 0x address on the main chain. The transaction is rejected.
Root cause: Main chain uses Base58Check addresses (starting with E), not hex addresses.
Solution: Always use the correct address format for each chain:
- Main chain: Base58Check (e.g.,
E...) - ESC/EID:
0x...(hex, EIP-55)
Gas Estimation on ESC
Problem: Transactions fail with "out of gas" even when you set a high gas limit.
Root cause: ESC's PBFT consensus can sometimes produce blocks faster than expected, and gas estimation may not account for state changes between estimation and execution.
Solution: Add a 20-30% buffer to gas estimates:
const estimated = await provider.estimateGas(tx);
tx.gasLimit = estimated * 130n / 100n; // 30% buffer
DID Resolution Timing
Problem: You publish a DID document and immediately try to resolve it. The resolution returns null.
Root cause: DID publication requires at least one EID block confirmation. EID block time is approximately 5-10 seconds, but network conditions can vary.
Solution: Wait for confirmation before resolving:
await doc.publish("storepass");
// Wait and retry with exponential backoff
async function waitForDIDResolution(did: DID, maxRetries: number = 10): Promise<DIDDocument | null> {
for (let i = 0; i < maxRetries; i++) {
const waitMs = Math.min(1000 * Math.pow(2, i), 30000);
await new Promise(resolve => setTimeout(resolve, waitMs));
const resolved = await DID.resolve(did);
if (resolved) return resolved;
console.log(`DID not yet available, retry ${i + 1}/${maxRetries}...`);
}
return null;
}
Hive Authentication Token Expiry
Problem: Your Hive API calls start returning 401 Unauthorized after some time.
Root cause: JWT tokens issued by Hive nodes have a limited lifetime.
Solution: Implement token refresh logic:
class HiveClient {
private token: string | null = null;
private tokenExpiry: number = 0;
async getToken(): Promise<string> {
if (this.token && Date.now() < this.tokenExpiry - 60000) {
return this.token;
}
return await this.authenticate();
}
private async authenticate(): Promise<string> {
// Step 1: Request challenge
const challengeRes = await fetch(`${this.hiveUrl}/api/v2/did/signin`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: this.appDid }),
});
const { challenge } = await challengeRes.json();
// Step 2: Sign challenge with DID key
const signedChallenge = await this.signChallenge(challenge);
// Step 3: Exchange for token
const authRes = await fetch(`${this.hiveUrl}/api/v2/did/auth`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ challenge_response: signedChallenge }),
});
const { token, exp } = await authRes.json();
this.token = token;
this.tokenExpiry = exp * 1000;
return token;
}
}
Wrong Network in MetaMask
Problem: MetaMask is connected to Ethereum mainnet while your dApp expects ESC.
Solution: Always check the chain, call wallet_switchEthereumChain for 0x14 (mainnet 20), and on error 4902 call wallet_addEthereumChain with ESC_MAINNET_PARAMS from Add Network to Wallet. See that page for all network parameters.
For MetaMask and wallet network setup, see Add Network to Wallet.
async function ensureESCNetwork(escMainnetParams: Record<string, unknown>): Promise<void> {
const chainId = await window.ethereum.request({ method: "eth_chainId" });
if (chainId !== "0x14") {
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x14" }],
});
} catch (switchError: any) {
if (switchError.code === 4902) {
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [escMainnetParams],
});
} else {
throw switchError;
}
}
}
}
DID Store Corruption
Problem: DID operations fail with store-related errors after app crash or unclean shutdown.
Solution: Always handle store lifecycle properly:
let store: DIDStore | null = null;
async function getDIDStore(): Promise<DIDStore> {
if (!store) {
store = await DIDStore.open("/path/to/store");
}
return store;
}
async function closeDIDStore(): Promise<void> {
if (store) {
store.close();
store = null;
}
}
// Register shutdown handler
process.on("SIGINT", async () => {
await closeDIDStore();
process.exit(0);
});
Precompiled Contract Return Format
Problem: Calling a precompiled contract returns raw bytes that cannot be decoded.
Root cause: The precompiled contracts at addresses 1000-1009 return UTF-8 encoded JSON, not ABI-encoded data.
Solution: Decode as UTF-8, not ABI:
const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["string"], result);
const decoded = ethers.toUtf8String(result);
const data = JSON.parse(decoded);
Cross-Chain Deposit Not Arriving
Problem: You sent a TransferCrossChainAsset transaction on the main chain but the ELA never appears on ESC.
Possible causes:
- Transaction not confirmed on main chain yet (check
getrawtransaction) - Arbiter bridge is processing (typically 6-12 minutes)
- Incorrect target address format (must be a valid
0xESC address) - Cross-chain fee not included (0.0001 ELA per output)
Solution: Always verify the main chain transaction first, then wait up to 20 minutes for arbiter processing:
async function waitForCrossChainDeposit(
escAddress: string,
expectedAmountELA: string,
timeoutMs: number = 1200000 // 20 minutes
): Promise<boolean> {
const provider = new ethers.JsonRpcProvider("https://api.elastos.io/esc");
const startBalance = await provider.getBalance(escAddress);
const expectedWei = ethers.parseEther(expectedAmountELA);
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const currentBalance = await provider.getBalance(escAddress);
if (currentBalance - startBalance >= expectedWei) {
return true;
}
await new Promise(resolve => setTimeout(resolve, 30000)); // check every 30s
}
return false;
}
Missing Cross-Chain Fee
Problem: Cross-chain transaction is rejected or funds are lost.
Root cause: The cross-chain output amount must include the cross-chain fee (0.0001 ELA = 10,000 sela) on top of the transfer amount.
Solution:
crossChainOutput.amount = transferAmount;
const CROSS_CHAIN_FEE = 10000n; // sela
crossChainOutput.amount = transferAmount + CROSS_CHAIN_FEE;