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.js | Selendra 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()withnew SelendraSDK() - Replace
api.query.*withsdk.query() - Replace
api.tx.*withsdk.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.
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
