@@ -5,7 +5,7 @@ import { createClient } from 'polkadot-api';
55import { getSmProvider } from 'polkadot-api/sm-provider' ;
66import { cryptoWaitReady } from '@polkadot/util-crypto' ;
77import { authorizeAccount , fetchCid , store } from './api.js' ;
8- import { setupKeyringAndSigners } from './common.js' ;
8+ import { setupKeyringAndSigners , waitForChainReady } from './common.js' ;
99import { cidFromBytes } from "./cid_dag_metadata.js" ;
1010import { bulletin } from './.papi/descriptors/dist/index.mjs' ;
1111
@@ -14,53 +14,111 @@ const SYNC_WAIT_SEC = 15;
1414const SMOLDOT_LOG_LEVEL = 3 ; // 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
1515const HTTP_IPFS_API = 'http://127.0.0.1:8080' // Local IPFS HTTP gateway
1616
17+ const TCP_BOOTNODE_REGEX = / ^ ( \/ i p [ 4 6 ] \/ [ ^ / ] + ) \/ t c p \/ ( \d + ) \/ p 2 p \/ ( .+ ) $ / ;
18+ const WS_BOOTNODE_REGEX = / \/ t c p \/ \d + \/ w s \/ p 2 p \/ / ;
19+
20+ /**
21+ * Converts a TCP bootnode to WebSocket format for smoldot compatibility.
22+ * Uses convention: WebSocket port = TCP p2p_port + 1
23+ *
24+ * Example: /ip4/127.0.0.1/tcp/30333/p2p/PEER_ID -> /ip4/127.0.0.1/tcp/30334/ws/p2p/PEER_ID
25+ */
26+ function convertBootNodeToWebSocket ( addr ) {
27+ // Already a WebSocket address
28+ if ( WS_BOOTNODE_REGEX . test ( addr ) ) {
29+ console . log ( ` ✅ Already WebSocket: ${ addr . substring ( 0 , 50 ) } ...` ) ;
30+ return addr ;
31+ }
32+
33+ const match = addr . match ( TCP_BOOTNODE_REGEX ) ;
34+ if ( match ) {
35+ const [ , hostPart , portStr , peerId ] = match ;
36+ const wsPort = parseInt ( portStr , 10 ) + 1 ;
37+ console . log ( ` 📡 Converted: tcp/${ portStr } -> tcp/${ wsPort } /ws` ) ;
38+ return `${ hostPart } /tcp/${ wsPort } /ws/p2p/${ peerId } ` ;
39+ }
40+
41+ return null ;
42+ }
43+
1744function readChainSpec ( chainspecPath ) {
18- const chainSpecContent = readFileSync ( chainspecPath , 'utf8' ) ;
19- const chainSpecObj = JSON . parse ( chainSpecContent ) ;
45+ const chainSpecObj = JSON . parse ( readFileSync ( chainspecPath , 'utf8' ) ) ;
2046 chainSpecObj . protocolId = null ;
47+
48+ const bootNodes = chainSpecObj . bootNodes || [ ] ;
49+ if ( bootNodes . length === 0 ) {
50+ console . log ( `⚠️ No bootnodes found in chain spec: ${ chainspecPath } ` ) ;
51+ return JSON . stringify ( chainSpecObj ) ;
52+ }
53+
54+ console . log ( `🔄 Converting ${ bootNodes . length } bootnode(s) to WebSocket for smoldot...` ) ;
55+ const wsBootNodes = bootNodes . map ( convertBootNodeToWebSocket ) . filter ( Boolean ) ;
56+
57+ if ( wsBootNodes . length > 0 ) {
58+ chainSpecObj . bootNodes = wsBootNodes ;
59+ console . log ( `✅ Using ${ wsBootNodes . length } WebSocket bootnode(s)` ) ;
60+ } else {
61+ console . log ( `⚠️ No bootnodes could be converted to WebSocket` ) ;
62+ }
63+
2164 return JSON . stringify ( chainSpecObj ) ;
2265}
2366
2467function initSmoldot ( ) {
25- const sd = smoldot . start ( {
68+ return smoldot . start ( {
2669 maxLogLevel : SMOLDOT_LOG_LEVEL ,
2770 logCallback : ( level , target , message ) => {
28- const levelNames = [ 'ERROR' , 'WARN' , 'INFO' , 'DEBUG' , 'TRACE' ] ;
29- const levelName = levelNames [ level - 1 ] || 'UNKNOWN' ;
71+ const levelName = [ 'ERROR' , 'WARN' , 'INFO' , 'DEBUG' , 'TRACE' ] [ level - 1 ] || 'UNKNOWN' ;
3072 console . log ( `[smoldot:${ levelName } ] ${ target } : ${ message } ` ) ;
3173 }
3274 } ) ;
33- return sd ;
3475}
3576
36- async function createSmoldotClient ( chainspecPath ) {
37- const chainSpec = readChainSpec ( chainspecPath ) ;
77+ async function createSmoldotClient ( chainSpecPath , parachainSpecPath = null ) {
3878 const sd = initSmoldot ( ) ;
39- const chain = await sd . addChain ( { chainSpec } ) ;
40- const client = createClient ( getSmProvider ( chain ) ) ;
41-
42- return { client, sd } ;
79+
80+ const mainChain = await sd . addChain ( { chainSpec : readChainSpec ( chainSpecPath ) } ) ;
81+ console . log ( `✅ Added main chain: ${ chainSpecPath } ` ) ;
82+
83+ let targetChain = mainChain ;
84+ if ( parachainSpecPath ) {
85+ targetChain = await sd . addChain ( {
86+ chainSpec : readChainSpec ( parachainSpecPath ) ,
87+ potentialRelayChains : [ mainChain ]
88+ } ) ;
89+ console . log ( `✅ Added parachain: ${ parachainSpecPath } ` ) ;
90+ }
91+
92+ return { client : createClient ( getSmProvider ( targetChain ) ) , sd } ;
4393}
4494
4595async function main ( ) {
4696 await cryptoWaitReady ( ) ;
4797
48- // Get chainspec path from command line argument
49- const chainspecPath = process . argv [ 2 ] ;
50- if ( ! chainspecPath ) {
51- console . error ( '❌ Error: Chainspec path is required as first argument' ) ;
52- console . error ( 'Usage: node authorize_and_store_papi_smoldot.js <chainspec-path>' ) ;
98+ // Get chainspec path from command line argument (required - main chain: relay for para, or solo)
99+ const chainSpecPath = process . argv [ 2 ] ;
100+ if ( ! chainSpecPath ) {
101+ console . error ( '❌ Error: Chain spec path is required as first argument' ) ;
102+ console . error ( 'Usage: node authorize_and_store_papi_smoldot.js <chain-spec-path> [parachain-spec-path]' ) ;
103+ console . error ( ' For parachains: <relay-chain-spec-path> <parachain-spec-path>' ) ;
104+ console . error ( ' For solochains: <solo-chain-spec-path>' ) ;
53105 process . exit ( 1 ) ;
54106 }
55107
108+ // Optional parachain chainspec path (only needed for parachains)
109+ const parachainSpecPath = process . argv [ 3 ] || null ;
110+
56111 let sd , client , resultCode ;
57112 try {
58113 // Init Smoldot PAPI client and typed api.
59- ( { client, sd } = await createSmoldotClient ( chainspecPath ) ) ;
114+ ( { client, sd } = await createSmoldotClient ( chainSpecPath , parachainSpecPath ) ) ;
60115 console . log ( `⏭️ Waiting ${ SYNC_WAIT_SEC } seconds for smoldot to sync...` ) ;
61116 // TODO: check better way, when smoldot is synced, maybe some RPC/runtime api that checks best vs finalized block?
62117 await new Promise ( resolve => setTimeout ( resolve , SYNC_WAIT_SEC * 1000 ) ) ;
118+
119+ console . log ( '🔍 Checking if chain is ready...' ) ;
63120 const bulletinAPI = client . getTypedApi ( bulletin ) ;
121+ await waitForChainReady ( bulletinAPI ) ;
64122
65123 // Signers.
66124 const { sudoSigner, whoSigner, whoAddress } = setupKeyringAndSigners ( '//Alice' , '//Alice' ) ;
0 commit comments