Build custom content types for use with XMTP
Building a custom content type enables you to manage data in a way that is more personalized or specialized to the needs of your app.
For more common content types, you can usually find a standard or standards-track content type to serve your needs.
If your custom content type generates interest within the developer community, consider proposing it as a standard content type through the XIP process.
This tutorial covers how to build two example custom content types:
- A basic example of a custom content type that multiplies two numbers 
- An advanced example of a custom content type that sends transaction hashes on the Polygon blockchain. This example also describes how to use the custom content type to render the transaction hash. 
Basic: Multiply numbers using a custom content type
Build a custom content type to multiply numbers.
- Create the custom content type by creating a new file
- JavaScript
- React Native
import { ContentTypeId } from "@xmtp/xmtp-js";
// Create a unique identifier for your content type
const ContentTypeMultiplyNumbers = new ContentTypeId({
  authorityId: "your.domain",
  typeId: "multiply-number",
  versionMajor: 1,
  versionMinor: 0,
});
// Define the MultiplyCodec class
class ContentTypeMultiplyNumberCodec {
  get contentType() {
    return ContentTypeMultiplyNumbers;
  }
  // The encode method accepts an object with two numbers (a, b) and encodes it as a byte array
  encode({ a, b }) {
    return {
      type: ContentTypeMultiplyNumbers,
      parameters: {},
      content: new TextEncoder().encode(JSON.stringify({ a, b })),
    };
  }
  // The decode method decodes the byte array, parses the string into numbers (a, b), and returns their product
  decode(content: { content: any }) {
    const uint8Array = content.content;
    const { a, b } = JSON.parse(new TextDecoder().decode(uint8Array));
    return a * b;
  }
}
const ContentTypeNumber: ContentTypeId = {
  authorityId: 'yourdomain.org',
  typeId: 'number',
  versionMajor: 1,
  versionMinor: 0,
}
class NumberCodec implements JSContentCodec<number> {
  contentType = ContentTypeNumber
  // a completely absurd way of encoding number values
  encode(content: number): EncodedContent {
    return {
      type: ContentTypeNumber,
      parameters: {
        number: JSON.stringify(content),
      },
      content: new Uint8Array(),
    }
  }
  decode(encodedContent: EncodedContent): number {
    return JSON.parse(encodedContent.parameters.number) as number
  }
  fallback(content: number): string | undefined {
    return 'A number was sent.'
  }
}
- Import and register the custom content type.
- JavaScript
- React Native
import { ContentTypeMultiplyNumberCodec } from "./xmtp-content-type-multiply-number";
const xmtp = await Client.create(signer, {
  env: "dev",
});
xmtp.registerCodec(new ContentTypeMultiplyNumberCodec());
import { NumberCodec } from "./xmtp-content-type-number";
const client = await Client.create({
  env: "production",
  codecs: [new NumberCodec()],
});
- Send a message using the custom content type. This code sample demonstrates how to use the MultiplyCodeccustom content type to perform multiplication operations.
- JavaScript
- React Native
const numbersToMultiply = { a: 3, b: 7 };
conversation.send(numbersToMultiply, {
  contentType: ContentTypeMultiplyNumbers,
});
await conversation.send(12, { contentType: ContentTypeNumber });
- To use the result of the multiplication operation, add a renderer for the custom content type.
- JavaScript
- React Native
if (message.contentType.sameAs(ContentTypeMultiplyNumber)) {
  return message.content; // 21
}
Because of this message content is now a function which returns the actual content. You can get that content by call message.content() now instead of message.content . This may involve more filtering on the message side to make sure you are handling different contentTypes appropriately.
if (message.contentTypeId === "yourdomain.org/number:1.0") {
  return message.content(); // 12
}
Advanced: Send token transaction hashes
Build a custom content type to send transaction hashes on the Polygon blockchain.
- Create the custom content type by creating a new file, xmtp-content-type-transaction-hash.tsx. This file hosts theTransactionHashclass for encoding and decoding the custom content type.
- JavaScript
import { ContentTypeId } from "@xmtp/xmtp-js";
export const ContentTypeTransactionHash = new ContentTypeId({
  authorityId: "your.domain",
  typeId: "transaction-hash",
  versionMajor: 1,
  versionMinor: 0,
});
export class ContentTypeTransactionHashCodec {
  get contentType() {
    return ContentTypeTransactionHash;
  }
  encode(hash) {
    return {
      type: ContentTypeTransactionHash,
      parameters: {},
      content: new TextEncoder().encode(hash),
    };
  }
  decode(content: { content: any }) {
    const uint8Array = content.content;
    const hash = new TextDecoder().decode(uint8Array);
    return hash;
  }
}
- Import and register the custom content type.
- JavaScript
import {
  ContentTypeTransactionHash,
  ContentTypeTransactionHashCodec,
} from "./xmtp-content-type-transaction-hash";
const xmtp = await Client.create(signer, {
  env: "dev",
});
xmtp.registerCodec(new ContentTypeTransactionHashCodec());
- Send a message using the custom content type. This code sample demonstrates how to use the TransactionHashcontent type to send a transaction.
- JavaScript
// Create a wallet from a known private key
const wallet = new ethers.Wallet(privateKey);
console.log(`Wallet address: ${wallet.address}`);
//im using a burner wallet with MATIC from a faucet
//https://faucet.polygon.technology/
// Set up provider for Polygon Testnet (Mumbai)
const provider = new ethers.providers.JsonRpcProvider(
  "https://rpc-mumbai.maticvigil.com",
);
// Connect the wallet to the provider
const signer = wallet.connect(provider);
// Define the recipient address and amount
const amount = ethers.utils.parseEther("0.01"); // Amount in ETH (0.01 in this case)
// Create a transaction
const transaction = {
  to: recipientAddress,
  value: amount,
};
// Sign and send the transaction
const tx = await signer.sendTransaction(transaction);
console.log(`Transaction hash: ${tx.hash}`);
const conversation = await xmtp.conversations.newConversation(WALLET_TO);
await conversation
  .send(tx.hash, {
    contentType: ContentTypeTransactionHash,
  })
  .then(() => {
    console.log("Transaction data sent", tx.hash);
  })
  .catch((error) => {
    console.log("Error sending transaction data: ", error);
  });
- To use the result of the hash, add an async renderer for the custom content type.
- JavaScript
if (message.contentType.sameAs(ContentTypeTransactionHash)) {
  // Handle ContentTypeAttachment
  return (
    <TransactionMonitor key={message.id} encodedContent={message.content} />
  );
}
const TransactionMonitor = ({ encodedContent }) => {
  const [retryCount, setRetryCount] = useState(0);
  const [transactionValue, setTransactionValue] = useState(null);
  useEffect(() => {
    const fetchTransactionReceipt = async () => {
      console.log(encodedContent);
      const provider = new ethers.providers.JsonRpcProvider(
        "https://rpc-mumbai.maticvigil.com",
      );
      const receipt = await provider.getTransactionReceipt(encodedContent);
      const tx = await provider.getTransaction(encodedContent);
      if (tx && tx.value) {
        setTransactionValue(ethers.utils.formatEther(tx.value));
      }
    };
    fetchTransactionReceipt();
  }, [encodedContent, retryCount]);
  return transactionValue ? (
    <div>Transaction value: {transactionValue} ETH</div>
  ) : (
    <div>
      Waiting for transaction to be mined...
      <button onClick={() => setRetryCount(retryCount + 1)}>
        Refresh Status 🔄
      </button>
    </div>
  );
};