> ## 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.

# Cosmos

> Derive Cosmos SDK chain addresses and sign transactions using Dynamic embedded wallets

## Overview

Cosmos SDK chains use the secp256k1 elliptic curve — the same as Ethereum. You recover the compressed public key from the EVM wallet, then derive bech32-encoded addresses. The same key works across all Cosmos chains by changing the bech32 prefix (e.g., `cosmos` for Cosmos Hub, `osmo` for Osmosis). Transactions use Amino JSON for signing and Protobuf for broadcasting.

| Property       | Value                                    |
| -------------- | ---------------------------------------- |
| Curve          | secp256k1                                |
| Root Wallet    | EVM                                      |
| Address Format | bech32 (prefix varies by chain)          |
| Hashing        | SHA-256 + RIPEMD-160                     |
| Serialization  | Amino JSON (sign) / Protobuf (broadcast) |
| Smallest Unit  | Varies (e.g., uatom for Cosmos Hub)      |

## Dependencies

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

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

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

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

## Derive Address

Recover the compressed secp256k1 public key from the EVM wallet, then compute RIPEMD-160(SHA-256(pubkey)) and bech32-encode:

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

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

### Multi-Chain Derivation

Derive addresses for multiple Cosmos chains from a single key recovery:

```typescript theme={"system"}
interface CosmosChainConfig {
  id: string;
  bech32Prefix: string;
  denom: string;
  displayDenom: string;
  decimals: number;
  gasLimit: string;
  feeAmount: string;
  lcdProxy: string;
}

const COSMOS_HUB: CosmosChainConfig = {
  id: "cosmos",
  bech32Prefix: "cosmos",
  denom: "uatom",
  displayDenom: "ATOM",
  decimals: 6,
  gasLimit: "200000",
  feeAmount: "5000",
  lcdProxy: "/api/cosmos-rpc?chain=cosmos",
};

const OSMOSIS: CosmosChainConfig = {
  id: "osmosis",
  bech32Prefix: "osmo",
  denom: "uosmo",
  displayDenom: "OSMO",
  decimals: 6,
  gasLimit: "250000",
  feeAmount: "7500",
  lcdProxy: "/api/cosmos-rpc?chain=osmosis",
};

async function deriveCosmosAddresses(
  evmWallet: WalletAccount,
  dynamicClient: DynamicClient,
  configs: CosmosChainConfig[],
): Promise<Record<string, { address: string; publicKey: string }>> {
  const compressedPubkey = await recoverEvmPublicKey(
    "COSMOS_PUBKEY_RECOVERY", evmWallet, dynamicClient,
  );
  const publicKeyHex = bytesToHex(compressedPubkey);
  const result: Record<string, { address: string; publicKey: string }> = {};
  for (const config of configs) {
    result[config.id] = {
      address: addressFromPubkey(compressedPubkey, config.bech32Prefix),
      publicKey: publicKeyHex,
    };
  }
  return result;
}
```

## Sign a Message

Sign using a chain-specific message prefix via the EVM WaaS provider:

```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;
}
```

## Verify a Signature

Recover the public key from the signature and compare it to the stored key:

```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;
  }
}
```

## Check Balance

Query the balance via the Cosmos LCD REST API:

```typescript theme={"system"}
async function getCosmosBalance(
  address: string,
  config: CosmosChainConfig,
): Promise<string> {
  const res = await fetch(
    `${config.lcdProxy}/cosmos/bank/v1beta1/balances/${address}`,
  );
  if (!res.ok) return "0";

  const data = await res.json();
  const coin = (data.balances || []).find(
    (b: { denom: string }) => b.denom === config.denom,
  );
  if (!coin) return "0";
  return (Number(BigInt(coin.amount)) / 10 ** config.decimals).toString();
}
```

## Send a Transfer

Build an Amino JSON sign document, sign the SHA-256 hash, normalize to low-S, then encode the transaction in Protobuf for broadcasting.

### Low-S Normalization

Cosmos SDK requires canonical low-S signatures:

```typescript theme={"system"}
const SECP256K1_N = BigInt(
  "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
);
const SECP256K1_HALF_N = SECP256K1_N / BigInt(2);

function normalizeLowS(s: bigint): bigint {
  return s > SECP256K1_HALF_N ? SECP256K1_N - s : s;
}
```

### Protobuf Encoding Helpers

```typescript theme={"system"}
function pbVarint(n: number | bigint): Uint8Array {
  const num = typeof n === "bigint" ? Number(n) : n;
  const bytes: number[] = [];
  let val = num >>> 0;
  while (val > 0x7f) {
    bytes.push((val & 0x7f) | 0x80);
    val >>>= 7;
  }
  bytes.push(val);
  return new Uint8Array(bytes);
}

function pbLengthDelimited(fieldNumber: number, data: Uint8Array): Uint8Array {
  const tag = pbVarint((fieldNumber << 3) | 2);
  const len = pbVarint(data.length);
  return concatBytes(tag, len, data);
}

function pbString(fieldNumber: number, s: string): Uint8Array {
  return pbLengthDelimited(fieldNumber, new TextEncoder().encode(s));
}

function pbVarintField(fieldNumber: number, value: number | bigint): Uint8Array {
  const tag = pbVarint((fieldNumber << 3) | 0);
  return concatBytes(tag, pbVarint(value));
}
```

### Full Transfer Implementation

```typescript theme={"system"}
async function sendCosmosTransfer(
  to: string,
  amount: number,
  fromAddress: string,
  publicKey: string,
  evmWallet: WalletAccount,
  dynamicClient: DynamicClient,
  config: CosmosChainConfig,
): Promise<string> {
  const provider = createWaasProvider({
    sdkClient: dynamicClient,
    chain: "EVM",
  });

  const microAmount = BigInt(Math.round(amount * 10 ** config.decimals)).toString();

  // Fetch account info and chain ID
  const [accountData, nodeData] = await Promise.all([
    cosmosLcd(config, `/cosmos/auth/v1beta1/accounts/${fromAddress}`),
    cosmosLcd(config, `/cosmos/base/tendermint/v1beta1/node_info`),
  ]);

  const account = accountData.account || {};
  const accountNumber = account.account_number || "0";
  const sequence = account.sequence || "0";
  const chainId = nodeData.default_node_info?.network || config.id;

  // Build Amino JSON sign doc (keys must be sorted)
  const signDoc = {
    account_number: accountNumber,
    chain_id: chainId,
    fee: {
      amount: [{ amount: config.feeAmount, denom: config.denom }],
      gas: config.gasLimit,
    },
    memo: "",
    msgs: [{
      type: "cosmos-sdk/MsgSend",
      value: {
        amount: [{ amount: microAmount, denom: config.denom }],
        from_address: fromAddress,
        to_address: to,
      },
    }],
    sequence: sequence,
  };

  // Sort keys and hash
  const signDocBytes = new TextEncoder().encode(sortedJsonStringify(signDoc));
  const digest = sha256(signDocBytes);

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

  // Normalize to low-S
  const sigRaw = hexToBytes(strip0x(signature));
  const rHex = bytesToHex(sigRaw.slice(0, 32));
  const sNormalized = normalizeLowS(BigInt("0x" + bytesToHex(sigRaw.slice(32, 64))));
  const sHex = sNormalized.toString(16).padStart(64, "0");
  const compactSig = hexToBytes(rHex + sHex);

  // Encode as Protobuf TxRaw and broadcast
  const txRaw = buildProtobufTxRaw(
    fromAddress, to, config, microAmount, publicKey, compactSig, sequence,
  );
  const txBase64 = btoa(String.fromCharCode(...txRaw));

  const result = await cosmosLcd(config, "/cosmos/tx/v1beta1/txs", "POST", {
    tx_bytes: txBase64,
    mode: "BROADCAST_MODE_SYNC",
  });

  return result.tx_response?.txhash || "";
}
```

<Note>
  The Amino JSON sign document requires keys to be sorted recursively. Use a canonical JSON serializer that sorts keys at every level.
</Note>
