Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
13 changes: 13 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,23 @@ npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util
```

### 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
npx papi add -w ws://localhost:10000 bulletin

# Then run the PAPI version
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
102 changes: 102 additions & 0 deletions examples/README_PAPI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Using PAPI with Polkadot Bulletin Chain

## Setup

1. **Install dependencies:**
```bash
npm install polkadot-api @polkadot-api/pjs-signer @polkadot/keyring @polkadot/util-crypto multiformats ipfs-http-client
```

2. **Generate PAPI descriptors:**

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

```bash
npx papi add -w ws://localhost:10000 bulletin
```

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

Alternatively, if you have issues connecting, you can use the pre-generated metadata from:
https://github.com/paritytech/product-infrastructure/blob/main/apps/bulletin-example/.papi/metadata/bulletin.scale

3. **Run the example:**
```bash
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: `npx papi add -w ws://localhost:10000 bulletin`

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

### Metadata errors
If metadata changes, regenerate descriptors: `npx papi`

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();
});

18 changes: 18 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"type": "module",
"dependencies": {
"@polkadot/api": "^16.5.2",
"@polkadot/keyring": "^13.5.8",
"@polkadot/util": "^13.5.8",
"@polkadot/util-crypto": "^13.5.8",
"@polkadot-api/descriptors": "^0.13.3",
"fs": "^0.0.1-security",
"ipfs-http-client": "^60.0.1",
"multiformats": "^13.4.1",
"polkadot-api": "^1.14.0",
"smoldot": "^1.0.17"
},
"devDependencies": {
"@polkadot-api/cli": "^0.13.3"
}
}
13 changes: 13 additions & 0 deletions examples/polkadot-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"entries": {
"bulletin": {
"wsUrl": "ws://localhost:10000",
"metadata": {
"source": "file",
"filePath": "./.papi/metadata/bulletin.scale"
}
}
}
}

13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"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-crypto": "^13.5.8",
"ipfs-http-client": "^60.0.1",
"multiformats": "^13.4.1",
"polkadot-api": "^1.20.6",
"smoldot": "^2.0.39"
}
}
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