mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 02:07:55 +00:00
11678fe7cd
- Add /subdomains page listing all 20 PezkuwiChain subdomains - Add Back to Home button to Subdomains page - Create NetworkPage reusable component for network details - Add 7 network subpages: /mainnet, /staging, /testnet, /beta, /alfa, /development, /local - Update ChainSpecs network cards to navigate to network subpages - Add i18n translations for chainSpecs section in en.ts - Add SDK docs with rebranding support (rebrand-rustdoc.cjs) - Add generate-docs-structure.cjs for automatic docs generation - Update shared libs: endpoints, polkadot, wallet, xcm-bridge - Add new token logos: TYR, ZGR, pezkuwi_icon - Add new pages: Explorer, Docs, Wallet, Api, Faucet, Developers, Grants, Wiki, Forum, Telemetry
404 lines
11 KiB
TypeScript
404 lines
11 KiB
TypeScript
/**
|
|
* XCM Bridge Service
|
|
*
|
|
* Handles Asset Hub USDT → wUSDT bridge configuration
|
|
* User-friendly abstraction over complex XCM operations
|
|
*
|
|
* ALFA TESTNET MODE: Mock XCM for standalone chain testing
|
|
* BETA+ MODE: Real XCM with Rococo/Westend Asset Hub
|
|
*/
|
|
|
|
import { ApiPromise, WsProvider } from '@polkadot/api';
|
|
import type { Signer } from '@polkadot/api/types';
|
|
|
|
// Detect mock mode (alfa testnet)
|
|
const IS_MOCK_MODE = typeof process !== 'undefined'
|
|
? process.env.VITE_MOCK_XCM === 'true'
|
|
: typeof import.meta !== 'undefined'
|
|
? import.meta.env?.VITE_MOCK_XCM === 'true'
|
|
: false;
|
|
|
|
// Mock XCM state management (localStorage)
|
|
const MOCK_XCM_STORAGE_KEY = 'pezkuwi_mock_xcm_configured';
|
|
|
|
function getMockXcmConfigured(): boolean {
|
|
if (typeof window === 'undefined') return false;
|
|
return localStorage.getItem(MOCK_XCM_STORAGE_KEY) === 'true';
|
|
}
|
|
|
|
function setMockXcmConfigured(configured: boolean): void {
|
|
if (typeof window === 'undefined') return;
|
|
localStorage.setItem(MOCK_XCM_STORAGE_KEY, String(configured));
|
|
}
|
|
|
|
// Westend Asset Hub endpoint (production)
|
|
export const ASSET_HUB_ENDPOINT = 'wss://westend-asset-hub-rpc.polkadot.io';
|
|
|
|
// Known Asset IDs
|
|
export const ASSET_HUB_USDT_ID = 1984; // USDT on Asset Hub
|
|
export const WUSDT_ASSET_ID = 1000; // wUSDT on PezkuwiChain (was 2, now 1000)
|
|
export const ASSET_HUB_PARACHAIN_ID = 1000;
|
|
|
|
/**
|
|
* Bridge status information
|
|
*/
|
|
export interface BridgeStatus {
|
|
isConfigured: boolean;
|
|
assetHubLocation: string | null;
|
|
usdtMapping: number | null;
|
|
assetHubConnected: boolean;
|
|
wusdtExists: boolean;
|
|
}
|
|
|
|
/**
|
|
* Asset Hub USDT metadata
|
|
*/
|
|
export interface AssetHubUsdtInfo {
|
|
id: number;
|
|
name: string;
|
|
symbol: string;
|
|
decimals: number;
|
|
supply: string;
|
|
}
|
|
|
|
/**
|
|
* Connect to Asset Hub
|
|
*/
|
|
export async function connectToAssetHub(): Promise<ApiPromise> {
|
|
if (IS_MOCK_MODE) {
|
|
console.log('[MOCK XCM] Simulating Asset Hub connection for alfa testnet');
|
|
// Return null to signal mock mode - calling code will handle gracefully
|
|
return null as any;
|
|
}
|
|
|
|
try {
|
|
const provider = new WsProvider(ASSET_HUB_ENDPOINT);
|
|
const api = await ApiPromise.create({ provider });
|
|
await api.isReady;
|
|
|
|
return api;
|
|
} catch (error) {
|
|
console.error('Failed to connect to Asset Hub:', error);
|
|
throw new Error(`Asset Hub connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch Asset Hub USDT metadata
|
|
*/
|
|
export async function fetchAssetHubUsdtInfo(
|
|
assetHubApi?: ApiPromise
|
|
): Promise<AssetHubUsdtInfo> {
|
|
if (IS_MOCK_MODE) {
|
|
console.log('[MOCK XCM] Returning simulated Asset Hub USDT info');
|
|
return {
|
|
id: ASSET_HUB_USDT_ID,
|
|
name: 'Tether USD',
|
|
symbol: 'USDT',
|
|
decimals: 6,
|
|
supply: '1000000000000000', // 1 billion USDT (simulated)
|
|
};
|
|
}
|
|
|
|
let api = assetHubApi;
|
|
let shouldDisconnect = false;
|
|
|
|
try {
|
|
// Connect if not provided
|
|
if (!api) {
|
|
api = await connectToAssetHub();
|
|
shouldDisconnect = true;
|
|
}
|
|
|
|
// Fetch USDT metadata from Asset Hub
|
|
const metadata = await api.query.assets.metadata(ASSET_HUB_USDT_ID);
|
|
const metadataJson = metadata.toJSON() as any;
|
|
|
|
// Fetch total supply
|
|
const asset = await api.query.assets.asset(ASSET_HUB_USDT_ID);
|
|
const assetJson = asset.toJSON() as any;
|
|
|
|
return {
|
|
id: ASSET_HUB_USDT_ID,
|
|
name: metadataJson?.name || 'Unknown',
|
|
symbol: metadataJson?.symbol || 'USDT',
|
|
decimals: metadataJson?.decimals || 6,
|
|
supply: assetJson?.supply?.toString() || '0',
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch Asset Hub USDT info:', error);
|
|
throw new Error(`Failed to fetch USDT info: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
} finally {
|
|
if (shouldDisconnect && api) {
|
|
await api.disconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check current XCM bridge configuration status
|
|
*/
|
|
export async function checkBridgeStatus(
|
|
api: ApiPromise
|
|
): Promise<BridgeStatus> {
|
|
try {
|
|
// Check if wUSDT asset exists
|
|
const wusdtAsset = await api.query.assets.asset(WUSDT_ASSET_ID);
|
|
const wusdtExists = wusdtAsset.isSome;
|
|
|
|
// Mock mode: Simulate successful bridge setup
|
|
if (IS_MOCK_MODE) {
|
|
const isConfigured = getMockXcmConfigured();
|
|
console.log('[MOCK XCM] Returning simulated bridge status for alfa testnet (configured:', isConfigured, ')');
|
|
return {
|
|
isConfigured,
|
|
assetHubLocation: isConfigured ? `ParaId(${ASSET_HUB_PARACHAIN_ID})` : null,
|
|
usdtMapping: isConfigured ? WUSDT_ASSET_ID : null,
|
|
assetHubConnected: true, // Simulated connection success
|
|
wusdtExists,
|
|
};
|
|
}
|
|
|
|
// Try to connect to Asset Hub
|
|
let assetHubConnected = false;
|
|
try {
|
|
const assetHubApi = await connectToAssetHub();
|
|
assetHubConnected = assetHubApi.isConnected;
|
|
await assetHubApi.disconnect();
|
|
} catch {
|
|
assetHubConnected = false;
|
|
}
|
|
|
|
// TODO: Check XCM configuration
|
|
// This requires checking the runtime configuration
|
|
// For now, we'll return a basic status
|
|
const isConfigured = false; // Will be updated when XCM pallet is available
|
|
|
|
return {
|
|
isConfigured,
|
|
assetHubLocation: isConfigured ? `ParaId(${ASSET_HUB_PARACHAIN_ID})` : null,
|
|
usdtMapping: isConfigured ? WUSDT_ASSET_ID : null,
|
|
assetHubConnected,
|
|
wusdtExists,
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to check bridge status:', error);
|
|
return {
|
|
isConfigured: false,
|
|
assetHubLocation: null,
|
|
usdtMapping: null,
|
|
assetHubConnected: false,
|
|
wusdtExists: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configure XCM bridge (requires sudo access)
|
|
*
|
|
* This sets up the ForeignAssetTransactor to map Asset Hub USDT → wUSDT
|
|
*/
|
|
export async function configureXcmBridge(
|
|
api: ApiPromise,
|
|
signer: Signer,
|
|
account: string,
|
|
onStatusUpdate?: (status: string) => void
|
|
): Promise<string> {
|
|
if (!api.tx.sudo) {
|
|
throw new Error('Sudo pallet not available');
|
|
}
|
|
|
|
// Mock mode: Simulate successful configuration
|
|
if (IS_MOCK_MODE) {
|
|
console.log('[MOCK XCM] Simulating XCM bridge configuration for alfa testnet');
|
|
|
|
onStatusUpdate?.('Preparing XCM configuration...');
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
onStatusUpdate?.('Simulating sudo transaction...');
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
onStatusUpdate?.('Mock XCM bridge configured successfully!');
|
|
|
|
// Store mock configuration state
|
|
setMockXcmConfigured(true);
|
|
|
|
// Return mock transaction hash
|
|
return '0x' + '0'.repeat(64);
|
|
}
|
|
|
|
try {
|
|
onStatusUpdate?.('Preparing XCM configuration...');
|
|
|
|
// Create Asset Hub location
|
|
const assetHubLocation = {
|
|
parents: 1,
|
|
interior: {
|
|
X2: [
|
|
{ Parachain: ASSET_HUB_PARACHAIN_ID },
|
|
{ GeneralIndex: ASSET_HUB_USDT_ID }
|
|
]
|
|
}
|
|
};
|
|
|
|
// Note: This is a placeholder for the actual XCM configuration
|
|
// The actual implementation depends on the runtime's XCM configuration pallet
|
|
// For now, we'll document the expected transaction structure
|
|
|
|
console.log('XCM Configuration (Placeholder):', {
|
|
assetHubLocation,
|
|
wusdtAssetId: WUSDT_ASSET_ID,
|
|
note: 'Actual implementation requires XCM config pallet in runtime'
|
|
});
|
|
|
|
onStatusUpdate?.('Waiting for user signature...');
|
|
|
|
// TODO: Implement actual XCM configuration when pallet is available
|
|
// const configTx = api.tx.sudo.sudo(
|
|
// api.tx.xcmConfig.configureForeignAsset(assetHubLocation, WUSDT_ASSET_ID)
|
|
// );
|
|
|
|
// For now, return a placeholder
|
|
return 'XCM configuration transaction placeholder - requires runtime XCM config pallet';
|
|
|
|
} catch (error) {
|
|
console.error('Failed to configure XCM bridge:', error);
|
|
throw new Error(`XCM configuration failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create wUSDT/HEZ liquidity pool
|
|
*/
|
|
export async function createWUsdtHezPool(
|
|
api: ApiPromise,
|
|
signer: Signer,
|
|
account: string,
|
|
wusdtAmount: string,
|
|
hezAmount: string,
|
|
onStatusUpdate?: (status: string) => void
|
|
): Promise<string> {
|
|
try {
|
|
onStatusUpdate?.('Creating wUSDT/HEZ pool...');
|
|
|
|
// Create pool transaction
|
|
const poolTx = api.tx.assetConversion.createPool(
|
|
{ Assets: WUSDT_ASSET_ID }, // wUSDT
|
|
'Native' // Native HEZ
|
|
);
|
|
|
|
onStatusUpdate?.('Adding initial liquidity...');
|
|
|
|
// Add liquidity transaction
|
|
const liquidityTx = api.tx.assetConversion.addLiquidity(
|
|
{ Assets: WUSDT_ASSET_ID },
|
|
'Native',
|
|
wusdtAmount,
|
|
hezAmount,
|
|
'0', // min_mint_amount
|
|
account
|
|
);
|
|
|
|
onStatusUpdate?.('Batching transactions...');
|
|
|
|
// Batch both transactions
|
|
const batchTx = api.tx.utility.batchAll([poolTx, liquidityTx]);
|
|
|
|
onStatusUpdate?.('Waiting for signature...');
|
|
|
|
// Sign and send
|
|
return new Promise((resolve, reject) => {
|
|
batchTx.signAndSend(
|
|
account,
|
|
{ signer },
|
|
({ status, dispatchError, events }) => {
|
|
if (status.isInBlock) {
|
|
if (dispatchError) {
|
|
if (dispatchError.isModule) {
|
|
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
|
|
} else {
|
|
reject(new Error(dispatchError.toString()));
|
|
}
|
|
} else {
|
|
onStatusUpdate?.('Pool created successfully!');
|
|
resolve(status.asInBlock.toHex());
|
|
}
|
|
}
|
|
}
|
|
);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to create wUSDT/HEZ pool:', error);
|
|
throw new Error(`Pool creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify wUSDT asset exists on chain
|
|
*/
|
|
export async function verifyWUsdtAsset(api: ApiPromise): Promise<boolean> {
|
|
try {
|
|
const asset = await api.query.assets.asset(WUSDT_ASSET_ID);
|
|
return asset.isSome;
|
|
} catch (error) {
|
|
console.error('Failed to verify wUSDT asset:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get wUSDT asset details
|
|
*/
|
|
export async function getWUsdtAssetDetails(api: ApiPromise) {
|
|
try {
|
|
const [asset, metadata] = await Promise.all([
|
|
api.query.assets.asset(WUSDT_ASSET_ID),
|
|
api.query.assets.metadata(WUSDT_ASSET_ID),
|
|
]);
|
|
|
|
if (!asset.isSome) {
|
|
return null;
|
|
}
|
|
|
|
const assetData = asset.unwrap().toJSON() as any;
|
|
const metadataData = metadata.toJSON() as any;
|
|
|
|
return {
|
|
supply: assetData.supply?.toString() || '0',
|
|
owner: assetData.owner,
|
|
issuer: assetData.issuer,
|
|
admin: assetData.admin,
|
|
freezer: assetData.freezer,
|
|
minBalance: assetData.minBalance?.toString() || '0',
|
|
name: metadataData.name || 'wUSDT',
|
|
symbol: metadataData.symbol || 'wUSDT',
|
|
decimals: metadataData.decimals || 6,
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to get wUSDT asset details:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format XCM location for display
|
|
*/
|
|
export function formatXcmLocation(location: any): string {
|
|
if (typeof location === 'string') return location;
|
|
|
|
try {
|
|
if (location.parents !== undefined) {
|
|
const junctions = location.interior?.X2 || location.interior?.X1 || [];
|
|
return `RelayChain → ${junctions.map((j: any) => {
|
|
if (j.Parachain) return `Para(${j.Parachain})`;
|
|
if (j.GeneralIndex) return `Asset(${j.GeneralIndex})`;
|
|
return JSON.stringify(j);
|
|
}).join(' → ')}`;
|
|
}
|
|
return JSON.stringify(location);
|
|
} catch {
|
|
return 'Invalid location';
|
|
}
|
|
}
|