> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Using chains with Tier 2 and 3 support — Technical Overview

> Embedded wallets via raw signing with helper methods and EOA support

## Overview

Tier 2 and Tier 3 chains both use the same raw signing primitives from Dynamic embedded wallets. Tier 2 includes helper methods and EOA support; Tier 3 is derivation only with no official helpers. Gas handling and policies are not yet supported for either tier.

Dynamic supports three Tier 1 embedded wallets: an **EVM wallet** (secp256k1), a **Solana wallet** (Ed25519), and a **Bitcoin wallet** (secp256k1). Because many blockchains share these same elliptic curves, you can derive addresses and sign transactions on additional chains without creating new keys.

The technique is straightforward: extract the public key from the existing wallet, apply chain-specific hashing and encoding, and use Dynamic's raw signing capability to sign chain-native transactions.

| Chain                                                    | Curve Family | Root Wallet | Address Format        | Hashing                |
| -------------------------------------------------------- | ------------ | ----------- | --------------------- | ---------------------- |
| [Aptos](/javascript/reference/tier-2-chains/aptos)       | Ed25519      | Solana      | `0x` + SHA3-256 hex   | SHA3-256               |
| [Algorand](/javascript/reference/tier-2-chains/algorand) | Ed25519      | Solana      | base32-encoded pubkey | SHA-512/256 (checksum) |
| [Cosmos](/javascript/reference/tier-2-chains/cosmos)     | secp256k1    | EVM         | bech32 (`cosmos`)     | SHA-256 + RIPEMD-160   |
| [Tron](/javascript/reference/tier-2-chains/tron)         | secp256k1    | EVM         | base58check (`T`)     | Keccak-256             |
| Spark                                                    | BIP-340      | Bitcoin     | —                     | —                      |

## User Management

<Info>
  Tier 2 chains are not natively represented in Dynamic's UI or session model. You are responsible for managing the user-facing experience: displaying derived addresses, associating them with your user records, and handling any chain-specific session or account state yourself.
</Info>

## Key Security Warning

<Warning>
  **Exporting the root wallet key exposes all derived addresses.** The EVM and Solana embedded wallet keys are the cryptographic root of every address you derive from them. If you export the private key of the EVM wallet (secp256k1) or the Solana wallet (Ed25519) — for example via `exportWaasPrivateKey` — anyone with that key can independently derive and control all Tier 2 addresses generated from it, across every chain. Never expose or transmit root wallet keys unless you have explicitly designed for that use case.
</Warning>

## Bitcoin Signing

Bitcoin is a Tier 1 chain with full embedded wallet support. Its secp256k1 signing primitives can also be used for Tier 2-style derivation — signing for other secp256k1 chains using the Bitcoin embedded wallet as the root, in the same way the EVM wallet is used. Complete example code is not yet published, but the signing primitives are available. Reach out if you need guidance.

## How It Works

Derived wallets follow a two-step pattern:

1. **Create embedded wallets** — When a user authenticates, create both EVM and Solana embedded wallets using Dynamic. These wallets hold the root keys.
2. **Use raw signing** — Extract the public key from the appropriate root wallet, derive a chain-native address, and sign transactions using Dynamic's `signMessage` or `signRawMessage` methods.

### Ed25519 chains (from Solana wallet)

For Ed25519-based chains (Aptos, Cardano, NEAR, Mavryk), the Solana wallet's base58 address directly decodes to the 32-byte Ed25519 public key. You apply chain-specific hashing to derive an address, and use `signMessage` to sign transaction digests.

```typescript theme={"system"}
import bs58 from "bs58";

// Extract the Ed25519 public key from a Solana address
function solanaAddressToPubkey(solanaAddress: string): Uint8Array {
  return bs58.decode(solanaAddress); // 32-byte Ed25519 pubkey
}
```

### secp256k1 chains (from EVM wallet)

For secp256k1-based chains (Cosmos, XRP, Tron), you recover the compressed public key by signing a deterministic SHA-256 digest of a recovery string with the EVM wallet using `signRawMessage`, then using ecrecover:

```typescript theme={"system"}
import { secp256k1 } from "@noble/curves/secp256k1";
import { sha256 } from "@noble/hashes/sha2";
import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";

async function recoverEvmPublicKey(
  recoveryMessage: string,
  evmWallet: WalletAccount,
  dynamicClient: DynamicClient,
): Promise<Uint8Array> {
  const provider = createWaasProvider({
    sdkClient: dynamicClient,
    chain: "EVM",
  });

  // Hash the recovery message with SHA-256 and sign the raw digest
  const msgHash = sha256(new TextEncoder().encode(recoveryMessage));

  const { signature } = await provider.signRawMessage({
    message: bytesToHex(msgHash),
    walletAccount: evmWallet,
  });

  // Parse signature: r (32 bytes) + s (32 bytes) + v (1 byte)
  const sigBytes = hexToBytes(strip0x(signature));
  const rsHex = bytesToHex(sigBytes.slice(0, 64));
  let v = sigBytes[64];
  if (v >= 27) v -= 27;

  const sig = secp256k1.Signature.fromHex(rsHex).addRecoveryBit(v);
  return sig.recoverPublicKey(msgHash).toBytes(true); // 33-byte compressed
}
```

<Note>
  Tron is a special case — since Tron addresses are derived from the same keccak256 hash as EVM, you can derive a Tron address directly from the EVM address without key recovery. See the [Tron page](/javascript/reference/tier-2-chains/tron) for details.
</Note>

## Common Setup

### Initialize Dynamic Client

```typescript theme={"system"}
import {
  createDynamicClient,
  initializeClient,
} from "@dynamic-labs-sdk/client";
import { addWaasEvmExtension } from "@dynamic-labs-sdk/evm/waas";
import { addWaasSolanaExtension } from "@dynamic-labs-sdk/solana/waas";

const dynamicClient = createDynamicClient({
  environmentId: "YOUR_ENVIRONMENT_ID",
});

addWaasEvmExtension(dynamicClient);
addWaasSolanaExtension(dynamicClient);

await initializeClient(dynamicClient);
```

### Create Embedded Wallets

```typescript theme={"system"}
import { createWaasWalletAccounts, getWalletAccounts } from "@dynamic-labs-sdk/client/waas";

// Create EVM, Solana, and Bitcoin embedded wallets
await createWaasWalletAccounts({ chains: ["EVM", "SOL", "BTC"] });

const accounts = getWalletAccounts();
const evmWallet = accounts.find((w) => w.chain === "EVM");
const solWallet = accounts.find((w) => w.chain === "SOL");
const btcWallet = accounts.find((w) => w.chain === "BTC");
```

### Shared Dependencies

All chain implementations use these utility packages:

<CodeGroup>
  ```bash npm theme={"system"}
  npm install @noble/curves @noble/hashes bs58 bech32
  ```

  ```bash yarn theme={"system"}
  yarn add @noble/curves @noble/hashes bs58 bech32
  ```

  ```bash pnpm theme={"system"}
  pnpm add @noble/curves @noble/hashes bs58 bech32
  ```

  ```bash bun theme={"system"}
  bun add @noble/curves @noble/hashes bs58 bech32
  ```
</CodeGroup>

### Byte Utilities

These helper functions are used across all chain implementations:

```typescript theme={"system"}
function strip0x(hex: string): string {
  return hex.startsWith("0x") ? hex.slice(2) : hex;
}

function bytesToHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

function hexToBytes(hex: string): Uint8Array {
  const clean = strip0x(hex);
  const bytes = new Uint8Array(clean.length / 2);
  for (let i = 0; i < clean.length; i += 2) {
    bytes[i / 2] = parseInt(clean.slice(i, i + 2), 16);
  }
  return bytes;
}

function concatBytes(...arrays: Uint8Array[]): Uint8Array {
  const totalLen = arrays.reduce((s, a) => s + a.length, 0);
  const result = new Uint8Array(totalLen);
  let offset = 0;
  for (const arr of arrays) {
    result.set(arr, offset);
    offset += arr.length;
  }
  return result;
}
```

## Full Example: Aptos (Ed25519)

This walkthrough demonstrates the complete flow using Aptos as an example.

<Steps>
  <Step title="Extract Ed25519 public key from Solana wallet">
    The Solana wallet address is a base58-encoded Ed25519 public key. Decode it to get the raw 32-byte key:

    ```typescript theme={"system"}
    import bs58 from "bs58";

    const solWallet = accounts.find((w) => w.chain === "SOL");
    const pubkey = bs58.decode(solWallet.address); // 32 bytes
    ```
  </Step>

  <Step title="Derive the Aptos address">
    Aptos uses a single-key Ed25519 scheme. The address is SHA3-256 of the public key concatenated with a scheme byte (`0x00`):

    ```typescript theme={"system"}
    import { sha3_256 } from "@noble/hashes/sha3";

    const payload = new Uint8Array(33);
    payload.set(pubkey, 0);
    payload[32] = 0x00; // single-key Ed25519 scheme byte
    const hash = sha3_256(payload);
    const aptosAddress = "0x" + bytesToHex(hash);
    ```
  </Step>

  <Step title="Build and sign a transaction">
    Build an Aptos transaction using the `@aptos-labs/ts-sdk`, generate the signing bytes, and sign with the Solana wallet:

    ```typescript theme={"system"}
    import {
      Aptos, AptosConfig, Network, generateSigningMessageForTransaction,
      AccountAuthenticatorEd25519, Ed25519PublicKey, Ed25519Signature,
    } from "@aptos-labs/ts-sdk";
    import { signMessage } from "@dynamic-labs-sdk/client";

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

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

    const signingBytes = generateSigningMessageForTransaction(transaction);
    const signResult = await signMessage({
      walletAccount: solWallet,
      message: bytesToHex(signingBytes),
    });
    const sigBytes = decodeSig(signResult.signature);
    ```
  </Step>

  <Step title="Submit the transaction">
    Assemble the authenticator and submit via the SDK:

    ```typescript theme={"system"}
    const authenticator = new AccountAuthenticatorEd25519(
      new Ed25519PublicKey(pubkey),
      new Ed25519Signature(bytesToHex(sigBytes)),
    );

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

    console.log("Transaction hash:", hash);
    ```
  </Step>
</Steps>

For the complete Aptos implementation including BCS serialization, message signing, and verification, see the [Aptos page](/javascript/reference/tier-2-chains/aptos).

***

## Ed25519 Chains (from Solana Wallet)

### Aptos

Aptos uses Ed25519 with SHA3-256 hashing for address derivation and BCS (Binary Canonical Serialization) for transaction encoding.

<Expandable title="Derive address">
  ```typescript theme={"system"}
  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 scheme byte
    const hash = sha3_256(payload);
    return "0x" + bytesToHex(hash);
  }
  ```
</Expandable>

<Expandable title="Sign a message">
  ```typescript theme={"system"}
  import { signMessage } from "@dynamic-labs-sdk/client";

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

<Expandable title="Verify a signature">
  ```typescript theme={"system"}
  import { ed25519 } from "@noble/curves/ed25519";

  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);
  }
  ```
</Expandable>

[Full implementation with transfers and BCS serialization →](/javascript/reference/tier-2-chains/aptos)

***

## secp256k1 Chains (from EVM Wallet)

### Cosmos

Cosmos SDK chains use secp256k1 with bech32-encoded addresses. The public key is recovered from the EVM wallet via ecrecover. The same key derives addresses for any Cosmos chain by changing the bech32 prefix (e.g., `cosmos` for Cosmos Hub, `osmo` for Osmosis).

<Expandable title="Derive address">
  ```typescript theme={"system"}
  import { sha256 } from "@noble/hashes/sha2";
  import { ripemd160 } from "@noble/hashes/legacy";
  import { bech32 } from "bech32";

  function addressFromPubkey(compressedPubkey: Uint8Array, prefix: string): string {
    const sha256Hash = sha256(compressedPubkey);
    const hash160 = ripemd160(sha256Hash);
    return bech32.encode(prefix, bech32.toWords(hash160));
  }

  async function deriveCosmosAddress(
    evmWallet: WalletAccount,
    dynamicClient: DynamicClient,
    bech32Prefix: string = "cosmos",
  ): Promise<{ address: string; publicKey: string }> {
    const compressedPubkey = await recoverEvmPublicKey(
      "COSMOS_PUBKEY_RECOVERY",
      evmWallet,
      dynamicClient,
    );
    return {
      address: addressFromPubkey(compressedPubkey, bech32Prefix),
      publicKey: bytesToHex(compressedPubkey),
    };
  }
  ```
</Expandable>

<Expandable title="Sign a message">
  ```typescript theme={"system"}
  import { sha256 } from "@noble/hashes/sha2";
  import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";

  async function signCosmosMessage(
    message: string,
    evmWallet: WalletAccount,
    dynamicClient: DynamicClient,
    displayDenom: string = "ATOM",
  ): Promise<string> {
    const provider = createWaasProvider({
      sdkClient: dynamicClient,
      chain: "EVM",
    });

    const messageBytes = new TextEncoder().encode(message);
    const prefix = new TextEncoder().encode(`\x19${displayDenom} Signed Message:\n`);
    const lengthStr = new TextEncoder().encode(String(messageBytes.length));
    const digest = sha256(concatBytes(prefix, lengthStr, messageBytes));

    const { signature } = await provider.signRawMessage({
      message: bytesToHex(digest),
      walletAccount: evmWallet,
    });

    return signature;
  }
  ```
</Expandable>

<Expandable title="Verify a signature">
  ```typescript theme={"system"}
  import { secp256k1 } from "@noble/curves/secp256k1";
  import { sha256 } from "@noble/hashes/sha2";

  function verifyCosmosSignature(
    message: string,
    signature: string,
    publicKey: string,
    displayDenom: string = "ATOM",
  ): boolean {
    const messageBytes = new TextEncoder().encode(message);
    const prefix = new TextEncoder().encode(`\x19${displayDenom} Signed Message:\n`);
    const lengthStr = new TextEncoder().encode(String(messageBytes.length));
    const digest = sha256(concatBytes(prefix, lengthStr, messageBytes));

    const sigBytes = hexToBytes(strip0x(signature));
    const rsHex = bytesToHex(sigBytes.slice(0, 64));
    let v = sigBytes[64];
    if (v >= 27) v -= 27;

    try {
      const sig = secp256k1.Signature.fromHex(rsHex).addRecoveryBit(v);
      const recovered = sig.recoverPublicKey(digest);
      return recovered.toHex(true) === publicKey.toLowerCase();
    } catch {
      return false;
    }
  }
  ```
</Expandable>

[Full implementation with Protobuf transactions →](/javascript/reference/tier-2-chains/cosmos)

### Tron

Tron uses secp256k1 with keccak256 hashing — the same cryptographic scheme as Ethereum. Since Tron addresses are derived from the same public key hash, you can derive a Tron address directly from the EVM address by swapping the `0x` prefix for `0x41` and base58check-encoding the result.

<Expandable title="Derive address">
  ```typescript theme={"system"}
  import { TronWeb } from "tronweb";

  function deriveTronAddress(evmAddress: string): string {
    const hex = "41" + evmAddress.slice(2).toLowerCase();
    return TronWeb.address.fromHex(hex);
  }
  ```
</Expandable>

<Expandable title="Sign a message">
  ```typescript theme={"system"}
  import { keccak256, toUtf8Bytes, concat } from "tronweb/utils";
  import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";

  async function signTronMessage(
    message: string,
    evmWallet: WalletAccount,
    dynamicClient: DynamicClient,
  ): Promise<string> {
    const provider = createWaasProvider({
      sdkClient: dynamicClient,
      chain: "EVM",
    });

    const messageBytes = toUtf8Bytes(message);
    const prefix = toUtf8Bytes("\x19TRON Signed Message:\n");
    const lengthStr = toUtf8Bytes(String(messageBytes.length));
    const messageDigest = keccak256(concat([prefix, lengthStr, messageBytes]));

    const { signature } = await provider.signRawMessage({
      message: strip0x(messageDigest),
      walletAccount: evmWallet,
    });

    return signature;
  }
  ```
</Expandable>

<Expandable title="Verify a signature">
  ```typescript theme={"system"}
  import { TronWeb } from "tronweb";

  async function verifyTronSignature(
    message: string,
    signature: string,
  ): Promise<string> {
    const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
    return tw.trx.verifyMessageV2(message, signature);
  }
  ```
</Expandable>

[Full implementation with TRX and TRC20 transfers →](/javascript/reference/tier-2-chains/tron)
