Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@
.envrc
.direnv
.idea

# Node.js
node_modules/
package-lock.json

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

# JavaScript Examples

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

## Quick Start

1. **Install dependencies:**
```bash
npm install
```

2. **Generate PAPI descriptors (for type-safe API):**

Make sure your Bulletin node is running, then:
```bash
npm run papi:generate
```

This generates TypeScript type definitions in `.papi/` based on your chain's metadata. The `.papi/` folder is generated and should not be committed to git (it's listed in `.gitignore`).

3. **Run examples:**
```bash
cd examples
node authorize_and_store_papi.js
```

For more details on PAPI (Polkadot API), see [examples/README_PAPI.md](./examples/README_PAPI.md).

# Troubleshooting

## Build Bulletin Mac OS
Expand Down
23 changes: 18 additions & 5 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,31 @@ docker exec -it ipfs-node ipfs swarm connect /ip4/172.17.0.1/tcp/10001/ws/p2p/12
# docker exec -it ipfs-node-v0.35.0 ipfs swarm connect /ip4/172.17.0.1/tcp/12347/ws/p2p/12D3KooWRkZhiRhsqmrQ28rt73K7V3aCBpqKrLGSXmZ99PTcTZby
```

### Trigger authorize, store and IPFS get
```
# cd polkadot-bulletin-chain - make sure we are here
cd examples
npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util multiformats ipfs-http-client ipfs-unixfs
### Install dependencies
```bash
# From the root of the repository
npm install
```

### Example for simple authorizing and store

**Using legacy @polkadot/api:**
```
node authorize_and_store.js
```

**Using modern PAPI (Polkadot API):**
```bash
# First, generate the PAPI descriptors (from the root of the repository)
npm run papi:generate

# Then run the PAPI version (from the examples directory)
cd examples
node authorize_and_store_papi.js
```

See [README_PAPI.md](./README_PAPI.md) for more details on using PAPI.

### Example for multipart / chunked content / big files
The code stores one file, splits into chunks and then uploads those chunks to the Bulletin.
It collects all the partial CIDs for each chunk and saves them as a custom metadata JSON file in the Bulletin.
Expand Down
103 changes: 103 additions & 0 deletions examples/README_PAPI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Using PAPI with Polkadot Bulletin Chain

## Setup

1. **Install dependencies (from the root of the repository):**
```bash
npm install
```

2. **Generate PAPI descriptors:**

Make sure your Bulletin node is running, then generate the type-safe descriptors:

```bash
# From the root of the repository
npm run papi:generate
```

This will:
- Connect to your local bulletin chain at ws://localhost:10000
- Download the metadata
- Generate TypeScript types in `.papi/descriptors/`
- Create metadata files in `.papi/metadata/bulletin.scale`

> **Note:** The `.papi/` folder is generated and should not be committed to git. It's listed in `.gitignore`.

3. **Run the example:**
```bash
cd examples
node authorize_and_store_papi.js
```

## Key Differences from @polkadot/api

### 1. Client Creation
**Old (@polkadot/api):**
```javascript
const ws = new WsProvider('ws://localhost:10000');
const api = await ApiPromise.create({ provider: ws });
```

**New (PAPI):**
```javascript
import { createClient } from 'polkadot-api';
import { getWsProvider } from 'polkadot-api/ws-provider/node';

const wsProvider = getWsProvider('ws://localhost:10000');
const client = createClient(wsProvider);
const typedApi = client.getTypedApi(bulletin);
```

### 2. Transactions
**Old:**
```javascript
const tx = api.tx.transactionStorage.store(data);
const result = await tx.signAndSend(pair);
```

**New:**
```javascript
const tx = typedApi.tx.TransactionStorage.store({ data });
const result = await tx.signAndSubmit(pair);
```

### 3. Type Safety
PAPI provides full TypeScript type safety based on your chain's metadata:
- Transaction parameters are type-checked
- Query results have proper types
- Auto-completion in IDEs

### 4. Signing
PAPI uses a different signing interface. The `@polkadot-api/pjs-signer` package bridges between `@polkadot/keyring` and PAPI:

```javascript
import { getPolkadotSignerFromPjs } from '@polkadot-api/pjs-signer';
import { Keyring } from '@polkadot/keyring';

const keyring = new Keyring({ type: 'sr25519' });
const account = keyring.addFromUri('//Alice');

// Create PAPI-compatible signer (simple!)
const signer = getPolkadotSignerFromPjs(account);
```

## Benefits of PAPI

1. **Type Safety**: Full TypeScript support with generated types
2. **Light Client Support**: Can use smoldot for light client connections
3. **Better Performance**: More efficient serialization/deserialization
4. **Modern API**: Cleaner, more intuitive API design
5. **Better Developer Experience**: Auto-completion and type checking

## Troubleshooting

### Error: Cannot find module '@polkadot-api/descriptors'
Run: `npm run papi:generate` from the root of the repository

### Connection issues
Make sure your bulletin chain node is running on ws://localhost:10000

### Metadata errors
If metadata changes, regenerate descriptors: `npm run papi:update` from the root of the repository

143 changes: 143 additions & 0 deletions examples/authorize_and_store_papi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// npm install polkadot-api @polkadot-api/signer @polkadot/keyring @polkadot/util-crypto multiformats ipfs-http-client
// npx papi add -w ws://localhost:10000 bulletin
// 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/12D3KooWRkZhiRhsqmrQ28rt73K7V3aCBpqKugpsqfpggl4nzazpieyemw6xme
// ipfs swarm peers - should be there
// ipfs bitswap stat
// ipfs block get /ipfs/bafk2bzacebcnty2x5l3jr2sk5rvn7engdfkugpsqfpggl4nzazpieyemw6xme

import { createClient } from 'polkadot-api';
import { Binary } from '@polkadot-api/substrate-bindings';
import { getWsProvider } from 'polkadot-api/ws-provider/node';
import { getPolkadotSigner } from '@polkadot-api/signer';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { create } from 'ipfs-http-client';
import { waitForNewBlock, 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
});

const result = await sudoTx.signAndSubmit(sudoPair);
console.log('Transaction authorizeAccount submitted:', result);
return result;
}

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 object with 'data' property
const binaryData = Binary.fromBytes(dataBytes);
const tx = typedApi.tx.TransactionStorage.store({ data: binaryData });

const result = await tx.signAndSubmit(pair);
console.log('Transaction store submitted:', result);

return cid;
}

// 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);
await waitForNewBlock();
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);
await waitForNewBlock();

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 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"
}
}
24 changes: 24 additions & 0 deletions scripts/ipfs-reconnect-solo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

# Peers to monitor
PEERS_TO_CHECK=(
"/ip4/127.0.0.1/tcp/10001/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm"
"/ip4/127.0.0.1/tcp/12347/ws/p2p/12D3KooWRkZhiRhsqmrQ28rt73K7V3aCBpqKrLGSXmZ99PTcTZby"
)

while true; do
# Read all current connections once
PEERS=$(./kubo/ipfs swarm peers)

for PEER in "${PEERS_TO_CHECK[@]}"; do
echo "$PEERS" | grep -q "$PEER"
if [ $? -ne 0 ]; then
echo "$(date) - $PEER disconnected. Reconnecting..."
./kubo/ipfs swarm connect "$PEER"
else
echo "$(date) - $PEER connected."
fi
done

sleep 2
done