Examples demonstrating how to interact with the Polkadot Bulletin Chain.
examples/
├── *.js # JavaScript examples and shared utilities
├── package.json # JS dependencies
├── typescript/ # TypeScript SDK examples
│ └── authorize_and_store.js # TS SDK authorize-and-store example
├── rust/ # Rust examples
│ └── authorize-and-store/ # Rust subxt example
└── justfile # Task automation
Install just command runner:
cargo install just # Using cargo
brew install just # Using Homebrew (macOS)
sudo apt install just # Using apt (Linux)It's only needed once after checkout or when dependencies change:
just npm-install
Standalone recipes handle full setup/teardown automatically:
# Westend parachain with WebSocket + Kubo Docker IPFS (default)
just run-authorize-and-store bulletin-westend-runtime ws
# Westend parachain with WebSocket + Kubo native (no Docker required)
just run-authorize-and-store bulletin-westend-runtime ws kubo-native
# Westend parachain with smoldot light client
just run-authorize-and-store bulletin-westend-runtime smoldotTwo IPFS backends are supported:
kubo-docker(default) — Runs Kubo inside a Docker container. Requires Docker.kubo-native— Runs Kubo as a local binary (downloaded automatically). No Docker required.
# Start services (zombienet + IPFS with Peering.Peers auto-reconnect)
just start-services /tmp/my-test bulletin-westend-runtime kubo-native
# Generate PAPI descriptors from running node
just papi-generate
# Run individual tests (services must be running)
just run-test-authorize-and-store /tmp/my-test bulletin-westend-runtime ws
just run-test-store-chunked-data /tmp/my-test
just run-test-store-big-data /tmp/my-test big32
# Stop services
just stop-services /tmp/my-test kubo-nativecd examples
# Run Rust SDK tests (services must already be running)
just test-rust-sdk <test_dir> <runtime>
# Run individual Rust example
just run-test-rust authorize-and-store <test_dir> <runtime>| File | Description |
|---|---|
authorize_and_store_papi.js |
Basic authorization and storage via WebSocket RPC |
authorize_and_store_papi_smoldot.js |
Same workflow using Smoldot light client |
authorize_preimage_and_store_papi.js |
Content-addressed authorization using preimage hashes |
store_chunked_data.js |
Large file storage with DAG-PB chunking |
store_big_data.js |
Very large file handling with parallel chunk uploads |
native_ipfs_dag_pb_chunked_data.js |
Native IPFS DAG-PB chunked data example |
api.js |
Shared transaction and storage API helpers |
common.js |
Shared utilities (signers, image generation, etc.) |
logger.js |
Unified logging functions |
cid_dag_metadata.js |
CID and DAG metadata utilities |
| Directory | Description |
|---|---|
rust/authorize-and-store/ |
Authorization and storage using subxt |
# Start all services (zombienet, IPFS, PAPI descriptors)
just start-services <test_dir> <runtime> [ipfs_mode]
# Stop all services
just stop-services <test_dir> [ipfs_mode]just run-test-authorize-and-store <test_dir> <runtime> [mode]
just run-test-store-chunked-data <test_dir>
just run-test-store-big-data <test_dir> [image_size]
just run-test-authorize-preimage-and-store <test_dir>
just run-test-rust <example> <test_dir> <runtime>
just test-rust-sdk <test_dir> <runtime>just run-live-tests-westend <seed> [ipfs_gateway_url] [image_size]
just run-live-tests-paseo <seed> [ipfs_gateway_url] [image_size]cd polkadot-bulletin-chain # make you are inside the project directory for the following stepsOS="$(uname -s)"
ARCH="$(uname -m)"
if [ "$OS" = "Linux" ]; then
zb_os=linux
else
zb_os=macos
fi
if [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then
zb_arch=arm64
else
zb_arch=x64
fi
zb_bin="zombienet-${zb_os}-${zb_arch}"
wget "https://github.com/paritytech/zombienet/releases/download/v1.3.133/${zb_bin}"
chmod +x "${zb_bin}"wget https://dist.ipfs.tech/kubo/v0.40.1/kubo_v0.40.1_darwin-arm64.tar.gz
tar -xvzf kubo_v0.40.1_darwin-arm64.tar.gz
./kubo/ipfs version
./kubo/ipfs init
./kubo/ipfs daemon & # run in the backgrounddocker pull ipfs/kubo:latest
docker run -d --name ipfs-node -v ipfs-data:/data/ipfs \
-p 127.0.0.1:4011:4011 -p 127.0.0.1:8283:8283 -p 127.0.0.1:5011:5011 \
--add-host=host.docker.internal:host-gateway \
ipfs/kubo:latest
docker logs -f ipfs-nodemkdir -p ~/local_bridge_testing/bin
# Ensures `polkadot` and `polkadot-parachain` exist
git clone https://github.com/paritytech/polkadot-sdk.git
# TODO: unless not merged: https://github.com/paritytech/polkadot-sdk/pull/10370
git reset --hard origin/bko-bulletin-para-support
cd polkadot-sdk
cargo build -p polkadot -r
ls -la target/release/polkadot
cp target/release/polkadot ~/local_bridge_testing/bin
cp target/release/polkadot-prepare-worker ~/local_bridge_testing/bin
cp target/release/polkadot-execute-worker ~/local_bridge_testing/bin
~/local_bridge_testing/bin/polkadot --version
# polkadot 1.20.2-165ba47dc91 or higher
cargo build -p polkadot-parachain-bin -r
ls -la target/release/polkadot-parachain
cp target/release/polkadot-parachain ~/local_bridge_testing/bin
~/local_bridge_testing/bin/polkadot-parachain --version
# polkadot-parachain 1.20.2-165ba47dc91 or higher# Bulletin Parachain (Westend)
./scripts/create_bulletin_westend_spec.sh
POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \
POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \
./$(ls zombienet-*-*) -p native spawn ./zombienet/bulletin-westend-local.tomlConfigure Peering.Peers for the Westend parachain nodes:
# Local Kubo
./kubo/ipfs config --json Peering.Peers '[
{"ID":"12D3KooWJKVVNYByvML4Pgx1GWAYryYo6exA68jQX9Mw3AJ6G5gQ","Addrs":["/ip4/127.0.0.1/tcp/10001/ws"]},
{"ID":"12D3KooWJ8sqAYtMBX3z3jy2iM98XGLFVzVfUPtmgDzxXSPkVpZZ","Addrs":["/ip4/127.0.0.1/tcp/12347/ws"]}
]'# Docker Kubo
docker exec ipfs-node ipfs config --json Peering.Peers '[
{"ID":"12D3KooWJKVVNYByvML4Pgx1GWAYryYo6exA68jQX9Mw3AJ6G5gQ","Addrs":["/dns4/host.docker.internal/tcp/10001/ws"]},
{"ID":"12D3KooWJ8sqAYtMBX3z3jy2iM98XGLFVzVfUPtmgDzxXSPkVpZZ","Addrs":["/dns4/host.docker.internal/tcp/12347/ws"]}
]'
docker restart ipfs-nodecd examples
npm install
# First, generate the PAPI descriptors:
# (Generate TypeScript types in `.papi/descriptors/`)
# (Create metadata files in `.papi/metadata/bulletin.scale`)
# Generate PAPI descriptors using local node:
# npx papi add -w ws://localhost:10000 bulletin
# npx papi
# or:
npm run papi:generate
# or if you already have .papi folder you can always update it
npm run papi:update
# Then run the PAPI version (from the examples directory)
node authorize_and_store_papi.jsThe code stores one file, splits it into chunks, and then uploads those chunks to Bulletin.
It collects all the partial CIDs for each chunk and saves them as a custom metadata JSON file in Bulletin.
Now we have two examples:
- Manual reconstruction -- return the metadata and chunk CIDs, then reconstruct the original file manually.
- IPFS DAG feature --
- converts the metadata into a DAG-PB descriptor,
- stores it directly in IPFS,
- and allows fetching the entire file using a single root CID from an IPFS HTTP gateway (for example:
http://localhost:8080/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ).
node store_chunked_data.jsIf you prefer to run examples without just:
cd examples
npm install
npx papi add -w ws://localhost:10000 bulletinSee the justfile for full setup details. At minimum you need:
- A running Bulletin Chain node (parachain via zombienet)
- An IPFS node connected to the chain's IPFS peers
cd examples
# JavaScript
node authorize_and_store_papi.js [ws_url] [seed] [http_ipfs_api]
node store_chunked_data.js [ws_url] [seed] [http_ipfs_api]
node store_big_data.js [ws_url] [seed] [ipfs_gateway_url] [image_size]
# Rust
cd rust/authorize-and-store
./fetch_metadata.sh ws://localhost:10000
cargo build --release
./target/release/authorize-and-store --ws ws://localhost:10000 --seed "//Alice"PAPI descriptors not found:
cd examples
npx papi add -w ws://localhost:10000 bulletinMetadata errors (Rust):
cd examples/rust/authorize-and-store
./fetch_metadata.sh ws://localhost:10000