For the complete documentation index, see llms.txt. This page is also available as Markdown.

Tutorial: custom privacy logic with PoD

This tutorial describes the second PoD integration model: a custom COTI-side contract paired with a host-chain (for example Avalanche Fuji) contract. Use this path when primitive-only PodLib / MpcLib operations are not enough for your business logic.

For the conceptual split between the two models and a comparison diagram, start at Tutorials: building Privacy on Avalanche (PoD) dApps.

Why two contracts?

The host EVM chain is excellent for accounts, assets, and user-facing flows, but your Fuji (or mainnet) contract cannot host the full garbled-circuit lifecycle: it is not the right place to keep intermediate encrypted MPC state (gt*) and arbitrarily compose it the way the COTI execution environment does.

So you partition the design:

  • Fuji side — Collect encrypted user inputs (it*), pay Inbox fees, and issue remote calls to COTI. In the success path, receive encrypted outputs (ct*) in an onlyInbox callback and store or emit them for clients. Think of this layer as a thin, client-facing orchestrator.

  • COTI side — Implement the actual private logic: receive gt* / it* payloads from the Inbox, run MpcCore operations, optionally off-board ciphertext to a specific user (offBoardToUser), and inbox.respond with an ABI-encoded blob the Fuji callback decodes.

The example below mirrors an encrypted messaging flow: the message is handled privately on COTI so the recipient-bound ciphertext is produced in a controlled way; Avalanche Fuji only routes the call and persists ctString for the UI.

Security habits on the host chain

Any onlyInbox callback that mutates state must verify the caller is the Inbox and that the source COTI contract and chain match your deployment. Otherwise a malicious remote peer could try to impersonate your COTI logic.

Always use something equivalent to:

(uint256 callerChain, address callerContract) = IInbox(inbox).inboxMsgSender();
require(callerChain == EXPECTED_COTI_CHAIN_ID && callerContract == myCotiPeer, "unauthorized peer");

Match EXPECTED_COTI_CHAIN_ID and myCotiPeer to the values you configured at deploy time (the SDK presets expose constants such as COTI_TESTNET_CHAIN_ID on PodUserFuji).

COTI-side contract: DirectMessageCotiSide

The COTI contract inherits InboxUser, so only the Inbox can enter receiveMessage. It:

  1. Accepts the garbled string and routing metadata (sender, recipient).

  2. Uses MpcCore.offBoardToUser to convert the payload into ctString ciphertext that only recipient can decrypt.

  3. Returns that ciphertext to the host chain via inbox.respond, ABI-encoding sender, recipient, and the ciphertext so the Fuji callback can update storage deterministically.

Paths like ../InboxUser.sol assume you follow the SDK’s example layout; adjust imports to your repo.

Avalanche Fuji-side contract: orchestration only

The Fuji contract inherits PodUserFuji (or your network’s PodUser preset), tracks the COTI peer address, and:

  1. sendMessage — Wraps encrypted input (itString) and public addresses in an IInbox.MpcMethodCall built with MpcAbiCodec (see the SDK’s Request builder and remote calls). It sends a two-way message so the result comes back asynchronously.

  2. onMessageReceived — Decodes the tuple produced on COTI, re-checks inboxMsgSender(), and stores ctString keyed by conversation participants (or whatever your product needs).

The Solidity below is structurally correct; wire MpcAbiCodec’s create / addArgument / build steps exactly as in your installed @coti-io/coti-contracts version (argument order and gt/it interface types must match the COTI method signature).

TypeScript and deployment

This chapter stops at Solidity to highlight the chain split. For encryption, Inbox fee estimation, and client-side decryption of ctString, continue with:

After encryptAndCallMethod("sendMessage", args, feeCfg) (or a raw ethers.Contract send), use await pod.extractRequestIds(receipt.hash) on the same PodContract instance so your UI stores the requestId emitted in the Inbox MessageSent logs—same helper as in the adder walkthrough.

Summary

Piece
Responsibility

DirectMessageCotiSide

Private transformation (gtString → recipient-bound ctString) and inbox.respond.

DirectMessageEvm

Fee-bearing Inbox send, peer verification, and storing ctString for the app.

Client

Encrypt inputs, submit transactions, decrypt outputs using the account key flow from the SDK.

When your feature fits this shape—heavy private logic on COTI, Avalanche Fuji as router and ciphertext cache—you are in the custom PoD dApp model described on the tutorials overview.

Tutorial: private Adder on Avalanche Fuji

Tutorial: custom privacy logic with PoD

Last updated

Was this helpful?