mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 21:47:56 +00:00
8f2b5c7136
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
330 lines
9.2 KiB
TypeScript
330 lines
9.2 KiB
TypeScript
import type { ApiPromise } from '@pezkuwi/api';
|
|
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
|
|
import type { Signer } from '@pezkuwi/api/types';
|
|
|
|
// Extended account type with signer for transaction signing
|
|
interface AccountWithSigner extends InjectedAccountWithMeta {
|
|
signer?: Signer;
|
|
}
|
|
|
|
/**
|
|
* Referral System Integration with pallet_referral
|
|
*
|
|
* Provides functions to interact with the referral pallet on PezkuwiChain.
|
|
*
|
|
* Workflow:
|
|
* 1. User A calls initiateReferral(userB_address) -> creates pending referral
|
|
* 2. User B completes KYC and gets approved
|
|
* 3. Pallet automatically confirms referral via OnKycApproved hook
|
|
* 4. User A's referral count increases
|
|
*/
|
|
|
|
export interface ReferralInfo {
|
|
referrer: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
export interface ReferralStats {
|
|
referralCount: number;
|
|
referralScore: number;
|
|
whoInvitedMe: string | null;
|
|
pendingReferral: string | null; // Who invited me (if pending)
|
|
}
|
|
|
|
/**
|
|
* Initiate a referral for a new user
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param signer User's Polkadot account with extension
|
|
* @param referredAddress Address of the user being referred
|
|
* @returns Transaction hash
|
|
*/
|
|
export async function initiateReferral(
|
|
api: ApiPromise,
|
|
signer: AccountWithSigner,
|
|
referredAddress: string
|
|
): Promise<string> {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
const tx = api.tx.referral.initiateReferral(referredAddress);
|
|
|
|
await tx.signAndSend(
|
|
signer.address,
|
|
{ signer: signer.signer },
|
|
({ status, events, dispatchError }) => {
|
|
if (dispatchError) {
|
|
if (dispatchError.isModule) {
|
|
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
const error = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
|
|
reject(new Error(error));
|
|
} else {
|
|
reject(new Error(dispatchError.toString()));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (status.isInBlock || status.isFinalized) {
|
|
const hash = status.asInBlock?.toString() || status.asFinalized?.toString() || '';
|
|
resolve(hash);
|
|
}
|
|
}
|
|
);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param address User address
|
|
* @returns Referrer address if pending, null otherwise
|
|
*/
|
|
export async function getPendingReferral(
|
|
api: ApiPromise,
|
|
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) {
|
|
return null;
|
|
}
|
|
|
|
return result.toString();
|
|
} catch (error) {
|
|
if (import.meta.env.DEV) console.error('Error fetching pending referral:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the number of successful referrals for a user
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param address User address
|
|
* @returns Number of confirmed referrals
|
|
*/
|
|
export async function getReferralCount(
|
|
api: ApiPromise,
|
|
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) {
|
|
if (import.meta.env.DEV) console.error('Error fetching referral count:', error);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get referral info for a user (who referred them, when)
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param address User address who was referred
|
|
* @returns ReferralInfo if exists, null otherwise
|
|
*/
|
|
export async function getReferralInfo(
|
|
api: ApiPromise,
|
|
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) {
|
|
return null;
|
|
}
|
|
|
|
const data = result.toJSON() as any;
|
|
return {
|
|
referrer: data.referrer,
|
|
createdAt: parseInt(data.createdAt),
|
|
};
|
|
} catch (error) {
|
|
if (import.meta.env.DEV) console.error('Error fetching referral info:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate referral score based on referral count
|
|
*
|
|
* This mirrors the logic in pallet_referral::ReferralScoreProvider
|
|
* Score calculation:
|
|
* - 0 referrals = 0 points
|
|
* - 1-10 referrals = count * 10 points (10, 20, 30, ..., 100)
|
|
* - 11-50 referrals = 100 + (count - 10) * 5 points (105, 110, ..., 300)
|
|
* - 51-100 referrals = 300 + (count - 50) * 4 points (304, 308, ..., 500)
|
|
* - 101+ referrals = 500 points (maximum capped)
|
|
*
|
|
* @param referralCount Number of confirmed referrals
|
|
* @returns Referral score
|
|
*/
|
|
export function calculateReferralScore(referralCount: number): number {
|
|
if (referralCount === 0) return 0;
|
|
if (referralCount <= 10) return referralCount * 10;
|
|
if (referralCount <= 50) return 100 + (referralCount - 10) * 5;
|
|
if (referralCount <= 100) return 300 + (referralCount - 50) * 4;
|
|
return 500; // Max score
|
|
}
|
|
|
|
/**
|
|
* Get comprehensive referral statistics for a user
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param address User address
|
|
* @returns Complete referral stats
|
|
*/
|
|
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),
|
|
getReferralInfo(api, address),
|
|
getPendingReferral(api, address),
|
|
]);
|
|
|
|
const referralScore = calculateReferralScore(referralCount);
|
|
|
|
return {
|
|
referralCount,
|
|
referralScore,
|
|
whoInvitedMe: referralInfo?.referrer || null,
|
|
pendingReferral,
|
|
};
|
|
} catch (error) {
|
|
if (import.meta.env.DEV) console.error('Error fetching referral stats:', error);
|
|
return {
|
|
referralCount: 0,
|
|
referralScore: 0,
|
|
whoInvitedMe: null,
|
|
pendingReferral: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of all users who were referred by this user
|
|
* (Note: requires iterating storage which can be expensive)
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param referrerAddress Referrer's address
|
|
* @returns Array of addresses referred by this user
|
|
*/
|
|
export async function getMyReferrals(
|
|
api: ApiPromise,
|
|
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
|
|
.filter(([_key, value]) => {
|
|
if (value.isEmpty) return false;
|
|
const data = value.toJSON() as any;
|
|
return data.referrer === referrerAddress;
|
|
})
|
|
.map(([key]) => {
|
|
// Extract the referred address from the storage key
|
|
const addressHex = key.args[0].toString();
|
|
return addressHex;
|
|
});
|
|
|
|
return myReferrals;
|
|
} catch (error) {
|
|
if (import.meta.env.DEV) console.error('Error fetching my referrals:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subscribe to referral events for real-time updates
|
|
*
|
|
* @param api Polkadot API instance
|
|
* @param callback Callback function for events
|
|
* @returns Unsubscribe function
|
|
*/
|
|
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
|
|
events.forEach((record: any) => {
|
|
const { event } = record;
|
|
|
|
if (event.section === 'referral') {
|
|
if (event.method === 'ReferralInitiated') {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [referrer, referred] = event.data as any;
|
|
callback({
|
|
type: 'initiated',
|
|
referrer: referrer.toString(),
|
|
referred: referred.toString(),
|
|
});
|
|
} else if (event.method === 'ReferralConfirmed') {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [referrer, referred, newCount] = event.data as any;
|
|
callback({
|
|
type: 'confirmed',
|
|
referrer: referrer.toString(),
|
|
referred: referred.toString(),
|
|
count: newCount.toNumber(),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
return unsub as unknown as () => void;
|
|
}
|