Files
pwap/shared/lib/usdt.ts
T
Claude 7b95b8a409 Centralize common code in shared folder
This commit reorganizes the codebase to eliminate duplication between web and mobile frontends by moving all commonly used files to the shared folder.

Changes:
- Moved lib files to shared/lib/:
  * wallet.ts, staking.ts, tiki.ts, identity.ts
  * multisig.ts, usdt.ts, scores.ts, citizenship-workflow.ts

- Moved utils to shared/utils/:
  * auth.ts, dex.ts
  * Created format.ts (extracted formatNumber from web utils)

- Created shared/theme/:
  * colors.ts (Kurdistan and App color definitions)

- Updated web configuration:
  * Added @pezkuwi/* path aliases in tsconfig.json and vite.config.ts
  * Updated all imports to use @pezkuwi/lib/*, @pezkuwi/utils/*, @pezkuwi/theme/*
  * Removed duplicate files from web/src/lib and web/src/utils

- Updated mobile configuration:
  * Added @pezkuwi/* path aliases in tsconfig.json
  * Updated theme/colors.ts to re-export from shared
  * Mobile already uses relative imports to shared (no changes needed)

Architecture Benefits:
- Single source of truth for common code
- No duplication between frontends
- Easier maintenance and consistency
- Clear separation of shared vs platform-specific code

Web-specific files kept:
- web/src/lib/supabase.ts
- web/src/lib/utils.ts (cn function for Tailwind, re-exports formatNumber from shared)

All imports updated and tested. Both web and mobile now use the centralized shared folder.
2025-11-14 22:46:39 +00:00

315 lines
9.1 KiB
TypeScript

// ========================================
// USDT Bridge Utilities
// ========================================
// Handles wUSDT minting, burning, and reserve management
import type { ApiPromise } from '@polkadot/api';
import { ASSET_IDS } from './wallet';
import { getMultisigMembers, createMultisigTx } from './multisig';
// ========================================
// CONSTANTS
// ========================================
export const WUSDT_ASSET_ID = ASSET_IDS.WUSDT;
export const WUSDT_DECIMALS = 6; // USDT has 6 decimals
// Withdrawal limits and timeouts
export const WITHDRAWAL_LIMITS = {
instant: {
maxAmount: 1000, // $1,000
delay: 0, // No delay
},
standard: {
maxAmount: 10000, // $10,000
delay: 3600, // 1 hour in seconds
},
large: {
maxAmount: Infinity,
delay: 86400, // 24 hours
},
};
// ========================================
// ASSET QUERIES
// ========================================
/**
* Get wUSDT balance for an account
* @param api - Polkadot API instance
* @param address - Account address
* @returns Balance in human-readable format
*/
export async function getWUSDTBalance(api: ApiPromise, address: string): Promise<number> {
try {
const balance = await api.query.assets.account(WUSDT_ASSET_ID, address);
if (balance.isSome) {
const balanceData = balance.unwrap().toJSON() as any;
return Number(balanceData.balance) / Math.pow(10, WUSDT_DECIMALS);
}
return 0;
} catch (error) {
console.error('Error fetching wUSDT balance:', error);
return 0;
}
}
/**
* Get total wUSDT supply
* @param api - Polkadot API instance
* @returns Total supply in human-readable format
*/
export async function getWUSDTTotalSupply(api: ApiPromise): Promise<number> {
try {
const assetDetails = await api.query.assets.asset(WUSDT_ASSET_ID);
if (assetDetails.isSome) {
const details = assetDetails.unwrap().toJSON() as any;
return Number(details.supply) / Math.pow(10, WUSDT_DECIMALS);
}
return 0;
} catch (error) {
console.error('Error fetching wUSDT supply:', error);
return 0;
}
}
/**
* Get wUSDT asset metadata
* @param api - Polkadot API instance
* @returns Asset metadata
*/
export async function getWUSDTMetadata(api: ApiPromise) {
try {
const metadata = await api.query.assets.metadata(WUSDT_ASSET_ID);
return metadata.toJSON();
} catch (error) {
console.error('Error fetching wUSDT metadata:', error);
return null;
}
}
// ========================================
// MULTISIG OPERATIONS
// ========================================
/**
* Create multisig transaction to mint wUSDT
* @param api - Polkadot API instance
* @param beneficiary - Who will receive the wUSDT
* @param amount - Amount in human-readable format (e.g., 100.50 USDT)
* @param signerAddress - Address of the signer creating this tx
* @param specificAddresses - Addresses for non-unique multisig members
* @returns Multisig transaction
*/
export async function createMintWUSDTTx(
api: ApiPromise,
beneficiary: string,
amount: number,
signerAddress: string,
specificAddresses: Record<string, string> = {}
) {
// Convert to smallest unit
const amountBN = BigInt(Math.floor(amount * Math.pow(10, WUSDT_DECIMALS)));
// Create the mint call
const mintCall = api.tx.assets.mint(WUSDT_ASSET_ID, beneficiary, amountBN.toString());
// Get all multisig members
const allMembers = await getMultisigMembers(api, specificAddresses);
// Other signatories (excluding current signer)
const otherSignatories = allMembers.filter((addr) => addr !== signerAddress);
// Create multisig transaction
return createMultisigTx(api, mintCall, otherSignatories);
}
/**
* Create multisig transaction to burn wUSDT
* @param api - Polkadot API instance
* @param from - Who will have their wUSDT burned
* @param amount - Amount in human-readable format
* @param signerAddress - Address of the signer creating this tx
* @param specificAddresses - Addresses for non-unique multisig members
* @returns Multisig transaction
*/
export async function createBurnWUSDTTx(
api: ApiPromise,
from: string,
amount: number,
signerAddress: string,
specificAddresses: Record<string, string> = {}
) {
const amountBN = BigInt(Math.floor(amount * Math.pow(10, WUSDT_DECIMALS)));
const burnCall = api.tx.assets.burn(WUSDT_ASSET_ID, from, amountBN.toString());
const allMembers = await getMultisigMembers(api, specificAddresses);
const otherSignatories = allMembers.filter((addr) => addr !== signerAddress);
return createMultisigTx(api, burnCall, otherSignatories);
}
// ========================================
// WITHDRAWAL HELPERS
// ========================================
/**
* Calculate withdrawal delay based on amount
* @param amount - Withdrawal amount in USDT
* @returns Delay in seconds
*/
export function calculateWithdrawalDelay(amount: number): number {
if (amount <= WITHDRAWAL_LIMITS.instant.maxAmount) {
return WITHDRAWAL_LIMITS.instant.delay;
} else if (amount <= WITHDRAWAL_LIMITS.standard.maxAmount) {
return WITHDRAWAL_LIMITS.standard.delay;
} else {
return WITHDRAWAL_LIMITS.large.delay;
}
}
/**
* Get withdrawal tier name
* @param amount - Withdrawal amount
* @returns Tier name
*/
export function getWithdrawalTier(amount: number): string {
if (amount <= WITHDRAWAL_LIMITS.instant.maxAmount) return 'Instant';
if (amount <= WITHDRAWAL_LIMITS.standard.maxAmount) return 'Standard';
return 'Large';
}
/**
* Format delay time for display
* @param seconds - Delay in seconds
* @returns Human-readable format
*/
export function formatDelay(seconds: number): string {
if (seconds === 0) return 'Instant';
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`;
return `${Math.floor(seconds / 86400)} days`;
}
// ========================================
// RESERVE CHECKING
// ========================================
export interface ReserveStatus {
wusdtSupply: number;
offChainReserve: number; // This would come from off-chain oracle/API
collateralRatio: number;
isHealthy: boolean;
}
/**
* Check reserve health
* @param api - Polkadot API instance
* @param offChainReserve - Off-chain USDT reserve amount (from treasury)
* @returns Reserve status
*/
export async function checkReserveHealth(
api: ApiPromise,
offChainReserve: number
): Promise<ReserveStatus> {
const wusdtSupply = await getWUSDTTotalSupply(api);
const collateralRatio = wusdtSupply > 0 ? (offChainReserve / wusdtSupply) * 100 : 0;
return {
wusdtSupply,
offChainReserve,
collateralRatio,
isHealthy: collateralRatio >= 100, // At least 100% backed
};
}
// ========================================
// EVENT MONITORING
// ========================================
/**
* Subscribe to wUSDT mint events
* @param api - Polkadot API instance
* @param callback - Callback function for each mint event
*/
export function subscribeToMintEvents(
api: ApiPromise,
callback: (beneficiary: string, amount: number, txHash: string) => void
) {
return api.query.system.events((events) => {
events.forEach((record) => {
const { event } = record;
if (api.events.assets.Issued.is(event)) {
const [assetId, beneficiary, amount] = event.data;
if (assetId.toNumber() === WUSDT_ASSET_ID) {
const amountNum = Number(amount.toString()) / Math.pow(10, WUSDT_DECIMALS);
callback(beneficiary.toString(), amountNum, record.hash.toHex());
}
}
});
});
}
/**
* Subscribe to wUSDT burn events
* @param api - Polkadot API instance
* @param callback - Callback function for each burn event
*/
export function subscribeToBurnEvents(
api: ApiPromise,
callback: (account: string, amount: number, txHash: string) => void
) {
return api.query.system.events((events) => {
events.forEach((record) => {
const { event } = record;
if (api.events.assets.Burned.is(event)) {
const [assetId, account, amount] = event.data;
if (assetId.toNumber() === WUSDT_ASSET_ID) {
const amountNum = Number(amount.toString()) / Math.pow(10, WUSDT_DECIMALS);
callback(account.toString(), amountNum, record.hash.toHex());
}
}
});
});
}
// ========================================
// DISPLAY HELPERS
// ========================================
/**
* Format wUSDT amount for display
* @param amount - Amount in smallest unit or human-readable
* @param fromSmallestUnit - Whether input is in smallest unit
* @returns Formatted string
*/
export function formatWUSDT(amount: number | string, fromSmallestUnit = false): string {
const value = typeof amount === 'string' ? parseFloat(amount) : amount;
if (fromSmallestUnit) {
return (value / Math.pow(10, WUSDT_DECIMALS)).toFixed(2);
}
return value.toFixed(2);
}
/**
* Parse human-readable USDT to smallest unit
* @param amount - Human-readable amount
* @returns Amount in smallest unit (BigInt)
*/
export function parseWUSDT(amount: number | string): bigint {
const value = typeof amount === 'string' ? parseFloat(amount) : amount;
return BigInt(Math.floor(value * Math.pow(10, WUSDT_DECIMALS)));
}