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