Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
.direnv
.idea

**/node_modules/
# Node.js
**/node_modules/
package-lock.json

# Polkadot API generated files
.papi/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,13 @@ or
Run on the dedicated machine from the root directory:
```
python3 scripts/cmd/cmd.py bench bulletin-polkadot
python3 scripts/cmd/cmd.py bench bulletin-westend
```

# Examples (JavaScript-based)

The `examples/` directory contains Node.js (PJS and/or PAPI) scripts demonstrating how to interact with the Bulletin chain. For detailed setup and usage instructions, see [examples/README.md](./examples/README.md).

# Troubleshooting

## Build Bulletin Mac OS
Expand Down
26 changes: 20 additions & 6 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,30 @@ docker exec -it ipfs-node ipfs swarm connect /ip4/172.17.0.1/tcp/12347/ws/p2p/12

## Trigger Authorize, Store and IPFS Get

```shell
# cd polkadot-bulletin-chain # make you are in this directory
### Example for Simple Authorizing and Store

#### Using Legacy @polkadot/api (PJS)
```
cd examples
npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util multiformats ipfs-http-client ipfs-unixfs
npm install

node authorize_and_store.js
```

### Example for Simple Authorizing and Store
#### Using Modern PAPI (Polkadot API)
```bash
cd examples
npm install

```shell
node authorize_and_store.js
# First, generate the PAPI descriptors:
# (Generate TypeScript types in `.papi/descriptors/`)
# (Create metadata files in `.papi/metadata/bulletin.scale`)
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.js
```

### Example for Multipart / Chunked Content / Big Files
Expand Down
12 changes: 1 addition & 11 deletions examples/authorize_and_store.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
// npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util multiformats ipfs-http-client
// ipfs daemon &
// ipfs swarm connect /ip4/127.0.0.1/tcp/10001/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm
// ipfs swarm connect /ip4/127.0.0.1/tcp/12347/ws/p2p/12D3KooWRkZhiRhsqmrQ28rt73K7V3aCBpqKrLGSXmZ99PTcTZby
// ipfs swarm peers - should be there
// ipfs bitswap stat
// ipfs block get /ipfs/bafk2bzacebcnty2x5l3jr2sk5rvn7engdfkugpsqfpggl4nzazpieyemw6xme

import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady, blake2AsU8a } from '@polkadot/util-crypto';
import { CID } from 'multiformats/cid';
import * as multihash from 'multiformats/hashes/digest';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { create } from 'ipfs-http-client';
import { waitForNewBlock, cidFromBytes } from './common.js';

Expand Down
169 changes: 169 additions & 0 deletions examples/authorize_and_store_papi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { createClient } from 'polkadot-api';
import { Binary } from '@polkadot-api/substrate-bindings';
import { getWsProvider } from 'polkadot-api/ws-provider';
import { getPolkadotSigner } from '@polkadot-api/signer';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { create } from 'ipfs-http-client';
import { cidFromBytes } from './common.js';
import { bulletin } from './.papi/descriptors/dist/index.mjs';

async function authorizeAccount(typedApi, sudoPair, who, transactions, bytes) {
console.log('Creating authorizeAccount transaction...');

const authorizeTx = typedApi.tx.TransactionStorage.authorize_account({
who,
transactions,
bytes
});

const sudoTx = typedApi.tx.Sudo.sudo({
call: authorizeTx.decodedCall
});

// Wait for a new block.
return new Promise((resolve, reject) => {
const sub = sudoTx
.signSubmitAndWatch(sudoPair)
.subscribe({
next: (ev) => {
if (ev.type === "txBestBlocksState" && ev.found) {
console.log("📦 Included in block:", ev.block.hash);
sub.unsubscribe();
resolve(ev);
}
},
error: (err) => {
console.log("Error:", err);
sub.unsubscribe();
reject(err);
},
complete: () => {
console.log("Subscription complete");
}
});
})
}

async function store(typedApi, pair, data) {
console.log('Storing data:', data);
const cid = cidFromBytes(data);

// Convert data to Uint8Array then wrap in Binary for PAPI typed API
const dataBytes = typeof data === 'string' ?
new Uint8Array(Buffer.from(data)) :
new Uint8Array(data);

// Wrap in Binary object for typed API - pass as an object with 'data' property
const binaryData = Binary.fromBytes(dataBytes);
const tx = typedApi.tx.TransactionStorage.store({ data: binaryData });

// Wait for a new block.
return new Promise((resolve, reject) => {
const sub = tx
.signSubmitAndWatch(pair)
.subscribe({
next: (ev) => {
if (ev.type === "txBestBlocksState" && ev.found) {
console.log("📦 Included in block:", ev.block.hash);
sub.unsubscribe();
resolve(cid);
}
},
error: (err) => {
console.log("Error:", err);
sub.unsubscribe();
reject(err);
},
complete: () => {
console.log("Subscription complete");
}
});
})
}

// Connect to a local IPFS gateway (e.g. Kubo)
const ipfs = create({
url: 'http://127.0.0.1:5001', // Local IPFS API
});

async function read_from_ipfs(cid) {
// Fetch the block (downloads via Bitswap if not local)
console.log('Trying to get cid: ', cid);
try {
const block = await ipfs.block.get(cid, {timeout: 10000});
console.log('Received block: ', block);
if (block.length !== 0) {
return block;
}
} catch (error) {
console.log('Block not found directly, trying cat...', error.message);
}

// Fetch the content from IPFS
console.log('Trying to chunk cid: ', cid);
const chunks = [];
for await (const chunk of ipfs.cat(cid)) {
chunks.push(chunk);
}

const content = Buffer.concat(chunks);
return content;
}

// Global client reference for cleanup
let client;

async function main() {
await cryptoWaitReady();

// Create PAPI client with WebSocket provider
client = createClient(getWsProvider('ws://localhost:10000'));

// Get typed API with generated descriptors
const typedApi = client.getTypedApi(bulletin);

// Create keyring and accounts
const keyring = new Keyring({ type: 'sr25519' });
const sudoAccount = keyring.addFromUri('//Alice');
const whoAccount = keyring.addFromUri('//Alice');

// Create PAPI-compatible signers using @polkadot-api/signer
// getPolkadotSigner expects (publicKey: Uint8Array, signingType, sign function)
const sudoSigner = getPolkadotSigner(
sudoAccount.publicKey,
'Sr25519',
(input) => sudoAccount.sign(input)
);
const whoSigner = getPolkadotSigner(
whoAccount.publicKey,
'Sr25519',
(input) => whoAccount.sign(input)
);

// Data
const who = whoAccount.address;
const transactions = 32; // u32 - regular number
const bytes = 64n * 1024n * 1024n; // u64 - BigInt for large numbers

console.log('Doing authorization...');
await authorizeAccount(typedApi, sudoSigner, who, transactions, bytes);
console.log('Authorized!');

console.log('Storing data ...');
const dataToStore = "Hello, Bulletin with PAPI - " + new Date().toString();
let cid = await store(typedApi, whoSigner, dataToStore);
console.log('Stored data with CID: ', cid);

console.log('Reading content... cid: ', cid);
let content = await read_from_ipfs(cid);
console.log('Content as bytes:', content);
console.log('Content as string:', content.toString());

client.destroy();
}

main().catch(console.error).finally(() => {
if (client) client.destroy();
});

23 changes: 23 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"type": "module",
"scripts": {
"papi:generate": "papi add -w ws://localhost:10000 bulletin",
"papi:update": "papi"
},
"dependencies": {
"@polkadot-api/descriptors": "file:.papi/descriptors",
"@polkadot-api/substrate-bindings": "^0.16.5",
"@polkadot/api": "^16.5.2",
"@polkadot/keyring": "^13.5.8",
"@polkadot/util": "^13.5.8",
"@polkadot/util-crypto": "^13.5.8",
"fs": "^0.0.1-security",
"ipfs-http-client": "^60.0.1",
"multiformats": "^13.4.1",
"polkadot-api": "^1.20.6",
"smoldot": "^2.0.39"
},
"devDependencies": {
"@polkadot-api/cli": "^0.13.3"
}
}
Loading