CIP-20 — Transaction messages
CIP20 (Cardano Improvement Proposal 20) defines a standard for adding messages, comments, or memos to Cardano transactions using transaction metadata. This allows you to attach informational text, invoice numbers, or similar data to transactions on the Cardano blockchain. The library’s MessageMetadata wraps the CIP20 layout (label 674 with msg array) so you only worry about adding strings and serializing the CBOR when needed.
Key Features
Section titled “Key Features”- Standard Compliance: Full CIP20 specification support
- Message Metadata: Add messages/comments to transactions
- Validation: Built-in validation for message length (max 64 bytes)
- Multiple Messages: Support for multiple messages in a single transaction
- Easy Integration: Simple API for adding metadata to transactions
Dependencies
Section titled “Dependencies”- Group ID: com.bloxbean.cardano
- Artifact ID: cardano-client-cip20
- Dependencies: metadata
Usage Examples
Section titled “Usage Examples”Creating Message Metadata
Section titled “Creating Message Metadata”Build the MessageMetadata object and serialize to CBOR when you need raw bytes.
The following example shows how to create CIP20 compliant message metadata with multiple messages.
// Create message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("Payment for services") .add("Invoice #12345") .add("Thank you for your business");
// Get metadata as CBOR bytesbyte[] cborBytes = messageMetadata.serialize();String hexMetadata = HexUtil.encodeHexString(cborBytes);Adding Messages to Transactions
Section titled “Adding Messages to Transactions”Attach the metadata directly in QuickTx so it is included in the auxiliary data of the transaction.
The following example shows how to add message metadata to a transaction using QuickTx.
// Create message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("Payment for NFT purchase") .add("Transaction ID: " + System.currentTimeMillis());
// Create transaction with message metadataTx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(senderAddress) .attachMetadata(messageMetadata);
// Build and sign transactionResult<String> result = quickTxBuilder.compose(tx) .feePayer(senderAddress) .withSigner(SignerProviders.signerFrom(senderAccount)) .completeAndWait(System.out::println);Working with Message Lists
Section titled “Working with Message Lists”Retrieve the stored messages to display or process them.
The following example shows how to retrieve and work with messages from metadata.
// Create message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("First message") .add("Second message") .add("Third message");
// Get all messagesList<String> messages = messageMetadata.getMessages();
// Process messagesfor (String message : messages) { System.out.println("Message: " + message);}
// Check message countSystem.out.println("Total messages: " + messages.size());Integration with Other Metadata
Section titled “Integration with Other Metadata”Merge CIP20 messages with other metadata maps if you need to send multiple labels in one transaction.
The following example shows how to combine CIP20 message metadata with other transaction metadata.
// Create CIP20 message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("Payment confirmation") .add("Order #789");
// Create additional custom metadataCBORMetadata customMetadata = new CBORMetadata() .put(BigInteger.valueOf(100), "Custom field value") .put(BigInteger.valueOf(101), "Another custom value");
// Combine metadata (CIP20 uses label 674)CBORMetadata combinedMetadata = (CBORMetadata) customMetadata.merge(messageMetadata);
// Use in transactionTx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(5)) .from(senderAddress) .attachMetadata(combinedMetadata);Practical Use Cases
Section titled “Practical Use Cases”Invoice Tracking
Section titled “Invoice Tracking”// Create invoice metadataMessageMetadata invoiceMetadata = MessageMetadata.create() .add("Invoice #INV-2024-001") .add("Due: 2024-01-15") .add("Amount: 100 ADA");
Tx paymentTx = new Tx() .payToAddress(merchantAddress, Amount.ada(100)) .from(customerAddress) .attachMetadata(invoiceMetadata);Payment Confirmations
Section titled “Payment Confirmations”// Create payment confirmation metadataMessageMetadata confirmationMetadata = MessageMetadata.create() .add("Payment confirmed") .add("Ref: " + generatePaymentReference()) .add("Thank you!");
Tx confirmationTx = new Tx() .payToAddress(recipientAddress, Amount.ada(50)) .from(payerAddress) .attachMetadata(confirmationMetadata);Service Requests
Section titled “Service Requests”// Create service request metadataMessageMetadata serviceMetadata = MessageMetadata.create() .add("Service: Web Development") .add("Client: Acme Corp") .add("Priority: High");
Tx serviceTx = new Tx() .payToAddress(developerAddress, Amount.ada(25)) .from(clientAddress) .attachMetadata(serviceMetadata);API Reference
Section titled “API Reference”MessageMetadata Class
Section titled “MessageMetadata Class”The main class for creating and managing CIP20 message metadata.
Constructor
Section titled “Constructor”// Create new instancepublic static MessageMetadata create()Methods
Section titled “Methods”add(String message)
Section titled “add(String message)”Adds a message to the metadata. Each message must be:
- Non-null
- Maximum 64 bytes in UTF-8 encoding
public MessageMetadata add(String message)Parameters:
message- The message to add (max 64 UTF-8 bytes)
Returns: The MessageMetadata instance for chaining
Throws: IllegalArgumentException if message is null or exceeds 64 bytes
getMessages()
Section titled “getMessages()”Retrieves all messages from the metadata.
public List<String> getMessages()Returns: List of all messages in the metadata
serialize()
Section titled “serialize()”Serializes the metadata to CBOR bytes.
public byte[] serialize()Returns: CBOR-encoded bytes of the metadata
CIP20 Specification Details
Section titled “CIP20 Specification Details”Metadata Label
Section titled “Metadata Label”CIP20 uses metadata label 674 for message metadata.
Message Format
Section titled “Message Format”Messages are stored as an array of strings under the “msg” key:
{ "674": { "msg": [ "First message", "Second message", "Third message" ] }}Constraints
Section titled “Constraints”- Each message must be at most 64 bytes when encoded in UTF-8
- Messages cannot be null
- Multiple messages are supported in a single transaction
Best Practices
Section titled “Best Practices”- Keep messages concise - The 64-byte limit encourages brief, informative messages
- Use meaningful content - Include relevant information like invoice numbers, references, or confirmations
- Validate input - Always handle validation errors when adding messages
- Consider privacy - Remember that metadata is publicly visible on the blockchain
- Combine with other standards - CIP20 works well with CIP25 (NFT metadata) and other standards
Integration Examples
Section titled “Integration Examples”With CIP25 (NFT Metadata)
Section titled “With CIP25 (NFT Metadata)”// Create CIP25 NFT metadataNFT nft = NFT.create() .assetName("MyNFT") .name("My NFT") .image("ipfs://...") .description("A sample NFT");NFTMetadata nftMetadata = NFTMetadata.create().addNFT(policyId, nft);
// Create CIP20 message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("NFT purchase") .add("Collection: Art Gallery");
// Combine metadataCBORMetadata combinedMetadata = (CBORMetadata) nftMetadata.merge(messageMetadata);With Custom Metadata
Section titled “With Custom Metadata”// Create custom metadataCBORMetadata customMetadata = new CBORMetadata() .put(BigInteger.valueOf(100), "Custom field");
// Create CIP20 message metadataMessageMetadata messageMetadata = MessageMetadata.create() .add("Transaction note");
// Combine and use in transactionCBORMetadata finalMetadata = (CBORMetadata) customMetadata.merge(messageMetadata);For more information about CIP20, refer to the official CIP20 specification.