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. No additional hashing is applied to the public key itself.
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";

// base32 alphabet (RFC 4648, no padding)
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;
}

/**
 * Derive an Algorand address from a Solana address.
 * Algorand address = base32( pubkey || sha512_256(pubkey)[28:32] )
 */
function deriveAlgorandAddress(solanaAddress: string): string {
  const pubkey = bs58.decode(solanaAddress); // 32-byte Ed25519 pubkey
  const checksum = sha512_256(pubkey).slice(28, 32); // last 4 bytes
  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:
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";

async function signAlgorandMessage(
  message: string,
  solWallet: WalletAccount
): Promise<string> {
  const messageBytes = new TextEncoder().encode(message);
  const result = await signMessage({
    walletAccount: solWallet,
    message: bytesToHex(messageBytes),
  });
  return result.signature;
}

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); // hex/base64/base58 decode
  return ed25519.verify(sigBytes, messageBytes, pubkey);
}

Check Balance

const ALGOD = "https://testnet-api.algonode.cloud";

async function getAlgorandBalance(address: string): Promise<string> {
  const res = await fetch(`${ALGOD}/v2/accounts/${address}`);
  if (!res.ok) return "0";
  const data = await res.json();
  return (data.amount / 1_000_000).toString();
}

Send a Transaction

Algorand transactions are MessagePack-encoded. This example uses algosdk to build and encode the transaction, then signs it with the Dynamic-managed Ed25519 key:
import algosdk from "algosdk";
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";
import bs58 from "bs58";

const ALGOD_TOKEN = "";
const ALGOD_SERVER = "https://testnet-api.algonode.cloud";

async function sendAlgorandTransfer(
  to: string,
  amountAlgo: number,
  fromAddress: string,
  solWallet: WalletAccount
): Promise<string> {
  const client = new algosdk.Algodv2(ALGOD_TOKEN, ALGOD_SERVER, "");
  const suggestedParams = await client.getTransactionParams().do();

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

  // The bytes to sign are "TX" prefix + MessagePack-encoded transaction
  const bytesToSign = txn.bytesToSign();
  const { signature } = await signMessage({
    walletAccount: solWallet,
    message: bytesToHex(bytesToSign),
  });
  const sigBytes = decodeSig(signature);

  const signedTxn = txn.attachSignature(
    bs58.decode(solWallet.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 hashing.

React Native

The same derivation and signing logic works in React Native. Replace the signMessage import with the React Native client:
// React Native — use the client SDK directly
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
const { signedMessage } = await dynamicClient.wallets.signMessage({
  wallet,
  message: bytesToHex(bytesToSign),
});
See the React Native Algorand guide for a full walkthrough.