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

# Mavryk

> Derive Mavryk addresses and sign transactions using Dynamic embedded wallets in React Native

## Overview

Mavryk (a fork of Tezos) uses the Ed25519 elliptic curve. Addresses are derived with Blake2b-160 hashing and base58check-encoded with the `mv1` prefix. Transactions are forged via RPC and injected directly — no webmavryk dependency required.

This guide covers **Ed25519 (`mv1`) addresses only**. Mavryk also supports Secp256k1 (`mv2`), P-256 (`mv3`), and BLS12-381 (`mv4`); Dynamic’s embedded wallet flow here assumes Ed25519 keys.

| Property       | Value                                        |
| -------------- | -------------------------------------------- |
| Curve          | Ed25519                                      |
| Address Format | base58check (`mv1...`)                       |
| Hashing        | Blake2b-160 (address), Blake2b-256 (signing) |
| Serialization  | Micheline / node-forged binary               |
| Smallest Unit  | Mumav (1 MAV = 10^6 mumav)                   |

## Dependencies

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

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

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

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

## Derive Address

Hash the Ed25519 public key with Blake2b-160 and base58check-encode with the `mv1` prefix bytes `[5, 186, 196]`:

```typescript theme={"system"}
import { blake2b } from "@noble/hashes/blake2b";
import { concatBytes } from "@noble/hashes/utils";
import { sha256 } from "@noble/hashes/sha2";
import bs58 from "bs58";

const PREFIX_MV1 = new Uint8Array([5, 186, 196]);

function mavrykB58cencode(payload: Uint8Array, prefixBytes: Uint8Array): string {
  const combined = concatBytes(prefixBytes, payload);
  const checksum = sha256(sha256(combined)).slice(0, 4);
  return bs58.encode(concatBytes(combined, checksum));
}

function deriveMavrykAddress(walletAddress: string): string {
  const pubkey = bs58.decode(walletAddress);
  const hash = blake2b(pubkey, { dkLen: 20 });
  return mavrykB58cencode(hash, PREFIX_MV1);
}
```

## Sign a Message

Mavryk uses a `0x05` watermark for arbitrary data signing, followed by Blake2b-256:

```typescript theme={"system"}
import { blake2b } from "@noble/hashes/blake2b";
import { concatBytes, bytesToHex } from "@noble/hashes/utils";
import type { BaseClient } from "@dynamic-labs/client";

async function signMavrykMessage(
  message: string,
  dynamicClient: BaseClient,
): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
  const msgBytes = new TextEncoder().encode(message);
  const watermarked = concatBytes(new Uint8Array([0x05]), msgBytes);
  const digest = blake2b(watermarked, { dkLen: 32 });
  const { signedMessage } = await dynamicClient.wallets.signMessage({
    wallet,
    message: bytesToHex(digest),
  });
  return signedMessage;
}
```

<Note>
  Dynamic’s React Native embedded wallet currently exposes Mavryk signing through the **Solana (`SOL`)** chain entry for the embedded key. Use the wallet returned by that lookup until Dynamic exposes a Mavryk-specific chain identifier.
</Note>

## Sign a Transaction

Forge the operation via RPC, apply the `0x03` watermark, hash with Blake2b-256, sign, and inject. Automatically prepends a `reveal` operation if the manager key hasn't been published yet:

```typescript theme={"system"}
import { blake2b } from "@noble/hashes/blake2b";
import { concatBytes, bytesToHex, hexToBytes } from "@noble/hashes/utils";
import bs58 from "bs58";
import type { BaseClient } from "@dynamic-labs/client";

const MAVRYK_RPC = "https://rpc.mavryk.network";
// Ed25519 public key prefix for Mavryk (edpk)
const PREFIX_EDPK = new Uint8Array([13, 15, 37, 217]);

async function sendMavrykTransfer(
  to: string,
  amountMav: number,
  fromAddress: string,
  walletAddress: string,
  dynamicClient: BaseClient,
): Promise<string> {
  const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
  const pubkeyBytes = bs58.decode(walletAddress);
  const encodedPubKey = mavrykB58cencode(pubkeyBytes, PREFIX_EDPK);
  const mumav = Math.round(amountMav * 1_000_000).toString();

  const [blockHead, counterStr, managerKey] = await Promise.all([
    fetch(`${MAVRYK_RPC}/chains/main/blocks/head/header`).then(r => r.json()),
    fetch(`${MAVRYK_RPC}/chains/main/blocks/head/context/contracts/${fromAddress}/counter`).then(r => r.json()),
    fetch(`${MAVRYK_RPC}/chains/main/blocks/head/context/contracts/${fromAddress}/manager_key`).then(r => r.json()).catch(() => null),
  ]);

  const branch = blockHead.hash;
  let counter = parseInt(counterStr) + 1;
  const isRevealed = managerKey !== null && managerKey !== "";
  const contents: object[] = [];

  if (!isRevealed) {
    contents.push({ kind: "reveal", source: fromAddress, fee: "1000",
      counter: counter.toString(), gas_limit: "1000", storage_limit: "0",
      public_key: encodedPubKey });
    counter++;
  }

  contents.push({ kind: "transaction", source: fromAddress, fee: "1000",
    counter: counter.toString(), gas_limit: "10300", storage_limit: "0",
    amount: mumav, destination: to });

  const forgeRes = await fetch(`${MAVRYK_RPC}/chains/main/blocks/head/helpers/forge/operations`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ branch, contents }),
  });
  const forgedHex: string = await forgeRes.json();

  // Mavryk operation signing: blake2b-256( 0x03 || forged_bytes )
  const forgedBytes = hexToBytes(forgedHex);
  const watermarked = concatBytes(new Uint8Array([0x03]), forgedBytes);
  const digest = blake2b(watermarked, { dkLen: 32 });

  const { signedMessage } = await dynamicClient.wallets.signMessage({
    wallet,
    message: bytesToHex(digest),
  });

  // Dynamic returns the signature as base58; normalize to hex for injection
  const sigHex = bytesToHex(bs58.decode(signedMessage));

  const injectRes = await fetch(`${MAVRYK_RPC}/injection/operation`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: `"${forgedHex}${sigHex}"`,
  });
  if (!injectRes.ok) throw new Error(`Injection failed: ${injectRes.status}`);
  return injectRes.json();
}
```

<Warning>
  `fee`, `gas_limit`, and `storage_limit` are **illustrative** values. For production, estimate fees via your node’s RPC (for example `helpers/scripts/operation` helpers) or another estimator — insufficient limits will cause the node to reject the operation.
</Warning>

## Verify a Signature

```typescript theme={"system"}
import { ed25519 } from "@noble/curves/ed25519";
import { blake2b } from "@noble/hashes/blake2b";
import { concatBytes, hexToBytes } from "@noble/hashes/utils";
import bs58 from "bs58";

function verifyMavrykSignature(
  message: string,
  signature: string,
  walletAddress: string,
): boolean {
  const pubkey = bs58.decode(walletAddress);
  const messageBytes = new TextEncoder().encode(message);
  const watermarked = concatBytes(new Uint8Array([0x05]), messageBytes);
  const digest = blake2b(watermarked, { dkLen: 32 });
  // Dynamic returns signatures as base58; decode before verifying
  const sigBytes = bs58.decode(signature);
  return ed25519.verify(sigBytes, digest, pubkey);
}
```

## Check Balance

```typescript theme={"system"}
async function getMavrykBalance(address: string): Promise<string> {
  try {
    const res = await fetch(
      `https://rpc.mavryk.network/chains/main/blocks/head/context/contracts/${address}/balance`,
    );
    if (!res.ok) return "0";
    return (parseInt(await res.json()) / 1_000_000).toString();
  } catch {
    return "0";
  }
}
```
