Skip to main content

Overview

Algorand uses the Ed25519 elliptic curve — the same curve as Solana. An Algorand address is the base32-encoded 32-byte public key with a 4-byte checksum appended.
PropertyValue
CurveEd25519
Root WalletSolana
Address Formatbase32(pubkey ‖ checksum[4]) — 58 chars
HashingSHA-512/256 (checksum only)
SerializationMessagePack
Smallest UnitmicroAlgo (1 ALGO = 10^6 microAlgo)

Dependencies

npm install @noble/hashes bs58

Derive Address

Algorand’s address is a 32-byte Ed25519 public key encoded in base32, with a 4-byte checksum from SHA-512/256 of the public key:
import { sha512_256 } from "@noble/hashes/sha512.js";
import bs58 from "bs58";

const BASE32_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

function base32Encode(bytes: Uint8Array): string {
  let result = "";
  let bits = 0;
  let value = 0;
  for (const byte of bytes) {
    value = (value << 8) | byte;
    bits += 8;
    while (bits >= 5) {
      result += BASE32_ALPHA[(value >>> (bits - 5)) & 0x1f];
      bits -= 5;
    }
  }
  if (bits > 0) result += BASE32_ALPHA[(value << (5 - bits)) & 0x1f];
  return result;
}

function deriveAlgorandAddress(solanaAddress: string): string {
  const pubkey = bs58.decode(solanaAddress);
  const checksum = sha512_256(pubkey).slice(28, 32);
  const payload = new Uint8Array(36);
  payload.set(pubkey, 0);
  payload.set(checksum, 32);
  return base32Encode(payload);
}

Sign a Message

Sign raw message bytes using the Solana wallet:
async function signAlgorandMessage(message: string): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
  const messageBytes = new TextEncoder().encode(message);
  const { signedMessage } = await dynamicClient.wallets.signMessage({
    wallet,
    message: bytesToHex(messageBytes),
  });
  return signedMessage;
}

Sign a Transaction

Use the signing bytes from the Algorand SDK. The SDK prepends the "TX" prefix before signing:
import algosdk from "algosdk";

async function sendAlgorandTransfer(
  to: string,
  amountAlgo: number,
  fromAddress: string,
): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
  const client = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
  const suggestedParams = await client.getTransactionParams().do();

  const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
    from: fromAddress,
    to,
    amount: Math.round(amountAlgo * 1_000_000),
    suggestedParams,
  });

  const bytesToSign = txn.bytesToSign();
  const { signedMessage } = await dynamicClient.wallets.signMessage({
    wallet,
    message: bytesToHex(bytesToSign),
  });
  const sigBytes = decodeSig(signedMessage);

  const signedTxn = txn.attachSignature(bs58.decode(wallet.address), sigBytes);
  const result = await client.sendRawTransaction(signedTxn).do();
  return result.txId;
}
The Algorand SDK handles the "TX" prefix required before signing. If you implement transaction signing without algosdk, prepend the bytes 0x54 0x58 to the transaction bytes before passing to signMessage.

Verify a Signature

import { ed25519 } from "@noble/curves/ed25519.js";
import bs58 from "bs58";

function verifyAlgorandSignature(
  message: string,
  signature: string,
  solanaAddress: string,
): boolean {
  const pubkey = bs58.decode(solanaAddress);
  const messageBytes = new TextEncoder().encode(message);
  const sigBytes = decodeSig(signature);
  return ed25519.verify(sigBytes, messageBytes, pubkey);
}

Check Balance

async function getAlgorandBalance(address: string): Promise<string> {
  const res = await fetch(`https://testnet-api.algonode.cloud/v2/accounts/${address}`);
  if (!res.ok) return "0";
  const data = await res.json();
  return (data.amount / 1_000_000).toString();
}
For full implementation details see the JavaScript Algorand guide. The only difference in React Native is the signing call — address derivation, transaction building, and RPC calls are identical.