This guide explains how to implement Zcash memo functionality for Maya Protocol transactions. Zcash uses transparent addresses and OP_RETURN outputs to include transaction memos, enabling cross-chain swaps and liquidity operations.
Overview
Maya Protocol uses memos to identify transaction intent (swap, add liquidity, withdraw). For Zcash, memos are implemented using OP_RETURN outputs with specific constraints:
Maximum memo size: 80 bytes (Zcash OP_RETURN limit)
Address support: Transparent addresses only (t1... format)
A typical Maya Protocol Zcash transaction includes:
Input(s): UTXOs from sender
Payment output: Amount to Maya vault address
Memo output: OP_RETURN with 0 value (memo)
Change output: Remaining funds back to sender (if any)
Fee Calculation (ZIP-317)
Where:
base_fee = 10,000 zatoshis
marginal_fee = 5,000 zatoshis
Memo outputs count toward output_count
Implementation Approaches
Approach 1: Using librustzcash (Rust)
Direct implementation using the core Zcash Rust library with the add_null_data_output function introduced in commit b26c998c.
Complete Rust Example
Cargo.toml:
src/main.rs:
Expected output:
Running the Rust Example
Approach 2: Using @mayaprotocol/zcash-js
For JavaScript/TypeScript developers who want direct control over transaction building, @mayaprotocol/zcash-js provides low-level bindings for Zcash operations.
Installation
Complete TypeScript Project Setup
To make this fully runnable, create the following files:
package.json:
tsconfig.json:
src/index.ts:
Quick Start
Expected Output
Approach 3: Using XChainJS
For developers who prefer a high-level abstraction, XChainJS provides the @xchainjs/xchain-zcash package with a simplified API that handles all the low-level details:
Installation
Complete Project Setup
package.json:
tsconfig.json:
src/index.ts:
Quick Start
Expected Output
Summary
This guide provides three comprehensive approaches for implementing Zcash memo functionality with Maya Protocol:
Approach 1: librustzcash - Direct Rust implementation using the core Zcash library
Approach 2: @mayaprotocol/zcash-js - Low-level TypeScript/JavaScript bindings for direct control
Approach 3: XChainJS - High-level TypeScript/JavaScript interface with simplified API
All examples are designed to be copy-pasteable and runnable, with complete project setup including dependencies, configuration files, and expected outputs. Each approach handles the 80-byte OP_RETURN limit and ZIP-317 fee calculation requirements properly.
For production use, always test on testnet first and ensure proper private key management and error handling.
Integrating Zcash with Maya Protocol requires proper handling of OP_RETURN outputs for memo communication. Whether implementing from scratch, using librustzcash, or leveraging XChainJS, the key requirements remain:
Respect the 80-byte memo limit
Use transparent addresses only
Calculate fees according to ZIP-317
Follow Maya Protocol memo format specifications
Place memo in OP_RETURN output with zero value
Choose the implementation approach that best fits your technology stack and requirements:
Approach 1 (Rust/librustzcash): Best for performance-critical applications, native Zcash integration, or when building Rust applications
Approach 2 (@mayaprotocol/zcash-js): Perfect for TypeScript/JavaScript developers who need direct control over transaction building and signing
Approach 3 (XChainJS): Ideal for web applications, Node.js services, or rapid prototyping with a simplified API
# Create new Rust project
cargo new maya-zcash-integration
cd maya-zcash-integration
# Replace Cargo.toml and src/main.rs with the code above
# Run the example
cargo run
import { buildTx, sendRawTransaction, getFee, memoToScript } from '@mayaprotocol/zcash-js';
import { ECPairFactory } from 'ecpair';
import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs';
import axios from 'axios';
const ECPair = ECPairFactory(ecc);
// Define UTXO interface for type safety
interface UTXO {
txid: string;
outputIndex: number;
satoshis: number;
address: string;
}
// Maya Protocol memo creation functions
function createSwapMemo(
destChain: string,
destAsset: string,
destAddress: string,
limit?: string,
affiliateAddress?: string,
affiliateFee?: number
): string {
let memo = `=:${destChain}.${destAsset}:${destAddress}`;
if (limit) memo += `:${limit}`;
if (affiliateAddress && affiliateFee) {
memo += `:${affiliateAddress}:${affiliateFee}`;
}
return memo;
}
function createAddLiquidityMemo(
asset: string,
address?: string,
affiliateAddress?: string,
affiliateFee?: number
): string {
let memo = `+:${asset}`;
if (address) memo += `:${address}`;
if (affiliateAddress && affiliateFee) {
memo += `:${affiliateAddress}:${affiliateFee}`;
}
return memo;
}
function validateMemoLength(memo: string): void {
if (memo.length > 80) {
throw new Error(`Memo too long: ${memo.length} bytes (max 80)`);
}
}
// Calculate fee with ZIP-317 structure
function calculateFeeWithMemo(
inputCount: number,
outputCount: number,
memoLength: number = 0
): number {
// Base relay fee: 10,000 zatoshis
// Marginal fee: 5,000 zatoshis per input/output
let totalOutputs = outputCount;
if (memoLength > 0) {
// Account for OP_RETURN outputs
const memoOutputs = Math.ceil((memoLength + 2) / 34);
totalOutputs += memoOutputs;
}
return getFee(inputCount, totalOutputs, memoLength > 0 ? 'memo' : undefined);
}
// Build and sign a transaction with @mayaprotocol/zcash-js
async function buildAndSignTransaction(
from: string,
to: string,
amount: number,
utxos: UTXO[],
privateKey: Buffer,
mayaMemo: string,
isMainnet: boolean = false
): Promise<string> {
// Validate memo length
validateMemoLength(mayaMemo);
// Get current block height (would come from API in production)
const height = 2500000; // Example height
// Build unsigned transaction
const unsignedTx = await buildTx(
height,
from,
to,
amount,
utxos,
isMainnet,
mayaMemo
);
console.log(`Transaction built:`);
console.log(` Fee: ${unsignedTx.fee} zatoshis`);
console.log(` Inputs: ${unsignedTx.inputs.length}`);
console.log(` Outputs: ${unsignedTx.outputs.length}`);
// Sign each input with private key
const keyPair = ECPair.fromPrivateKey(privateKey);
const signedInputs = unsignedTx.inputs.map((input: any) => {
const signature = keyPair.sign(Buffer.from(input.sighash, 'hex'));
return {
...input,
signature: signature.toString('hex')
};
});
// Apply signatures to transaction
const signedTx = {
...unsignedTx,
inputs: signedInputs
};
// In production, you would broadcast this:
// const txHash = await sendRawTransaction(signedTx.hex, isMainnet);
// return txHash;
// For this example, return a mock transaction hash
return '0x' + Buffer.from(mayaMemo).toString('hex').substring(0, 64);
}
// Main example function
async function main() {
console.log('=== Maya Protocol Zcash Integration (@mayaprotocol/zcash-js) ===\n');
try {
// Example 1: Create Maya Protocol memos
console.log('--- Example 1: Maya Protocol Memos ---');
const swapMemo = createSwapMemo(
'ETH',
'ETH',
'0xe6a30f4f3bad978910e2cbb4d97581f5b5a0ade0',
'1e18', // 1 ETH in scientific notation
'maya1affiliate',
10
);
console.log(`Swap memo: ${swapMemo}`);
console.log(`Length: ${swapMemo.length} bytes`);
validateMemoLength(swapMemo);
const liquidityMemo = createAddLiquidityMemo(
'BTC.BTC',
'maya1provider',
'maya1affiliate',
5
);
console.log(`\nLiquidity memo: ${liquidityMemo}`);
console.log(`Length: ${liquidityMemo.length} bytes`);
// Example 2: Fee calculation
console.log('\n--- Example 2: Fee Calculation (ZIP-317) ---');
const fees = [
{ inputs: 1, outputs: 1, memo: null, desc: 'Simple transfer' },
{ inputs: 1, outputs: 2, memo: swapMemo, desc: 'Swap with change' },
{ inputs: 3, outputs: 1, memo: liquidityMemo, desc: 'Multi-input liquidity' }
];
for (const { inputs, outputs, memo, desc } of fees) {
const fee = calculateFeeWithMemo(inputs, outputs, memo ? memo.length : 0);
console.log(`${desc}: ${fee} zatoshis (${fee / 1e8} ZEC)`);
}
// Example 3: Build and sign transaction
console.log('\n--- Example 3: Building and Signing Transaction ---');
// Example private key (32 bytes) - NEVER use in production!
const privateKey = Buffer.from('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 'hex');
// Example UTXOs (would come from blockchain API)
const utxos: UTXO[] = [
{
txid: 'abc123def456789012345678901234567890abcdef1234567890abcdef123456',
outputIndex: 0,
satoshis: 100000000, // 1 ZEC
address: 't1YourAddress123456789012345678'
}
];
console.log('Building transaction:');
console.log(` From: ${utxos[0].address}`);
console.log(` To: t1MayaVaultAddress123456789`);
console.log(` Amount: 0.5 ZEC`);
console.log(` Memo: ${swapMemo}`);
// Build and sign (mock - would fail with example data)
try {
const txHash = await buildAndSignTransaction(
utxos[0].address,
't1MayaVaultAddress123456789',
50000000, // 0.5 ZEC
utxos,
privateKey,
swapMemo,
false // testnet
);
console.log(`\nβ Transaction built successfully`);
console.log(` Mock TX hash: ${txHash}`);
} catch (error) {
console.log(`\nβ Transaction failed (expected with mock data)`);
console.log(` Error: ${(error as Error).message}`);
}
console.log('\n=== Example Complete ===');
console.log('\nKey takeaways:');
console.log('β’ Use buildTx() to create unsigned transactions');
console.log('β’ Sign with ECPair from ecpair library');
console.log('β’ Memos go in OP_RETURN outputs (max 80 bytes)');
console.log('β’ Follow ZIP-317 fee structure');
console.log('\nNext steps for production:');
console.log('1. Connect to Zcash node for real UTXOs');
console.log('2. Use sendRawTransaction() to submit signed transactions');
console.log('3. Handle errors and retry logic');
console.log('4. Test thoroughly on testnet first');
} catch (error) {
console.error('Example failed:', error);
}
}
// Run the example if this file is executed directly
if (require.main === module) {
main().catch(console.error);
}
# Create project directory
mkdir maya-zcash-js-integration
cd maya-zcash-js-integration
# Initialize npm project
npm init -y
# Install dependencies
npm install @mayaprotocol/zcash-js ecpair @bitcoin-js/tiny-secp256k1-asmjs axios
npm install -D @types/node typescript ts-node
# Create the TypeScript config and source file from above
# Then run the example
npm run dev
=== Maya Protocol Zcash Integration (@mayaprotocol/zcash-js) ===
--- Example 1: Maya Protocol Memos ---
Swap memo: =:ETH.ETH:0xe6a30f4f3bad978910e2cbb4d97581f5b5a0ade0:1e18:maya1affiliate:10
Length: 73 bytes
Liquidity memo: +:BTC.BTC:maya1provider:maya1affiliate:5
Length: 42 bytes
--- Example 2: Fee Calculation (ZIP-317) ---
Simple transfer: 20000 zatoshis (0.0002 ZEC)
Swap with change: 35000 zatoshis (0.00035 ZEC)
Multi-input liquidity: 35000 zatoshis (0.00035 ZEC)
--- Example 3: Building and Signing Transaction ---
Building transaction:
From: t1YourAddress123456789012345678
To: t1MayaVaultAddress123456789
Amount: 0.5 ZEC
Memo: =:ETH.ETH:0xe6a30f4f3bad978910e2cbb4d97581f5b5a0ade0:1e18:maya1affiliate:10
Transaction built:
Fee: 35000 zatoshis
Inputs: 1
Outputs: 3
β Transaction built successfully
Mock TX hash: 0x3d3a4554482e4554483a3078653661336130663466336261643937383931
=== Example Complete ===
2. Switch to Network.Mainnet for mainnet usage
3. Get Maya Protocol vault addresses from Midgard API
4. Ensure sufficient ZEC balance for transactions
5. Test on testnet first before using mainnet
# Create project directory
mkdir maya-xchain-zcash
cd maya-xchain-zcash
# Initialize npm project
npm init -y
# Install dependencies
npm install @xchainjs/xchain-zcash @xchainjs/xchain-client @xchainjs/xchain-util
npm install -D @types/node typescript ts-node
# Create the TypeScript config and source file from above
# Then run the example
npm run dev
=== Maya Protocol Zcash Integration (XChainJS) ===
Address: t1VkJLnCvNJGjhH4QeF4VTaobpMcQF7YVKb
--- Example 1: Maya Protocol Memos ---
Swap memo: =:ETH.ETH:0xe6a30f4f3bad978910e2cbb4d97581f5b5a0ade0:1e18:maya1affiliate:10
Length: 73 bytes
--- Example 2: Fee Calculation ---
Average fee: 35000 satoshis
Fast fee: 40000 satoshis
Fastest fee: 45000 satoshis
--- Example 3: Prepare Transaction ---
Transaction prepared:
Inputs: 1
Fee: 35000 satoshis
Ready to sign and broadcast
=== Example Complete ===
Key advantages of XChainJS:
β’ Simple, high-level API
β’ Automatic UTXO selection
β’ Built-in fee calculation
β’ Handles signing internally
β’ Explorer integration