Skip to main content

Overview

Aptos uses the Ed25519 elliptic curve — the same curve as Solana. You derive an Aptos address by hashing the Solana wallet’s public key with SHA3-256 and appending a scheme byte. Transactions use BCS (Binary Canonical Serialization).
PropertyValue
CurveEd25519
Root WalletSolana
Address Format0x + SHA3-256 hex (64 chars)
HashingSHA3-256
SerializationBCS
Smallest UnitOctas (1 APT = 10^8 octas)

Dependencies

npm install @aptos-labs/ts-sdk @noble/hashes bs58

Derive Address

Extract the Ed25519 public key from the Solana wallet address, append the single-key scheme byte (0x00), and hash with SHA3-256:
import { sha3_256 } from "@noble/hashes/sha3";
import bs58 from "bs58";

function deriveAptosAddress(solanaAddress: string): string {
  const pubkey = bs58.decode(solanaAddress);
  const payload = new Uint8Array(33);
  payload.set(pubkey, 0);
  payload[32] = 0x00; // single-key Ed25519 scheme byte
  return "0x" + bytesToHex(sha3_256(payload));
}

Sign a Message

Sign the raw UTF-8 bytes of the message using the Solana wallet:
async function signAptosMessage(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 @aptos-labs/ts-sdk to build the transaction, generate the signing message, and submit with an Ed25519 authenticator:
import {
  Aptos, AptosConfig, Network, generateSigningMessageForTransaction,
  AccountAuthenticatorEd25519, Ed25519PublicKey, Ed25519Signature,
} from "@aptos-labs/ts-sdk";
import bs58 from "bs58";

const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

async function sendAptosTransfer(
  recipient: string,
  amountOctas: number,
  aptosAddress: string,
): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
  const pubkeyBytes = bs58.decode(wallet.address);

  const transaction = await aptos.transaction.build.simple({
    sender: aptosAddress,
    data: {
      function: "0x1::aptos_account::transfer",
      functionArguments: [recipient, amountOctas],
    },
  });

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

  const authenticator = new AccountAuthenticatorEd25519(
    new Ed25519PublicKey(pubkeyBytes),
    new Ed25519Signature(bytesToHex(sigBytes)),
  );

  const { hash } = await aptos.transaction.submit.simple({
    transaction,
    senderAuthenticator: authenticator,
  });

  return hash;
}

Verify a Signature

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

function verifyAptosSignature(
  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 getAptosBalance(address: string): Promise<string> {
  const BASE = "https://fullnode.testnet.aptoslabs.com/v1";

  // Try legacy CoinStore model
  try {
    const res = await fetch(
      `${BASE}/accounts/${address}/resource/0x1::coin::CoinStore%3C0x1::aptos_coin::AptosCoin%3E`,
    );
    if (res.ok) {
      const data = await res.json();
      const octas = BigInt(data.data?.coin?.value ?? "0");
      if (octas > BigInt(0)) {
        return (Number(octas) / 1e8).toFixed(8).replace(/\.?0+$/, "");
      }
    }
  } catch {
    // fall through to FA model
  }

  // Fall back to Fungible Asset model
  try {
    const res = await fetch(`${BASE}/view`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        function: "0x1::primary_fungible_store::balance",
        type_arguments: ["0x1::fungible_asset::Metadata"],
        arguments: [address, "0x000000000000000000000000000000000000000000000000000000000000000a"],
      }),
    });
    if (res.ok) {
      const data = await res.json();
      const octas = BigInt(data[0] ?? "0");
      return (Number(octas) / 1e8).toFixed(8).replace(/\.?0+$/, "");
    }
  } catch {
    // ignore
  }

  return "0";
}
For full implementation details see the JavaScript Aptos guide. The only difference in React Native is the signing call — address derivation, transaction building, and RPC calls are identical.