Skip to content

Commit 6f0d444

Browse files
authored
Merge branch 'main' into smoldot_read
2 parents 91ef137 + bb0d238 commit 6f0d444

File tree

6 files changed

+224
-18
lines changed

6 files changed

+224
-18
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@
1111
.direnv
1212
.idea
1313

14-
**/node_modules/
14+
# Node.js
15+
**/node_modules/
16+
package-lock.json
17+
18+
# Polkadot API generated files
19+
.papi/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,13 @@ or
246246
Run on the dedicated machine from the root directory:
247247
```
248248
python3 scripts/cmd/cmd.py bench bulletin-polkadot
249+
python3 scripts/cmd/cmd.py bench bulletin-westend
249250
```
250251

252+
# Examples (JavaScript-based)
253+
254+
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).
255+
251256
# Troubleshooting
252257

253258
## Build Bulletin Mac OS

examples/README.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,30 @@ docker exec -it ipfs-node ipfs swarm connect /ip4/172.17.0.1/tcp/12347/ws/p2p/12
149149

150150
## Trigger Authorize, Store and IPFS Get
151151

152-
```shell
153-
# cd polkadot-bulletin-chain # make you are in this directory
152+
### Example for Simple Authorizing and Store
153+
154+
#### Using Legacy @polkadot/api (PJS)
155+
```
154156
cd examples
155-
npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util multiformats ipfs-http-client ipfs-unixfs
157+
npm install
158+
159+
node authorize_and_store.js
156160
```
157161

158-
### Example for Simple Authorizing and Store
162+
#### Using Modern PAPI (Polkadot API)
163+
```bash
164+
cd examples
165+
npm install
159166

160-
```shell
161-
node authorize_and_store.js
167+
# First, generate the PAPI descriptors:
168+
# (Generate TypeScript types in `.papi/descriptors/`)
169+
# (Create metadata files in `.papi/metadata/bulletin.scale`)
170+
npm run papi:generate
171+
# or if you already have .papi folder you can always update it
172+
npm run papi:update
173+
174+
# Then run the PAPI version (from the examples directory)
175+
node authorize_and_store_papi.js
162176
```
163177

164178
### Example for Multipart / Chunked Content / Big Files

examples/authorize_and_store.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
1-
// npm install @polkadot/api @polkadot/keyring @polkadot/util-crypto @polkadot/util multiformats ipfs-http-client
2-
// ipfs daemon &
3-
// ipfs swarm connect /ip4/127.0.0.1/tcp/10001/ws/p2p/12D3KooWQCkBm1BYtkHpocxCwMgR8yjitEeHGx8spzcDLGt2gkBm
4-
// ipfs swarm connect /ip4/127.0.0.1/tcp/12347/ws/p2p/12D3KooWRkZhiRhsqmrQ28rt73K7V3aCBpqKrLGSXmZ99PTcTZby
5-
// ipfs swarm peers - should be there
6-
// ipfs bitswap stat
7-
// ipfs block get /ipfs/bafk2bzacebcnty2x5l3jr2sk5rvn7engdfkugpsqfpggl4nzazpieyemw6xme
8-
91
import { ApiPromise, WsProvider } from '@polkadot/api';
102
import { Keyring } from '@polkadot/keyring';
11-
import { cryptoWaitReady, blake2AsU8a } from '@polkadot/util-crypto';
12-
import { CID } from 'multiformats/cid';
13-
import * as multihash from 'multiformats/hashes/digest';
3+
import { cryptoWaitReady } from '@polkadot/util-crypto';
144
import { create } from 'ipfs-http-client';
155
import { waitForNewBlock, cidFromBytes } from './common.js';
166

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { createClient } from 'polkadot-api';
2+
import { Binary } from '@polkadot-api/substrate-bindings';
3+
import { getWsProvider } from 'polkadot-api/ws-provider';
4+
import { getPolkadotSigner } from '@polkadot-api/signer';
5+
import { Keyring } from '@polkadot/keyring';
6+
import { cryptoWaitReady } from '@polkadot/util-crypto';
7+
import { create } from 'ipfs-http-client';
8+
import { cidFromBytes } from './common.js';
9+
import { bulletin } from './.papi/descriptors/dist/index.mjs';
10+
11+
async function authorizeAccount(typedApi, sudoPair, who, transactions, bytes) {
12+
console.log('Creating authorizeAccount transaction...');
13+
14+
const authorizeTx = typedApi.tx.TransactionStorage.authorize_account({
15+
who,
16+
transactions,
17+
bytes
18+
});
19+
20+
const sudoTx = typedApi.tx.Sudo.sudo({
21+
call: authorizeTx.decodedCall
22+
});
23+
24+
// Wait for a new block.
25+
return new Promise((resolve, reject) => {
26+
const sub = sudoTx
27+
.signSubmitAndWatch(sudoPair)
28+
.subscribe({
29+
next: (ev) => {
30+
if (ev.type === "txBestBlocksState" && ev.found) {
31+
console.log("📦 Included in block:", ev.block.hash);
32+
sub.unsubscribe();
33+
resolve(ev);
34+
}
35+
},
36+
error: (err) => {
37+
console.log("Error:", err);
38+
sub.unsubscribe();
39+
reject(err);
40+
},
41+
complete: () => {
42+
console.log("Subscription complete");
43+
}
44+
});
45+
})
46+
}
47+
48+
async function store(typedApi, pair, data) {
49+
console.log('Storing data:', data);
50+
const cid = cidFromBytes(data);
51+
52+
// Convert data to Uint8Array then wrap in Binary for PAPI typed API
53+
const dataBytes = typeof data === 'string' ?
54+
new Uint8Array(Buffer.from(data)) :
55+
new Uint8Array(data);
56+
57+
// Wrap in Binary object for typed API - pass as an object with 'data' property
58+
const binaryData = Binary.fromBytes(dataBytes);
59+
const tx = typedApi.tx.TransactionStorage.store({ data: binaryData });
60+
61+
// Wait for a new block.
62+
return new Promise((resolve, reject) => {
63+
const sub = tx
64+
.signSubmitAndWatch(pair)
65+
.subscribe({
66+
next: (ev) => {
67+
if (ev.type === "txBestBlocksState" && ev.found) {
68+
console.log("📦 Included in block:", ev.block.hash);
69+
sub.unsubscribe();
70+
resolve(cid);
71+
}
72+
},
73+
error: (err) => {
74+
console.log("Error:", err);
75+
sub.unsubscribe();
76+
reject(err);
77+
},
78+
complete: () => {
79+
console.log("Subscription complete");
80+
}
81+
});
82+
})
83+
}
84+
85+
// Connect to a local IPFS gateway (e.g. Kubo)
86+
const ipfs = create({
87+
url: 'http://127.0.0.1:5001', // Local IPFS API
88+
});
89+
90+
async function read_from_ipfs(cid) {
91+
// Fetch the block (downloads via Bitswap if not local)
92+
console.log('Trying to get cid: ', cid);
93+
try {
94+
const block = await ipfs.block.get(cid, {timeout: 10000});
95+
console.log('Received block: ', block);
96+
if (block.length !== 0) {
97+
return block;
98+
}
99+
} catch (error) {
100+
console.log('Block not found directly, trying cat...', error.message);
101+
}
102+
103+
// Fetch the content from IPFS
104+
console.log('Trying to chunk cid: ', cid);
105+
const chunks = [];
106+
for await (const chunk of ipfs.cat(cid)) {
107+
chunks.push(chunk);
108+
}
109+
110+
const content = Buffer.concat(chunks);
111+
return content;
112+
}
113+
114+
// Global client reference for cleanup
115+
let client;
116+
117+
async function main() {
118+
await cryptoWaitReady();
119+
120+
// Create PAPI client with WebSocket provider
121+
client = createClient(getWsProvider('ws://localhost:10000'));
122+
123+
// Get typed API with generated descriptors
124+
const typedApi = client.getTypedApi(bulletin);
125+
126+
// Create keyring and accounts
127+
const keyring = new Keyring({ type: 'sr25519' });
128+
const sudoAccount = keyring.addFromUri('//Alice');
129+
const whoAccount = keyring.addFromUri('//Alice');
130+
131+
// Create PAPI-compatible signers using @polkadot-api/signer
132+
// getPolkadotSigner expects (publicKey: Uint8Array, signingType, sign function)
133+
const sudoSigner = getPolkadotSigner(
134+
sudoAccount.publicKey,
135+
'Sr25519',
136+
(input) => sudoAccount.sign(input)
137+
);
138+
const whoSigner = getPolkadotSigner(
139+
whoAccount.publicKey,
140+
'Sr25519',
141+
(input) => whoAccount.sign(input)
142+
);
143+
144+
// Data
145+
const who = whoAccount.address;
146+
const transactions = 32; // u32 - regular number
147+
const bytes = 64n * 1024n * 1024n; // u64 - BigInt for large numbers
148+
149+
console.log('Doing authorization...');
150+
await authorizeAccount(typedApi, sudoSigner, who, transactions, bytes);
151+
console.log('Authorized!');
152+
153+
console.log('Storing data ...');
154+
const dataToStore = "Hello, Bulletin with PAPI - " + new Date().toString();
155+
let cid = await store(typedApi, whoSigner, dataToStore);
156+
console.log('Stored data with CID: ', cid);
157+
158+
console.log('Reading content... cid: ', cid);
159+
let content = await read_from_ipfs(cid);
160+
console.log('Content as bytes:', content);
161+
console.log('Content as string:', content.toString());
162+
163+
client.destroy();
164+
}
165+
166+
main().catch(console.error).finally(() => {
167+
if (client) client.destroy();
168+
});
169+

examples/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"type": "module",
3+
"scripts": {
4+
"papi:generate": "papi add -w ws://localhost:10000 bulletin",
5+
"papi:update": "papi"
6+
},
7+
"dependencies": {
8+
"@polkadot-api/descriptors": "file:.papi/descriptors",
9+
"@polkadot-api/substrate-bindings": "^0.16.5",
10+
"@polkadot/api": "^16.5.2",
11+
"@polkadot/keyring": "^13.5.8",
12+
"@polkadot/util": "^13.5.8",
13+
"@polkadot/util-crypto": "^13.5.8",
14+
"fs": "^0.0.1-security",
15+
"ipfs-http-client": "^60.0.1",
16+
"multiformats": "^13.4.1",
17+
"polkadot-api": "^1.20.6",
18+
"smoldot": "^2.0.39"
19+
},
20+
"devDependencies": {
21+
"@polkadot-api/cli": "^0.13.3"
22+
}
23+
}

0 commit comments

Comments
 (0)