Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ git commit -m "Register for OpenGuild Sub0 Challenges"
| 2 | Set an on-chain identity from the client with Dedot | [Take Challenge](./challenge-2) | $100 |
| 3 | Building a parachain from a solochain | [Take Challenge](./challenge-3) | $100 |


## PARTICIPANT REGISTRATION 🏆

| 🦄 | Armielyn Obinguar | armlynobinguar | DevRel @Virtuals Protocol |

</div>

<br/>
Expand Down
102 changes: 74 additions & 28 deletions challenge-1/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
#![allow(missing_docs)]
use futures::StreamExt;
use subxt::{client::OnlineClient, lightclient::LightClient, PolkadotConfig};
use std::{fs::OpenOptions, io::Write, sync::{Arc, Mutex}};
use subxt::{client::OnlineClient, lightclient::LightClient, PolkadotConfig, Block, EventDetails, ExtrinsicDetails, blocks::ExtrinsicEvents};
use tracing_subscriber;

// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}

// Examples chain specs.
const POLKADOT_SPEC: &str = include_str!("../artifacts/chain_specs/polkadot.json");
const ASSET_HUB_SPEC: &str = include_str!("../artifacts/chain_specs/polkadot_asset_hub.json");

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The lightclient logs are informative:
tracing_subscriber::fmt::init();

// Instantiate a light client with the Polkadot relay chain,
// and connect it to Asset Hub, too.
let (lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?;
let asset_hub_rpc = lightclient.parachain(ASSET_HUB_SPEC)?;

// TODO: `🍭 Easy` Initialize RPCs to new relaychains and parachains.

// Create Subxt clients from these Smoldot backed RPC clients.
let polkadot_api = OnlineClient::<PolkadotConfig>::from_rpc_client(polkadot_rpc).await?;
let asset_hub_api = OnlineClient::<PolkadotConfig>::from_rpc_client(asset_hub_rpc).await?;

// TODO: `🍭 Easy` Create Subxt clients from newly added Smoldot backed RPC clients.

let polkadot_sub = polkadot_api
.blocks()
.subscribe_finalized()
Expand All @@ -39,28 +31,82 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.await?
.map(|block| ("AssetHub", block));

// TODO: `🍭 Easy` Fetch blocks from new chains using the added APIs.

let mut stream_combinator = futures::stream::select(polkadot_sub, parachain_sub);

while let Some((chain, block)) = stream_combinator.next().await {
let block = block?;
println!(
"📦 Chain {:?} | hash={:?} | height={:?}",
chain,
block.hash(),
block.number()
let log_file = Arc::new(Mutex::new(OpenOptions::new()
.append(true)
.create(true)
.open("blocks_log.txt")?));
let pallet_file = Arc::new(Mutex::new(OpenOptions::new()
.append(true)
.create(true)
.open("pallets.txt")?));
let events_file = Arc::new(Mutex::new(OpenOptions::new()
.append(true)
.create(true)
.open("events.txt")?));

let mut highest_block = None;
let mut lowest_block = None;

while let Some((chain, block_result)) = stream_combinator.next().await {
let block = block_result?;
let block_number = block.number();

let block_log = format!(
"📦 Chain: {:?} | Hash: {:?} | Height: {:?}\n",
chain, block.hash(), block_number
);

// TODO: `🍫 Intermediate` Store the fetched block data to a log file.

// TODO: `🍫 Intermediate` Finding the chain with highest block number.

// TODO: `🍫 Intermediate` Finding the chain with lowest block number.

// TODO: `🔥 Advanced` Processing extrinsics of each block and aggregate the number of transactions made based on the pallet name. Store the data in the log file named `pallets.txt`.
{
let mut file = log_file.lock().unwrap();
file.write_all(block_log.as_bytes())?;
}

if let Some((_, highest)) = highest_block {
if block_number > highest {
highest_block = Some((chain, block_number));
}
} else {
highest_block = Some((chain, block_number));
}

if let Some((_, lowest)) = lowest_block {
if block_number < lowest {
lowest_block = Some((chain, block_number));
}
} else {
lowest_block = Some((chain, block_number));
}

let extrinsics = block.extrinsics();
let extrinsics_data: Vec<_> = extrinsics.iter()
.filter_map(|ext| ext.as_ref().ok())
.map(|ext| ext.pallet_name())
.collect();

{
let mut file = pallet_file.lock().unwrap();
for pallet in extrinsics_data {
writeln!(file, "Chain: {:?} | Pallet: {:?}", chain, pallet)?;
}
}

let events: ExtrinsicEvents<PolkadotConfig> = block.events().await?;
{
let mut file = events_file.lock().unwrap();
for event in events.iter() {
let event_name = event?.event().pallet_name();
writeln!(file, "Chain: {:?} | Event: {:?}", chain, event_name)?;
}
}
}

// TODO: `🔥 Advanced` Processing events emitted from each block and aggregate the number of events made based on the event name. Store the data in the log file named `events.txt`.
if let Some((chain, highest_block)) = highest_block {
println!("🚀 Highest Block: Chain: {:?} | Height: {:?}", chain, highest_block);
}
if let Some((chain, lowest_block)) = lowest_block {
println!("🐢 Lowest Block: Chain: {:?} | Height: {:?}", chain, lowest_block);
}

Ok(())
Expand Down
141 changes: 131 additions & 10 deletions challenge-2/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,147 @@
import { useEffect, useState } from 'react';
import dedotLogo from './assets/dedot-dark-logo.png';
import { Container, Flex, Heading } from '@chakra-ui/react';
import { Container, Flex, Heading, Box, Button, Text, Input, FormControl, FormLabel, Spinner } from '@chakra-ui/react';
import { web3Enable, web3Accounts } from '@polkadot/extension-dapp';
import { ApiPromise, WsProvider } from '@polkadot/api';

function App() {
// 1. Connect to SubWallet
// 2. Show connected account (name & address)
// 3. Initialize `DedotClient` to connect to the network (Westend testnet)
const [account, setAccount] = useState<{ name: string; address: string } | null>(null);
const [api, setApi] = useState<ApiPromise | null>(null);
const [balance, setBalance] = useState<string | null>(null);
const [destination, setDestination] = useState('');
const [amount, setAmount] = useState<number | string>('');
const [status, setStatus] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

// 1. Connect to SubWallet and get account
const connectWallet = async () => {
const extensions = await web3Enable('DedotClient');
if (extensions.length === 0) {
alert('Please install SubWallet to continue.');
return;
}

const accounts = await web3Accounts();
if (accounts.length > 0) {
const { address, meta } = accounts[0];
setAccount({ address, name: meta.name || 'Unnamed Account' });
}
};

// 3. Initialize DedotClient to connect to Westend testnet
const connectToNetwork = async () => {
const provider = new WsProvider('wss://westend-rpc.polkadot.io');
const apiInstance = await ApiPromise.create({ provider });
setApi(apiInstance);
};

// 4. Fetch & show balance for connected account
// 5. Build a form to transfer balance (destination address & amount to transfer)
// 6. Check transaction status (in-block & finalized)
// 7. Check transaction result (success or not)
// 8. Subscribe to balance changing
const fetchBalance = async () => {
if (api && account) {
const { data: { free } } = await api.query.system.account(account.address);
setBalance(free.toHuman());
}
};

// 6. Check transaction status and 7. transaction result
const sendTransaction = async () => {
if (!api || !account) return;
setLoading(true);

try {
const transfer = api.tx.balances.transfer(destination, amount);
const unsub = await transfer.signAndSend(account.address, ({ status, dispatchError }) => {
if (status.isInBlock) {
setStatus('Transaction included in block');
} else if (status.isFinalized) {
setStatus('Transaction finalized');
unsub();
}

if (dispatchError) {
setStatus('Transaction failed');
console.error('Error:', dispatchError.toString());
}
});
} catch (error) {
setStatus('Transaction failed');
console.error('Transaction error:', error);
} finally {
setLoading(false);
}
};

// 8. Subscribe to balance changes
useEffect(() => {
if (api && account) {
const unsub = api.query.system.account(account.address, ({ data: { free } }) => {
setBalance(free.toHuman());
});
return () => unsub();
}
}, [api, account]);

// Initial setup
useEffect(() => {
connectToNetwork();
}, []);

return (
<Container maxW='container.md' my={16}>
<Flex justifyContent='center'>
<a href='https://dedot.dev' target='_blank'>
<img width='100' src={dedotLogo} className='logo' alt='Vite logo' />
<a href='https://dedot.dev' target='_blank' rel='noreferrer'>
<img width='100' src={dedotLogo} className='logo' alt='Dedot logo' />
</a>
</Flex>
<Heading my={4} textAlign='center'>
Open Hack Dedot
</Heading>

{!account ? (
<Button onClick={connectWallet} colorScheme='blue' my={4}>
Connect SubWallet
</Button>
) : (
<>
<Box my={4}>
<Text><strong>Connected Account:</strong> {account.name} ({account.address})</Text>
<Text><strong>Balance:</strong> {balance || 'Loading...'}</Text>
</Box>

<FormControl my={4}>
<FormLabel>Destination Address</FormLabel>
<Input
placeholder='Enter destination address'
value={destination}
onChange={(e) => setDestination(e.target.value)}
/>
</FormControl>

<FormControl my={4}>
<FormLabel>Amount to Transfer</FormLabel>
<Input
placeholder='Enter amount to transfer'
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</FormControl>

<Button
onClick={sendTransaction}
colorScheme='teal'
my={4}
isLoading={loading}
>
Send Transaction
</Button>

{status && (
<Text color={status.includes('failed') ? 'red.500' : 'green.500'}>
{status}
</Text>
)}
</>
)}
</Container>
);
}
Expand Down