Last active
November 6, 2025 10:43
-
-
Save franciscoaguirre/17de599e41477c04db8901b1bf051fb4 to your computer and use it in GitHub Desktop.
People Hollar DCA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { XcmV5Junction, XcmV5Junctions } from "@polkadot-api/descriptors"; | |
| // ===== Dev accounts ===== | |
| export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; | |
| // ===== Parachain IDs ===== | |
| export const PEOPLE_PARA_ID = 1004; | |
| export const HYDRATION_PARA_ID = 2034; | |
| export const ASSET_HUB_PARA_ID = 1000; | |
| export const PEOPLE_PALLET_INSTANCE = 53; | |
| // ===== Token Constants ===== | |
| export const DOT_UNITS = 10_000_000_000n; // 10 decimals. | |
| export const HOLLAR_UNITS = 1_000_000_000_000_000_000n; // 18 decimals. | |
| export const HOLLAR_CENTS = 10_000_000_000_000_000n; // 16 decimals. | |
| export const USDX_UNITS = 1_000_000n; // 6 decimals. | |
| // From the perspective of the relay. | |
| export const DOT_ID_FROM_RELAY = { | |
| parents: 0, | |
| interior: XcmV5Junctions.Here(), | |
| }; | |
| // From the perspective of Asset Hub. | |
| export const DOT_ID = { parents: 1, interior: XcmV5Junctions.Here() }; | |
| export const USDT_ID = { | |
| parents: 0, | |
| interior: XcmV5Junctions.X2([ | |
| XcmV5Junction.PalletInstance(50), | |
| XcmV5Junction.GeneralIndex(1984n), | |
| ]), | |
| }; | |
| export const USDC_ID = { | |
| parents: 0, | |
| interior: XcmV5Junctions.X2([ | |
| XcmV5Junction.PalletInstance(50), | |
| XcmV5Junction.GeneralIndex(1337n), | |
| ]), | |
| }; | |
| // From the perspective of Hydration. | |
| export const USDT_ID_FROM_HYDRATION = { | |
| parents: 1, | |
| interior: XcmV5Junctions.X3([ | |
| XcmV5Junction.Parachain(ASSET_HUB_PARA_ID), | |
| XcmV5Junction.PalletInstance(50), | |
| XcmV5Junction.GeneralIndex(1984n), | |
| ]), | |
| }; | |
| // ===== Treasury Spend Parameters ===== | |
| export const USDT_FOR_FEES = 200n * USDX_UNITS; | |
| export const USDT_FOR_DCA = 1_501_200n * USDX_UNITS; | |
| export const USDT_SPEND_AMOUNT = USDT_FOR_DCA + USDT_FOR_FEES; | |
| export const USDC_FOR_FEES = 200n * USDX_UNITS; | |
| export const USDC_FOR_DCA = 1_501_200n * USDX_UNITS; | |
| export const USDC_SPEND_AMOUNT = USDC_FOR_DCA + USDC_FOR_FEES; | |
| export const SPEND_AMOUNT = USDT_SPEND_AMOUNT + USDC_SPEND_AMOUNT; // In both USDT and USDC. | |
| export const TREASURY_ACCOUNT = | |
| "13UVJyLnbVp9RBZYFwFGyDvVd1y27Tt8tkntv6Q7JVPhFsTB"; | |
| // ===== Hydration Asset IDs ===== | |
| export const HYDRATION_DOT_ID = 5; | |
| export const HYDRATION_HOLLAR_ID = 222; | |
| export const HYDRATION_USDC_ID = 22; | |
| export const HYDRATION_USDT_ID = 10; | |
| export const HOLLAR_LOCATION_FROM_HYDRATION = { | |
| parents: 0, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.GeneralIndex(BigInt(HYDRATION_HOLLAR_ID)), | |
| ), | |
| }; | |
| // ===== DCA Parameters ===== | |
| // We want to DCA for 10 days. There are 57_600 blocks in 4 days. | |
| // If we do a trade every 20 blocks, we'll do 2880 trades in total. | |
| // If we sell 278 DOT in each trade, we'll have sold 800640 DOT at the end | |
| // of the 4 days. | |
| export const DCA_FREQUENCY = 20; // Do a trade every 20 blocks, approx 2 minutes. | |
| export const USDX_IN_PER_TRADE = 417n * USDX_UNITS; // Max sell USDX each trade. | |
| export const MIN_HOLLAR_OUT_PER_TRADE = 375n * HOLLAR_UNITS; // Min buy HOLLAR each trade. | |
| export const DCA_MAX_RETRIES = 10; | |
| export const SLIPPAGE_LIMIT = 10_000; // 1%. It's per million. | |
| export const STABILITY_THRESHOLD = 20_000; // 2%. It's per million. | |
| export const WARM_UP_PERIOD = 2; // Blocks before DCA is scheduled. | |
| // ==== Time ==== | |
| export const MINUTES = 10; // Ten block since a block is 6 seconds so 6 * 10 = 60. | |
| export const HOURS = 60 * MINUTES; | |
| export const DAYS = 24 * HOURS; | |
| export const DCA_DURATION = 10 * DAYS; // In blocks. | |
| export const NUMBER_OF_TRADES = DCA_DURATION / DCA_FREQUENCY; // 7200 trades. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| DispatchRawOrigin, | |
| HdxXcmVersionedLocation, | |
| XcmV2OriginKind, | |
| XcmV3MultiassetFungibility, | |
| XcmV3WeightLimit, | |
| XcmV5AssetFilter, | |
| XcmV5Instruction, | |
| XcmV5Junction, | |
| XcmV5Junctions, | |
| XcmV5WildAsset, | |
| XcmVersionedLocation, | |
| XcmVersionedXcm, | |
| } from "@polkadot-api/descriptors"; | |
| import { Binary, Enum, FixedSizeBinary, type SS58String } from "polkadot-api"; | |
| import { getPolkadotSigner } from "@polkadot-api/signer"; | |
| import { sr25519CreateDerive } from "@polkadot-labs/hdkd"; | |
| import { | |
| entropyToMiniSecret, | |
| mnemonicToEntropy, | |
| DEV_PHRASE, | |
| } from "@polkadot-labs/hdkd-helpers"; | |
| import type { ChopstickClients } from "./setup.js"; | |
| import { | |
| DOT_UNITS, | |
| TREASURY_ACCOUNT, | |
| PEOPLE_PARA_ID, | |
| HYDRATION_PARA_ID, | |
| HYDRATION_HOLLAR_ID, | |
| DCA_FREQUENCY, | |
| MIN_HOLLAR_OUT_PER_TRADE, | |
| DCA_MAX_RETRIES, | |
| SLIPPAGE_LIMIT, | |
| WARM_UP_PERIOD, | |
| USDT_ID, | |
| USDT_SPEND_AMOUNT, | |
| USDX_UNITS, | |
| USDC_ID, | |
| USDC_SPEND_AMOUNT, | |
| USDT_ID_FROM_HYDRATION, | |
| HYDRATION_USDC_ID, | |
| HYDRATION_USDT_ID, | |
| USDX_IN_PER_TRADE, | |
| DOT_ID, | |
| ASSET_HUB_PARA_ID, | |
| STABILITY_THRESHOLD, | |
| } from "./constants.js"; | |
| // The location we want to use to hold funds in Hydration during DCA | |
| // and to hold funds in Asset Hub post DCA. | |
| export const peopleLocationFromPara = { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(PEOPLE_PARA_ID)), | |
| }; | |
| // ===== Helper functions ===== | |
| export async function getXcmWeight( | |
| clients: ChopstickClients, | |
| message: XcmVersionedXcm, | |
| ): Promise<{ ref_time: bigint; proof_size: bigint } | null> { | |
| const maxWeightQuery = | |
| await clients.ahApi.apis.XcmPaymentApi.query_xcm_weight(message); | |
| if (maxWeightQuery.success) { | |
| return maxWeightQuery.value; | |
| } else { | |
| return null; | |
| } | |
| } | |
| export async function getSovAccountOnHydration( | |
| clients: ChopstickClients, | |
| location: { parents: number; interior: XcmV5Junctions }, | |
| ): Promise<SS58String | null> { | |
| const query = | |
| await clients.hydrationApi.apis.LocationToAccountApi.convert_location( | |
| HdxXcmVersionedLocation.V4(location), | |
| ); | |
| if (query.success) { | |
| return query.value; | |
| } else { | |
| return null; | |
| } | |
| } | |
| // ===== Signer Setup ===== | |
| function createAliceSigner() { | |
| const entropy = mnemonicToEntropy(DEV_PHRASE); | |
| const miniSecret = entropyToMiniSecret(entropy); | |
| const derive = sr25519CreateDerive(miniSecret); | |
| const keyPair = derive("//Alice"); | |
| return getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign); | |
| } | |
| // ===== Preimage Storage ===== | |
| export async function storePreimage( | |
| clients: ChopstickClients, | |
| callData: Binary, | |
| ) { | |
| console.log("π Storing preimage on asset hub..."); | |
| const signer = createAliceSigner(); | |
| const preimageCall = clients.ahApi.tx.Preimage.note_preimage({ | |
| bytes: callData, | |
| }); | |
| // Submit the transaction | |
| const tx = await preimageCall.signAndSubmit(signer); | |
| let preimageHash: string | null = null; | |
| // Find the Preimage.Noted events. | |
| tx.events.find((event) => { | |
| if (event.type === "Preimage" && event.value.type === "Noted") { | |
| preimageHash = event.value.value.hash.asHex(); | |
| } | |
| }); | |
| if (!preimageHash) { | |
| throw new Error("Failed to get preimage hash from events"); | |
| } | |
| console.log(`β Preimage stored with hash: ${preimageHash}`); | |
| return { | |
| preimageHash, | |
| preimageCall, | |
| }; | |
| } | |
| // Tries to get a preimage, returns null if not there. | |
| export async function getPreimage( | |
| clients: ChopstickClients, | |
| hash: FixedSizeBinary<32>, | |
| length: number, | |
| ): Promise<Binary | undefined> { | |
| return await clients.ahApi.query.Preimage.PreimageFor.getValue([ | |
| hash, | |
| length, | |
| ]); | |
| } | |
| // ===== Governance Execution via Scheduler Storage Manipulation ===== | |
| export async function executeGovernanceCall( | |
| clients: ChopstickClients, | |
| preimageHash: string, | |
| callSize: number, | |
| ) { | |
| console.log( | |
| "βοΈ Executing governance call via scheduler storage manipulation...", | |
| ); | |
| // Get current block number to schedule execution in the next block | |
| const executeAtBlock = | |
| await clients.ahApi.query.ParachainSystem.LastRelayChainBlockNumber.getValue(); | |
| console.log(`π Scheduling governance execution at block ${executeAtBlock}`); | |
| // Use dev_setStorage to add this to the scheduler agenda | |
| await clients.ahClient._request("dev_setStorage", [ | |
| { | |
| scheduler: { | |
| agenda: [ | |
| [ | |
| [executeAtBlock], | |
| [ | |
| { | |
| call: { | |
| Lookup: { | |
| hash: preimageHash, | |
| len: callSize, // The call size from our governance call | |
| }, | |
| }, | |
| origin: { | |
| system: "Root", | |
| }, | |
| }, | |
| ], | |
| ], | |
| ], | |
| }, | |
| }, | |
| ]); | |
| console.log( | |
| `β Governance call scheduled for execution at block ${executeAtBlock}`, | |
| ); | |
| return { | |
| executeAtBlock, | |
| }; | |
| } | |
| // ===== Governance Call Construction ===== | |
| export async function buildGovernanceCall(clients: ChopstickClients) { | |
| console.log("ποΈ Building governance call..."); | |
| // Get sovereign accounts | |
| const peopleSovAccount = await getSovAccountOnHydration( | |
| clients, | |
| peopleLocationFromPara, | |
| ); | |
| if (!peopleSovAccount) { | |
| throw new Error("Failed to get sovereign account addresses"); | |
| } | |
| console.log(`β People sovereign account on Hydration: ${peopleSovAccount}`); | |
| // XCM that sends `SPEND_AMOUNT` of USDT and USDC from the treasury to Hydration and puts it all | |
| // on the people para sovereign account. | |
| const xcmSendStablesToAccountOnHydration = XcmVersionedXcm.V5([ | |
| // Get some DOT. | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: DOT_ID, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * DOT_UNITS), | |
| }, | |
| ]), | |
| // Pay local fees. | |
| XcmV5Instruction.PayFees({ | |
| asset: { | |
| id: DOT_ID, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * DOT_UNITS), | |
| }, | |
| }), | |
| // Get the stables. | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: USDC_ID, | |
| fun: XcmV3MultiassetFungibility.Fungible(USDC_SPEND_AMOUNT), | |
| }, | |
| { | |
| id: USDT_ID, | |
| // We get some more to pay remote fees now and in the future. | |
| fun: XcmV3MultiassetFungibility.Fungible( | |
| USDT_SPEND_AMOUNT + 2n * USDX_UNITS, | |
| ), | |
| }, | |
| ]), | |
| // Send everything to Hydration. | |
| XcmV5Instruction.DepositReserveAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(2)), | |
| dest: { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(HYDRATION_PARA_ID)), | |
| }, | |
| xcm: [ | |
| // We have to use `BuyExecution` instead of `PayFees` because Hydration | |
| // doesn't support V5. | |
| XcmV5Instruction.BuyExecution({ | |
| fees: { | |
| id: USDT_ID_FROM_HYDRATION, | |
| // We use 2 units so we get back at least 1 to deposit for future fee payment. | |
| fun: XcmV3MultiassetFungibility.Fungible(2n * USDX_UNITS), | |
| }, | |
| weight_limit: XcmV3WeightLimit.Unlimited(), | |
| }), | |
| // We deposit the USDT for fees into our sovereign account for later. | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Definite([ | |
| { | |
| id: USDT_ID_FROM_HYDRATION, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * USDX_UNITS), | |
| }, | |
| ]), | |
| beneficiary: { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.Parachain(ASSET_HUB_PARA_ID), | |
| ), | |
| }, | |
| }), | |
| // And all the rest into people's sovereign account. | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(2)), | |
| // We use this location to generate the sovereign account. | |
| beneficiary: peopleLocationFromPara, | |
| }), | |
| ], | |
| }), | |
| // We refund all leftover fees and return them to the treasury. | |
| XcmV5Instruction.RefundSurplus(), | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(2)), | |
| beneficiary: { | |
| parents: 0, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.AccountId32({ | |
| network: undefined, | |
| id: FixedSizeBinary.fromAccountId32(TREASURY_ACCOUNT), | |
| }), | |
| ), | |
| }, | |
| }), | |
| ]); | |
| // We create the treasury spend tx. | |
| const maxWeight = await getXcmWeight( | |
| clients, | |
| xcmSendStablesToAccountOnHydration, | |
| ); | |
| const treasurySpendTx = clients.ahApi.tx.Utility.dispatch_as({ | |
| as_origin: Enum("system", DispatchRawOrigin.Signed(TREASURY_ACCOUNT)), | |
| call: clients.ahApi.tx.PolkadotXcm.execute({ | |
| message: xcmSendStablesToAccountOnHydration, | |
| max_weight: maxWeight!, | |
| }).decodedCall, | |
| }); | |
| // We need to schedule 2 DCAs on Hydration. | |
| const usdcHollarDcaTx = clients.hydrationApi.tx.DCA.schedule({ | |
| schedule: { | |
| // The owner is people's sovereign account. | |
| owner: peopleSovAccount, | |
| // How often we want to make a trade. | |
| period: DCA_FREQUENCY, | |
| total_amount: USDC_SPEND_AMOUNT, | |
| max_retries: DCA_MAX_RETRIES, | |
| stability_threshold: STABILITY_THRESHOLD, | |
| slippage: SLIPPAGE_LIMIT, | |
| order: Enum("Sell", { | |
| // We want to sell USDC. | |
| asset_in: HYDRATION_USDC_ID, | |
| // To buy HOLLAR. | |
| asset_out: HYDRATION_HOLLAR_ID, | |
| // Minimum HOLLAR we need to get each trade. | |
| min_amount_out: MIN_HOLLAR_OUT_PER_TRADE, | |
| // USDC we want to sell each trade. | |
| amount_in: USDX_IN_PER_TRADE, | |
| route: [], | |
| }), | |
| }, | |
| start_execution_block: undefined, | |
| }); | |
| const usdtHollarDcaTx = clients.hydrationApi.tx.DCA.schedule({ | |
| schedule: { | |
| // The owner is people's sovereign account. | |
| owner: peopleSovAccount, | |
| // How often we want to make a trade. | |
| period: DCA_FREQUENCY, | |
| total_amount: USDT_SPEND_AMOUNT, | |
| max_retries: DCA_MAX_RETRIES, | |
| stability_threshold: STABILITY_THRESHOLD, | |
| slippage: SLIPPAGE_LIMIT, | |
| order: Enum("Sell", { | |
| // We want to sell USDT. | |
| asset_in: HYDRATION_USDT_ID, | |
| // To buy HOLLAR. | |
| asset_out: HYDRATION_HOLLAR_ID, | |
| // Minimum HOLLAR we need to get each trade. | |
| min_amount_out: MIN_HOLLAR_OUT_PER_TRADE, | |
| // USDT we want to sell each trade. | |
| amount_in: USDX_IN_PER_TRADE, | |
| route: [], | |
| }), | |
| }, | |
| start_execution_block: undefined, | |
| }); | |
| // The XCM that schedules the HOLLAR DCA on Hydration. | |
| const xcmForHollarDca = XcmVersionedXcm.V5([ | |
| // We withdraw funds for paying fees from the sovereign account. | |
| // The ones we specifically left before. | |
| // We do it this way because of the barrier. | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: USDT_ID_FROM_HYDRATION, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * USDX_UNITS), | |
| }, | |
| ]), | |
| // Pay fees. | |
| XcmV5Instruction.BuyExecution({ | |
| fees: { | |
| id: USDT_ID_FROM_HYDRATION, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * USDX_UNITS), | |
| }, | |
| weight_limit: XcmV3WeightLimit.Unlimited(), | |
| }), | |
| // Now we want to be People Chain to set up the DCAs. | |
| XcmV5Instruction.AliasOrigin({ | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(PEOPLE_PARA_ID)), | |
| }), | |
| // Enable the USDC->HOLLAR DCA. | |
| XcmV5Instruction.Transact({ | |
| origin_kind: XcmV2OriginKind.SovereignAccount(), | |
| fallback_max_weight: { ref_time: 10_000_000_000n, proof_size: 100_000n }, // Just a guess. | |
| call: await usdcHollarDcaTx.getEncodedData(), | |
| }), | |
| // Enable the USDT->HOLLAR DCA. | |
| XcmV5Instruction.Transact({ | |
| origin_kind: XcmV2OriginKind.SovereignAccount(), | |
| fallback_max_weight: { ref_time: 10_000_000_000n, proof_size: 100_000n }, // Just a guess. | |
| call: await usdtHollarDcaTx.getEncodedData(), | |
| }), | |
| // Refund leftover fees. | |
| XcmV5Instruction.RefundSurplus(), | |
| // And deposit everything again in our account on hydration. | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)), | |
| beneficiary: { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(PEOPLE_PARA_ID)), | |
| }, | |
| }), | |
| ]); | |
| // Now we create a tx on relay to schedule the HOLLAR DCA. | |
| const scheduleDcaTx = clients.ahApi.tx.Scheduler.schedule_after({ | |
| after: WARM_UP_PERIOD, | |
| maybe_periodic: undefined, | |
| priority: 0, | |
| call: clients.ahApi.tx.PolkadotXcm.send({ | |
| dest: XcmVersionedLocation.V5({ | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(HYDRATION_PARA_ID)), | |
| }), | |
| message: xcmForHollarDca, | |
| }).decodedCall, | |
| }); | |
| // The actual tx we want to submit to governance. | |
| const tx = clients.ahApi.tx.Utility.batch_all({ | |
| calls: [ | |
| // Send DOT to an account on Hydration. | |
| treasurySpendTx.decodedCall, | |
| // In 2 blocks, send an XCM to start the HOLLAR DCAs. | |
| scheduleDcaTx.decodedCall, | |
| ], | |
| }); | |
| console.log("β Governance call built successfully"); | |
| return { | |
| tx, | |
| peopleSovAccount, | |
| }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { setupNetworks } from "@acala-network/chopsticks-testing"; | |
| import { createClient, type TypedApi } from "polkadot-api"; | |
| import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat"; | |
| import { getWsProvider } from "polkadot-api/ws-provider"; | |
| import { dot, hdx, polkadotAh } from "@polkadot-api/descriptors"; | |
| import { HYDRATION_PARA_ID, ASSET_HUB_PARA_ID } from "./constants.js"; | |
| export interface ChopstickClients { | |
| relayClient: ReturnType<typeof createClient>; | |
| hydrationClient: ReturnType<typeof createClient>; | |
| ahClient: ReturnType<typeof createClient>; | |
| relayApi: TypedApi<typeof dot>; | |
| hydrationApi: TypedApi<typeof hdx>; | |
| ahApi: TypedApi<typeof polkadotAh>; | |
| cleanup: () => Promise<void>; | |
| } | |
| export async function setupChopsticksNetwork(): Promise<ChopstickClients> { | |
| console.log("π Spawning chopsticks networks..."); | |
| // Configure the networks to spawn | |
| // const networks = await setupNetworks({ | |
| // polkadot: { | |
| // endpoint: "wss://rpc.polkadot.io", | |
| // port: 8000, | |
| // "wasm-override": process.env.POLKADOT_WASM_OVERRIDE, | |
| // }, | |
| // hydration: { | |
| // endpoint: "wss://hydradx-rpc.dwellir.com", | |
| // port: 8001, | |
| // "wasm-override": process.env.HYDRATION_WASM_OVERRIDE, | |
| // }, | |
| // assetHub: { | |
| // endpoint: "wss://polkadot-asset-hub-rpc.polkadot.io", | |
| // port: 8002, | |
| // "wasm-override": process.env.ASSET_HUB_WASM_OVERRIDE, | |
| // }, | |
| // }); | |
| // console.log("β Chopsticks networks spawned successfully"); | |
| // console.log("- Polkadot relay: ws://localhost:8000"); | |
| // console.log("- Hydration: ws://localhost:8001"); | |
| // console.log("- Asset Hub: ws://localhost:8002"); | |
| // Create clients for each network | |
| const relayClient = createClient( | |
| withPolkadotSdkCompat(getWsProvider("ws://localhost:8002")), | |
| ); | |
| const hydrationClient = createClient( | |
| withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), | |
| ); | |
| const ahClient = createClient( | |
| withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), | |
| ); | |
| // Create typed APIs | |
| const relayApi = relayClient.getTypedApi(dot); | |
| const hydrationApi = hydrationClient.getTypedApi(hdx); | |
| const ahApi = ahClient.getTypedApi(polkadotAh); | |
| // Wait for clients to be ready | |
| console.log("β³ Waiting for clients to connect..."); | |
| // Add a small delay to ensure networks are fully ready | |
| await new Promise((resolve) => setTimeout(resolve, 2000)); | |
| try { | |
| // Test connection by getting block numbers | |
| const [relayBlock, hydrationBlock, assetHubBlock] = await Promise.all([ | |
| relayApi.query.System.Number.getValue(), | |
| hydrationApi.query.System.Number.getValue(), | |
| ahApi.query.System.Number.getValue(), | |
| ]); | |
| console.log("β All clients connected successfully"); | |
| console.log( | |
| `π Block numbers - Polkadot: ${relayBlock}, Hydration: ${hydrationBlock}, Asset Hub: ${assetHubBlock}`, | |
| ); | |
| } catch (error) { | |
| console.error("β Failed to connect to clients:", error); | |
| throw error; | |
| } | |
| return { | |
| relayClient, | |
| hydrationClient, | |
| ahClient, | |
| relayApi, | |
| hydrationApi, | |
| ahApi, | |
| cleanup: async () => { | |
| console.log("π§Ή Cleaning up chopsticks networks..."); | |
| // try { | |
| // // Check if each network has a teardown method | |
| // if (networks.polkadot?.teardown) await networks.polkadot.teardown(); | |
| // if (networks.hydration?.teardown) await networks.hydration.teardown(); | |
| // if (networks.assetHub?.teardown) await networks.assetHub.teardown(); | |
| // console.log("β Cleanup complete"); | |
| // } catch (error) { | |
| // console.log("β οΈ Cleanup had some issues:", error.message); | |
| // } | |
| }, | |
| }; | |
| } | |
| // Helper function to get current block numbers from all chains | |
| export async function getCurrentBlocks(clients: ChopstickClients) { | |
| const [relayBlock, hydrationBlock, assetHubBlock] = await Promise.all([ | |
| clients.relayApi.query.System.Number.getValue(), | |
| clients.hydrationApi.query.System.Number.getValue(), | |
| clients.ahApi.query.System.Number.getValue(), | |
| ]); | |
| console.log("π Current blocks:"); | |
| console.log(` Polkadot: ${relayBlock}`); | |
| console.log(` Hydration: ${hydrationBlock}`); | |
| console.log(` Asset Hub: ${assetHubBlock}`); | |
| return { | |
| polkadot: Number(relayBlock), | |
| hydration: Number(hydrationBlock), | |
| assetHub: Number(assetHubBlock), | |
| }; | |
| } | |
| // Helper function to advance blocks on all networks | |
| export async function advanceAllBlocks( | |
| clients: ChopstickClients, | |
| count: number, | |
| ) { | |
| console.log(`βοΈ Advancing ${count} blocks on all networks...`); | |
| await Promise.all([ | |
| clients.relayClient._request("dev_newBlock", [{ count }]), | |
| clients.hydrationClient._request("dev_newBlock", [{ count }]), | |
| clients.ahClient._request("dev_newBlock", [{ count }]), | |
| ]); | |
| console.log(`β Advanced ${count} blocks on all networks`); | |
| } | |
| // Helper function to fund Alice's account with DOT | |
| export async function fundAliceAccount(clients: ChopstickClients) { | |
| console.log("π° Funding Alice's account..."); | |
| // Alice's address | |
| const aliceAddress = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; | |
| // Set the storage using dev_setStorage following the coretime example | |
| await clients.relayClient._request("dev_setStorage", [ | |
| { | |
| system: { | |
| account: [ | |
| [ | |
| [aliceAddress], | |
| { | |
| nonce: 0, | |
| consumers: 0, | |
| providers: 1, | |
| sufficients: 0, | |
| data: { | |
| free: "10000000000000000000", // 1_000_000_000 DOT | |
| reserved: "0", | |
| miscFrozen: "0", | |
| feeFrozen: "0", | |
| }, | |
| }, | |
| ], | |
| ], | |
| }, | |
| }, | |
| ]); | |
| console.log(`β Alice's account funded with 1_000_000_000 DOT`); | |
| // Verify the funding worked | |
| try { | |
| const balance = | |
| await clients.relayApi.query.System.Account.getValue(aliceAddress); | |
| console.log( | |
| `π Alice's balance: ${balance.data.free} (${Number(balance.data.free) / 10_000_000_000} DOT)`, | |
| ); | |
| } catch (error) { | |
| console.log("β οΈ Could not verify balance:", error); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment