# Programability

{% hint style="info" %}
Programmability support in the Plasma Next Mainnet is currently under development. The contents of this document cannot be tested on the Mainnet at present. Please wait until the support is fully implemented.
{% endhint %}

In this section, we will delve into the programmability of Plasma Next, specifically focusing on ZKPTLC. For the sake of explanation, let's refer to the sender as Alice and the recipient as Bob. Transfers in Plasma Next are conducted through a payment channel from Alice to the operator and then via an airdrop (bulk transfer) from the operator to Bob.&#x20;

<figure><img src="https://4075258850-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKMOibM8eBKNlzr0QLksv%2Fuploads%2FvtDmri5EnpotARI12Tee%2Fimage.png?alt=media&#x26;token=8b121ee1-e30b-4e55-8127-554beb2d7069" alt=""><figcaption></figcaption></figure>

In the payment channel from Alice to the operator, both parties sign a structure called `Payment`, defined as follows:

```solidity
struct Payment {
    bytes32 uniqueIdentifier;
    address user;
    uint32 round;
    uint32 nonce;
    Assets userBalance;
    Assets operatorBalance;
    Assets airdropped; 
    Assets spentDeposit; 
    uint64 latestEbn; 
    address zkptlcAddress;
    bytes32 zkptlcInstance;
}
```

`zkptlcAddress` specifies the address of a ZKPTLC. `zkptlcInstance` is a fixed value inputted into the ZKPTLC. `zkptlcAddress` and `zkptlcInstance` become necessary only in the event of a dispute between Alice and the operator. If the transaction proceeds without any disputes, the `zkptlcAddress` and `zkptlcInstance` are either cleared or replaced with a different `zkptlcAddress` and `zkptlcInstance` in the next payment.

The ZKPTLC must have the following interface.

```solidity
function verifyCondition(
    bytes32 instance,
    bytes memory witness
) external view;
```

Here, the `instance` is input directly as `zkptlcInstance`. witness is additional data required to verify that the state of the ZKPTLC has been satisfied. While the `instance` is a fixed value, `witness` is variable.

## Default ZKPTLC

Here, we introduce the most basic ZKPTLC that is used by default in Plasma Next. This ZKPTLC verifies the existence of an airdrop from the operator to Bob (the receiver) and only allows the settlement of the payment if the verification is successful. The procedure for Alice to send funds X to Bob is as follows:

1. Alice creates a transfer of funds X from the operator to Bob.&#x20;
2. Alice calculates the hash value of the transfer, specifies it in the ZKPTLC instance, prepares a `Payment` to the operator including X + δ (δ is the transaction fee), signs it, and hands it over to the operator.&#x20;
3. Once the operator agrees to Alice's `Payment`, the operator actually airdrops the specified transfer to Bob and return the Payment with the operator's signature to Alice. The operator also provides Alice with the Merkle Proof of the transfer as evidence of the airdrop to Bob.&#x20;
4. Alice verifies the Merkle Proof and, if convinced that the transfer to Bob was successful, does nothing further. For the next transfer, she will specify a different ZKPTLC instance.&#x20;
5. In case of a dispute about whether the operator actually performed the airdrop to Bob, the operator is obligated to execute the ZKPTLC with evidence of the airdrop to Bob and prove that the verification passes. If the operator cannot pass the ZKPTLC verification within a specified period, the Payment will be settled in favor of Alice's claim.

<figure><img src="https://4075258850-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKMOibM8eBKNlzr0QLksv%2Fuploads%2FNtKsTAEVOe7hZ0dNNrpS%2Fimage.png?alt=media&#x26;token=723650e9-e491-4ece-be55-fd99f206e371" alt=""><figcaption></figcaption></figure>

Here is the complete code for DefaultZKPTLC.

<details>

<summary>The complete code of Default ZKPTLC</summary>

```solidity
/// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {IZKPTLC} from "../common-interface/IZKPTLC.sol";
import {ITransfer} from "../common-interface/ITransfer.sol";
import {TransferLib} from "../utils/TransferLib.sol";
import {IRootManager} from "../root-manager/IRootManager.sol";
import {ITransfer} from "../common-interface/ITransfer.sol";
import {IMerkleProof} from "../common-interface/IMerkleProof.sol";
import {TransferLib} from "../utils/TransferLib.sol";

contract DefaultZKPTLC is IZKPTLC {
    using TransferLib for ITransfer.Transfer;

    address public rootManagerAddress;

    constructor(address _rootManagerAddress) {
        rootManagerAddress = _rootManagerAddress;
    }

    function computeInstance(
        ITransfer.Transfer memory transfer
    ) external pure returns (bytes32) {
        return transfer.transferCommitment();
    }

    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);
    }

    struct Witness {
        ITransfer.Transfer transfer;
        IMerkleProof.EvidenceWithMerkleProof proof;
    }

    function encodeWitness(
        Witness memory witness
    ) external pure returns (bytes memory) {
        return abi.encode(witness);
    }

    function verifyCondition(
        bytes32 instance,
        bytes memory witness
    ) external view {
        Witness memory w = abi.decode(witness, (Witness));
        if (instance != w.transfer.transferCommitment()) {
            revert("Invalid instance");
        }
        _verifyExistence(w.transfer, w.proof);
    }
}
```

</details>

Here, we compute an instance of ZKPTLC. This time, since the actual transfer from the operator to Bob is a condition for the ZKPTLC verification, we adopt the hash value of the transfer as the instance.

```solidity
function computeInstance(
    ITransfer.Transfer memory transfer
) external pure returns (bytes32) {
    return transfer.transferCommitment();
}
```

Here, we verify that the transfer from the operator to Bob has been made. The ZKPs that the specified transfer is included in Plasma Next Blocks is batched in a Merkle Tree called the evidence tree. The ZKP is aggregated through recursive ZKP, and the RootManager verifies the ZKP along with the root of the evidence tree. Therefore, if the Merkle path of the evidence tree is provided, it is possible to prove that the transfer is included in a Plasma Next Block by querying the RootManager.

```solidity
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);
}
```

Here, we specify the contents of the witness as a struct. The witness consists of the transfer from the operator to Bob and the Merkle proof of the evidence tree.

```solidity
struct Witness {
    ITransfer.Transfer transfer;
    IMerkleProof.EvidenceWithMerkleProof proof;
}
```

This is the implementation of `verifyCondition`, which is the interface of ZKPTLC. The `witness` is decoded as the `Witness` structure mentioned above, and after confirming that the `transfer` within the `witness` matches the `instance`, it is passed to the `_verifyExistence` function.

```solidity
function verifyCondition(
    bytes32 instance,
    bytes memory witness
) external view {
    Witness memory w = abi.decode(witness, (Witness));
    if (instance != w.transfer.transferCommitment()) {
        revert("Invalid instance");
    }
    _verifyExistence(w.transfer, w.proof);
}
```
