Guides

Guides · Part 2 of 5

Publish your first PoE

Publishing is the half of Label 309 that does need a gateway. Verifying is trustless and account-free; publishing puts a transaction on chain, and that costs a fee — so you submit through a Label 309 gateway that builds the transaction, pays the fee, and debits its own balance model. The SDK and CLI are gateway-agnostic: point them at whichever gateway you hold an API key for.

Every publish is the same two-step shape — lock a price, then submit. The gateway prices the record from a live FX snapshot and hands back a quote_id valid for 15 minutes; the publish call consumes it atomically with the on-chain insert. The high-level helpers hash your content and build the canonical-CBOR record for you.

With the CLI

Install the cardanowall binary, then point submit at a file. It hashes the contents, anchors the digest, and prints the result:

cardanowall submit \
  --file ./contract.pdf \
  --base-url https://your-gateway.example \
  --api-key "$CW_API_KEY"

--base-url and --api-key also read from CARDANOWALL_BASE_URL and CARDANOWALL_API_KEY, or from a saved gateway profile, so they drop out of the command in CI. If you already hold the digest, anchor it directly with --hash instead of --file; add --alg blake2b-256 to switch from the default sha2-256, and --json for a machine-readable summary:

cardanowall submit --hash 3b9f…c1a2 --json

To sign the record with your identity key, pass the 32-byte master seed via --seed-file or --seed-stdin (never a bare --seed on argv — it lands in shell history). Omit it to publish unsigned.

With the TypeScript SDK

Construct a client against your gateway, then quote and publish in one helper call. publishContent hashes the bytes, builds the record, and submits:

import { Label309Client } from '@cardanowall/sdk-ts';

const client = new Label309Client({
  baseUrl: 'https://your-gateway.example',
  apiKey: process.env.CW_API_KEY,
});

const quote = await client.poe.quote({
  recordBytes: 256,
  recipientCount: 0,
  fileBytesTotal: 0,
});

const result = await client.poe.publishContent({
  content: fileBytes, // Uint8Array or a UTF-8 string
  quoteId: quote.quote_id,
  // idempotencyKey: crypto.randomUUID(), // safe to retry the same submit
  // signer is optional — omit to publish unsigned (profile=core)
});

console.log(result.id, result.tx_hash, result.status);
console.log(result.balance_after_usd_micros); // decimal string of USD micro-cents

result.status starts at submitting; tx_hash is null until the gateway builds the transaction. Pass an idempotencyKey to make retries safe — a repeated identical submit returns the original record (dedup_hit: true) without debiting twice. For a precomputed digest use publishPrehashed; for a recipient-sealed envelope use publishSealed.

With the Python SDK

cardanowall-sdk mirrors the TypeScript helpers method-for-method. The client is an async context manager; responses come back as plain dicts:

import asyncio
from cardanowall.client import Label309Client


async def main() -> None:
    async with Label309Client(
        base_url="https://your-gateway.example",
        api_key="<opaque-bearer>",
    ) as client:
        quote = await client.poe.quote(
            record_bytes=256, recipient_count=0, file_bytes_total=0
        )
        out = await client.poe.publish_content(
            content="hello world",  # str (UTF-8) or bytes
            quote_id=quote["quote_id"],
        )
        print(out["id"], out["tx_hash"], out["status"])


asyncio.run(main())

With the Rust SDK

The cardanowall crate exposes the same gateway-agnostic client. Calls are blocking, so no async runtime is needed; the helpers return typed results:

use cardanowall::client::{
    Label309Client, Label309ClientConfig, PublishContentInput, QuoteInput,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Label309Client::new(Label309ClientConfig {
        base_url: Some("https://your-gateway.example".into()),
        api_key: std::env::var("CW_API_KEY").ok(),
    })?;

    let quote = client.poe().quote(&QuoteInput {
        record_bytes: 256,
        recipient_count: 0,
        file_bytes_total: 0,
    })?;

    let result = client.poe().publish_content(&PublishContentInput {
        content: file_bytes, // Vec<u8>
        quote_id: quote.quote_id,
        hash_alg: None, // defaults to sha2-256
        signer: None,   // omit to publish unsigned (profile = core)
        idempotency_key: None,
    })?;

    println!("{} {:?} {:?}", result.id, result.tx_hash, result.status);
    Ok(())
}

For a precomputed digest use publish_prehashed; for a recipient-sealed envelope use publish_sealed.

The fee is the gateway's, the proof is yours

The gateway you publish through pays the Cardano fee and debits its balance model — but the record it anchors is plain Label 309 metadata under label 309. Anyone can verify it from the public chain alone, with no account and no trust in the gateway. To address a record to a recipient instead, see build a sealed PoE; for the exact on-chain shape, read the record.