Selendra

Documentation

Wallet Integration

Connecting wallets to Selendra applications

Wallet integration connects users to blockchain applications. Multiple approaches based on target audience and technical requirements. Cambodia market considerations influence strategy.

Wallet Comparison

Wallet TypeUser ExperienceSecurityBest For
MetaMaskFamiliar to crypto usersSelf-custodyEVM contracts, experienced users
Polkadot.jsSubstrate-nativeSelf-custodyWASM contracts, full features
CustodialSimplified, no seed phrasesBackend-managedMainstream users, onboarding
Social LoginEmail/phone authenticationCustodial with 2FACambodia market, beginners

MetaMask Integration

Adding Selendra Network

async function addSelendraNetwork() {
  try {
    await window.ethereum.request({
      method: "wallet_addEthereumChain",
      params: [
        {
          chainId: "0x1F6",
          chainName: "Selendra Network",
          nativeCurrency: {
            name: "Selendra",
            symbol: "SEL",
            decimals: 18,
          },
          rpcUrls: ["https://rpc.selendra.org"],
          blockExplorerUrls: ["https://explorer.selendra.org"],
        },
      ],
    });
  } catch (error) {
    console.error("Failed to add network:", error);
  }
}

Connection Flow

async function connectMetaMask() {
  if (typeof window.ethereum === "undefined") {
    alert("Please install MetaMask");
    return;
  }

  try {
    const accounts = await window.ethereum.request({
      method: "eth_requestAccounts",
    });

    const chainId = await window.ethereum.request({
      method: "eth_chainId",
    });

    if (chainId !== "0x1F6") {
      await window.ethereum.request({
        method: "wallet_switchEthereumChain",
        params: [{ chainId: "0x1F6" }],
      });
    }

    return accounts[0];
  } catch (error) {
    console.error("Connection failed:", error);
    throw error;
  }
}

Network and Account Listeners

// Network changes
window.ethereum.on("chainChanged", (chainId) => {
  if (chainId !== "0x1F6") {
    switchToSelendra();
  } else {
    window.location.reload();
  }
});

// Account changes
window.ethereum.on("accountsChanged", (accounts) => {
  if (accounts.length === 0) {
    handleDisconnect();
  } else {
    handleAccountChange(accounts[0]);
  }
});

Mobile MetaMask

const dappUrl = "https://yourdapp.com";
const metamaskAppDeepLink = `https://metamask.app.link/dapp/${dappUrl}`;

function isMetaMaskBrowser() {
  return (
    window.ethereum?.isMetaMask &&
    window.navigator.userAgent.includes("MetaMask")
  );
}

function isMobile() {
  return /Android|webOS|iPhone|iPad|iPod/i.test(navigator.userAgent);
}

// Redirect mobile users to MetaMask browser
if (isMobile && !isMetaMaskBrowser) {
  window.location.href = metamaskAppDeepLink;
}

Polkadot.js Extension

Connection

import {
  web3Accounts,
  web3Enable,
  web3FromAddress,
} from "@polkadot/extension-dapp";

async function connectPolkadotWallet(appName) {
  const extensions = await web3Enable(appName);

  if (extensions.length === 0) {
    throw new Error("Polkadot.js extension not installed");
  }

  const accounts = await web3Accounts();

  if (accounts.length === 0) {
    throw new Error("No accounts available");
  }

  return accounts;
}

Signing Transactions

async function signAndSendTransaction(accountAddress, extrinsic) {
  const injector = await web3FromAddress(accountAddress);

  const hash = await extrinsic.signAndSend(accountAddress, {
    signer: injector.signer,
  });

  return hash;
}

Account Selection

function AccountSelector({ accounts, onSelect }) {
  return (
    <select onChange={(e) => onSelect(e.target.value)}>
      {accounts.map((account) => (
        <option key={account.address} value={account.address}>
          {account.meta.name || account.address.slice(0, 8)}
        </option>
      ))}
    </select>
  );
}

Custodial Wallet Implementation

Architecture

Backend manages keys. Frontend handles authentication and transaction approval. Suitable for mainstream users.

Security requirements:

  • Hardware Security Module (HSM) for key storage
  • Multi-signature withdrawal approval
  • Rate limiting per user
  • Daily withdrawal limits
  • Audit logging

Social Login

import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";

async function loginWithGoogle() {
  const provider = new GoogleAuthProvider();
  const result = await signInWithPopup(auth, provider);

  // Backend creates/retrieves wallet
  const response = await fetch("/api/wallet/create", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      userId: result.user.uid,
      email: result.user.email,
    }),
  });

  const { walletAddress } = await response.json();
  return walletAddress;
}

Transaction Approval

Frontend request:

async function requestTransaction(action, params) {
  const response = await fetch("/api/transaction/request", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${authToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ action, params }),
  });

  const { transactionId, estimatedCost } = await response.json();

  const approved = await showConfirmationDialog(action, estimatedCost);

  if (approved) {
    return await executeTransaction(transactionId);
  }
}

Backend execution:

async function executeTransaction(transactionId, userWallet) {
  const privateKey = await getPrivateKey(userWallet); // From HSM
  const wallet = new ethers.Wallet(privateKey, provider);

  const tx = await getPendingTransaction(transactionId);
  const receipt = await wallet.sendTransaction(tx);

  await logTransaction(transactionId, receipt.hash);

  return receipt;
}

StadiumX Implementation

Hybrid approach: MetaMask for experienced users, custodial for new users.

Onboarding:

  1. Social login creates account
  2. Backend generates wallet automatically
  3. User receives NFT ticket immediately
  4. Option to export key later (advanced)

Security:

  • Rate limit: 10 transactions/user/day
  • Max transaction: $10 equivalent
  • SMS verification for withdrawals
  • Insurance fund

Multi-Wallet Support

Detection and Fallback

async function connectWallet() {
  // MetaMask
  if (window.ethereum?.isMetaMask) {
    return await connectMetaMask();
  }

  // Polkadot.js
  const extensions = await web3Enable("YourApp");
  if (extensions.length > 0) {
    return await connectPolkadotWallet();
  }

  // Custodial fallback
  return await showSocialLoginOptions();
}

Unified Interface

class WalletAdapter {
  constructor(type, provider) {
    this.type = type;
    this.provider = provider;
  }

  async getAddress() {
    switch (this.type) {
      case "metamask":
        return (
          await this.provider.request({
            method: "eth_accounts",
          })
        )[0];
      case "polkadot":
        return (await web3Accounts())[0].address;
      case "custodial":
        return this.provider.walletAddress;
    }
  }

  async signTransaction(tx) {
    // Implement per wallet type
  }
}

Cambodia Market Considerations

Mobile-first: Desktop usage minimal. Optimize for small screens. MetaMask mobile browser critical.

Low data usage: Internet data expensive. Minimize API calls. Cache aggressively. Optimize bundle size.

Language: Khmer option increases accessibility. English OK for technical terms. Visual indicators reduce text dependency.

Support: Telegram most popular in Cambodia. Provide Telegram bot/group support. Phone support for older users.


Transaction Handling | Event Monitoring | EVM Smart Contracts

Community

Telegram: t.me/selendranetwork | X: @selendranetwork | GitHub: github.com/selendra/selendra

Contribute

Found an issue or want to contribute?

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

Edit this page on GitHub