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 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
npm install @noble/hashes bs58 @noble/curves
Derive Address
Hash the Ed25519 public key with Blake2b-160 and base58check-encode with the mv1 prefix bytes [5, 186, 196]:
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:
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;
}
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.
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:
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();
}
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.
Verify a Signature
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
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";
}
}