Skip to content
Merged
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
168 changes: 168 additions & 0 deletions src/lib/examples/AssetHubExample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type { Network } from "../types/network";
import { ExampleFactory } from "./factory";

export class AssetHubExample extends ExampleFactory {
constructor() {
super({
id: "asset-hub-operations",
name: "Asset Hub Operations",
description: "Demonstrate Asset Hub operations: foreign assets, teleport assets, and reserve transfers",
level: "advanced",
categories: ["assets", "xcm", "transfers", "parachains"],
});
}

generateCode(network: Network): string {
return `// Asset Hub Operations Example on ${network.name}
${this.getImports(network, true)}

// Connect to ${network.name} Asset Hub
const client = createClient(
withPolkadotSdkCompat(
getWsProvider("${network.endpoint}")
)
);

// Get the typed API using the descriptors
const typedApi = client.getTypedApi(${network.descriptorKey});

// Asset Hub operations demonstration
const demonstrateAssetHubOperations = async () => {
try {
console.log("🚀 Starting Asset Hub operations demonstration...");

// 1. Query foreign assets
console.log("\\n🔍 Querying foreign assets:");
const foreignAssets = await typedApi.query.ForeignAssets.Asset.getEntries();
console.log("Found", foreignAssets.length, "foreign assets");

if (foreignAssets.length > 0) {
const firstAsset = foreignAssets[0];
console.log("First asset details:", {
assetId: firstAsset.key?.args?.[0],
details: firstAsset.value
});
}

// 2. Query asset balances
console.log("\\n💰 Querying asset balances:");
const aliceAddress = "${this.getTestAccount("alice")}";

// Native token balance (DOT/KSM)
const nativeBalance = await typedApi.query.System.Account.getValue(aliceAddress);
console.log("Native balance:", nativeBalance.data.free.toString(), "${network.tokenSymbol}");

// 3. Demonstrate asset transfer (would require foreign asset)
console.log("\\n📤 Asset Transfer Example:");
console.log("This would transfer foreign assets between accounts:");
const transferTx = typedApi.tx.ForeignAssets.transfer({
id: { parents: 0, interior: { X1: { PalletInstance: 50 } } }, // Example asset ID
target: MultiAddress.Id("${this.getTestAccount("bob")}"),
amount: 1000000000000n // 1000 tokens (adjust for decimals)
});
console.log("Asset transfer transaction created (not submitted in simulator)");

// 4. Demonstrate teleport assets (XCM)
console.log("\\n✈️ Teleport Assets Example:");
console.log("This would teleport assets to another parachain:");
const teleportTx = typedApi.tx.PolkadotXcm.teleport_assets({
dest: {
V4: {
parents: 1,
interior: {
X1: { Parachain: 2000 } // Example parachain ID
}
}
},
beneficiary: {
V4: {
parents: 0,
interior: {
X1: { AccountId32: { id: "${this.getTestAccount("alice")}" } }
}
}
},
assets: {
V4: [{
id: { Concrete: { parents: 1, interior: "Here" } },
fun: { Fungible: 1000000000000n }
}]
},
fee_asset_item: 0
});
console.log("Teleport transaction created (not submitted in simulator)");

// 5. Query reserve transfers
console.log("\\n🔄 Reserve Transfer Example:");
console.log("This would perform a reserve transfer to another parachain:");
const reserveTransferTx = typedApi.tx.PolkadotXcm.reserve_transfer_assets({
dest: {
V4: {
parents: 1,
interior: {
X1: { Parachain: 2000 }
}
}
},
beneficiary: {
V4: {
parents: 0,
interior: {
X1: { AccountId32: { id: "${this.getTestAccount("bob")}" } }
}
}
},
assets: {
V4: [{
id: { Concrete: { parents: 1, interior: "Here" } },
fun: { Fungible: 500000000000n }
}]
},
fee_asset_item: 0
});
console.log("Reserve transfer transaction created (not submitted in simulator)");

// 6. Query asset metadata
console.log("\\n📋 Asset Metadata:");
try {
const assetMetadata = await typedApi.query.ForeignAssets.Metadata.getEntries();
console.log("Found metadata for", assetMetadata.length, "assets");

if (assetMetadata.length > 0) {
const firstMetadata = assetMetadata[0];
console.log("Asset metadata:", {
assetId: firstMetadata.key?.args?.[0],
name: firstMetadata.value?.name?.toString(),
symbol: firstMetadata.value?.symbol?.toString(),
decimals: firstMetadata.value?.decimals?.toString()
});
}
} catch (error) {
console.log("Asset metadata not available or different structure");
}

// 7. Query asset approvals
console.log("\\n✅ Asset Approvals:");
try {
const approvals = await typedApi.query.ForeignAssets.Approvals.getEntries();
console.log("Found", approvals.length, "asset approvals");
} catch (error) {
console.log("Asset approvals query not available");
}

console.log("\\n✅ Asset Hub operations demonstration completed!");
console.log("Note: Asset Hub operations typically require:");
console.log("- Foreign asset registration on the hub");
console.log("- Sufficient balance of the asset");
console.log("- Proper XCM configuration between chains");
console.log("- In a real application, you would sign and submit these transactions");

} catch (error) {
console.error("❌ Error in Asset Hub operations:", error);
}
};

demonstrateAssetHubOperations().catch(console.error);
`;
}
}
4 changes: 4 additions & 0 deletions src/lib/examples/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { exampleRegistry } from "./factory";
import { StakingOperationsExample } from "./StakingOperationsExample";
import { AssetHubExample } from "./AssetHubExample";


import { SimpleTransferExample } from "./SimpleTransferExample";
import { NetworkDashboardExample } from "./NetworkDashboardExample";
Expand All @@ -10,6 +12,8 @@ import { AcalaDeFiExample } from "./AcalaDeFiExample";
import type { Example, ExampleLevel } from "../types/example";
import { PolkadotGovernanceExample } from "./PolkadotGovernanceExample";

new AssetHubExample(),

new StakingOperationsExample(),

exampleRegistry.registerMany([
Expand Down
158 changes: 120 additions & 38 deletions src/lib/hooks/useCodeRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ interface UseCodeRunnerOptions {
onRunStart?: () => void;
onRunComplete?: () => void;
onRunError?: (error: Error) => void;
timeoutMs?: number;
maxRetries?: number;
}

class CodeExecutionError extends Error {
constructor(
message: string,
public readonly type: 'network' | 'timeout' | 'simulation' | 'unknown',
public readonly originalError?: Error
) {
super(message);
this.name = 'CodeExecutionError';
}
}

export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
Expand All @@ -21,6 +34,11 @@ export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
timestamp: number;
} | null>(null);

const {
timeoutMs = 30000, // 30 seconds default
maxRetries = 2,
} = options;

const updateCode = useCallback((newCode: string) => {
setCode(newCode);
}, []);
Expand All @@ -40,52 +58,116 @@ export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
setOutputs([
{
type: "log",
content: "Running code...",
content: `🚀 Starting execution on ${network.name}...`,
timestamp: Date.now(),
},
]);

try {
setProgress(30);
await new Promise((resolve) => setTimeout(resolve, 300));
setProgress(50);

const simulatedOutputs = await simulateCodeExecution(example, network);
setProgress(90);

await new Promise((resolve) => setTimeout(resolve, 200));

setOutputs(simulatedOutputs);
setLastRun({
example,
network,
timestamp: Date.now(),
});

options.onRunComplete?.();
} catch (error) {
setOutputs([
{
type: "error",
content:
error instanceof Error
? `Error: ${error.message}`
: "An unknown error occurred",
let attempt = 0;
let lastError: Error | null = null;

while (attempt <= maxRetries) {
try {
setProgress(20 + (attempt * 10));

if (attempt > 0) {
setOutputs(prev => [...prev, {
type: "warning",
content: `🔄 Retry attempt ${attempt}/${maxRetries}...`,
timestamp: Date.now(),
}]);
}

// Create a timeout promise
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new CodeExecutionError(
`Execution timed out after ${timeoutMs}ms`,
'timeout'
));
}, timeoutMs);
});

// Race between simulation and timeout
const simulatedOutputs = await Promise.race([
simulateCodeExecution(example, network),
timeoutPromise
]);

setProgress(90);

// Add success message
const successOutputs = [
...simulatedOutputs,
{
type: "log" as const,
content: `✅ Execution completed successfully on ${network.name}`,
timestamp: Date.now(),
}
];

setOutputs(successOutputs);
setLastRun({
example,
network,
timestamp: Date.now(),
},
]);
});

options.onRunComplete?.();
return; // Success, exit retry loop

} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
attempt++;

let errorType: CodeExecutionError['type'] = 'unknown';
let errorMessage = lastError.message;

// Categorize errors
if (lastError instanceof CodeExecutionError) {
errorType = lastError.type;
} else if (lastError.message.includes('network') || lastError.message.includes('connection')) {
errorType = 'network';
errorMessage = `Network error: ${lastError.message}. Please check your internet connection and try again.`;
} else if (lastError.message.includes('timeout')) {
errorType = 'timeout';
errorMessage = `Execution timed out: ${lastError.message}. The operation took too long to complete.`;
} else {
errorType = 'simulation';
errorMessage = `Simulation error: ${lastError.message}`;
}

const categorizedError = new CodeExecutionError(errorMessage, errorType, lastError);

// If this was the last attempt, report the error
if (attempt > maxRetries) {
setOutputs(prev => [...prev, {
type: "error",
content: `❌ ${categorizedError.message}${errorType === 'network' ? ' (Check network connection)' : ''}${errorType === 'timeout' ? ' (Try simplifying the code)' : ''}`,
timestamp: Date.now(),
}]);

options.onRunError?.(categorizedError);
}

// Wait before retry (exponential backoff)
if (attempt <= maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

options.onRunError?.(
error instanceof Error ? error : new Error("Unknown error"),
);
} finally {
setIsRunning(false);
setProgress(100);
// If we get here, all retries failed
setOutputs(prev => [...prev, {
type: "error",
content: `❌ All ${maxRetries + 1} attempts failed. Last error: ${lastError?.message || 'Unknown error'}`,
timestamp: Date.now(),
}]);

setTimeout(() => setProgress(0), 500);
}
options.onRunError?.(lastError || new Error('All retry attempts failed'));
},
[options],
[options, timeoutMs, maxRetries],
);

const clearOutput = useCallback(() => {
Expand Down