Custom Messages
Extend the message format for advanced verification use cases
The default Range message format is {timestamp}_{pubkey}. You can extend this format to include additional verified data that’s checked on-chain.
Default Format
Section titled “Default Format”{timestamp}_{pubkey}Example: 1704067200_7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
Extended Format
Section titled “Extended Format”Add additional fields separated by underscores:
{timestamp}_{pubkey}_{field1}_{field2}_{...}Examples:
1704067200_7xKXt..._1000- Amount limit1704067200_7xKXt..._transfer_vault1- Action and target1704067200_7xKXt..._kyc_2- KYC level
Use Cases
Section titled “Use Cases”Amount Limits
Section titled “Amount Limits”Authorize transfers up to a specific amount:
// Backendconst maxAmount = 1000;const message = `${timestamp}_${pubkey}_${maxAmount}`;const signature = sign(message, rangeSignerKeypair);// On-chainfn verify_with_amount( ctx: Context<VerifyWithAmount>, signature: [u8; 64], message: Vec<u8>, requested_amount: u64,) -> Result<()> { // Standard Range verification first verify_range_signature(&ctx, &signature, &message)?;
// Parse extended message let message_str = String::from_utf8(message) .map_err(|_| ErrorCode::InvalidMessage)?;
let parts: Vec<&str> = message_str.split('_').collect(); require!(parts.len() >= 3, ErrorCode::InvalidMessageFormat);
// Extract and validate amount let max_amount: u64 = parts[2].parse() .map_err(|_| ErrorCode::InvalidAmount)?;
require!( requested_amount <= max_amount, ErrorCode::AmountExceedsLimit );
// Proceed with transfer Ok(())}Action Authorization
Section titled “Action Authorization”Authorize specific actions:
// Backend - only sign for allowed actionsconst allowedActions = ['deposit', 'withdraw', 'stake'];
if (!allowedActions.includes(requestedAction)) { return res.status(403).json({ error: 'Action not allowed' });}
const message = `${timestamp}_${pubkey}_${requestedAction}`;// On-chain#[derive(PartialEq)]enum Action { Deposit, Withdraw, Stake,}
fn verify_action( ctx: Context<VerifyAction>, signature: [u8; 64], message: Vec<u8>, action: Action,) -> Result<()> { verify_range_signature(&ctx, &signature, &message)?;
let message_str = String::from_utf8(message)?; let parts: Vec<&str> = message_str.split('_').collect();
let signed_action = match parts.get(2) { Some(&"deposit") => Action::Deposit, Some(&"withdraw") => Action::Withdraw, Some(&"stake") => Action::Stake, _ => return Err(ErrorCode::InvalidAction.into()), };
require!(signed_action == action, ErrorCode::ActionMismatch);
Ok(())}KYC Levels
Section titled “KYC Levels”Different verification levels for different access:
// Backend - determine KYC level from compliance checkconst kycLevel = await getKYCLevel(pubkey); // 0, 1, 2, 3
const message = `${timestamp}_${pubkey}_kyc_${kycLevel}`;// On-chainfn verify_kyc_level( ctx: Context<VerifyKYC>, signature: [u8; 64], message: Vec<u8>, required_level: u8,) -> Result<()> { verify_range_signature(&ctx, &signature, &message)?;
let message_str = String::from_utf8(message)?; let parts: Vec<&str> = message_str.split('_').collect();
// Expect: timestamp_pubkey_kyc_level require!(parts.len() >= 4, ErrorCode::InvalidMessageFormat); require!(parts[2] == "kyc", ErrorCode::NotKYCMessage);
let user_level: u8 = parts[3].parse() .map_err(|_| ErrorCode::InvalidKYCLevel)?;
require!( user_level >= required_level, ErrorCode::InsufficientKYCLevel );
Ok(())}Multi-Parameter Messages
Section titled “Multi-Parameter Messages”Combine multiple fields:
// Backendconst message = `${timestamp}_${pubkey}_${action}_${amount}_${target}`;// Example: 1704067200_7xKXt..._transfer_1000_vault1// On-chain#[derive(Debug)]struct ParsedMessage { timestamp: i64, pubkey: Pubkey, action: String, amount: u64, target: String,}
fn parse_extended_message(message: &[u8]) -> Result<ParsedMessage> { let message_str = String::from_utf8(message.to_vec()) .map_err(|_| ErrorCode::InvalidMessage)?;
let parts: Vec<&str> = message_str.split('_').collect(); require!(parts.len() == 5, ErrorCode::InvalidMessageFormat);
Ok(ParsedMessage { timestamp: parts[0].parse().map_err(|_| ErrorCode::InvalidTimestamp)?, pubkey: Pubkey::from_str(parts[1]).map_err(|_| ErrorCode::InvalidPubkey)?, action: parts[2].to_string(), amount: parts[3].parse().map_err(|_| ErrorCode::InvalidAmount)?, target: parts[4].to_string(), })}Implementation Pattern
Section titled “Implementation Pattern”1. Define Your Format
Section titled “1. Define Your Format”interface ExtendedMessage { timestamp: number; pubkey: string; action: 'transfer' | 'stake' | 'unstake'; amount?: number; target?: string;}
function encodeMessage(msg: ExtendedMessage): string { const parts = [msg.timestamp, msg.pubkey, msg.action]; if (msg.amount !== undefined) parts.push(msg.amount.toString()); if (msg.target !== undefined) parts.push(msg.target); return parts.join('_');}2. Backend Signing
Section titled “2. Backend Signing”app.post('/api/verify-extended', async (req, res) => { const { pubkey, action, amount, target } = req.body;
// Validate action is allowed if (!isActionAllowed(pubkey, action, amount)) { return res.status(403).json({ error: 'Action not permitted' }); }
const message = encodeMessage({ timestamp: Math.floor(Date.now() / 1000), pubkey, action, amount, target, });
const signature = sign(message, rangeSignerKeypair);
res.json({ signature, message });});3. Client Usage
Section titled “3. Client Usage”async function verifyExtendedAction( action: string, amount: number, target: string) { // Get signed message from backend const { signature, message } = await fetch('/api/verify-extended', { method: 'POST', body: JSON.stringify({ pubkey, action, amount, target }), }).then(r => r.json());
// Build and send transaction const instruction = await buildVerifyExtendedInstruction({ signer: publicKey, admin: settingsAdmin, signature: Buffer.from(signature, 'base64'), message: Buffer.from(message), action, amount, target, });
await sendTransaction(new Transaction().add(instruction));}Security Considerations
Section titled “Security Considerations”Alternative Delimiters
Section titled “Alternative Delimiters”If your values might contain underscores:
// Use | as delimiterconst message = `${timestamp}|${pubkey}|${action}|${amount}`;let parts: Vec<&str> = message_str.split('|').collect();JSON Alternative
Section titled “JSON Alternative”For complex structured data:
const payload = JSON.stringify({ timestamp, pubkey, action, params: { amount, target, metadata: {...} },});const message = payload;See Also
Section titled “See Also”- verify_range - Base verification instruction
- Rate Limiting - Action-specific rate limits
- Security - Security best practices