auto-commit for bed65b0f-c949-4d15-b953-0bf08c7c9e55

This commit is contained in:
emergent-agent-e1
2025-10-24 02:45:21 +00:00
parent b734c7ac00
commit 208ae2176c
40 changed files with 9223 additions and 56 deletions
+307
View File
@@ -0,0 +1,307 @@
/**
* PezkuwiChain Blockchain Service
* Handles all interactions with the PezkuwiChain blockchain via Polkadot.js
*/
import { ApiPromise, WsProvider } from '@polkadot/api';
import { CURRENT_CHAIN_CONFIG, ASSET_IDS } from '../constants/blockchain';
import { Balance, Transaction, Proposal } from '../types';
class BlockchainService {
private api: ApiPromise | null = null;
private provider: WsProvider | null = null;
private isConnected: boolean = false;
/**
* Initialize connection to PezkuwiChain
*/
async connect(): Promise<boolean> {
try {
console.log(`Connecting to ${CURRENT_CHAIN_CONFIG.name}...`);
console.log(`RPC URL: ${CURRENT_CHAIN_CONFIG.rpcUrl}`);
this.provider = new WsProvider(CURRENT_CHAIN_CONFIG.rpcUrl);
this.api = await ApiPromise.create({ provider: this.provider });
await this.api.isReady;
this.isConnected = true;
console.log('✅ Connected to PezkuwiChain');
console.log(`Chain: ${await this.api.rpc.system.chain()}`);
console.log(`Node: ${await this.api.rpc.system.name()}`);
console.log(`Version: ${await this.api.rpc.system.version()}`);
return true;
} catch (error) {
console.error('❌ Failed to connect to PezkuwiChain:', error);
this.isConnected = false;
return false;
}
}
/**
* Disconnect from blockchain
*/
async disconnect(): Promise<void> {
if (this.api) {
await this.api.disconnect();
this.api = null;
this.provider = null;
this.isConnected = false;
console.log('Disconnected from PezkuwiChain');
}
}
/**
* Check if connected to blockchain
*/
isApiConnected(): boolean {
return this.isConnected && this.api !== null;
}
/**
* Get API instance (throws if not connected)
*/
private getApi(): ApiPromise {
if (!this.api || !this.isConnected) {
throw new Error('Not connected to blockchain. Call connect() first.');
}
return this.api;
}
/**
* Get account balances (HEZ and PEZ)
*/
async getBalances(address: string): Promise<Balance> {
try {
const api = this.getApi();
// Get HEZ balance (native token)
const { data: hezBalance } = await api.query.system.account(address);
const hezFree = hezBalance.free.toString();
const hezReserved = hezBalance.reserved.toString();
// Get PEZ balance (asset)
const pezBalance = await api.query.assets.account(ASSET_IDS.PEZ, address);
const pezFree = pezBalance.isSome ? pezBalance.unwrap().balance.toString() : '0';
// Get staked HEZ
const stakingInfo = await api.query.staking.ledger(address);
const hezStaked = stakingInfo.isSome ? stakingInfo.unwrap().active.toString() : '0';
// Calculate governance power (PEZ balance as percentage)
const totalPezSupply = '5000000000000000000000'; // 5 billion
const governancePower = (parseFloat(pezFree) / parseFloat(totalPezSupply) * 100).toFixed(2);
// Mock USD values (would come from price oracle in production)
const hezUsd = (parseFloat(hezFree) / 1e12 * 1.0).toFixed(2);
const pezUsd = (parseFloat(pezFree) / 1e12 * 0.1).toFixed(2);
return {
hez: this.formatBalance(hezFree, 12),
pez: this.formatBalance(pezFree, 12),
hezStaked: this.formatBalance(hezStaked, 12),
hezUsd,
pezUsd,
governancePower,
};
} catch (error) {
console.error('Error fetching balances:', error);
// Return mock data if blockchain not available
return this.getMockBalances();
}
}
/**
* Get transaction history
*/
async getTransactions(address: string, limit: number = 10): Promise<Transaction[]> {
try {
// This would query blockchain events and filter for transfers
// For now, return mock data
return this.getMockTransactions();
} catch (error) {
console.error('Error fetching transactions:', error);
return this.getMockTransactions();
}
}
/**
* Get active governance proposals
*/
async getProposals(): Promise<Proposal[]> {
try {
const api = this.getApi();
// Query welati pallet for active proposals
const proposals = await api.query.welati.proposals.entries();
return proposals.map(([key, value]: any) => {
const proposalId = key.args[0].toNumber();
const proposal = value.unwrap();
return {
id: proposalId,
title: `Proposal ${proposalId}`,
description: proposal.description?.toString() || 'No description',
proposer: proposal.proposer.toString(),
votingDeadline: proposal.deadline.toNumber(),
yesVotes: proposal.yesVotes.toString(),
noVotes: proposal.noVotes.toString(),
status: proposal.status.toString() as any,
};
});
} catch (error) {
console.error('Error fetching proposals:', error);
return this.getMockProposals();
}
}
/**
* Send HEZ or PEZ tokens
*/
async sendTokens(
from: string,
to: string,
amount: string,
token: 'HEZ' | 'PEZ',
signer: any
): Promise<string> {
try {
const api = this.getApi();
let tx;
if (token === 'HEZ') {
tx = api.tx.balances.transfer(to, amount);
} else {
tx = api.tx.assets.transfer(ASSET_IDS.PEZ, to, amount);
}
const hash = await tx.signAndSend(signer);
return hash.toString();
} catch (error) {
console.error('Error sending tokens:', error);
throw error;
}
}
/**
* Stake HEZ tokens
*/
async stakeTokens(
address: string,
amount: string,
signer: any
): Promise<string> {
try {
const api = this.getApi();
const tx = api.tx.staking.bond(address, amount, 'Staked');
const hash = await tx.signAndSend(signer);
return hash.toString();
} catch (error) {
console.error('Error staking tokens:', error);
throw error;
}
}
/**
* Vote on governance proposal
*/
async voteOnProposal(
proposalId: number,
vote: 'yes' | 'no',
amount: string,
signer: any
): Promise<string> {
try {
const api = this.getApi();
const tx = api.tx.welati.vote(proposalId, vote === 'yes', amount);
const hash = await tx.signAndSend(signer);
return hash.toString();
} catch (error) {
console.error('Error voting on proposal:', error);
throw error;
}
}
/**
* Format balance with decimals
*/
private formatBalance(balance: string, decimals: number): string {
const value = parseFloat(balance) / Math.pow(10, decimals);
return value.toLocaleString('en-US', { maximumFractionDigits: 2 });
}
/**
* Mock data for development/testing
*/
private getMockBalances(): Balance {
return {
hez: '45,750.5',
pez: '1,234,567',
hezStaked: '30,000',
hezUsd: '45,234',
pezUsd: '123,456',
governancePower: '2.5',
};
}
private getMockTransactions(): Transaction[] {
return [
{
id: '1',
type: 'send' as any,
amount: '500',
token: 'HEZ',
from: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
timestamp: Date.now() - 86400000,
blockNumber: 123456,
hash: '0x1234567890abcdef',
status: 'confirmed',
},
{
id: '2',
type: 'receive' as any,
amount: '300',
token: 'PEZ',
from: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
to: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
timestamp: Date.now() - 172800000,
blockNumber: 123450,
hash: '0xabcdef1234567890',
status: 'confirmed',
},
];
}
private getMockProposals(): Proposal[] {
return [
{
id: 1,
title: 'Proposal 1',
description: 'Description of proposal 1',
proposer: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
votingDeadline: Date.now() + 172800000,
yesVotes: '10400',
noVotes: '4600',
status: 'active',
},
{
id: 2,
title: 'Proposal 2',
description: 'Description of proposal 2',
proposer: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
votingDeadline: Date.now() + 432000000,
yesVotes: '198',
noVotes: '0',
status: 'active',
},
];
}
}
// Export singleton instance
export const blockchainService = new BlockchainService();
export default blockchainService;
+262
View File
@@ -0,0 +1,262 @@
/**
* KYC Service
* Handles Identity-KYC form submission, encryption, and blockchain interaction
*/
import * as SecureStore from 'expo-secure-store';
import { KYCFormData, KYCSubmission, KurdistanCitizen, KYCStatus } from '../types/kyc';
import { blockchainService } from './blockchain';
const KYC_DATA_KEY = 'pezkuwi_kyc_data';
const CITIZEN_DATA_KEY = 'pezkuwi_citizen_data';
const KYC_STATUS_KEY = 'pezkuwi_kyc_status';
class KYCService {
/**
* Generate hash from KYC data
* This hash will be submitted to blockchain
*/
private async generateHash(data: KYCFormData): Promise<string> {
// In production, use proper cryptographic hash (SHA-256)
const dataString = JSON.stringify(data);
// Simple hash for development (replace with proper crypto in production)
let hash = 0;
for (let i = 0; i < dataString.length; i++) {
const char = dataString.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return `0x${Math.abs(hash).toString(16).padStart(64, '0')}`;
}
/**
* Save KYC form data locally (encrypted)
*/
async saveKYCData(data: KYCFormData): Promise<void> {
try {
const encrypted = JSON.stringify(data);
await SecureStore.setItemAsync(KYC_DATA_KEY, encrypted);
console.log('✅ KYC data saved locally (encrypted)');
} catch (error) {
console.error('❌ Failed to save KYC data:', error);
throw error;
}
}
/**
* Get saved KYC data
*/
async getKYCData(): Promise<KYCFormData | null> {
try {
const encrypted = await SecureStore.getItemAsync(KYC_DATA_KEY);
if (!encrypted) return null;
return JSON.parse(encrypted);
} catch (error) {
console.error('❌ Failed to get KYC data:', error);
return null;
}
}
/**
* Submit KYC to blockchain
* Only hash is sent, not the actual data
*/
async submitKYC(data: KYCFormData, signer: any): Promise<KYCSubmission> {
try {
// 1. Save data locally first
await this.saveKYCData(data);
// 2. Generate hash
const dataHash = await this.generateHash(data);
console.log('📝 Generated KYC hash:', dataHash);
// 3. Submit hash to blockchain (identity-kyc pallet)
// TODO: Replace with actual blockchain call when testnet is active
const txHash = await this.submitHashToBlockchain(dataHash, signer);
const submission: KYCSubmission = {
dataHash,
submittedAt: Date.now(),
txHash,
};
// 4. Update KYC status
await this.updateKYCStatus({ started: true, submitted: true, approved: false });
console.log('✅ KYC submitted to blockchain');
return submission;
} catch (error) {
console.error('❌ Failed to submit KYC:', error);
throw error;
}
}
/**
* Submit hash to blockchain (identity-kyc pallet)
*/
private async submitHashToBlockchain(dataHash: string, signer: any): Promise<string> {
try {
// Check if blockchain is connected
if (!blockchainService.isApiConnected()) {
console.log('⚠️ Blockchain not connected, using mock submission');
// Mock transaction hash for development
return `0x${Math.random().toString(16).substr(2, 64)}`;
}
// TODO: Actual blockchain submission
// const api = blockchainService.getApi();
// const tx = api.tx.identityKyc.submitKyc(dataHash);
// const hash = await tx.signAndSend(signer);
// return hash.toString();
// Mock for now
return `0x${Math.random().toString(16).substr(2, 64)}`;
} catch (error) {
console.error('❌ Failed to submit to blockchain:', error);
throw error;
}
}
/**
* Check KYC approval status on blockchain
*/
async checkApprovalStatus(address: string): Promise<boolean> {
try {
// TODO: Query blockchain for approval status
// const api = blockchainService.getApi();
// const approval = await api.query.identityKyc.approvals(address);
// return approval.isSome;
// Mock: Auto-approve after 5 seconds (for development)
const status = await this.getKYCStatus();
if (status.submitted) {
const timeSinceSubmission = Date.now() - (status.citizen?.approvedAt || 0);
return timeSinceSubmission > 5000; // 5 seconds
}
return false;
} catch (error) {
console.error('❌ Failed to check approval status:', error);
return false;
}
}
/**
* Generate Kurdistan Digital Citizen certificate
* Called after KYC is approved on blockchain
*/
async generateCitizen(data: KYCFormData, dataHash: string): Promise<KurdistanCitizen> {
try {
// Generate unique Citizen ID
const citizenId = `KRD-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
// Generate QR code data
const qrData = JSON.stringify({
citizenId,
name: data.fullName,
region: data.region,
hash: dataHash,
});
const citizen: KurdistanCitizen = {
citizenId,
fullName: data.fullName,
photo: data.photo || '',
region: data.region,
kycApproved: true,
approvedAt: Date.now(),
dataHash,
qrCode: qrData,
};
// Save citizen data locally (encrypted)
await SecureStore.setItemAsync(CITIZEN_DATA_KEY, JSON.stringify(citizen));
// Update KYC status
await this.updateKYCStatus({
started: true,
submitted: true,
approved: true,
citizen,
});
console.log('✅ Kurdistan Digital Citizen generated:', citizenId);
return citizen;
} catch (error) {
console.error('❌ Failed to generate citizen:', error);
throw error;
}
}
/**
* Get citizen data
*/
async getCitizen(): Promise<KurdistanCitizen | null> {
try {
const data = await SecureStore.getItemAsync(CITIZEN_DATA_KEY);
if (!data) return null;
return JSON.parse(data);
} catch (error) {
console.error('❌ Failed to get citizen data:', error);
return null;
}
}
/**
* Get KYC status
*/
async getKYCStatus(): Promise<KYCStatus> {
try {
const statusData = await SecureStore.getItemAsync(KYC_STATUS_KEY);
if (!statusData) {
return { started: false, submitted: false, approved: false };
}
return JSON.parse(statusData);
} catch (error) {
console.error('❌ Failed to get KYC status:', error);
return { started: false, submitted: false, approved: false };
}
}
/**
* Update KYC status
*/
private async updateKYCStatus(status: KYCStatus): Promise<void> {
try {
await SecureStore.setItemAsync(KYC_STATUS_KEY, JSON.stringify(status));
} catch (error) {
console.error('❌ Failed to update KYC status:', error);
}
}
/**
* Check if user has access to Governance
*/
async hasGovernanceAccess(): Promise<boolean> {
const status = await this.getKYCStatus();
return status.approved;
}
/**
* Clear all KYC data (for testing)
*/
async clearKYCData(): Promise<void> {
try {
await SecureStore.deleteItemAsync(KYC_DATA_KEY);
await SecureStore.deleteItemAsync(CITIZEN_DATA_KEY);
await SecureStore.deleteItemAsync(KYC_STATUS_KEY);
console.log('✅ KYC data cleared');
} catch (error) {
console.error('❌ Failed to clear KYC data:', error);
}
}
}
export const kycService = new KYCService();
export default kycService;