mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 05:37:56 +00:00
feat: add XCM teleport and CI/CD deployment workflow
Features: - Add XCMTeleportModal for cross-chain HEZ transfers - Support Asset Hub and People Chain teleports - Add "Fund Fees" button with user-friendly tooltips - Use correct XCM V3 format with teyrchain junction Fixes: - Fix PEZ transfer to use Asset Hub API - Silence unnecessary pallet availability warnings - Fix transaction loading performance (10 blocks limit) - Remove Supabase admin_roles dependency CI/CD: - Add auto-deploy to VPS on main branch push - Add version bumping on deploy - Upload build artifacts for deployment
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -126,7 +126,7 @@ export async function getKycStatus(
|
||||
}
|
||||
|
||||
if (!api?.query?.identityKyc) {
|
||||
console.warn('Identity KYC pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Identity KYC pallet not available on this chain');
|
||||
return 'NotStarted';
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ export async function getUserTikis(
|
||||
): Promise<TikiInfo[]> {
|
||||
try {
|
||||
if (!api?.query?.tiki?.userTikis) {
|
||||
console.warn('Tiki pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Tiki pallet not available on this chain');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export async function isStakingScoreTracking(
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (!api?.query?.stakingScore?.stakingStartBlock) {
|
||||
console.warn('Staking score pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Staking score pallet not available on this chain');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function checkCitizenStatus(
|
||||
try {
|
||||
// Check if Identity KYC pallet exists
|
||||
if (!api.query?.identityKyc?.kycStatuses) {
|
||||
console.warn('Identity KYC pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Identity KYC pallet not available on this chain');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function checkValidatorStatus(
|
||||
try {
|
||||
// Check if ValidatorPool pallet exists
|
||||
if (!api.query?.validatorPool?.poolMembers) {
|
||||
console.warn('ValidatorPool pallet not available');
|
||||
if (import.meta.env.DEV) console.log('ValidatorPool pallet not available on this chain');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ export async function checkTikiRole(
|
||||
try {
|
||||
// Check if Tiki pallet exists
|
||||
if (!api.query?.tiki?.userTikis) {
|
||||
console.warn('Tiki pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Tiki pallet not available on this chain');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ export async function checkStakingScoreTracking(
|
||||
|
||||
try {
|
||||
if (!api.query?.stakingScore?.stakingStartBlock) {
|
||||
console.warn('Staking score pallet not available');
|
||||
if (import.meta.env.DEV) console.log('Staking score pallet not available on this chain');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+48
-5
@@ -75,6 +75,13 @@ export async function initiateReferral(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the referral pallet is available on the chain
|
||||
*/
|
||||
function isReferralPalletAvailable(api: ApiPromise): boolean {
|
||||
return !!(api.query.referral && api.query.referral.pendingReferrals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pending referral for a user (who invited them, if they haven't completed KYC)
|
||||
*
|
||||
@@ -87,6 +94,12 @@ export async function getPendingReferral(
|
||||
address: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
// Check if referral pallet exists
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
if (import.meta.env.DEV) console.log('Referral pallet not available on this chain');
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await api.query.referral.pendingReferrals(address);
|
||||
|
||||
if (result.isEmpty) {
|
||||
@@ -95,7 +108,7 @@ export async function getPendingReferral(
|
||||
|
||||
return result.toString();
|
||||
} catch (error) {
|
||||
console.error('Error fetching pending referral:', error);
|
||||
if (import.meta.env.DEV) console.error('Error fetching pending referral:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -112,10 +125,15 @@ export async function getReferralCount(
|
||||
address: string
|
||||
): Promise<number> {
|
||||
try {
|
||||
// Check if referral pallet exists
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const count = await api.query.referral.referralCount(address);
|
||||
return count.toNumber();
|
||||
} catch (error) {
|
||||
console.error('Error fetching referral count:', error);
|
||||
if (import.meta.env.DEV) console.error('Error fetching referral count:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -132,6 +150,11 @@ export async function getReferralInfo(
|
||||
address: string
|
||||
): Promise<ReferralInfo | null> {
|
||||
try {
|
||||
// Check if referral pallet exists
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await api.query.referral.referrals(address);
|
||||
|
||||
if (result.isEmpty) {
|
||||
@@ -144,7 +167,7 @@ export async function getReferralInfo(
|
||||
createdAt: parseInt(data.createdAt),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching referral info:', error);
|
||||
if (import.meta.env.DEV) console.error('Error fetching referral info:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -182,6 +205,16 @@ export async function getReferralStats(
|
||||
api: ApiPromise,
|
||||
address: string
|
||||
): Promise<ReferralStats> {
|
||||
// Check if referral pallet exists first
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
return {
|
||||
referralCount: 0,
|
||||
referralScore: 0,
|
||||
whoInvitedMe: null,
|
||||
pendingReferral: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const [referralCount, referralInfo, pendingReferral] = await Promise.all([
|
||||
getReferralCount(api, address),
|
||||
@@ -198,7 +231,7 @@ export async function getReferralStats(
|
||||
pendingReferral,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching referral stats:', error);
|
||||
if (import.meta.env.DEV) console.error('Error fetching referral stats:', error);
|
||||
return {
|
||||
referralCount: 0,
|
||||
referralScore: 0,
|
||||
@@ -221,6 +254,11 @@ export async function getMyReferrals(
|
||||
referrerAddress: string
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
// Check if referral pallet exists
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries = await api.query.referral.referrals.entries();
|
||||
|
||||
const myReferrals = entries
|
||||
@@ -237,7 +275,7 @@ export async function getMyReferrals(
|
||||
|
||||
return myReferrals;
|
||||
} catch (error) {
|
||||
console.error('Error fetching my referrals:', error);
|
||||
if (import.meta.env.DEV) console.error('Error fetching my referrals:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -253,6 +291,11 @@ export async function subscribeToReferralEvents(
|
||||
api: ApiPromise,
|
||||
callback: (event: { type: 'initiated' | 'confirmed'; referrer: string; referred: string; count?: number }) => void
|
||||
): Promise<() => void> {
|
||||
// Check if referral pallet exists - if not, return no-op unsubscribe
|
||||
if (!isReferralPalletAvailable(api)) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const unsub = await api.query.system.events((events: any[]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function getTrustScore(
|
||||
): Promise<number> {
|
||||
try {
|
||||
if (!api?.query?.trust) {
|
||||
console.warn('Trust pallet not available');
|
||||
// Trust pallet not available on this chain - this is expected
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ export async function getStakingScoreFromPallet(
|
||||
): Promise<number> {
|
||||
try {
|
||||
if (!api?.query?.stakingScore) {
|
||||
console.warn('Staking score pallet not available');
|
||||
// Staking score pallet not available on this chain - this is expected
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -217,7 +217,7 @@ export const fetchUserTikis = async (
|
||||
}
|
||||
|
||||
if (!api || !api.query.tiki) {
|
||||
console.warn('Tiki pallet not available on this chain');
|
||||
// Tiki pallet not available on this chain - this is expected
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ export const fetchUserTikiNFTs = async (
|
||||
): Promise<TikiNFTDetails[]> => {
|
||||
try {
|
||||
if (!api || !api.query.tiki) {
|
||||
console.warn('Tiki pallet not available on this chain');
|
||||
// Tiki pallet not available on this chain - this is expected
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,224 @@ export async function testXCMTransfer(
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// XCM TELEPORT: RELAY CHAIN → ASSET HUB
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Teleport HEZ from Relay Chain to Asset Hub
|
||||
* This is needed to pay fees on Asset Hub for PEZ transfers
|
||||
*
|
||||
* @param relayApi - Polkadot.js API instance (connected to relay chain)
|
||||
* @param amount - Amount in smallest unit (e.g., 100000000000 for 0.1 HEZ with 12 decimals)
|
||||
* @param account - Account to sign and receive on Asset Hub
|
||||
* @param assetHubParaId - Asset Hub parachain ID (default: 1000)
|
||||
* @returns Transaction hash
|
||||
*/
|
||||
export async function teleportToAssetHub(
|
||||
relayApi: ApiPromise,
|
||||
amount: string | bigint,
|
||||
account: InjectedAccountWithMeta,
|
||||
assetHubParaId: number = 1000
|
||||
): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const injector = await (window as any).injectedWeb3[account.meta.source]?.enable?.('PezkuwiChain');
|
||||
if (!injector) {
|
||||
throw new Error('Failed to get injector from wallet extension');
|
||||
}
|
||||
|
||||
const signer = injector.signer;
|
||||
|
||||
// Destination: Asset Hub parachain
|
||||
const dest = {
|
||||
V3: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
X1: { Parachain: assetHubParaId }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Beneficiary: Same account on Asset Hub
|
||||
const beneficiary = {
|
||||
V3: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
X1: {
|
||||
AccountId32: {
|
||||
network: null,
|
||||
id: relayApi.createType('AccountId32', account.address).toHex()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Assets: Native token (HEZ)
|
||||
const assets = {
|
||||
V3: [{
|
||||
id: {
|
||||
Concrete: {
|
||||
parents: 0,
|
||||
interior: 'Here'
|
||||
}
|
||||
},
|
||||
fun: {
|
||||
Fungible: amount.toString()
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
// Fee asset item (index 0 = first asset)
|
||||
const feeAssetItem = 0;
|
||||
|
||||
// Weight limit: Unlimited
|
||||
const weightLimit = 'Unlimited';
|
||||
|
||||
// Create teleport transaction
|
||||
const tx = relayApi.tx.xcmPallet.limitedTeleportAssets(
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
feeAssetItem,
|
||||
weightLimit
|
||||
);
|
||||
|
||||
let unsub: () => void;
|
||||
|
||||
await tx.signAndSend(account.address, { signer }, ({ status, events, dispatchError }) => {
|
||||
if (dispatchError) {
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = relayApi.registry.findMetaError(dispatchError.asModule);
|
||||
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
|
||||
} else {
|
||||
reject(new Error(dispatchError.toString()));
|
||||
}
|
||||
if (unsub) unsub();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.isInBlock) {
|
||||
console.log(`✅ XCM Teleport included in block: ${status.asInBlock}`);
|
||||
|
||||
// Check for XCM events
|
||||
const xcmSent = events.find(({ event }) =>
|
||||
event.section === 'xcmPallet' && event.method === 'Sent'
|
||||
);
|
||||
|
||||
if (xcmSent) {
|
||||
console.log('✅ XCM message sent successfully');
|
||||
}
|
||||
|
||||
resolve(status.asInBlock.toString());
|
||||
if (unsub) unsub();
|
||||
}
|
||||
}).then(unsubscribe => { unsub = unsubscribe; });
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleport HEZ from Asset Hub back to Relay Chain
|
||||
*
|
||||
* @param assetHubApi - Polkadot.js API instance (connected to Asset Hub)
|
||||
* @param amount - Amount in smallest unit
|
||||
* @param account - Account to sign and receive on relay chain
|
||||
* @returns Transaction hash
|
||||
*/
|
||||
export async function teleportToRelayChain(
|
||||
assetHubApi: ApiPromise,
|
||||
amount: string | bigint,
|
||||
account: InjectedAccountWithMeta
|
||||
): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const injector = await (window as any).injectedWeb3[account.meta.source]?.enable?.('PezkuwiChain');
|
||||
if (!injector) {
|
||||
throw new Error('Failed to get injector from wallet extension');
|
||||
}
|
||||
|
||||
const signer = injector.signer;
|
||||
|
||||
// Destination: Relay chain (parent)
|
||||
const dest = {
|
||||
V3: {
|
||||
parents: 1,
|
||||
interior: 'Here'
|
||||
}
|
||||
};
|
||||
|
||||
// Beneficiary: Same account on relay chain
|
||||
const beneficiary = {
|
||||
V3: {
|
||||
parents: 0,
|
||||
interior: {
|
||||
X1: {
|
||||
AccountId32: {
|
||||
network: null,
|
||||
id: assetHubApi.createType('AccountId32', account.address).toHex()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Assets: Native token
|
||||
const assets = {
|
||||
V3: [{
|
||||
id: {
|
||||
Concrete: {
|
||||
parents: 1,
|
||||
interior: 'Here'
|
||||
}
|
||||
},
|
||||
fun: {
|
||||
Fungible: amount.toString()
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const feeAssetItem = 0;
|
||||
const weightLimit = 'Unlimited';
|
||||
|
||||
const tx = assetHubApi.tx.polkadotXcm.limitedTeleportAssets(
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
feeAssetItem,
|
||||
weightLimit
|
||||
);
|
||||
|
||||
let unsub: () => void;
|
||||
|
||||
await tx.signAndSend(account.address, { signer }, ({ status, dispatchError }) => {
|
||||
if (dispatchError) {
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
|
||||
} else {
|
||||
reject(new Error(dispatchError.toString()));
|
||||
}
|
||||
if (unsub) unsub();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.isInBlock) {
|
||||
resolve(status.asInBlock.toString());
|
||||
if (unsub) unsub();
|
||||
}
|
||||
}).then(unsubscribe => { unsub = unsubscribe; });
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ========================================
|
||||
|
||||
Reference in New Issue
Block a user