1import {ApiPromise, WsProvider} from '@polkadot/api';2import {blake2AsHex, encodeAddress} from '@polkadot/util-crypto';34export const paraChildSovereignAccount = (relayApi: ApiPromise, paraid: number) => {5 6 7 const childPrefix = '70617261';89 const encodedParaId = relayApi10 .createType('u32', paraid)11 .toHex(true)12 .substring(2 );1314 const addrBytesLen = 32;15 const byteLenInHex = 2;16 return '0x' + (childPrefix + encodedParaId).padEnd(addrBytesLen * byteLenInHex, '0');17};1819const proposeOpenChannel = async (relayApi: ApiPromise, relayFee: bigint, senderParaId: number, receiverParaId: number) => {20 const conf: any = await relayApi.query.configuration.activeConfig()21 .then(data => data.toJSON());22 const maxCapacity = conf.hrmpChannelMaxCapacity;23 const maxSize = conf.hrmpChannelMaxMessageSize;2425 const senderDeposit = BigInt(conf.hrmpSenderDeposit);2627 const requiredBalance = relayFee + senderDeposit;28 const sovereignAccount = paraChildSovereignAccount(relayApi, senderParaId);29 const balance = await relayApi.query.system.account(sovereignAccount)30 .then(accountInfo => (accountInfo.toJSON() as any).data.free as bigint);3132 if(balance < requiredBalance) {33 throw Error(`Not enough balance on the sender's sovereign account: balance(${balance}) < requiredBalance(${requiredBalance})`);34 }3536 return relayApi.tx.hrmp.hrmpInitOpenChannel(37 receiverParaId,38 maxCapacity,39 maxSize,40 ).method.toHex();41};4243const proposeAcceptChannel = async (relayApi: ApiPromise, relayFee: bigint, senderParaId: number, recipientParaId: number) => {44 const conf: any = await relayApi.query.configuration.activeConfig()45 .then(data => data.toJSON());46 const recipientDeposit = BigInt(conf.hrmpRecipientDeposit);4748 const requiredBalance = relayFee + recipientDeposit;4950 const sovereignAccount = paraChildSovereignAccount(relayApi, recipientParaId);51 const balance = await relayApi.query.system.account(sovereignAccount)52 .then(accountInfo => (accountInfo.toJSON() as any).data.free as bigint);5354 if(balance < requiredBalance) {55 throw Error(`Not enough balance on the recipient's sovereign account: balance(${balance}) < requiredBalance(${requiredBalance})`);56 }5758 return relayApi.tx.hrmp.hrmpAcceptOpenChannel(senderParaId).method.toHex();59};6061async function main() {62 const relayUrl = process.argv[2]63 const uniqueParachainUrl = process.argv[3];64 const otherParachainUrl = process.argv[4];65 const op = process.argv[5];6667 const relayWsProvider = new WsProvider(relayUrl);68 const relayApi = await ApiPromise.create({provider: relayWsProvider});6970 const uniqueWsProvider = new WsProvider(uniqueParachainUrl);71 const uniqueApi = await ApiPromise.create({provider: uniqueWsProvider});7273 const otherWsProvider = new WsProvider(otherParachainUrl);74 const otherApi = await ApiPromise.create({provider: otherWsProvider});7576 const uniqueParaId = await uniqueApi.query.parachainInfo.parachainId()77 .then(data => data.toJSON() as number);7879 const otherParaId = await otherApi.query.parachainInfo.parachainId()80 .then(data => data.toJSON() as number);8182 const relayDecimals = await relayApi.rpc.system.properties()83 .then(data => data.tokenDecimals.unwrap()[0].toNumber());8485 const relayFee = 2n * BigInt(10 ** relayDecimals);8687 let encodedRelayCall: string;88 if(op == 'open') {89 encodedRelayCall = await proposeOpenChannel(relayApi, relayFee, uniqueParaId, otherParaId);90 } else if(op == 'accept') {91 encodedRelayCall = await proposeAcceptChannel(relayApi, relayFee, otherParaId, uniqueParaId);92 } else {93 throw Error(`Unknown hrmp channel operation: ${op}`);94 }9596 const proposal = uniqueApi.tx.polkadotXcm.send(97 {98 V3: {99 parents: 1,100 interior: 'Here',101 }102 },103 {104 V3: [105 {106 WithdrawAsset: [107 {108 id: {109 Concrete: {110 parents: 0,111 interior: 'Here',112 },113 },114 fun: {115 Fungible: relayFee,116 },117 },118 ],119 },120 {121 BuyExecution: {122 fees: {123 id: {124 Concrete: {125 parents: 0,126 interior: 'Here',127 },128 },129 fun: {130 Fungible: relayFee,131 },132 },133 weightLimit: 'Unlimited',134 },135 },136 {137 Transact: {138 originKind: 'Native',139 requireWeightAtMost: {140 refTime: 1000000000,141 proofSize: 65536,142 },143 call: {144 encoded: encodedRelayCall,145 },146 },147 },148 'RefundSurplus',149 {150 DepositAsset: {151 assets: {152 Wild: {153 AllCounted: 1,154 },155 },156 beneficiary: {157 parents: 0,158 interior: {159 X1: {160 Parachain: uniqueParaId,161 },162 },163 },164 },165 },166 ],167 },168 ).method.toHex();169170 const councilMembers = (await uniqueApi.query.council.members()).toJSON() as any[];171 const councilProposalThreshold = Math.floor(councilMembers.length / 2) + 1;172173 const democracyProposalHash = blake2AsHex(proposal, 256);174 const democracyProposalPreimage = uniqueApi.tx.preimage.notePreimage(proposal).method.toHex();175176 const democracyProposal = uniqueApi.tx.democracy.externalProposeDefault({177 Legacy: democracyProposalHash,178 });179180 const councilProposal = uniqueApi.tx.council.propose(181 councilProposalThreshold,182 democracyProposal,183 democracyProposal.method.encodedLength,184 ).method.toHex();185186 const proposeBatch = uniqueApi.tx.utility.batchAll([187 democracyProposalPreimage,188 councilProposal,189 ]);190191 const encodedCall = proposeBatch.method.toHex();192193 console.log('-----------------');194 console.log('Council Proposal: ', `https://polkadot.js.org/apps/?rpc=${uniqueParachainUrl}#/extrinsics/decode/${encodedCall}`);195 console.log('-----------------');196197 await relayApi.disconnect();198 await uniqueApi.disconnect();199 await otherApi.disconnect();200}201202await main();