# Custom Smart Contracts

Users can deploy their own zero-knowledge contracts, written for the
DarkFi [zkVM][1], becoming anonymous engineers themselves!

More information about the smart contracts architecture can be found
[here][2].

## Hello World

For the purposes of this guide we are going to use the smart contract
template found [here][3]. It's a very simple smart contract simulating
a registration ledger, where users can create their membership keys and
register or remove themselves from it. Each user is represented as the
[poseidon hash][4] of their membership public key.

First, open another terminal, clone the template repository and enter
its directory:

```shell
$ git clone https://codeberg.org/darkrenaissance/smart-contract
$ cd smart-contract
```

We will use the provided Makefile to generate the contract WASM bincode
and its client. The Makefile uses [zkas][5], DarkFi's zero-knowledge
proof circuit compiler, to do this.

Running `make` will compile the ZK proof circuits that protect the
contract's privacy, build the WASM module that runs on the DarkFi
network, and build a command-line client for interacting with the
contract.

We first need to ensure the Makefile is configured so that it can find
the `zkas` binary. That might mean you need to modify the path shown
below.

```makefile
# Path to the zkas compiler (or just `zkas` if it's in your $PATH)
ZKAS = ../darkfi/zkas
```

```shell
$ make

../darkfi/zkas proof/membership_proof.zk -o proof/membership_proof.zk.bin
Wrote output to proof/membership_proof.zk.bin
cargo build --target=wasm32-unknown-unknown --release --lib
...
    Compiling membership v0.0.1 (/home/anon/smart-contract)
    Finished `release` profile [optimized] target(s) in 14.45s
cp -f target/wasm32-unknown-unknown/release/membership.wasm membership.wasm
cargo build --release --features=client --bin membership
...
Compiling membership v0.0.1 (/home/anon/smart-contract)
    Finished `release` profile [optimized] target(s) in 30.78s
cp -f target/release/membership membership
```

Now both the contract and its client are ready to use. Leave this
terminal open, as we will come back to it later in the guide, and
return back to your `drk` interactive shell.

## Creating contracts

Each contract is controlled by a secret key, from which its Contract
ID derives. To deploy a smart contract, we first need to generate an
authority keypair. The Contract ID shown in the outputs is a
placeholder for the one that will be generated from you. In the rest of
the guide, use the one you generated by replacing the corresponding
placeholder. We can create our own contract authority by executing the
following command:

```shell
drk> contract generate-deploy

Generating a new keypair
Created new contract deploy authority
Contract ID: {CONTRACT_ID}
```

You can list your mint authorities with:

```shell
drk> contract list

 Contract ID   | Secret Key            | Locked | Lock Height
---------------+-----------------------+--------+-------------
 {CONTRACT_ID} | {CONTRACT_SECRET_KEY} | false  | -
```

## Deploy transaction

Now that we have a contract authority, we can deploy the example
contract we compiled earlier using it:

```shell
drk> contract deploy {CONTRACT_ID} ../smart-contract/membership.wasm | broadcast

[mark_tx_spend] Processing transaction: d0824bb0ecb9b12af69579c01c570c0275e399b80ef10f0a9c645af65bdd0415
[mark_tx_spend] Found Money contract in call 1
Broadcasting transaction...
Transaction ID: d0824bb0ecb9b12af69579c01c570c0275e399b80ef10f0a9c645af65bdd0415
```

Now the transaction should be published to the network. When the
transaction is confirmed, the contract history will show its record:

```shell
drk> contract list {CONTRACT_ID}

 Transaction Hash | Type       | Block Height
------------------+------------+--------------
 {TX_HASH}        | DEPLOYMENT | 34
```

We can redeploy the contract as many times as we want, as long as it's
not locked. Each redeployment will show a new record in the contract
history. We can also export the deployed data by executing:

```shell
drk> contract export-data {TX_HASH} > membership.dat
```

The exported file contains the WASM bincode and instruction data
deployed by that transaction, encoded in `base64` as a tuple.

## Lock transaction

After we finished deploying our contract and don't require further code
changes, we can lock it. This will not allow further deployment
transactions, effectively locking the smart contract on-chain code. To
lock down the contract, execute:

```shell
drk> contract lock {CONTRACT_ID} | broadcast

[mark_tx_spend] Processing transaction: 9eee9799d77d0ef1dd115738982296c9c481b4412c75a0a0955fd67d87bfe6a0
[mark_tx_spend] Found Money contract in call 1
Broadcasting transaction...
Transaction ID: 9eee9799d77d0ef1dd115738982296c9c481b4412c75a0a0955fd67d87bfe6a0
```

After the transaction has been confirmed, we will see our contract
`Locked` status set to `true`, along with the block height it was
locked on:

```shell
drk> contract list

 Contract ID   | Secret Key            | Locked | Lock Height
---------------+-----------------------+--------+-------------
 {CONTRACT_ID} | {CONTRACT_SECRET_KEY} | true   | 36
```

We will also see the lock transaction in its history:

```shell
drk> contract list {CONTRACT_ID}

 Transaction Hash | Type       | Block Height
------------------+------------+--------------
 {TX_HASH}        | DEPLOYMENT | 34
 {LOCK_TX_HASH}   | LOCK       | 36
```

## Interacting with the smart contract

Now that the contract code is set on-chain and cannot be modified
further, let's interact with it using its client!

### Registration

Let's go to the contract client terminal, and create our membership
keys:

```shell
$ ./membership generate
Secret key: {IDENTITY_SECRET_KEY}
Public key: {IDENTITY_PUBLIC_KEY}
```

> Note: This is a very basic example client so secret keys are used
> as plaintext for simplicity. Do not run this in a machine with
> commands history or in a hostile environment where your secret key
> can be exposed.

Now we can create our `register` call using our membership secret
key:

```shell
$ ./membership register {CONTRACT_ID} {IDENTITY_SECRET_KEY} > register.call
```

Now we need to go back to our `drk` interactive shell, to generate the
actual registration transaction, attach a fee to it and broadcast it to
the network:

```shell
drk> tx-from-calls < ../smart-contract/register.call | broadcast

[mark_tx_spend] Processing transaction: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840
[mark_tx_spend] Found Money contract in call 1
Broadcasting transaction...
Transaction ID: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840
```

After the transaction has been confirmed, our membership commitment
will exist in our contract registry.

### Deregistration

To remove ourselves from the registry, we create a `deregister` call
with the contract client:

```shell
$ ./membership deregister {CONTRACT_ID} {IDENTITY_SECRET_KEY} > deregister.call
```

Then, we build the actual deregistration transaction again, attach its
fee and broadcast it to the network:

```shell
drk> tx-from-calls < ../smart-contract/deregister.call | broadcast

[mark_tx_spend] Processing transaction: f3304e6f5673d9ece211af6dd85c70ec8c8e85e91439b8cffbcf5387b11de1d0
[mark_tx_spend] Found Money contract in call 1
Broadcasting transaction...
Transaction ID: f3304e6f5673d9ece211af6dd85c70ec8c8e85e91439b8cffbcf5387b11de1d0
```

When the transaction gets confirmed, our membership commitment will
not exist in our contract registry.

### Extending the smart contract client

The template client is barebones and doesn't provide us with a way to
view the on chain records of our registry. For that purpose we can
create a new small program, or extend the client to support this
functionality. Following you will find example code for retrieving
a smart contract's on-chain records from the [JSON-RPC][6] server in
`darkfid` which we can use to list our registry records:

{{#tabs }}
{{#tab name="Rust" }}
```rust
// Parse Contract ID
let contract_id = match ContractId::from_str(&args.contract_id)?;

// Initialize an rpc client
let rpc_client = RpcClient::new(args.endpoint, executor.clone()).await?;

// Create the request params
let params = JsonValue::Array(vec![
    JsonValue::String(contract_id.to_string()),
    JsonValue::String(String::from("smart-contract-members")),
]);

// Execute the request
let req = JsonRequest::new("blockchain.get_contract_state", params);
let rep = rpc_client.request(req).await?;

// Parse response
let bytes = base64::decode(rep.get::<String>().unwrap()).unwrap();
let members: BTreeMap<Vec<u8>, Vec<u8>> = deserialize(&bytes)?;

// Print records
println!("{contract_id} members:");
if members.is_empty() {
    println!("No members found");
} else {
    let mut index = 1;
    for member in members.keys() {
        let member: pallas::Base = deserialize(member)?;
        println!("{index}. {member:?}");
        index += 1;
    }
}
```
{{#endtab }}
{{#tab name="Python" }}
```python
import json
import socket

payload = json.dumps({
    "jsonrpc": "2.0",
    "method": "blockchain.get_contract_state",
    "params": [contract_id, "smart-contract-members"],
    "id": 1,
})

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((host, port))
    sock.sendall((payload + "\n").encode("utf-8"))

    response = b""
    while True:
        chunk = sock.recv(1)
        if not chunk or chunk == b"\n":
            break
        response += chunk

    print(json.loads(response.decode("utf-8")))
```
{{#endtab }}
{{#endtabs }}

[1]: ../zkas/index.md
[2]: ../arch/sc/sc.md
[3]: https://codeberg.org/darkrenaissance/smart-contract
[4]: ../spec/crypto-schemes.md#poseidonhash-function
[5]: ../zkas/zkas.md
[6]: ../clients/darkfid_jsonrpc.md
