Example 1: Atomic Swap on Plasma Next
In this example, we consider a scenario where Alice, who holds ETH on Plasma Next, conducts an Atomic Swap with Bob, who holds DAI on the same platform. The flow of ETH and DAI follows the diagram below.

Here, we discuss the ZKPTLC for transfers from Alice to the operator. The ZKPTLC for transfers from the operator to Bob is exactly the same. The ZKPTLC for transfers from Alice to the operator is conditioned on two airdrops: one is an airdrop of ETH from the operator to Bob, and the other is an airdrop of DAI from the operator to Alice. Illustrated, this would look as follows: compared to Plasma Next's Default ZKPTLC, there is one additional condition related to the airdrops.

The instance
of the ZKPTLC must be bound to the transfer from Operator to Bob and the transfer from Operator to Alice. To generalize, let's call them transfer1
and transfer2
, respectively. The instance should be the hash values of these two transfers.
function computeInstance(
ITransfer.Transfer memory transfer1,
ITransfer.Transfer memory transfer2
) external pure returns (bytes32) {
bytes32 tc1 = transfer1.transferCommitment();
bytes32 tc2 = transfer2.transferCommitment();
return keccak256(abi.encodePacked(tc1, tc2));
}
We use the same _verifyExistence function as the default ZKPTLC. This internal function verifies that the given transfer exists in Plasma Next.
function _verifyExistence(
ITransfer.Transfer memory transfer,
IMerkleProof.EvidenceWithMerkleProof memory proof
) internal view {
if (transfer.transferCommitment() != proof.leaf.transferCommitment) {
revert("Transfer commitment does not match");
}
IRootManager(rootManagerAddress).verifyEvidenceMerkleProof(proof);
}
Let's implement VerifyCondition
. The witness consists of transfer1
, transfer2
, and their corresponding settlement proofs proof1
, proof2
. Decode the witness
from bytes, ensure it matches the instance
, and then pass it through the _verifyExistence
function we implemented earlier.
struct Witness {
ITransfer.Transfer transfer1;
ITransfer.Transfer transfer2;
IMerkleProof.EvidenceWithMerkleProof proof1;
IMerkleProof.EvidenceWithMerkleProof proof2;
}
function verifyCondition(
bytes32 instance,
bytes memory witness
) external view {
Witness memory w = abi.decode(witness, (Witness));
bytes32 expectedInstance = computeInstance(w.transfer1, w.transfer2);
if (instance != expectedInstance) {
revert("Invalid instance");
}
_verifyExistence(w.transfer1, w.proof1);
_verifyExistence(w.transfer2, w.proof2);
}
The entire contract is as follows.
Last updated