Skip to main content

Overview

Mavryk (formerly Tezos) uses the Ed25519 elliptic curve — the same as Solana. Addresses are derived with Blake2b-160 hashing and encoded using b58cencode from @mavrykdynamics/taquito-utils. Transactions are built and submitted using MavrykToolkit from @mavrykdynamics/taquito.
PropertyValue
CurveEd25519
Root WalletSolana
Address Formatbase58check (mv1...)
HashingBlake2b-160 (address), Blake2b-256 (signing)
SerializationMicheline / node-forged binary
Smallest UnitMutez (1 MAV = 10^6 mutez)

Dependencies

npm install @mavrykdynamics/taquito @mavrykdynamics/taquito-utils @noble/hashes bs58

Derive Address

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

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

Sign a Message

Sign the raw UTF-8 bytes of the message using the Solana wallet:
import { signMessage } from "@dynamic-labs-sdk/client";
import type { WalletAccount } from "@dynamic-labs-sdk/client";

async function signMavrykMessage(
  message: string,
  solWallet: WalletAccount,
): Promise<string> {
  const messageBytes = new TextEncoder().encode(message);
  const result = await signMessage({
    walletAccount: solWallet,
    message: bytesToHex(messageBytes),
  });
  return result.signature;
}

Verify a Signature

Verify the Ed25519 signature against the raw message bytes:
import { ed25519 } from "@noble/curves/ed25519";

function verifyMavrykSignature(
  message: string,
  signature: string,
  solanaAddress: string,
): boolean {
  const pubkey = bs58.decode(solanaAddress);
  const messageBytes = new TextEncoder().encode(message);
  const sigBytes = decodeSig(signature);
  return ed25519.verify(sigBytes, messageBytes, 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 mutezStr = await res.json();
  const mutez = BigInt(mutezStr);
  const divisor = BigInt(1_000_000);
  const whole = mutez / divisor;
  const frac = mutez % 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 Solana wallet. The toolkit handles forging, watermarking, and injection automatically.
import { blake2b } from "@noble/hashes/blake2";
import { createWaasProvider } from "@dynamic-labs-sdk/client/waas/core";
import {
  hex2buf, buf2hex, mergebuf,
  b58cencode, prefix, Prefix,
} from "@mavrykdynamics/taquito-utils";
import { MavrykToolkit } from "@mavrykdynamics/taquito";
import bs58 from "bs58";

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

class DynamicMavrykSigner {
  constructor(
    private pubkeyBytes: Uint8Array,
    private solWallet: WalletAccount,
    private dynamicClient: DynamicClient,
  ) {}

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

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

  async secretKey(): Promise<string> {
    throw new Error("Signing routed through Dynamic.");
  }

  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 = blake2b(new Uint8Array(buf), { dkLen: 32 });

    const provider = createWaasProvider({ sdkClient: this.dynamicClient, chain: "SOL" });
    const { signature } = await provider.signMessage({
      walletAccount: this.solWallet,
      message: bytesToHex(digest),
    });
    const sigBytes = decodeSig(signature);

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

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

  const signer = new DynamicMavrykSigner(pubkeyBytes, solWallet, dynamicClient);
  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.