By Gauthier Sebille

Previously, we have posted two blog posts about DAC. First one to announce that we, Marigold and TriliTech, have successfully deployed the Community DAC on Tezos Ghostnet.

The second one is a tutorial to give you all the details to configure and deploy your own DAC for your Smart Rollups!

In this blog post, we are going to integrate the DAC inside Tzwitter. Without the DAC, Tzwitter was limited to text tzweets. With the DAC, we can now post large text tzweet (>4KB) and images!

Please note that this is not a Smart Rollup tutorial, or a front web app tutorial. Hence, we are going to focus only on the useful functions to push data to Ghostnet Community DAC and reveal them on Smart Rollup side.


  1. Introducing DAC,
  2. Ghostnet Community DAC is alive,
  3. Originating a Smart Rollup,
  4. Deploy your own DAC to run your DAC Observer.

Improving Tzwitter

As explained in the introduction, we are going to enable Tzwitter user to post images.

  1. Post an image (> 4KB) to Ghostnet Community DAC using Tzwitter front web-app,
  2. Send received DAC Certificate from front to Smart Rollup,
  3. Process received DAC Certificate on Smart Rollup and reveal corresponding data.

DAC Certificate

As you have read in our previous blog posts, a DAC message is composed of three fields, this is a DAC Certificate from the Kernel-SDK point of view:

pub enum Certificate {

/// A Dac V0 certificate.
#[derive(Debug, HasEncoding, NomReader, BinWriter)]
pub struct V0Certificate {
    /// The preimage hash of the root [`V0HashPage`].
    /// [`V0HashPage`]: crate::dac::pages::V0HashPage
    pub root_hash: PreimageHash,
    /// Aggregated signature of the DAC committee.
    pub aggregated_signature: BlsSignature,
    /// Data_encoding.Bit_set.t is actually a Z.t
    pub witnesses: Zarith,

If you have followed our previous blog posts, you know there is a fourth field version. This one is not present in the previous struct which is sent to Smart Rollups because it is captured by the Certificate enum. This previous struct is defined inside the Kernel-SDK

fn get_dac_committee() -> Result> {
    // Assume that we have 3 DAC committee Members PK stored as raw bytes of CompressedPK.
    // In the future, we probably want to have a bigger DAC committee.

    // Current PK of member-1 of Community DAC. Public key hash is "tz4EXupF2QRtHyrHW7Cy7y1jtW48NhgcZvXP"
    let pk_0 = PublicKeyBls::from_base58_check(
    .map_err(|_err| Error::FromBase58CheckError)?;

    //Current PK of member-2 of Community DAC. Public key hash is "tz4RnqbpM3PiFGWgAAr2xZGAQKeRM5nqYppq"
    let pk_1 = PublicKeyBls::from_base58_check(
    .map_err(|_err| Error::FromBase58CheckError)?;

    //Current PK of TT member of Community DAC. Public key hash is "tz4CdFt1ktHZz3mfLtmt8e1YwbSvioL7aQEs"
    let pk_2 = PublicKeyBls::from_base58_check(
    .map_err(|_err| Error::FromBase58CheckError)?;

    Ok(vec![pk_0, pk_1, pk_2])

Order of this list is important, it must be the same that the configuration of DAC Coordinator for the Community DAC.

I provided them in the correct order.

You can copy/paste the provided public keys, they are the actual ones of the TriliTech and Marigold DAC Members who sign the data.

You will have to use the Certificate.verify function to validate the received DAC Certificate against the public keys you set before.

In our case, we do:

let committee_members_pks = get_dac_committee()?;

// We require at least 2/3 members to have signed the certificate
let min_required_dac_sigs = 2;

certificate.verify(&committee_members_pks, min_required_dac_sigs)

Reveal data

On our Smart Rollup, we have:

  • received the DAC Certificate,
  • validate its signature against the known public keys.

You guess the next and last step: reveal the corresponding data! This will be done in two different steps.

First one: reveal_to_store The kernel SDK exposes the reveal_to_store function that we are going to call right after verifying the DAC Certificate:

// We require at least 2/3 members to have signed the certificate
let min_required_dac_sigs = 2;

certificate.verify(&committee_members_pks, min_required_dac_sigs)
let path: RefPath = RefPath::assert_from(b"/reveal/image");
    .reveal_to_store(host, &path)

Now, we need to call the store_read_all function to finally get the bytes of the provided DAC Certificate!

// We require at least 2/3 members to have signed the certificate
let min_required_dac_sigs = 2;

certificate.verify(&committee_members_pks, min_required_dac_sigs)
let path: RefPath = RefPath::assert_from(b"/reveal/image");
    .reveal_to_store(host, &path)
let bytes = host.store_read_all(&path).map_err(Error::from)?;
let tweet = Tweet {
    author: post_image.author,
    content: TweetContent::Image(bytes),
    likes: 0,

Your Smart Rollup is now using DAC! Let’s move to the other part needed by the Smart Rollup: your DAC Observer.

Using verify, reveal_to_store and store_read_all, and also processing, all that in a single run will push the tick limit near the upper limit of 10MB images in DAC messages (as explained in the documentation). We recommend you to split up the DAC messages processing into several kernel runs for safety.

Configure and run your DAC Observer

As explained previously, for your Smart Rollup to have access to the data corresponding to the DAC Certificates, you must run your own DAC Observer, sharing the reveal data directory with the Smart Rollup.

# Configure your DAC Observer
# ${REVEAL_DATA_DIR} is the shared folder between DAC Observer and your Smart Rollup.
$ octez-dac-node \
configure as observer with coordinator \
https://dac-ghost-coordinator.gcp.marigold.dev \
and committee member rpc addresses \
https://dac-ghost-member1.gcp.marigold.dev \
https://dac-ghost-member2.gcp.marigold.dev \ \
--data-dir ${OBSERVER_DIR} --reveal-data-dir ${REVEAL_DATA_DIR}


                   DAC is in an experimental release phase.

           We encourage the community to develop rollups using DAC in
           Testnets and lower environments. Mainnet is NOT recommended.

DAC node configuration written in ${OBSERVER_DIR}/config.json

Then now we can run it

# Launch your DAC Observer
$ octez-dac-node -d ${OCTEZ_CLIENT_DIR} -E ${GHOSTNET_RPC} run --data-dir ${OBSERVER_DIR}

Oct 11 09:47:58.426: Starting the DAC node
Oct 11 09:47:58.430: The DAC node is running in mode Observer
Oct 11 09:47:58.449: DAC Node RPC server is started
Oct 11 09:47:58.449: The DAC node is listening to
Oct 11 09:47:58.449: Started tracking layer 1's node
Oct 11 09:47:58.723: Resolved plugin for protocol PtNairobiyss
Oct 11 09:47:58.723: The DAC node is ready
Oct 11 09:47:58.723: Subscribed to root hashes stream
Oct 11 09:47:58.723: Started tracking layer 1's node
Oct 11 09:47:58.861: Head of layer 1's node updated to
Oct 11 09:47:58.861:   BLrGxHNDrDq1bCNNThgxhLrTQxY9renAmfijvwCJDCvEyYVzHw5 at level 4158498

Your DAC Observer is ready and receiving all the data pushed to Ghostnet Community DAC Coordinator, which means that your Smart Rollup is able to reveal the corresponding data!

Push data from front web app

Last step, on which one I will not spend to much time because you already had seen it with the previous blog posts, we have to push the data.

This will be done by calling two different endpoints:

- POST /preimage to post data and get corresponding root_hash
- GET /serialized_certificates/{root_hash} to get related DAC Certificate.

Push data to Ghostnet Community DAC

We have to transfom the image posted by the user to a valid hexadecimal value needed by the Ghostnet Community DAC Coordinator. These transformation are not really interesting so I won’t develop them.

Let’s create a handleLoadedImage function which takes a valid hexadecimal value as a parameter.
This function will call the /preimage endpoint of Ghostnet Community DAC Coordinator to provide the data and get the corresponding root_hash which will be needed to get the DAC Certificate and send this last one to your Smart Rollup.

Please note that getting the DAC Certificate is not instantaneous. With the current DAC resources (which are very low) you need to wait around 30 seconds per megabyte of data.

async handleLoadedImage(hexaString: string) {

    console.log("Posting Hexa image to DAC: ", hexaString);
    const preimageUrl = "https://dac-ghost-coordinator.gcp.marigold.dev/v0/preimage";
    const preimageResponse = await fetch(preimageUrl, {
      method: "POST", // or 'PUT'
      headers: {
        "Content-Type": "application/json",
      body: JSON.stringify(hexaString),

    const root_hash = await preimageResponse.json();

Retrieve corresponding DAC Certificate

We did the choice to do that inside the same function, if you have followed, we need to call the second endpoint GET /serialized_certificates/{root_hash}:

    const root_hash = await preimageResponse.json();

    // After quick calculation
    // With the actual resources of the DAC
    // We need to wait around 30s/1MB of data
    // Before retrieving the certificate
    await new Promise(resolve => setTimeout(resolve, hexaString.length / 2_000_000 * 30000));

    console.log("Successfully posted image, root_hash is: ", root_hash);
    console.log("Retrieving serialized_certificate from root_hash: ", root_hash);

    const serializedCertificateUrl = "https://dac-ghost-coordinator.gcp.marigold.dev/v0/serialized_certificates/" + root_hash;
    const serializedCertificateResponse = await fetch(serializedCertificateUrl);
    const serializedCertificate = await serializedCertificateResponse.json();

    console.log("Successfully retrieved serialized_certificate: ", serializedCertificate);

    return serializedCertificate;

Send DAC Certificate to Smart Rollup

We can finally send the message (with the format expected by our Smart Rollup, which I am not going to detail here because it is not the purpose of this blog post) by using Tezos.contract.smartRollupAddMessages from Taquito.

Voilà! 🎉

You can have a look of the full example on Tzwitter and the full code example on Kernel-gallery gitlab repository.

Thank you so much for reading us! Any feedback is welcome, as long as it is constructive 😉.

If you want to know more about Marigold, please follow us on social media (Twitter, Reddit, Linkedin)!

Scroll to top