Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Mavryk (a fork of Tezos) uses the Ed25519 elliptic curve. Addresses are derived with Blake2b-160 hashing and encoded using b58cencode from @mavrykdynamics/webmavryk-utils. Transactions are built and submitted using MavrykToolkit from @mavrykdynamics/webmavryk. 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.
PropertyValue
CurveEd25519
Address Formatbase58check (mv1...)
HashingBlake2b-160 (address), Blake2b-256 (signing)
SerializationMicheline / node-forged binary
Smallest UnitMumav (1 MAV = 10^6 mumav)

Dependencies

npm install @mavrykdynamics/webmavryk @mavrykdynamics/webmavryk-utils @stablelib/blake2b bs58 @noble/curves

Derive Address

Hash the Ed25519 public key with Blake2b-160 and encode with b58cencode using the MV1 prefix:
import { hash } from "@stablelib/blake2b";
import { b58cencode, prefix, Prefix } from "@mavrykdynamics/webmavryk-utils";
import bs58 from "bs58";

function deriveMavrykAddress(walletAddress: string): string {
  const pubkey = bs58.decode(walletAddress);
  const h = hash(pubkey, 20);
  return b58cencode(h, prefix[Prefix.MV1]);
}

Sign a Message

Mavryk uses a 0x05 watermark for arbitrary data signing, followed by Blake2b-256 — the same scheme as the React Native example below:
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";
import { mergebuf, buf2hex } from "@mavrykdynamics/webmavryk-utils";
import { hash } from "@stablelib/blake2b";

async function signMavrykMessage(
  message: string,
  wallet: WalletAccount,
): Promise<string> {
  const messageBytes = new TextEncoder().encode(message);
  const watermarked = mergebuf(new Uint8Array([0x05]), messageBytes);
  const digest = hash(watermarked, 32);
  const result = await signMessage({
    walletAccount: wallet,
    message: buf2hex(digest),
  });
  return result.signature;
}

Verify a Signature

Verify the Ed25519 signature against the same Blake2b-256 digest produced when signing (watermark 0x05 + message):
import { ed25519 } from "@noble/curves/ed25519";
import { mergebuf } from "@mavrykdynamics/webmavryk-utils";
import { hash } from "@stablelib/blake2b";
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 = mergebuf(new Uint8Array([0x05]), messageBytes);
  const digest = hash(watermarked, 32);
  const sigBytes = bs58.decode(signature);
  return ed25519.verify(sigBytes, digest, pubkey);
}

Check Balance

Query the MAV balance via the Mavryk node RPC:
async function getMavrykBalance(address: string): Promise<string> {
  const res = await fetch(
    `https://rpc.mavryk.network/chains/main/blocks/head/context/contracts/${address}/balance`,
  );

  if (!res.ok) return "0";

  const mumavStr = await res.json();
  const mumav = BigInt(mumavStr);
  const divisor = BigInt(1_000_000);
  const whole = mumav / divisor;
  const frac = mumav % divisor;
  const fracStr = frac.toString().padStart(6, "0").replace(/0+$/, "");
  return fracStr.length === 0 ? whole.toString() : `${whole}.${fracStr}`;
}

Send a Transfer

Use MavrykToolkit with a custom signer that routes signing through Dynamic’s signMessage. The toolkit handles forging, watermarking, and injection automatically.
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";
import { hash } from "@stablelib/blake2b";
import {
  hex2buf, buf2hex, mergebuf,
  b58cencode, prefix, Prefix,
} from "@mavrykdynamics/webmavryk-utils";
import { MavrykToolkit } from "@mavrykdynamics/webmavryk";
import bs58 from "bs58";

const MAVRYK_RPC = "https://rpc.mavryk.network";

class DynamicMavrykSigner {
  constructor(
    private pubkeyBytes: Uint8Array,
    private wallet: WalletAccount,
  ) {}

  async publicKey() {
    return b58cencode(this.pubkeyBytes, prefix[Prefix.EDPK]);
  }

  async publicKeyHash() {
    return b58cencode(hash(this.pubkeyBytes, 20), prefix[Prefix.MV1]);
  }

  async secretKey(): Promise<string | undefined> {
    return undefined;
  }

  async sign(bytes: string, watermark?: Uint8Array) {
    let buf = hex2buf(bytes);
    if (watermark) buf = mergebuf(watermark, buf);

    // Mavryk requires Blake2b-256 pre-hash before signing
    const digest = hash(buf, 32);

    const { signature } = await signMessage({
      walletAccount: this.wallet,
      message: buf2hex(digest),
    });
    // Dynamic returns the signature as base58; decode before encoding
    const sigBytes = bs58.decode(signature);

    return {
      bytes,
      sig: b58cencode(sigBytes, prefix[Prefix.SIG]),
      prefixSig: b58cencode(sigBytes, prefix[Prefix.EDSIG]),
      sbytes: bytes + buf2hex(sigBytes),
    };
  }
}

async function sendMavrykTransfer(
  to: string,
  amount: number,
  wallet: WalletAccount,
): Promise<string> {
  const pubkeyBytes = bs58.decode(wallet.address);

  const signer = new DynamicMavrykSigner(pubkeyBytes, wallet);
  const toolkit = new MavrykToolkit(MAVRYK_RPC);
  toolkit.setSignerProvider(signer);

  const op = await toolkit.contract.transfer({ to, amount });
  await op.confirmation(1);
  return op.hash;
}
MavrykToolkit handles operation forging, watermarking (0x03), and injection. The custom signer applies Blake2b-256 pre-hashing before passing the digest to Dynamic’s signMessage.