bitriel-wallet-sdk
Version:
This is bitriel Wallet SDK
1,756 lines (1,742 loc) • 42.5 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
BitrielWalletSDK: () => BitrielWalletSDK,
EVMWalletProvider: () => EVMWalletProvider,
EVM_NETWORKS: () => EVM_NETWORKS,
GENERIC_ABI: () => GENERIC_ABI,
SUBSTRATE_NETWORKS: () => SUBSTRATE_NETWORKS,
SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
SubstrateWalletProvider: () => SubstrateWalletProvider,
formatTokenAmount: () => formatTokenAmount,
formatTokenBalance: () => formatTokenBalance,
parseTokenBalance: () => parseTokenBalance,
parseTransactionAmount: () => parseTransactionAmount
});
module.exports = __toCommonJS(index_exports);
// src/data/genericAbi/ERC20.ts
var ERC20_JSON_ABI = [
{
constant: true,
inputs: [],
name: "name",
outputs: [
{
name: "",
type: "string"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [],
name: "symbol",
outputs: [
{
name: "",
type: "string"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [],
name: "decimals",
outputs: [
{
name: "",
type: "uint8"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [],
name: "totalSupply",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [
{
name: "account",
type: "address"
}
],
name: "balanceOf",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "recipient",
type: "address"
},
{
name: "amount",
type: "uint256"
}
],
name: "transfer",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: true,
inputs: [
{
name: "owner",
type: "address"
},
{
name: "spender",
type: "address"
}
],
name: "allowance",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "spender",
type: "address"
},
{
name: "amount",
type: "uint256"
}
],
name: "approve",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: false,
inputs: [
{
name: "sender",
type: "address"
},
{
name: "recipient",
type: "address"
},
{
name: "amount",
type: "uint256"
}
],
name: "transferFrom",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "from",
type: "address"
},
{
indexed: true,
name: "to",
type: "address"
},
{
indexed: false,
name: "value",
type: "uint256"
}
],
name: "Transfer",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "owner",
type: "address"
},
{
indexed: true,
name: "spender",
type: "address"
},
{
indexed: false,
name: "value",
type: "uint256"
}
],
name: "Approval",
type: "event"
}
];
// src/data/genericAbi/ERC721.ts
var ERC721_JSON_ABI = [
{
constant: true,
inputs: [
{
name: "owner",
type: "address"
}
],
name: "balanceOf",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [
{
name: "tokenId",
type: "uint256"
}
],
name: "ownerOf",
outputs: [
{
name: "",
type: "address"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "from",
type: "address"
},
{
name: "to",
type: "address"
},
{
name: "tokenId",
type: "uint256"
}
],
name: "safeTransferFrom",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: false,
inputs: [
{
name: "from",
type: "address"
},
{
name: "to",
type: "address"
},
{
name: "tokenId",
type: "uint256"
},
{
name: "_data",
type: "bytes"
}
],
name: "safeTransferFrom",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: false,
inputs: [
{
name: "from",
type: "address"
},
{
name: "to",
type: "address"
},
{
name: "tokenId",
type: "uint256"
}
],
name: "transferFrom",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: false,
inputs: [
{
name: "to",
type: "address"
},
{
name: "tokenId",
type: "uint256"
}
],
name: "approve",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: true,
inputs: [
{
name: "tokenId",
type: "uint256"
}
],
name: "getApproved",
outputs: [
{
name: "",
type: "address"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "operator",
type: "address"
},
{
name: "approved",
type: "bool"
}
],
name: "setApprovalForAll",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: true,
inputs: [
{
name: "owner",
type: "address"
},
{
name: "operator",
type: "address"
}
],
name: "isApprovedForAll",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [
{
name: "interfaceId",
type: "bytes4"
}
],
name: "supportsInterface",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "from",
type: "address"
},
{
indexed: true,
name: "to",
type: "address"
},
{
indexed: true,
name: "tokenId",
type: "uint256"
}
],
name: "Transfer",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "owner",
type: "address"
},
{
indexed: true,
name: "approved",
type: "address"
},
{
indexed: true,
name: "tokenId",
type: "uint256"
}
],
name: "Approval",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "owner",
type: "address"
},
{
indexed: true,
name: "operator",
type: "address"
},
{
indexed: false,
name: "approved",
type: "bool"
}
],
name: "ApprovalForAll",
type: "event"
}
];
// src/data/genericAbi/ERC1155.ts
var ERC1155_JSON_ABI = [
{
constant: true,
inputs: [
{
name: "account",
type: "address"
},
{
name: "id",
type: "uint256"
}
],
name: "balanceOf",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: true,
inputs: [
{
name: "accounts",
type: "address[]"
},
{
name: "ids",
type: "uint256[]"
}
],
name: "balanceOfBatch",
outputs: [
{
name: "",
type: "uint256[]"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "operator",
type: "address"
},
{
name: "approved",
type: "bool"
}
],
name: "setApprovalForAll",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: true,
inputs: [
{
name: "account",
type: "address"
},
{
name: "operator",
type: "address"
}
],
name: "isApprovedForAll",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
constant: false,
inputs: [
{
name: "from",
type: "address"
},
{
name: "to",
type: "address"
},
{
name: "id",
type: "uint256"
},
{
name: "amount",
type: "uint256"
},
{
name: "data",
type: "bytes"
}
],
name: "safeTransferFrom",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: false,
inputs: [
{
name: "from",
type: "address"
},
{
name: "to",
type: "address"
},
{
name: "ids",
type: "uint256[]"
},
{
name: "amounts",
type: "uint256[]"
},
{
name: "data",
type: "bytes"
}
],
name: "safeBatchTransferFrom",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
},
{
constant: true,
inputs: [
{
name: "interfaceId",
type: "bytes4"
}
],
name: "supportsInterface",
outputs: [
{
name: "",
type: "bool"
}
],
payable: false,
stateMutability: "view",
type: "function"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "operator",
type: "address"
},
{
indexed: true,
name: "from",
type: "address"
},
{
indexed: true,
name: "to",
type: "address"
},
{
indexed: false,
name: "id",
type: "uint256"
},
{
indexed: false,
name: "value",
type: "uint256"
}
],
name: "TransferSingle",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "operator",
type: "address"
},
{
indexed: true,
name: "from",
type: "address"
},
{
indexed: true,
name: "to",
type: "address"
},
{
indexed: false,
name: "ids",
type: "uint256[]"
},
{
indexed: false,
name: "values",
type: "uint256[]"
}
],
name: "TransferBatch",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: "account",
type: "address"
},
{
indexed: true,
name: "operator",
type: "address"
},
{
indexed: false,
name: "approved",
type: "bool"
}
],
name: "ApprovalForAll",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: false,
name: "value",
type: "string"
},
{
indexed: true,
name: "id",
type: "uint256"
}
],
name: "URI",
type: "event"
}
];
// src/data/genericAbi/index.ts
var GENERIC_ABI = {
ERC20: ERC20_JSON_ABI,
ERC721: ERC721_JSON_ABI,
ERC1155: ERC1155_JSON_ABI
};
// src/config/networks.ts
var SUBSTRATE_NETWORKS = [
{
type: "substrate",
name: "Selendra",
chainId: "0x0000000000000000000000000000000000000000000000000000000000000003",
rpcUrl: "wss://rpc.selendra.org",
explorerUrl: "https://selendra.subscan.io",
logo: "https://www.selendra.org/logo/sel-logo-blue-notext.png",
nativeCurrency: {
name: "Selendra",
symbol: "SEL",
decimals: 18,
logoURI: "https://www.selendra.org/logo/sel-logo-blue-notext.png"
},
ss58Format: 42,
genesisHash: "0x9e17c622381c36351de3ff9dc662282bf89ea2f420a9c55e23ff4fd815d2886a"
// Replace with actual genesis hash
}
// {
// type: "substrate",
// name: "Polkadot",
// chainId:
// "0x0000000000000000000000000000000000000000000000000000000000000000",
// rpcUrl: "wss://rpc.polkadot.io",
// explorerUrl: "https://polkadot.subscan.io",
// logo: "https://cryptologos.cc/logos/polkadot-new-dot-logo.svg?v=040",
// nativeCurrency: {
// name: "Polkadot",
// symbol: "DOT",
// decimals: 10,
// },
// ss58Format: 0,
// genesisHash:
// "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
// },
// {
// type: "substrate",
// name: "Kusama",
// chainId:
// "0x0000000000000000000000000000000000000000000000000000000000000002",
// rpcUrl: "wss://kusama-rpc.polkadot.io",
// explorerUrl: "https://kusama.subscan.io",
// logo: "https://raw.githubusercontent.com/polkadot-js/apps/master/packages/apps/public/kusama_circle.svg",
// nativeCurrency: {
// name: "Kusama",
// symbol: "KSM",
// decimals: 12,
// },
// ss58Format: 2,
// genesisHash:
// "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe",
// },
];
var EVM_NETWORKS = [
{
type: "evm",
name: "Selendra Mainnet",
chainId: 1961,
rpcUrl: "https://rpc.selendra.org",
explorerUrl: "http://explorer.selendra.org/",
logo: "https://www.selendra.org/logo/sel-logo-blue-notext.png",
nativeCurrency: {
name: "Selendra",
symbol: "SEL",
decimals: 18,
logoURI: "https://www.selendra.org/logo/sel-logo-blue-notext.png"
},
tokens: [
{
address: "0x2402Ed00D1223500bA3B45fa30549Be28Dbe50B3",
name: "SAM Token",
symbol: "SAM",
decimals: 18,
logoURI: "https://selendra.org/tokens/sam.png"
},
{
address: "0xffFEdB07dbc5A93A3c7653930e46Bd9332468559",
name: "KHR Stable",
symbol: "KHR",
decimals: 18,
logoURI: "https://selendra.org/tokens/khr.png"
}
]
},
{
type: "evm",
name: "Ethereum Mainnet",
chainId: 1,
rpcUrl: "https://eth.public-rpc.com",
explorerUrl: "https://etherscan.io",
logo: "https://cryptologos.cc/logos/ethereum-eth-logo.svg?v=040",
nativeCurrency: {
name: "Ethereum",
symbol: "ETH",
decimals: 18,
logoURI: "https://cryptologos.cc/logos/ethereum-eth-logo.svg?v=040"
},
tokens: [
{
address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
name: "Tether USD",
symbol: "USDT",
decimals: 6,
logoURI: "https://cryptologos.cc/logos/tether-usdt-logo.svg?v=040"
},
{
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
name: "USD Coin",
symbol: "USDC",
decimals: 6,
logoURI: "https://cryptologos.cc/logos/usd-coin-usdc-logo.svg?v=040"
}
]
},
{
type: "evm",
name: "Polygon Mainnet",
chainId: 137,
rpcUrl: "https://polygon-rpc.com",
explorerUrl: "https://polygonscan.com",
logo: "https://cryptologos.cc/logos/polygon-matic-logo.svg?v=040",
nativeCurrency: {
name: "MATIC",
symbol: "MATIC",
decimals: 18,
logoURI: "https://cryptologos.cc/logos/polygon-matic-logo.svg?v=040"
},
tokens: [
{
address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
name: "Tether USD",
symbol: "USDT",
decimals: 6,
logoURI: "https://cryptologos.cc/logos/tether-usdt-logo.svg?v=040"
},
{
address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
name: "USD Coin",
symbol: "USDC",
decimals: 6,
logoURI: "https://cryptologos.cc/logos/usd-coin-usdc-logo.svg?v=040"
}
]
}
];
var SUPPORTED_NETWORKS = [...SUBSTRATE_NETWORKS, ...EVM_NETWORKS];
// src/wallet/substrate.ts
var import_api = require("@polkadot/api");
var import_keyring = require("@polkadot/keyring");
// src/utils/tokenFormatters.ts
var DEFAULT_FORMAT_OPTIONS = {
precision: 5,
useThousandSeparator: true,
thousandSeparator: ",",
trimTrailingZeros: true
};
function formatTokenBalance(balance, decimals, options = {}) {
const {
precision,
useThousandSeparator,
thousandSeparator,
trimTrailingZeros
} = { ...DEFAULT_FORMAT_OPTIONS, ...options };
try {
const balanceBigInt = BigInt(balance);
const divisor = BigInt(10) ** BigInt(decimals);
let wholePart = (balanceBigInt / divisor).toString();
let fractionalPart = (balanceBigInt % divisor).toString().padStart(decimals, "0");
if (useThousandSeparator) {
wholePart = wholePart.replace(
/\B(?=(\d{3})+(?!\d))/g,
thousandSeparator || ","
);
}
if (precision !== void 0 && precision >= 0) {
fractionalPart = fractionalPart.slice(0, precision);
}
if (trimTrailingZeros) {
fractionalPart = fractionalPart.replace(/0+$/, "");
}
return fractionalPart ? `${wholePart}.${fractionalPart}` : wholePart;
} catch (error) {
console.warn("Failed to format token balance:", error);
return balance;
}
}
function parseTokenBalance(formattedBalance, decimals) {
try {
const cleanedBalance = formattedBalance.replace(/,/g, "");
const [wholePart, fractionalPart = ""] = cleanedBalance.split(/[.,]/);
const paddedFractional = fractionalPart.padEnd(decimals, "0");
const finalFractional = paddedFractional.slice(0, decimals);
return wholePart + finalFractional;
} catch (error) {
console.warn("Failed to parse token balance:", error);
return formattedBalance;
}
}
function formatTokenAmount(amount, decimals, symbol, options = {}) {
const formattedAmount = formatTokenBalance(amount, decimals, options);
return `${formattedAmount} ${symbol}`;
}
// src/wallet/substrate.ts
var SubstrateWalletProvider = class {
api = null;
keyring = null;
pair = null;
network;
mnemonic;
constructor(network, mnemonic) {
this.network = network;
this.mnemonic = mnemonic;
}
async connect() {
try {
const provider = new import_api.WsProvider(this.network.rpcUrl);
const api = await import_api.ApiPromise.create({
provider,
types: {
// Add any custom types needed for Selendra
// This is where you would add Selendra-specific types
}
});
this.api = api;
this.keyring = new import_keyring.Keyring({
type: "sr25519",
ss58Format: this.network.ss58Format || 42
// Default to 42 for Selendra
});
this.pair = this.keyring.addFromMnemonic(this.mnemonic);
} catch (error) {
throw new Error(`Failed to connect to Substrate network: ${error}`);
}
}
async disconnect() {
if (this.api) {
await this.api.disconnect();
this.api = null;
}
this.keyring = null;
this.pair = null;
}
async getAddress() {
if (!this.pair) {
throw new Error("Wallet not connected");
}
return this.pair.address;
}
async signMessage(message) {
if (!this.pair) {
throw new Error("Wallet not connected");
}
const signature = this.pair.sign(message);
return Buffer.from(signature).toString("hex");
}
async getBalance() {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
const accountInfo = await this.api.query.system.account(
this.pair.address
);
return accountInfo.data.free.toString();
}
/**
* Get detailed account information including locked balance
* @returns Account information
*/
async getAccountInfo() {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
return await this.api.query.system.account(
this.pair.address
);
}
async sendTransaction(tx) {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
const { method, params } = tx;
let extrinsic;
if (this.network.name === "Selendra") {
if (method === "balances") {
const [action, recipient, amount] = params;
if (action === "transfer") {
extrinsic = this.api.tx.balances.transferAllowDeath(
recipient,
amount
);
} else {
throw new Error(`Unsupported balances action: ${action}`);
}
} else {
throw new Error(
`Unsupported transaction method for Selendra: ${method}`
);
}
} else {
const txModule = this.api.tx[method];
if (!txModule) {
throw new Error(`Transaction method ${method} not found`);
}
const txMethod = txModule[params[0]];
if (!txMethod) {
throw new Error(
`Transaction method ${params[0]} not found in module ${method}`
);
}
extrinsic = txMethod(...params.slice(1));
}
return new Promise((resolve, reject) => {
extrinsic.signAndSend(
this.pair,
(result) => {
if (result.isFinalized) {
resolve(result.txHash.toString());
}
}
).catch(reject);
});
}
async getTokenBalance(tokenAddress) {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
if (this.network.name === "Selendra") {
throw new Error(
"Token balance not supported for this implementation"
);
} else {
throw new Error(
"Token balance not supported for this implementation"
);
}
}
async listTokens() {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
const tokens = [];
const nativeBalance = await this.getBalance();
tokens.push({
address: "0x0000000000000000000000000000000000000000",
// Placeholder for native token
name: this.network.nativeCurrency.name,
symbol: this.network.nativeCurrency.symbol,
decimals: this.network.nativeCurrency.decimals,
balance: nativeBalance,
formatted: this.formatTokenBalance(
nativeBalance,
this.network.nativeCurrency.decimals
)
});
if (this.network.tokens) {
for (const token of this.network.tokens) {
try {
tokens.push({
...token,
balance: "0",
formatted: "0.0"
});
} catch (error) {
console.warn(
`Failed to get balance for token ${token.symbol}:`,
error
);
tokens.push({
...token,
balance: "0",
formatted: "0.0"
});
}
}
}
return tokens;
}
formatTokenBalance(balance, decimals, precision = 5) {
return formatTokenBalance(balance, decimals, { precision });
}
async estimateFee(tx) {
if (!this.api || !this.pair) {
throw new Error("Wallet not connected");
}
try {
const { method, params } = tx;
let extrinsic;
if (this.network.name === "Selendra") {
if (method === "balances") {
const [action, recipient, amount] = params;
if (action === "transfer") {
extrinsic = this.api.tx.balances.transferAllowDeath(
recipient,
amount
);
} else {
throw new Error(
`Unsupported balances action: ${action}`
);
}
} else {
throw new Error(
`Unsupported transaction method for Selendra: ${method}`
);
}
} else {
const txModule = this.api.tx[method];
if (!txModule) {
throw new Error(`Transaction method ${method} not found`);
}
const txMethod = txModule[params[0]];
if (!txMethod) {
throw new Error(
`Transaction method ${params[0]} not found in module ${method}`
);
}
extrinsic = txMethod(...params.slice(1));
}
const paymentInfo = await extrinsic.paymentInfo(this.pair);
const fee = paymentInfo.partialFee.toString();
const formatted = this.formatTokenBalance(
fee,
this.network.nativeCurrency.decimals
);
return {
fee,
formatted,
currency: this.network.nativeCurrency.symbol
};
} catch (error) {
console.error("Failed to estimate fee:", error);
throw new Error(`Failed to estimate fee: ${error}`);
}
}
isConnected() {
return this.api !== null && this.pair !== null;
}
};
// src/wallet/evm.ts
var import_ethers = require("ethers");
var EVMWalletProvider = class {
provider = null;
signer = null;
network;
mnemonic;
constructor(network, mnemonic) {
this.network = network;
this.mnemonic = mnemonic;
}
async connect() {
try {
this.provider = new import_ethers.ethers.JsonRpcProvider(
this.network.rpcUrl
);
this.signer = import_ethers.ethers.Wallet.fromPhrase(
this.mnemonic,
this.provider
);
} catch (error) {
throw new Error(`Failed to connect to EVM network: ${error}`);
}
}
async disconnect() {
this.provider = null;
this.signer = null;
}
async getAddress() {
if (!this.signer) {
throw new Error("Wallet not connected");
}
return await this.signer.getAddress();
}
async signMessage(message) {
if (!this.signer) {
throw new Error("Wallet not connected");
}
return await this.signer.signMessage(message);
}
async getBalance() {
if (!this.provider || !this.signer) {
throw new Error("Wallet not connected");
}
const address = await this.signer.getAddress();
const balance = await this.provider.getBalance(address);
return balance.toString();
}
async sendTransaction(tx) {
if (!this.signer) {
throw new Error("Wallet not connected");
}
if (!this.isEVMTransaction(tx)) {
throw new Error("Invalid transaction type for EVM network");
}
const transaction = await this.signer.sendTransaction(tx);
return transaction.hash;
}
async getTokenBalance(tokenAddress) {
if (!this.provider || !this.signer) {
throw new Error("Wallet not connected");
}
const address = await this.signer.getAddress();
if (tokenAddress.toLowerCase() === "0x0000000000000000000000000000000000000000") {
const balance = await this.provider.getBalance(address);
return balance.toString();
}
try {
console.log(
`Checking contract at address ${tokenAddress} on network ${this.network.name} (chainId: ${this.network.chainId})`
);
const code = await this.provider.getCode(tokenAddress);
console.log(`Contract code length: ${code.length}`);
if (code === "0x") {
console.warn(
`No contract found at address ${tokenAddress} on network ${this.network.name}`
);
return "0";
}
const tokenContract = new import_ethers.ethers.Contract(
tokenAddress,
[
"function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)"
],
this.provider
);
try {
const symbol = await tokenContract.symbol();
const decimals = await tokenContract.decimals();
console.log(
`Contract implements ERC20 interface. Symbol: ${symbol}, Decimals: ${decimals}`
);
} catch (error) {
console.warn(
`Contract at ${tokenAddress} does not implement ERC20 interface: ${error}`
);
return "0";
}
const balance = await tokenContract.balanceOf(address);
return balance.toString();
} catch (error) {
console.warn(
`Failed to get balance for token ${tokenAddress}:`,
error
);
return "0";
}
}
async listTokens() {
if (!this.provider || !this.signer) {
throw new Error("Wallet not connected");
}
const address = await this.signer.getAddress();
const tokens = [];
const nativeBalance = await this.provider.getBalance(address);
tokens.push({
address: "0x0000000000000000000000000000000000000000",
name: this.network.nativeCurrency.name,
symbol: this.network.nativeCurrency.symbol,
decimals: this.network.nativeCurrency.decimals,
balance: nativeBalance.toString(),
formatted: this.formatTokenBalance(
nativeBalance.toString(),
this.network.nativeCurrency.decimals
)
});
if (this.network.tokens) {
for (const token of this.network.tokens) {
try {
console.log(
`Checking token contract at address ${token.address} on network ${this.network.name} (chainId: ${this.network.chainId})`
);
const code = await this.provider.getCode(token.address);
console.log(`Token contract code length: ${code.length}`);
if (code === "0x") {
console.warn(
`No contract found at address ${token.address} on network ${this.network.name}`
);
tokens.push({
...token,
balance: "0",
formatted: "0.0"
});
continue;
}
const tokenContract = new import_ethers.ethers.Contract(
token.address,
[
"function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)"
],
this.provider
);
try {
const symbol = await tokenContract.symbol();
const decimals = await tokenContract.decimals();
console.log(
`Token contract implements ERC20 interface. Symbol: ${symbol}, Decimals: ${decimals}`
);
} catch (error) {
console.warn(
`Contract at ${token.address} does not implement ERC20 interface: ${error}`
);
tokens.push({
...token,
balance: "0",
formatted: "0.0"
});
continue;
}
const balance = await tokenContract.balanceOf(address);
tokens.push({
...token,
balance: balance.toString(),
formatted: this.formatTokenBalance(
balance.toString(),
token.decimals
)
});
} catch (error) {
console.warn(
`Failed to get balance for token ${token.symbol}:`,
error
);
tokens.push({
...token,
balance: "0",
formatted: "0.0"
});
}
}
}
return tokens;
}
formatTokenBalance(balance, decimals, precision = 5) {
return formatTokenBalance(balance, decimals, { precision });
}
isConnected() {
return this.provider !== null && this.signer !== null;
}
isEVMTransaction(tx) {
return "to" in tx && "value" in tx && typeof tx.value === "string";
}
async estimateFee(tx) {
if (!this.provider || !this.signer) {
throw new Error("Wallet not connected");
}
if (!this.isEVMTransaction(tx)) {
throw new Error("Invalid transaction type for EVM network");
}
try {
const gasPrice = await this.provider.getFeeData();
if (!gasPrice.gasPrice) {
throw new Error("Failed to get gas price");
}
const gasLimit = await this.provider.estimateGas({
from: await this.signer.getAddress(),
...tx
});
const fee = gasLimit * gasPrice.gasPrice;
const formatted = this.formatTokenBalance(
fee.toString(),
this.network.nativeCurrency.decimals
);
return {
fee: fee.toString(),
formatted,
currency: this.network.nativeCurrency.symbol
};
} catch (error) {
console.error("Failed to estimate fee:", error);
throw new Error(`Failed to estimate fee: ${error}`);
}
}
};
// src/utils/mnemonic.ts
var import_util_crypto = require("@polkadot/util-crypto");
function generateMnemonic(options = {}) {
const {
wordCount = 12,
strength = 128
// 12 words = 128 bits of entropy
} = options;
if (![12, 15, 18, 21, 24].includes(wordCount)) {
throw new Error("Invalid word count. Must be 12, 15, 18, 21, or 24");
}
if (![128, 160, 192, 224, 256].includes(strength)) {
throw new Error("Invalid strength. Must be 128, 160, 192, 224, or 256");
}
const expectedStrength = wordCount / 3 * 32;
if (strength !== expectedStrength) {
throw new Error(
`Strength ${strength} does not match word count ${wordCount}. Expected strength: ${expectedStrength}`
);
}
return (0, import_util_crypto.mnemonicGenerate)(wordCount);
}
// src/sdk.ts
var BitrielWalletSDK = class {
providers = /* @__PURE__ */ new Map();
currentNetwork = null;
constructor(mnemonic) {
[...SUPPORTED_NETWORKS].forEach((network) => {
const provider = network.type === "substrate" ? new SubstrateWalletProvider(network, mnemonic) : new EVMWalletProvider(network, mnemonic);
this.providers.set(network.chainId.toString(), provider);
});
}
static createMnemonic(options = {}) {
return generateMnemonic(options);
}
async connect(chainId) {
const network = [...SUPPORTED_NETWORKS].find(
(n) => n.chainId.toString() === chainId
);
if (!network) {
throw new Error(`Network with chain ID ${chainId} not supported`);
}
const provider = this.providers.get(chainId);
if (!provider) {
throw new Error(`Provider not found for chain ID ${chainId}`);
}
await provider.connect();
this.currentNetwork = network;
}
async disconnect() {
if (this.currentNetwork) {
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (provider) {
await provider.disconnect();
}
this.currentNetwork = null;
}
}
async getWalletState() {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
const address = await provider.getAddress();
const nativeBalance = await provider.getBalance();
const tokenBalances = [];
if (this.currentNetwork.tokens) {
for (const token of this.currentNetwork.tokens) {
const balance = await provider.getTokenBalance(token.address);
const formatted = this.formatTokenBalance(
balance,
token.decimals
);
tokenBalances.push({
token,
balance,
formatted
});
}
}
const detailedBalance = await this.getDetailedBalance();
return {
address,
balances: {
native: nativeBalance,
tokens: tokenBalances,
detailed: detailedBalance
},
network: this.currentNetwork
};
}
/**
* Get detailed balance information including locked and transferable balances
* @returns Detailed balance information
*/
async getDetailedBalance() {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
const totalBalance = await provider.getBalance();
const decimals = this.currentNetwork.nativeCurrency.decimals;
if (this.currentNetwork.type === "substrate") {
const substrateProvider = provider;
const accountInfo = await substrateProvider.getAccountInfo();
const locked = accountInfo.data.frozen.toString();
const transferable = (BigInt(totalBalance) - BigInt(locked)).toString();
return {
total: totalBalance,
locked,
transferable,
formatted: {
total: this.formatTokenBalance(totalBalance, decimals),
locked: this.formatTokenBalance(locked, decimals),
transferable: this.formatTokenBalance(
transferable,
decimals
)
}
};
} else {
return {
total: totalBalance,
locked: "0",
transferable: totalBalance,
formatted: {
total: this.formatTokenBalance(totalBalance, decimals),
locked: "0.0",
transferable: this.formatTokenBalance(
totalBalance,
decimals
)
}
};
}
}
async sendTransaction(tx) {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
return provider.sendTransaction(tx);
}
async signMessage(message) {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
return provider.signMessage(message);
}
getSupportedNetworks() {
return [...SUPPORTED_NETWORKS];
}
getCurrentNetwork() {
return this.currentNetwork;
}
async listTokens() {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
return provider.listTokens();
}
async estimateFee(tx) {
if (!this.currentNetwork) {
throw new Error("No network connected");
}
const provider = this.providers.get(
this.currentNetwork.chainId.toString()
);
if (!provider) {
throw new Error("Provider not found");
}
return provider.estimateFee(tx);
}
/**
* Format a token balance with the specified options
* @param balance - The token balance as a string
* @param decimals - The number of decimals for the token
* @param options - Formatting options
*/
formatTokenBalance(balance, decimals, options) {
return formatTokenBalance(balance, decimals, options);
}
/**
* Format a token amount with its symbol
* @param amount - The token amount as a string
* @param decimals - The number of decimals for the token
* @param symbol - The token symbol
* @param options - Formatting options
*/
formatTokenAmount(amount, decimals, symbol, options) {
return formatTokenAmount(amount, decimals, symbol, options);
}
/**
* Parse a formatted token balance back to its raw form
* @param formattedBalance - The formatted balance string
* @param decimals - The number of decimals for the token
*/
parseTokenBalance(formattedBalance, decimals) {
return parseTokenBalance(formattedBalance, decimals);
}
};
// src/utils/amount.ts
var import_util = require("@polkadot/util");
var import_ethers2 = require("ethers");
function parseTransactionAmount(amount, chainType, decimals = 18) {
if (amount === null || amount === void 0) {
throw new Error("Amount cannot be null or undefined");
}
if (chainType === "evm") {
return import_ethers2.ethers.parseEther(amount.toString()).toString();
} else {
const amountStr = amount.toString();
const [whole, fraction = ""] = amountStr.split(".");
const paddedFraction = fraction.padEnd(decimals, "0").slice(0, decimals);
const fullNumber = whole + paddedFraction;
return new import_util.BN(fullNumber).toString();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BitrielWalletSDK,
EVMWalletProvider,
EVM_NETWORKS,
GENERIC_ABI,
SUBSTRATE_NETWORKS,
SUPPORTED_NETWORKS,
SubstrateWalletProvider,
formatTokenAmount,
formatTokenBalance,
parseTokenBalance,
parseTransactionAmount
});