-
Notifications
You must be signed in to change notification settings - Fork 6
Multi-language client SDKs with complete transaction submission #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 18 commits
9422450
708a43b
0f33af5
f20f319
ebd210e
8cd29de
83bf05b
9fec966
30f03a0
70d5349
e5902e7
8e8f878
20f2301
5f92b4c
7078cbd
f4be514
a85176f
e7140a3
e01d510
76722fd
9293e31
54b5cdb
cb7b428
64e8f79
898d608
b4d66c3
48fcd23
6edbe3a
64c8069
fbaeea5
acbff2f
2bb1a52
5b638ba
fe46c73
f896f2b
72e0007
a1ca91f
b8f6ee0
ddc0296
c59eb27
3994785
b4299d7
80f7836
4579113
f0d9d1d
8f505e8
210036e
4c540b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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/`. |
| 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"] |
| 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**. | ||
|
|
||
| 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`. | ||
| 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) |
| 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: | ||
mudigal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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. | ||
|
||
| 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*. | ||
| 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. | ||
mudigal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## 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. | ||
|
||
|
|
||
| **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); | ||
| ``` | ||
| 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. | ||
mudigal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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. | ||
| 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) |
| 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. |
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a convenience method |
||
| ``` | ||
| 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?; | ||
| ``` |
There was a problem hiding this comment.
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