@@ -12,118 +12,168 @@ import { Binary } from '@polkadot-api/substrate-bindings';
1212// Generate PAPI descriptors using local node:
1313// npx papi add -w ws://localhost:10000 bulletin
1414// npx papi
15- async function main ( ) {
16- await cryptoWaitReady ( ) ;
17-
18- // Bob's address - to get the chainspec
19- console . log ( 'Fetching chainspec from Bob node...' ) ;
20- const bobWs = new WsProvider ( 'ws://localhost:12346' ) ;
21- const bobApi = await ApiPromise . create ( { provider : bobWs } ) ;
22- await bobApi . isReady ;
23-
24- // Create keyring and accounts
25- const keyring = new Keyring ( { type : 'sr25519' } ) ;
26- const sudoAccount = keyring . addFromUri ( '//Alice' ) ;
27- const whoAccount = keyring . addFromUri ( '//Alice' ) ;
28- const sudoSigner = getPolkadotSigner (
29- sudoAccount . publicKey ,
30- 'Sr25519' ,
31- ( input ) => sudoAccount . sign ( input )
32- ) ;
33- const whoSigner = getPolkadotSigner (
34- whoAccount . publicKey ,
35- 'Sr25519' ,
36- ( input ) => whoAccount . sign ( input )
37- ) ;
3815
39- // Data
40- const who = whoAccount . publicKey ;
41- const transactions = 32 ;
42- const bytes = 64n * 1024n * 1024n ; // 64 MB
16+ // Constants
17+ const BOB_NODE_WS = 'ws://localhost:12346' ;
18+ const ALICE_ADDRESS = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" ;
19+ const SYNC_WAIT_MS = 15000 ;
20+ const SMOLDOT_LOG_LEVEL = 1 ; // 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
4321
44- // Prepare data for storage
45- const dataToStore = "Hello, Bulletin with PAPI + Smoldot - " + new Date ( ) . toString ( ) ;
46- const expectedCid = cidFromBytes ( dataToStore ) ;
22+ // Authorization parameters
23+ const AUTH_TRANSACTIONS = 32 ;
24+ const AUTH_BYTES = 64n * 1024n * 1024n ; // 64 MB
4725
48- // Note: In real usage, this step is not required — the chain spec with bootNodes should be included as part of the dApp.
49- // For local testing, we use this to fetch the actual chain spec from the local node.
50- // Get chain spec from Bob node and remove protocolId to allow smoldot to sync with local chain.
51- // Use false to get full genesis spec, not light sync spec starting at finalized block
52- const chainSpec = ( await bobApi . rpc . syncstate . genSyncSpec ( true ) ) . toString ( ) ;
26+ /**
27+ * Fetches and modifies the chain spec from a node for local testing
28+ * Note: In production, the chain spec with bootNodes should be bundled with the dApp
29+ */
30+ async function fetchChainSpec ( nodeWs ) {
31+ console . log ( 'Fetching chainspec from node...' ) ;
32+ const provider = new WsProvider ( nodeWs ) ;
33+ const api = await ApiPromise . create ( { provider } ) ;
34+ await api . isReady ;
35+
36+ const chainSpec = ( await api . rpc . syncstate . genSyncSpec ( true ) ) . toString ( ) ;
5337 const chainSpecObj = JSON . parse ( chainSpec ) ;
54- chainSpecObj . protocolId = null ;
55- const modifiedChainSpec = JSON . stringify ( chainSpecObj ) ;
38+ chainSpecObj . protocolId = null ; // Allow smoldot to sync with local chain
39+
40+ await api . disconnect ( ) ;
41+ return JSON . stringify ( chainSpecObj ) ;
42+ }
5643
57- // Initialize Smoldot client
44+ /**
45+ * Initializes Smoldot client with logging
46+ */
47+ function initSmoldot ( ) {
5848 const sd = smoldot . start ( {
59- maxLogLevel : 4 , // 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
49+ maxLogLevel : SMOLDOT_LOG_LEVEL ,
6050 logCallback : ( level , target , message ) => {
6151 const levelNames = [ 'ERROR' , 'WARN' , 'INFO' , 'DEBUG' , 'TRACE' ] ;
6252 const levelName = levelNames [ level - 1 ] || 'UNKNOWN' ;
6353 console . log ( `[smoldot:${ levelName } ] ${ target } : ${ message } ` ) ;
6454 }
6555 } ) ;
66- const chain = await sd . addChain ( { chainSpec : modifiedChainSpec } ) ;
67- const client = createClient ( getSmProvider ( chain ) ) ;
68- const bulletinAPI = client . getTypedApi ( bulletin ) ;
56+ return sd ;
57+ }
58+
59+ /**
60+ * Creates signers for accounts
61+ */
62+ function createSigner ( account ) {
63+ return getPolkadotSigner (
64+ account . publicKey ,
65+ 'Sr25519' ,
66+ ( input ) => account . sign ( input )
67+ ) ;
68+ }
6969
70- console . log ( '⏭️ Waiting for 15 seconds for smoldot to sync...' ) ;
71- await new Promise ( resolve => setTimeout ( resolve , 15000 ) ) ;
70+ /**
71+ * Waits for a transaction to complete using observables
72+ */
73+ function waitForTransaction ( tx , signer , eventPrefix = "tx" ) {
74+ return new Promise ( ( resolve , reject ) => {
75+ tx . signSubmitAndWatch ( signer ) . subscribe ( {
76+ next : ( ev ) => {
77+ console . log ( `✅ ${ eventPrefix } event:` , ev . type ) ;
78+ if ( ev . type === "txBestBlocksState" && ev . found ) {
79+ console . log ( `✅ ${ eventPrefix } included in block:` , ev . block . hash ) ;
80+ }
81+ } ,
82+ error : ( err ) => {
83+ console . error ( `❌ ${ eventPrefix } error:` , err ) ;
84+ reject ( err ) ;
85+ } ,
86+ complete : ( ) => {
87+ console . log ( `✅ ${ eventPrefix } complete!` ) ;
88+ resolve ( ) ;
89+ }
90+ } ) ;
91+ } ) ;
92+ }
7293
73- const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" ;
94+ /**
95+ * Authorizes an account for transaction storage
96+ */
97+ async function authorizeAccount ( bulletinAPI , signer , who , transactions , bytes ) {
98+ console . log ( 'Authorizing account...' ) ;
7499 const authorizeTx = bulletinAPI . tx . TransactionStorage . authorize_account ( {
75- who : ALICE ,
100+ who,
76101 transactions,
77102 bytes
78103 } ) ;
79104 const sudoTx = bulletinAPI . tx . Sudo . sudo ( {
80105 call : authorizeTx . decodedCall
81106 } ) ;
82107
83- sudoTx . signSubmitAndWatch ( sudoSigner ) . subscribe ( {
84- next : ( ev ) => {
85- console . log ( "✅ Authorize event: " , ev . type )
86- if ( ev . type === "txBestBlocksState" && ev . found ) {
87- console . log ( "✅ Authorization included in block:" , ev . block . hash )
88- }
89- } ,
90- error : ( err ) => {
91- console . error ( "❌ authorize error: " , err )
92- client . destroy ( )
93- sd . terminate ( )
94- process . exit ( 1 ) ;
95- } ,
96- complete ( ) {
97- console . log ( "✅ Authorized! Now storing data..." ) ;
98-
99- // Convert data to Uint8Array then wrap in Binary for PAPI typed API
100- const dataBytes = new Uint8Array ( Buffer . from ( dataToStore ) ) ;
101- const binaryData = Binary . fromBytes ( dataBytes ) ;
102-
103- bulletinAPI . tx . TransactionStorage . store ( { data : binaryData } )
104- . signSubmitAndWatch ( whoSigner ) . subscribe ( {
105- next : ( ev ) => {
106- console . log ( "⏭️ Store event: " , ev . type ) ;
107- if ( ev . type === "txBestBlocksState" && ev . found ) {
108- console . log ( "✅ Data stored in block:" , ev . block . hash ) ;
109- console . log ( "✅ Expected CID:" , expectedCid ) ;
110- }
111- } ,
112- error : ( err ) => {
113- console . error ( "❌ store error: " , err ) ;
114- client . destroy ( ) ;
115- sd . terminate ( ) ;
116- process . exit ( 1 ) ;
117- } ,
118- complete ( ) {
119- console . log ( "✅ Complete! Data stored successfully." ) ;
120- client . destroy ( ) ;
121- sd . terminate ( ) ;
122- process . exit ( 0 ) ;
123- } ,
124- } ) ;
125- } ,
126- } ) ;
108+ await waitForTransaction ( sudoTx , signer , "Authorize" ) ;
109+ }
110+
111+ /**
112+ * Stores data to the bulletin chain
113+ */
114+ async function storeData ( bulletinAPI , signer , data ) {
115+ console . log ( 'Storing data...' ) ;
116+ const dataBytes = new Uint8Array ( Buffer . from ( data ) ) ;
117+ const binaryData = Binary . fromBytes ( dataBytes ) ;
118+
119+ const storeTx = bulletinAPI . tx . TransactionStorage . store ( { data : binaryData } ) ;
120+ await waitForTransaction ( storeTx , signer , "Store" ) ;
121+
122+ const expectedCid = cidFromBytes ( data ) ;
123+ console . log ( "✅ Expected CID:" , expectedCid ) ;
124+ return expectedCid ;
125+ }
126+
127+ async function main ( ) {
128+ await cryptoWaitReady ( ) ;
129+
130+ let sd , client ;
131+
132+ try {
133+ // Setup keyring and accounts
134+ const keyring = new Keyring ( { type : 'sr25519' } ) ;
135+ const sudoAccount = keyring . addFromUri ( '//Alice' ) ;
136+ const whoAccount = keyring . addFromUri ( '//Alice' ) ;
137+ const sudoSigner = createSigner ( sudoAccount ) ;
138+ const whoSigner = createSigner ( whoAccount ) ;
139+
140+ // Prepare data for storage
141+ const dataToStore = "Hello, Bulletin with PAPI + Smoldot - " + new Date ( ) . toString ( ) ;
142+
143+ // Fetch chain spec and initialize Smoldot
144+ const chainSpec = await fetchChainSpec ( BOB_NODE_WS ) ;
145+ sd = initSmoldot ( ) ;
146+
147+ const chain = await sd . addChain ( { chainSpec } ) ;
148+ client = createClient ( getSmProvider ( chain ) ) ;
149+ const bulletinAPI = client . getTypedApi ( bulletin ) ;
150+
151+ // Wait for smoldot to sync
152+ console . log ( `⏭️ Waiting ${ SYNC_WAIT_MS / 1000 } seconds for smoldot to sync...` ) ;
153+ await new Promise ( resolve => setTimeout ( resolve , SYNC_WAIT_MS ) ) ;
154+
155+ // Execute authorization and storage sequentially
156+ await authorizeAccount (
157+ bulletinAPI ,
158+ sudoSigner ,
159+ ALICE_ADDRESS ,
160+ AUTH_TRANSACTIONS ,
161+ AUTH_BYTES
162+ ) ;
163+
164+ await storeData ( bulletinAPI , whoSigner , dataToStore ) ;
165+
166+ console . log ( "✅ Data stored successfully." ) ;
167+
168+ } catch ( error ) {
169+ console . error ( "❌ Error:" , error ) ;
170+ process . exit ( 1 ) ;
171+ } finally {
172+ // Cleanup
173+ if ( client ) client . destroy ( ) ;
174+ if ( sd ) sd . terminate ( ) ;
175+ process . exit ( 0 ) ;
176+ }
127177}
128178
129179await main ( ) ;
0 commit comments