Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9422450
feat(sdk): Implement Rust and TypeScript Client SDKs
mudigal Jan 27, 2026
708a43b
docs(sdk): Add Comprehensive SDK Documentation Book
mudigal Jan 27, 2026
0f33af5
chore: update Cargo.lock after adding SDK dependencies
mudigal Jan 27, 2026
f20f319
fix(sdk): Resolve test failures and add build utilities
mudigal Jan 27, 2026
ebd210e
Optimizing the README with sdk-book
mudigal Jan 27, 2026
8cd29de
fix: Warnings while building
mudigal Jan 27, 2026
83bf05b
fixing formatting issues through check-fmt
mudigal Jan 27, 2026
9fec966
fix(sdk): resolve all clippy warnings and CI issues
mudigal Jan 27, 2026
30f03a0
fix(sdk): add runtime-benchmarks and try-runtime features for workspa…
mudigal Jan 27, 2026
70d5349
fix(sdk): propagate runtime-benchmarks and try-runtime to sp-runtime
mudigal Jan 28, 2026
e5902e7
fix(sdk): add trailing commas in Cargo.toml arrays
mudigal Jan 28, 2026
8e8f878
fix(sdk): consolidate MAX_CHUNK_SIZE to 2 MiB per Bitswap limit
bkontur Jan 28, 2026
20f2301
fix(sdk): centralize chunk size constants and change max to 2 MiB
mudigal Jan 28, 2026
5f92b4c
Merge branch 'main' into naren-client
mudigal Jan 28, 2026
7078cbd
fix(sdk): remove extra blank line in utils.rs
mudigal Jan 28, 2026
f4be514
fix(sdk): resolve all clippy warnings
mudigal Jan 28, 2026
a85176f
fix(sdk): update docs and error messages to reflect 2 MiB chunk limit
bkontur Jan 28, 2026
e7140a3
Merge remote-tracking branch 'origin/naren-client' into naren-client
bkontur Jan 28, 2026
e01d510
feat(examples): add Rust authorize-and-store example using bulletin-s…
bkontur Jan 28, 2026
76722fd
feat(examples): add Rust authorize-and-store example using bulletin-s…
bkontur Jan 28, 2026
9293e31
fix(example): add ProvideCidConfig signed extension for Bulletin Chain
bkontur Jan 28, 2026
54b5cdb
Merge remote-tracking branch 'origin/main' into naren-client
bkontur Jan 28, 2026
cb7b428
feat(sdk): add transaction submitter implementations
mudigal Jan 28, 2026
64e8f79
feat(sdk): add from_url constructor to SubxtSubmitter
mudigal Jan 28, 2026
898d608
feat(examples): update rust-authorize-and-store to use from_url pattern
mudigal Jan 28, 2026
b4d66c3
docs(sdk-book): add comprehensive submitters documentation
mudigal Jan 28, 2026
48fcd23
docs(sdk-book): update chunked uploads guide for AsyncBulletinClient
mudigal Jan 28, 2026
6edbe3a
fix: resolve CI failures in SDK and example code
mudigal Jan 28, 2026
64c8069
fix(sdk): return error for unsupported SHA2-512 hash algorithm
bkontur Jan 29, 2026
fbaeea5
Merge remote-tracking branch 'origin/main' into naren-client
bkontur Jan 29, 2026
acbff2f
fmt
bkontur Jan 29, 2026
2bb1a52
feat: add automatic authorization checking to fail fast before uploads
mudigal Jan 29, 2026
5b638ba
feat: add authorization expiration checking
mudigal Jan 29, 2026
fe46c73
feat: unify store() API with automatic chunking
mudigal Jan 29, 2026
f896f2b
fix: update rust-authorize-and-store example for unified store() API
mudigal Jan 29, 2026
72e0007
feat(typescript): unify store() API with automatic chunking
mudigal Jan 29, 2026
a1ca91f
feat(typescript): add authorization pre-flight checking
mudigal Jan 29, 2026
b8f6ee0
docs(typescript): update SDK book for unified API and authorization c…
mudigal Jan 29, 2026
ddc0296
Merge branch 'main' into naren-client
mudigal Jan 29, 2026
c59eb27
Update docs/sdk-book/src/concepts/authorization.md
mudigal Jan 30, 2026
3994785
Update docs/sdk-book/src/concepts/manifests.md
mudigal Jan 30, 2026
b4299d7
Update docs/sdk-book/src/concepts/README.md
mudigal Jan 30, 2026
80f7836
Consolidate integration test commands into unified just recipe
mudigal Jan 30, 2026
4579113
Simplify store() API by moving options to separate method
mudigal Jan 30, 2026
f0d9d1d
Improve authorization documentation
mudigal Jan 30, 2026
8f505e8
Address remaining PR #202 review comments
mudigal Jan 30, 2026
210036e
Refactor rust-authorize-and-store example to use subxt metadata codegen
mudigal Jan 31, 2026
4c540b3
Update docs to use generic node URLs instead of hardcoded localhost:1…
mudigal Jan 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,176 changes: 1,056 additions & 120 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ westend-runtime-constants = { git = "https://github.com/paritytech/polkadot-sdk.

# Local workspace members
bulletin-polkadot-runtime = { path = "runtimes/bulletin-polkadot" }
bulletin-sdk-rust = { path = "sdk/rust", default-features = false }
bulletin-westend-runtime = { path = "runtimes/bulletin-westend" }
pallet-relayer-set = { path = "pallets/relayer-set", default-features = false }
pallet-transaction-storage = { path = "pallets/transaction-storage", default-features = false }
Expand Down Expand Up @@ -180,6 +181,7 @@ members = [
"runtimes/bulletin-polkadot",
"runtimes/bulletin-westend",
"runtimes/bulletin-westend/integration-tests",
"sdk/rust",
]
[profile.release]
# Polkadot runtime requires unwinding.
Expand Down
33 changes: 33 additions & 0 deletions docs/sdk-book/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Polkadot Bulletin SDK Book

This directory contains the source for the Polkadot Bulletin SDK documentation book.

## How to Build & View

This documentation is built using [mdBook](https://github.com/rust-lang/mdBook).

### Prerequisites

You need to have `mdbook` installed. If you have Rust installed, you can install it via Cargo:

```bash
cargo install mdbook
```

### Viewing the Book

1. Navigate to this directory:
```bash
cd docs/sdk-book
```

2. Serve the book locally:
```bash
mdbook serve --open
```

3. Build the static HTML:
```bash
mdbook build
```
The output will be in `book/`.
9 changes: 9 additions & 0 deletions docs/sdk-book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[book]
authors = ["Polkadot Bulletin Team"]
language = "en"
multilingual = false
src = "src"
title = "Polkadot Bulletin SDK Documentation"

[output.html]
additional-css = ["custom.css"]
64 changes: 64 additions & 0 deletions docs/sdk-book/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Polkadot Bulletin SDK

Welcome to the official documentation for the **Polkadot Bulletin Chain SDKs**.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename it to the Polkadot Storage SDKs


These SDKs provide high-level abstractions for interacting with the Bulletin Chain, specifically designed to simplify the process of storing data, managing authorizations, and generating IPFS-compatible manifests.

## Available SDKs

| Language | Package | Status | Description |
|----------|---------|--------|-------------|
| **Rust** | `bulletin-sdk-rust` | Alpha | Native Rust client, supports `std` and `no_std` (WASM/ink!) |
| **TypeScript** | `@bulletin/sdk` | Alpha | JS/TS client for Node.js and Browser, integrates with PAPI |

## What is Bulletin Chain?

Polkadot Bulletin Chain is a decentralized storage ledger that allows users to prove the existence and timestamp of data. Unlike typical file storage chains (like Filecoin or Arweave), Bulletin Chain focuses on:

1. **Immutability**: Once the hash/CID is on-chain, it cannot be changed.
2. **Verifiability**: Data is content-addressed using CIDs (Content Identifiers).
3. **Flexibility**: Supports small on-chain storage and large off-chain storage with on-chain manifests.

## Key Features

- **Automatic Chunking**: The SDKs handle splitting large files into optimal chunks (default 1 MiB) to fit within blockchain transaction limits.
- **DAG-PB Manifests**: Automatically generates Merkle DAGs (Directed Acyclic Graphs) compliant with IPFS standards.
- **Authorization Management**: Tools to estimate costs and manage the two-step "Authorize -> Store" flow required by the network.

## Next Steps

- Read about [Core Concepts](./concepts/README.md) to understand how storage works.
- Jump to [Rust SDK](./rust/README.md) if you are building a Rust application or Parachain.
- Jump to [TypeScript SDK](./typescript/README.md) if you are building a dApp or web interface.

---

## How to Build & View Locally

This documentation is built using [mdBook](https://github.com/rust-lang/mdBook).

### Prerequisites

You need to have `mdbook` installed. If you have Rust installed, you can install it via Cargo:

```bash
cargo install mdbook
```

### Viewing the Book

1. Navigate to the book directory:
```bash
cd docs/sdk-book
```

2. Serve the book locally (it will open in your browser and live-reload on changes):
```bash
mdbook serve --open
```

3. Build the static HTML:
```bash
mdbook build
```
The output will be in `docs/sdk-book/book/html`.
18 changes: 18 additions & 0 deletions docs/sdk-book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Summary

- [Introduction](./README.md)
- [Core Concepts](./concepts/README.md)
- [Storage Model](./concepts/storage.md)
- [Authorization](./concepts/authorization.md)
- [Manifests & IPFS](./concepts/manifests.md)
- [Rust SDK](./rust/README.md)
- [Installation](./rust/installation.md)
- [Basic Storage](./rust/basic-storage.md)
- [Chunked Uploads](./rust/chunked-uploads.md)
- [Authorization](./rust/authorization.md)
- [no_std & ink!](./rust/no_std.md)
- [TypeScript SDK](./typescript/README.md)
- [Installation](./typescript/installation.md)
- [Basic Storage](./typescript/basic-storage.md)
- [Chunked Uploads](./typescript/chunked-uploads.md)
- [PAPI Integration](./typescript/papi-integration.md)
27 changes: 27 additions & 0 deletions docs/sdk-book/src/concepts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Core Concepts

To effectively use the SDKs, it's helpful to understand a few underlying concepts of the Bulletin Chain.

## The "Authorize -> Store" Flow

To prevent spam and manage state growth, Bulletin Chain requires a two-step process for storing data:

1. **Authorize**: A user reserves space on the chain. This costs tokens (fees) based on the size of the data and the number of transactions required.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be called by Root and doesn't cost any fees

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added clarification.

2. **Store**: The user (or anyone with the data) uploads the actual data. This transaction is free (or very cheap) because the space was already paid for.

The SDKs help you calculate the required authorization and track the status of your uploads.

## Data Limits

- **Max Transaction Size**: ~8 MiB (typical Substrate limit).
- **Recommended Chunk Size**: 1 MiB.

If your file is larger than the transaction limit, it *must* be split into chunks. The SDKs handle this automatically.

## CIDs (Content Identifiers)

Bulletin Chain uses CIDs to identify data. A CID is a self-describing label used in IPFS.
- **Codec**: Describes the format (e.g., `0x55` for Raw, `0x70` for DAG-PB).
- **Multihash**: Describes the hash algorithm (e.g., `blake2b-256`, `sha2-256`).

When you store data, the chain records the CID. This proves that *this specific data* existed at *this specific block number*.
43 changes: 43 additions & 0 deletions docs/sdk-book/src/concepts/authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Authorization

Before storing data, you must authorize the storage. This mechanism prevents spam and ensures users pay for the state bloat they introduce.

## Types of Authorization

### 1. Account Authorization (`authorize_account`)
This authorizes a specific **account** (public key) to store a certain amount of data.
- **Flexible**: The account can store *any* data up to the limit.
- **Usage**: Good for active users or applications uploading dynamic content.
- **Parameters**:
- `who`: The account to authorize.
- `transactions`: Number of transactions allowed.
- `bytes`: Total bytes allowed.

### 2. Preimage Authorization (`authorize_preimage`)
This authorizes a specific **piece of data** (identified by its hash) to be stored by *anyone*.
- **Restricted**: Only data matching the authorized hash can be stored.
- **Usage**: Good for "sponsored" uploads where you want to pay for a specific file to be hosted, regardless of who submits the transaction.
- **Parameters**:
- `content_hash`: Hash of the data.
- `max_size`: Maximum size of the data.

## SDK Helpers

The SDK provides `estimate_authorization` / `estimateAuthorization` to help you calculate the required values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required values of what?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Changed to 'calculate how many transactions and bytes you need to authorize' for clarity.


**Example Calculation:**
If you want to store a 100 MiB file with 1 MiB chunks:
- **Chunks**: 100
- **Manifest**: 1
- **Total Transactions**: 101
- **Total Bytes**: 100 MiB + size of manifest (~2KB)

```rust
// Rust
let (txs, bytes) = client.estimate_authorization(100_000_000);
```

```typescript
// TypeScript
const { transactions, bytes } = client.estimateAuthorization(100000000);
```
38 changes: 38 additions & 0 deletions docs/sdk-book/src/concepts/manifests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Manifests & IPFS

## What is a Manifest?

In the context of the SDK, a **Manifest** is a small data structure that describes how to reassemble a large file from its chunks. We use **DAG-PB** (Merkle DAG Protobuf), which is the default format for IPFS.

When you upload a large file using the SDK:
1. The file is split into chunks (leaves).
2. Each chunk is hashed and uploaded.
3. A "Root Node" (the manifest) is created. It contains:
- `Links`: A list of CIDs pointing to the chunks.
- `Data`: UnixFS metadata (file size, type).

## IPFS Compatibility

Because we use standard DAG-PB:
- The **Root CID** generated by the SDK is identical to the CID generated by running `ipfs add --chunker=size-1048576 file.bin`.
- If you run an IPFS node and pin the chunks, the file becomes available on the public IPFS network.
- Bulletin Chain acts as the "ledger of record" for these CIDs.

## Manifest Structure

A simplified view of a manifest node:

```protobuf
message PBNode {
repeated PBLink Links = 2;
optional bytes Data = 1;
}

message PBLink {
optional bytes Hash = 1; // CID of the chunk
optional string Name = 2;
optional uint64 Tsize = 3; // Size of the chunk
}
```

The SDKs include a `DagBuilder` (Rust) or `UnixFsDagBuilder` (TS) that constructs this binary format for you.
32 changes: 32 additions & 0 deletions docs/sdk-book/src/concepts/storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Storage Models

The SDK supports two primary modes of operation.

## Simple Storage (Direct)

For small data (less than 8 MiB), you can store the data directly in a single transaction.

- **Pros**: Simple, atomic (all or nothing).
- **Cons**: Limited by block size and transaction size limits.
- **Underlying Call**: `TransactionStorage.store(data)`

The SDK calculates the CID for you and wraps the data in a `StorageOperation`.

## Chunked Storage (DAG-PB)

For larger files, the data is split into a **Merkle DAG** (Directed Acyclic Graph).

1. **Chunking**: The file is split into 1 MiB chunks.
2. **Upload**: Each chunk is uploaded as a separate transaction.
3. **Manifest**: A "Manifest" node is created. This is a small Protobuf message that lists the links (CIDs) to all the chunks.
4. **Finalize**: The Manifest is uploaded last. Its CID represents the *entire file*.

### Why DAG-PB?

We use the **DAG-PB** (UnixFS) standard used by IPFS. This means:
- You can retrieve the file from Bulletin Chain using standard IPFS tools if you have the chunks.
- The root CID generated by the SDK matches the CID generated by `ipfs add`.

The SDK manages this complexity for you:
- `client.prepare_store_chunked` (Rust)
- `client.prepareStoreChunked` (TS)
17 changes: 17 additions & 0 deletions docs/sdk-book/src/rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Rust SDK

The `bulletin-sdk-rust` crate provides a robust client for interacting with the Bulletin Chain. It is designed to be:

- **Type-Safe**: Leverages Rust's type system to prevent common errors.
- **Flexible**: Works with `std` (standard library) and `no_std` (WASM/embedded) environments.
- **Modular**: Use only what you need (chunking, CID calculation, or full client).

## Modules

- `client`: High-level entry point (`BulletinClient`).
- `chunker`: Splits data into chunks (`FixedSizeChunker`).
- `cid`: CID calculation utilities.
- `storage`: Transaction preparation.
- `authorization`: Authorization helpers.

Proceed to [Installation](./installation.md) to get started.
22 changes: 22 additions & 0 deletions docs/sdk-book/src/rust/authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Authorization

Use the `estimate_authorization` helper to calculate costs.

```rust
let client = BulletinClient::new();
let file_size = 100 * 1024 * 1024; // 100 MiB

let (txs, bytes) = client.estimate_authorization(file_size);

println!("Need to authorize {} transactions and {} bytes", txs, bytes);
```

Then submit the authorization transaction:

```rust
let tx = bulletin::tx().transaction_storage().authorize_account(
target_account,
txs,
bytes
);
Comment on lines +17 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not offer an API that already calls the estimation underneath? Would be very useful for testing without needing to call estimate every time

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a convenience method authorize_account_for_size(who, data_size) that combines estimation and authorization in one call

```
44 changes: 44 additions & 0 deletions docs/sdk-book/src/rust/basic-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Basic Storage

This guide shows how to store a small piece of data (< 8 MiB).

## 1. Initialize Client

```rust
use bulletin_sdk_rust::prelude::*;

let client = BulletinClient::new();
```

## 2. Prepare Data

```rust
let data = b"Hello, Bulletin!".to_vec();
```

## 3. Prepare Operation

The `prepare_store` method validates the data and calculates the CID.

```rust
let options = StoreOptions::default();
let operation = client.prepare_store(data, options)?;

println!("Data Size: {}", operation.size());
// operation.data contains the bytes to submit
```

## 4. Submit Transaction (with subxt)

Assuming you have a `subxt` client connected:

```rust
// This part depends on your subxt generation
let tx = bulletin::tx().transaction_storage().store(operation.data);

api.tx()
.sign_and_submit_then_watch_default(&tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
```
Loading