Selendra

Documentation

Migration Guide: From Polkadot.js to Selendra SDK

Guide for developers transitioning from Polkadot.js to Selendra SDK, preserving Substrate knowledge while adding EVM capabilities.

Quick Reference

Polkadot.jsSelendra SDK
ApiPromise.create()new SelendraSDK()
api.query.system.account()sdk.query('System', 'Account')
api.tx.balances.transfer()sdk.tx('Balances', 'transfer')
keyring.addFromMnemonic()sdk.createAccount()
formatBalance()sdk.formatBalance()

Core Concepts


Core Concepts

API Connection & Account Management

Polkadot.js

import { ApiPromise, WsProvider } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";

const wsProvider = new WsProvider("wss://rpc.selendra.org");
const api = await ApiPromise.create({ provider: wsProvider });
const keyring = new Keyring({ type: "sr25519" });
const pair = keyring.addFromMnemonic("phrase words go here");

Selendra SDK

import { SelendraSDK } from "@selendrajs/sdk";

const sdk = new SelendraSDK({
  network: "mainnet",
  wsEndpoint: "wss://rpc.selendra.org",
  autoConnect: true,
});

const account = await sdk.importAccountFromMnemonic("phrase words go here", {
  type: "substrate",
});

Transaction Operations

Basic Transfers

Polkadot.js

const tx = api.tx.balances.transfer(address, 1000000000000);
const hash = await tx.signAndSend(pair);

Selendra SDK

const tx = await sdk.transfer({
  to: address,
  amount: BigInt("1000000000000"),
});
console.log("Hash:", tx.hash, "Status:", tx.status);

Custom Extrinsics

Polkadot.js

const tx = api.tx.democracy.propose(proposalHash, 1000000000000);
const hash = await tx.signAndSend(pair, { tip: 1000000000 });

Selendra SDK

const tx = await sdk
  .tx("Democracy", "Propose")
  .proposal(proposalHash)
  .value(BigInt("1000000000000"))
  .tip(BigInt("1000000000"))
  .signAndSend("mnemonic-or-private-key");

Storage Queries

Polkadot.js

const accountInfo = await api.query.system.account(address);
const balance = accountInfo.data.free.toNumber();

// Map queries
const allValidators = await api.query.staking.validators.entries();

// Multi queries
const balances = await api.query.system.account.multi([addr1, addr2, addr3]);

Selendra SDK

const accountInfo = await sdk.query("System", "Account", address);
const balance = accountInfo.free;

// Map queries
const allValidators = await sdk.queryMap("Staking", "Validators");

// Multi queries
const balances = await Promise.all([
  sdk.query("System", "Account", addr1),
  sdk.query("System", "Account", addr2),
  sdk.query("System", "Account", addr3),
]);

Events and Subscriptions

Polkadot.js

api.query.system.events((events) => {
  events.forEach((record) => {
    const { event } = record;
    console.log(`${event.section}.${event.method}`);
  });
});

const unsubscribe = await api.rpc.chain.subscribeNewHeads((header) => {
  console.log("Block:", header.number.toHuman());
});

Selendra SDK

import { useEventListener } from "@selendrajs/sdk/react";

const { events } = useEventListener({ eventType: "system" });

// Programmatic
const unsubscribe = await sdk.subscribeToEvents((event) => {
  console.log(`${event.pallet}.${event.method}`);
});

const unsubHeads = await sdk.subscribeToHeads((header) => {
  console.log("Block:", header.number);
});

Utility Functions

| Operation | Polkadot.js | Selendra SDK |
| ---------------- | ----------------------------------------- | --------------------------------------------------- | ----- | ---------- |
| Format balance | formatBalance(amount, { decimals: 12 }) | sdk.formatBalance(BigInt(amount), 12) |
| Validate address | isAddress(address) | sdk.validateAddress(address) returns 'substrate' | 'evm' | 'invalid' |


Advanced Patterns

Batch Transactions

Polkadot.js

const txs = [
  api.tx.balances.transfer(addr1, amount1),
  api.tx.balances.transfer(addr2, amount2),
];
const batchTx = api.tx.utility.batch(txs);
await batchTx.signAndSend(pair);

Selendra SDK

const batch = sdk.createBatch();
batch.addTransfer(addr1, amount1);
batch.addTransfer(addr2, amount2);
const tx = await batch.execute();

React Integration

Polkadot.js

import { useEffect, useState } from "react";
import { ApiPromise, WsProvider } from "@polkadot/api";

function useApi() {
  const [api, setApi] = useState(null);
  useEffect(() => {
    const init = async () => {
      const provider = new WsProvider("wss://rpc.selendra.org");
      const api = await ApiPromise.create({ provider });
      setApi(api);
    };
    init();
  }, []);
  return api;
}

Selendra SDK

import { useSelendra, useAccount, useBalance } from "@selendrajs/sdk/react";

function MyComponent() {
  const sdk = useSelendra();
  const { account, connect, disconnect } = useAccount();
  const { balance } = useBalance();

  return <div>Balance: {balance?.free}</div>;
}

Migration Checklist

Direct Replacements

  • Replace ApiPromise.create() with new SelendraSDK()
  • Replace api.query.* with sdk.query()
  • Replace api.tx.* with sdk.tx()
  • Replace manual keyring with SDK account management

Adaptation Required

  • Update transaction patterns to SDK format
  • Adapt subscription patterns to SDK hooks
  • Update error handling for SDK-specific errors
  • Replace manual storage encoding/decoding

New Opportunities

  • Explore EVM compatibility features
  • Leverage unified account management
  • Use built-in React hooks
  • Implement cross-chain functionality

Complete Migration Example

Before (Polkadot.js)

import { ApiPromise, WsProvider } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
import { formatBalance } from "@polkadot/util";

function BalancesTransfer() {
  const [api, setApi] = useState(null);
  const [pair, setPair] = useState(null);
  const [balance, setBalance] = useState("0");

  useEffect(() => {
    const init = async () => {
      const provider = new WsProvider("wss://rpc.selendra.org");
      const api = await ApiPromise.create({ provider });
      const keyring = new Keyring({ type: "sr25519" });
      const pair = keyring.addFromMnemonic("//Alice");

      setApi(api);
      setPair(pair);

      const accountInfo = await api.query.system.account(pair.address);
      setBalance(formatBalance(accountInfo.data.free));
    };
    init();
  }, []);

  const handleTransfer = async () => {
    const tx = api.tx.balances.transfer(destAddress, parseInt(amount) * 1e12);
    await tx.signAndSend(pair);

    const accountInfo = await api.query.system.account(pair.address);
    setBalance(formatBalance(accountInfo.data.free));
  };

  return (
    <div>
      <p>Balance: {balance}</p>
      <input
        value={destAddress}
        onChange={(e) => setDestAddress(e.target.value)}
      />
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
      />
      <button onClick={handleTransfer}>Transfer</button>
    </div>
  );
}

After (Selendra SDK)

import { useAccount, useBalance, useTransaction } from "@selendrajs/sdk/react";

function BalancesTransfer() {
  const { account } = useAccount();
  const { balance, refetch } = useBalance();
  const { send, isLoading } = useTransaction();
  const [destAddress, setDestAddress] = useState("");
  const [amount, setAmount] = useState("");

  const handleTransfer = async () => {
    await send({
      to: destAddress,
      amount: BigInt(amount) * BigInt("1000000000000"),
    });
    await refetch();
  };

  return (
    <div>
      <p>
        Balance: {balance?.free} {balance?.tokenSymbol}
      </p>
      <input
        value={destAddress}
        onChange={(e) => setDestAddress(e.target.value)}
      />
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
      />
      <button onClick={handleTransfer} disabled={isLoading}>
        {isLoading ? "Sending..." : "Transfer"}
      </button>
    </div>
  );
}

Enhanced Features

EVM Compatibility

// Access EVM features
const evmBalance = await sdk.getEvmBalance(evmAddress);

// Deploy EVM contracts
const deployed = await sdk.deployContract({
  bytecode: evmBytecode,
  abi: evmAbi,
  type: "evm",
});

Unified Account Management

// Create unified account (Substrate + EVM)
const account = await sdk.createAccount({ type: "both" });
console.log("Substrate:", account.substrateAddress);
console.log("EVM:", account.evmAddress);

// Transfer between account types
await sdk.transferSubstrateToEvm(amount);

Type Safety & Error Handling

// Fully typed transactions
const tx = await sdk.transfer({
  to: string,
  amount: bigint,
  memo: string,
});

// Typed error handling
try {
  await sdk.transfer({ to, amount });
} catch (error) {
  if (error instanceof SelendraError) {
    switch (error.code) {
      case "INSUFFICIENT_BALANCE":
        console.error("Not enough balance");
        break;
      case "INVALID_ADDRESS":
        console.error("Invalid address");
        break;
    }
  }
}

Resources

Need help? Open an issue or start a discussion.

Contribute

Found an issue or want to contribute?

Help us improve this documentation by editing this page on GitHub.

Edit this page on GitHub