fix: universal getSigner helper for WalletConnect + extension signing

Replace all web3FromAddress calls with getSigner() that auto-detects
walletSource and uses WC signer or extension signer accordingly.
This fixes all signing operations when connected via WalletConnect.
This commit is contained in:
2026-02-23 07:01:18 +03:00
parent bcee7c2a7d
commit 80d8bbbcb1
21 changed files with 139 additions and 142 deletions
+19 -25
View File
@@ -4,11 +4,13 @@
// Handles citizenship verification, status checks, and workflow logic
import type { ApiPromise } from '@pezkuwi/api';
import { web3Enable, web3FromAddress as web3FromAddressOriginal } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
import type { Signer } from '@pezkuwi/api/types';
type WalletSource = 'extension' | 'walletconnect' | 'native' | null;
interface SignRawPayload {
address: string;
data: string;
@@ -23,20 +25,6 @@ interface InjectedSigner {
signRaw?: (payload: SignRawPayload) => Promise<SignRawResult>;
}
interface InjectedExtension {
signer: Signer & InjectedSigner;
}
// Use real extension in browser, throw error in unsupported environments
const web3FromAddress = async (address: string): Promise<InjectedExtension> => {
// Check if we're in a browser environment with extension support
if (typeof window !== 'undefined') {
await web3Enable('PezkuwiChain');
return web3FromAddressOriginal(address) as Promise<InjectedExtension>;
}
throw new Error('Pezkuwi Wallet extension not available. Please install the extension.');
};
// ========================================
// TYPE DEFINITIONS
// ========================================
@@ -477,7 +465,8 @@ export async function submitKycApplication(
api: ApiPromise,
account: InjectedAccountWithMeta,
identityHash: string,
referrerAddress?: string
referrerAddress?: string,
walletSource?: WalletSource
): Promise<{ success: boolean; error?: string; blockHash?: string }> {
try {
if (!api?.tx?.identityKyc?.applyForCitizenship) {
@@ -502,7 +491,7 @@ export async function submitKycApplication(
};
}
const injector = await web3FromAddress(account.address);
const injector = await getSigner(account.address, walletSource ?? 'extension', api);
if (import.meta.env.DEV) {
console.log('=== submitKycApplication Debug ===');
@@ -629,14 +618,15 @@ export function subscribeToKycApproval(
export async function approveReferral(
api: ApiPromise,
account: InjectedAccountWithMeta,
applicantAddress: string
applicantAddress: string,
walletSource?: WalletSource
): Promise<{ success: boolean; error?: string; blockHash?: string }> {
try {
if (!api?.tx?.identityKyc?.approveReferral) {
return { success: false, error: 'Identity KYC pallet not available' };
}
const injector = await web3FromAddress(account.address);
const injector = await getSigner(account.address, walletSource ?? 'extension', api);
const result = await new Promise<{ success: boolean; error?: string; blockHash?: string }>((resolve, reject) => {
api.tx.identityKyc
@@ -679,14 +669,15 @@ export async function approveReferral(
*/
export async function cancelApplication(
api: ApiPromise,
account: InjectedAccountWithMeta
account: InjectedAccountWithMeta,
walletSource?: WalletSource
): Promise<{ success: boolean; error?: string }> {
try {
if (!api?.tx?.identityKyc?.cancelApplication) {
return { success: false, error: 'Identity KYC pallet not available' };
}
const injector = await web3FromAddress(account.address);
const injector = await getSigner(account.address, walletSource ?? 'extension', api);
const result = await new Promise<{ success: boolean; error?: string }>((resolve, reject) => {
api.tx.identityKyc
@@ -723,14 +714,15 @@ export async function cancelApplication(
*/
export async function confirmCitizenship(
api: ApiPromise,
account: InjectedAccountWithMeta
account: InjectedAccountWithMeta,
walletSource?: WalletSource
): Promise<{ success: boolean; error?: string; blockHash?: string }> {
try {
if (!api?.tx?.identityKyc?.confirmCitizenship) {
return { success: false, error: 'Identity KYC pallet not available' };
}
const injector = await web3FromAddress(account.address);
const injector = await getSigner(account.address, walletSource ?? 'extension', api);
const result = await new Promise<{ success: boolean; error?: string; blockHash?: string }>((resolve, reject) => {
api.tx.identityKyc
@@ -850,10 +842,12 @@ export function generateAuthChallenge(tikiNumber: string): AuthChallenge {
*/
export async function signChallenge(
account: InjectedAccountWithMeta,
challenge: AuthChallenge
challenge: AuthChallenge,
walletSource?: WalletSource,
api?: ApiPromise | null
): Promise<string> {
try {
const injector = await web3FromAddress(account.address);
const injector = await getSigner(account.address, walletSource ?? 'extension', api);
if (!injector?.signer?.signRaw) {
throw new Error('Signer not available');
+4 -5
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { X, Plus, Info, AlertCircle } from 'lucide-react';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { useWallet } from '@/contexts/WalletContext';
import { Button } from '@/components/ui/button';
@@ -62,7 +62,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
asset1 = 1 // Default to PEZ
}) => {
// Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub)
const { assetHubApi, selectedAccount, isAssetHubReady } = usePezkuwi();
const { assetHubApi, selectedAccount, isAssetHubReady, walletSource } = usePezkuwi();
const { balances, refreshBalances } = useWallet();
const [amount0, setAmount0] = useState('');
@@ -357,9 +357,8 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
return;
}
// Get the signer from the extension
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
// Get the signer (extension or WalletConnect)
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
// Convert amounts to proper decimals
const amount0BN = BigInt(Math.floor(parseFloat(amount0) * Math.pow(10, asset0Decimals)));
+3 -4
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { X, Lock, AlertCircle, Loader2, Clock } from 'lucide-react';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
@@ -50,7 +50,7 @@ export const LPStakeModal: React.FC<LPStakeModalProps> = ({
onStakeSuccess,
}) => {
const { t } = useTranslation();
const { assetHubApi, selectedAccount, isAssetHubReady } = usePezkuwi();
const { assetHubApi, selectedAccount, isAssetHubReady, walletSource } = usePezkuwi();
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
@@ -85,8 +85,7 @@ export const LPStakeModal: React.FC<LPStakeModalProps> = ({
try {
const amountBN = BigInt(Math.floor(amount * 1e12));
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.assetRewards.stake(poolId, amountBN.toString());
+5 -8
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { X, Lock, Unlock, Gift, AlertCircle, Loader2, Info } from 'lucide-react';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
@@ -31,7 +31,7 @@ const LP_TOKEN_NAMES: Record<number, string> = {
};
export const LPStakingModal: React.FC<LPStakingModalProps> = ({ isOpen, onClose }) => {
const { assetHubApi, selectedAccount, isAssetHubReady } = usePezkuwi();
const { assetHubApi, selectedAccount, isAssetHubReady, walletSource } = usePezkuwi();
const { t } = useTranslation();
const [pools, setPools] = useState<StakingPool[]>([]);
const [isLoading, setIsLoading] = useState(true);
@@ -143,8 +143,7 @@ export const LPStakingModal: React.FC<LPStakingModalProps> = ({ isOpen, onClose
if (!pool) throw new Error('Pool not found');
const amountBN = BigInt(Math.floor(parseFloat(stakeAmount) * 1e12));
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.assetRewards.stake(selectedPool, amountBN.toString());
@@ -190,8 +189,7 @@ export const LPStakingModal: React.FC<LPStakingModalProps> = ({ isOpen, onClose
if (!pool) throw new Error('Pool not found');
const amountBN = BigInt(Math.floor(parseFloat(unstakeAmount) * 1e12));
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.assetRewards.unstake(selectedPool, amountBN.toString());
@@ -233,8 +231,7 @@ export const LPStakingModal: React.FC<LPStakingModalProps> = ({ isOpen, onClose
setSuccess(null);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.assetRewards.harvestRewards(selectedPool);
await new Promise<void>((resolve, reject) => {
+4 -5
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { X, Minus, AlertCircle, Info } from 'lucide-react';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { useWallet } from '@/contexts/WalletContext';
import { Button } from '@/components/ui/button';
@@ -63,7 +63,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
asset1,
}) => {
// Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub)
const { assetHubApi, selectedAccount } = usePezkuwi();
const { assetHubApi, selectedAccount, walletSource } = usePezkuwi();
const { refreshBalances } = useWallet();
const [percentage, setPercentage] = useState(100);
@@ -159,9 +159,8 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
setError(null);
try {
// Get the signer from the extension
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
// Get the signer (extension or WalletConnect)
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
// Get decimals for each asset
const asset0Decimals = getAssetDecimals(asset0);
+4 -5
View File
@@ -25,7 +25,7 @@ const AVAILABLE_TOKENS = [
const TokenSwap = () => {
// Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub)
const { assetHubApi, isAssetHubReady, selectedAccount } = usePezkuwi();
const { assetHubApi, isAssetHubReady, selectedAccount, walletSource } = usePezkuwi();
const { balances, refreshBalances } = useWallet();
const { toast } = useToast();
const { t } = useTranslation();
@@ -583,10 +583,9 @@ const TokenSwap = () => {
minAmountOut: minAmountOut.toString()
});
// Get signer from extension
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
// Get signer (extension or WalletConnect)
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
// Build transaction based on token types
let tx;
+4 -5
View File
@@ -67,7 +67,7 @@ const TOKENS: Token[] = [
];
export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose, selectedAsset }) => {
const { api, assetHubApi, isApiReady, isAssetHubReady, selectedAccount } = usePezkuwi();
const { api, assetHubApi, isApiReady, isAssetHubReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const { t } = useTranslation();
@@ -129,10 +129,9 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose, s
setTxStatus('signing');
try {
// Import web3FromAddress to get the injector
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
// Get signer (extension or WalletConnect)
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, isAssetHubTransfer ? assetHubApi : api);
// Convert amount to smallest unit
const amountInSmallestUnit = BigInt(parseFloat(amount) * Math.pow(10, currentToken.decimals));
+3 -4
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { X, ArrowDown, ArrowUp, AlertCircle, Info, Clock, CheckCircle2 } from 'lucide-react';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { useWallet } from '@/contexts/WalletContext';
import { Button } from '@/components/ui/button';
@@ -30,7 +30,7 @@ export const USDTBridge: React.FC<USDTBridgeProps> = ({
specificAddresses = {},
}) => {
const { t } = useTranslation();
const { api, selectedAccount, isApiReady } = usePezkuwi();
const { api, selectedAccount, isApiReady, walletSource } = usePezkuwi();
const { refreshBalances } = useWallet();
const [depositAmount, setDepositAmount] = useState('');
@@ -114,8 +114,7 @@ export const USDTBridge: React.FC<USDTBridgeProps> = ({
setSuccess(null);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, api);
// Burn wUSDT
const amountBN = BigInt(Math.floor(amount * 1e6)); // 6 decimals
+3 -4
View File
@@ -54,7 +54,7 @@ interface XCMTeleportModalProps {
}
export const XCMTeleportModal: React.FC<XCMTeleportModalProps> = ({ isOpen, onClose }) => {
const { api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, selectedAccount } = usePezkuwi();
const { api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const { t } = useTranslation();
@@ -157,9 +157,8 @@ export const XCMTeleportModal: React.FC<XCMTeleportModalProps> = ({ isOpen, onCl
setTxStatus('signing');
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
// Convert to smallest unit (12 decimals)
const amountInSmallestUnit = BigInt(Math.floor(parseFloat(amount) * 1e12));
@@ -11,7 +11,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
export function CommissionSetupTab() {
const { t } = useTranslation();
const { api, isApiReady, selectedAccount } = usePezkuwi();
const { api, isApiReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const [loading, setLoading] = useState(true);
@@ -70,9 +70,8 @@ export function CommissionSetupTab() {
setProcessing(true);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
// Parse addresses (one per line, trim whitespace)
const newAddresses = newMemberAddress
@@ -175,9 +174,8 @@ export function CommissionSetupTab() {
setProcessing(true);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
if (import.meta.env.DEV) console.log('Initializing KYC Commission...');
if (import.meta.env.DEV) console.log('Proxy account:', COMMISSIONS.KYC.proxyAccount);
@@ -28,7 +28,7 @@ interface Proposal {
export function CommissionVotingTab() {
const { t } = useTranslation();
const { api, isApiReady, selectedAccount } = usePezkuwi();
const { api, isApiReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const [loading, setLoading] = useState(true);
@@ -150,9 +150,8 @@ export function CommissionVotingTab() {
setVoting(proposal.hash);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
if (import.meta.env.DEV) console.log(`Voting ${approve ? 'AYE' : 'NAY'} on proposal:`, proposal.hash);
@@ -258,9 +257,8 @@ export function CommissionVotingTab() {
setVoting(proposal.hash);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
if (import.meta.env.DEV) console.log('Executing proposal:', proposal.hash);
@@ -433,9 +431,8 @@ export function CommissionVotingTab() {
if (!api || !selectedAccount) return;
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
// Get current members
const currentMembers = await api.query.dynamicCommissionCollective.members();
+2 -2
View File
@@ -21,7 +21,7 @@ import type { PendingApproval } from '@pezkuwi/lib/citizenship-workflow';
export function KycApprovalTab() {
const { t } = useTranslation();
// identityKyc pallet is on People Chain - use peopleApi
const { peopleApi, isPeopleReady, selectedAccount, connectWallet } = usePezkuwi();
const { peopleApi, isPeopleReady, selectedAccount, connectWallet, walletSource } = usePezkuwi();
const { toast } = useToast();
const [loading, setLoading] = useState(true);
@@ -75,7 +75,7 @@ export function KycApprovalTab() {
setProcessingAddress(applicantAddress);
try {
const result = await approveReferral(peopleApi, selectedAccount, applicantAddress);
const result = await approveReferral(peopleApi, selectedAccount, applicantAddress, walletSource);
if (!result.success) {
toast({
@@ -17,7 +17,7 @@ interface ExistingCitizenAuthProps {
export const ExistingCitizenAuth: React.FC<ExistingCitizenAuthProps> = ({ onClose }) => {
const { t } = useTranslation();
const { peopleApi, isPeopleReady, selectedAccount, connectWallet } = usePezkuwi();
const { peopleApi, isPeopleReady, selectedAccount, connectWallet, walletSource } = usePezkuwi();
const [citizenNumber, setCitizenNumber] = useState('');
const [step, setStep] = useState<'input' | 'verifying' | 'signing' | 'success' | 'error'>('input');
@@ -69,7 +69,7 @@ export const ExistingCitizenAuth: React.FC<ExistingCitizenAuthProps> = ({ onClos
try {
// Sign the challenge
const signature = await signChallenge(selectedAccount, challenge);
const signature = await signChallenge(selectedAccount, challenge, walletSource, peopleApi);
// Verify signature (self-verification for demonstration)
const isValid = await verifySignature(signature, challenge, selectedAccount.address);
@@ -25,7 +25,7 @@ type FormData = Omit<CitizenshipData, 'walletAddress' | 'timestamp'>;
export const NewCitizenApplication: React.FC<NewCitizenApplicationProps> = ({ onClose, referrerAddress }) => {
// identityKyc pallet is on People Chain
const { peopleApi, isPeopleReady, selectedAccount, connectWallet } = usePezkuwi();
const { peopleApi, isPeopleReady, selectedAccount, connectWallet, walletSource } = usePezkuwi();
const { t } = useTranslation();
const { register, handleSubmit, watch, setValue, formState: { errors } } = useForm<FormData>();
@@ -51,7 +51,7 @@ export const NewCitizenApplication: React.FC<NewCitizenApplicationProps> = ({ on
setConfirming(true);
setError(null);
try {
const result = await confirmCitizenship(peopleApi, selectedAccount);
const result = await confirmCitizenship(peopleApi, selectedAccount, walletSource);
if (!result.success) {
setError(result.error || t('newCitizen.failedToConfirm'));
@@ -83,7 +83,7 @@ export const NewCitizenApplication: React.FC<NewCitizenApplicationProps> = ({ on
setCanceling(true);
setError(null);
try {
const result = await cancelApplication(peopleApi, selectedAccount);
const result = await cancelApplication(peopleApi, selectedAccount, walletSource);
if (!result.success) {
setError(result.error || t('newCitizen.failedToCancel'));
@@ -243,7 +243,8 @@ export const NewCitizenApplication: React.FC<NewCitizenApplicationProps> = ({ on
peopleApi,
selectedAccount,
identityHash,
effectiveReferrer
effectiveReferrer,
walletSource
);
if (!result.success) {
@@ -19,7 +19,7 @@ interface Proposal {
export function CommissionProposalsCard() {
const { t } = useTranslation();
const { api, isApiReady, selectedAccount } = usePezkuwi();
const { api, isApiReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const [loading, setLoading] = useState(true);
@@ -109,9 +109,8 @@ export function CommissionProposalsCard() {
setVoting(proposal.hash);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
const tx = api.tx.dynamicCommissionCollective.vote(
proposal.hash,
@@ -200,9 +199,8 @@ export function CommissionProposalsCard() {
setVoting(proposal.hash);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, api);
// Get proposal length bound
const proposalOption = await api.query.dynamicCommissionCollective.proposalOf(proposal.hash);
@@ -23,7 +23,7 @@ interface InviteUserModalProps {
export const InviteUserModal: React.FC<InviteUserModalProps> = ({ isOpen, onClose }) => {
const { t } = useTranslation();
const { peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
const { peopleApi, isPeopleReady, selectedAccount, walletSource } = usePezkuwi();
const [copied, setCopied] = useState(false);
const [inviteeAddress, setInviteeAddress] = useState('');
const [initiating, setInitiating] = useState(false);
@@ -81,9 +81,8 @@ export const InviteUserModal: React.FC<InviteUserModalProps> = ({ isOpen, onClos
setInitiateSuccess(false);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const { getSigner } = await import('@/lib/get-signer');
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
if (import.meta.env.DEV) console.log(`Initiating referral from ${selectedAccount.address} to ${inviteeAddress}...`);
@@ -14,7 +14,7 @@ import type { PendingApproval } from '@pezkuwi/lib/citizenship-workflow';
export const ReferralDashboard: React.FC = () => {
const { t } = useTranslation();
const { stats, myReferrals, loading } = useReferral();
const { peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
const { peopleApi, isPeopleReady, selectedAccount, walletSource } = usePezkuwi();
const { toast } = useToast();
const [showInviteModal, setShowInviteModal] = useState(false);
const [pendingApprovals, setPendingApprovals] = useState<PendingApproval[]>([]);
@@ -45,7 +45,7 @@ export const ReferralDashboard: React.FC = () => {
setProcessingAddress(applicantAddress);
try {
const result = await approveReferral(peopleApi, selectedAccount, applicantAddress);
const result = await approveReferral(peopleApi, selectedAccount, applicantAddress, walletSource);
if (result.success) {
toast({
title: 'Referral Approved',
@@ -11,7 +11,7 @@ import { usePezkuwi } from '@/contexts/PezkuwiContext';
import { useWallet } from '@/contexts/WalletContext';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { web3FromAddress, web3Enable } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import {
getStakingInfo,
getActiveValidators,
@@ -31,21 +31,8 @@ import { LoadingState } from '@pezkuwi/components/AsyncComponent';
import { ValidatorPoolDashboard } from './ValidatorPoolDashboard';
import { handleBlockchainError, handleBlockchainSuccess } from '@pezkuwi/lib/error-handler';
// Get signer with auto-reconnect if extension session expired
async function getInjectorSigner(address: string) {
let injector = await web3FromAddress(address);
if (!injector?.signer) {
await web3Enable('PezkuwiChain');
injector = await web3FromAddress(address);
if (!injector?.signer) {
throw new Error('Wallet signer not available. Please reconnect your wallet.');
}
}
return injector;
}
export const StakingDashboard: React.FC = () => {
const { assetHubApi, peopleApi, selectedAccount, isAssetHubReady, isPeopleReady } = usePezkuwi();
const { assetHubApi, peopleApi, selectedAccount, isAssetHubReady, isPeopleReady, walletSource } = usePezkuwi();
const { balances, refreshBalances } = useWallet();
const { t } = useTranslation();
@@ -129,7 +116,7 @@ export const StakingDashboard: React.FC = () => {
setIsRecordingScore(true);
try {
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
@@ -157,7 +144,7 @@ export const StakingDashboard: React.FC = () => {
setIsClaimingReward(true);
try {
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await claimPezReward(peopleApi, selectedAccount.address, epochIndex, injector.signer);
if (result.success) {
@@ -198,7 +185,7 @@ export const StakingDashboard: React.FC = () => {
throw new Error(t('staking.insufficientHez'));
}
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
// If already bonded, use bondExtra, otherwise use bond
let tx;
@@ -251,7 +238,7 @@ export const StakingDashboard: React.FC = () => {
setIsLoading(true);
try {
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.staking.nominate(selectedValidators);
@@ -294,7 +281,7 @@ export const StakingDashboard: React.FC = () => {
throw new Error(t('staking.insufficientStaked'));
}
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
const tx = assetHubApi.tx.staking.unbond(amount);
await tx.signAndSend(
@@ -338,7 +325,7 @@ export const StakingDashboard: React.FC = () => {
setIsLoading(true);
try {
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, assetHubApi);
// Number of slashing spans (usually 0)
const tx = assetHubApi.tx.staking.withdrawUnbonded(0);
@@ -381,7 +368,7 @@ export const StakingDashboard: React.FC = () => {
setIsLoading(true);
try {
const injector = await getInjectorSigner(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
// stakingScore pallet is on People Chain - uses cached staking data from Asset Hub
const tx = peopleApi.tx.stakingScore.startScoreTracking();
+40
View File
@@ -0,0 +1,40 @@
/**
* Universal signer helper - works with both browser extension and WalletConnect
*
* Usage:
* const injector = await getSigner(selectedAccount.address, walletSource, api);
* // injector.signer works for signAndSend, signRaw, etc.
*/
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { createWCSigner, isWCConnected, validateSession } from '@/lib/walletconnect-service';
import type { ApiPromise } from '@pezkuwi/api';
type WalletSource = 'extension' | 'walletconnect' | 'native' | null;
interface SignerResult {
signer: any; // Compatible with @pezkuwi/api Signer
}
export async function getSigner(
address: string,
walletSource: WalletSource,
api?: ApiPromise | null
): Promise<SignerResult> {
if (walletSource === 'walletconnect') {
if (!isWCConnected() || !validateSession()) {
throw new Error('WalletConnect session expired. Please reconnect your wallet.');
}
if (!api) {
throw new Error('API not ready');
}
const genesisHash = api.genesisHash.toHex();
const wcSigner = createWCSigner(genesisHash, address);
return { signer: wcSigner };
}
// Extension or native: use web3FromAddress
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(address);
return injector;
}
+6 -11
View File
@@ -12,7 +12,7 @@ import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowL
import { useToast } from '@/hooks/use-toast';
import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories, getAllTikiNFTDetails, generateCitizenNumber, type TikiNFTDetails } from '@pezkuwi/lib/tiki';
import { getAllScores, getStakingScoreStatus, startScoreTracking, getPezRewards, recordTrustScore, claimPezReward, type UserScores, type StakingScoreStatus, type PezRewardInfo, formatDuration } from '@pezkuwi/lib/scores';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { getKycStatus } from '@pezkuwi/lib/kyc';
import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
// Commission proposals card removed - no longer using notary system for KYC approval
@@ -21,7 +21,7 @@ import { ReferralDashboard } from '@/components/referral/ReferralDashboard';
export default function Dashboard() {
const { t } = useTranslation();
const { user } = useAuth();
const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount, walletSource } = usePezkuwi();
const navigate = useNavigate();
const { toast } = useToast();
const [profile, setProfile] = useState<Record<string, unknown> | null>(null);
@@ -157,8 +157,7 @@ export default function Dashboard() {
setStartingScoreTracking(true);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
// startScoreTracking on People Chain - staking data comes from Asset Hub via XCM
const result = await startScoreTracking(peopleApi, selectedAccount.address, injector.signer);
@@ -193,8 +192,7 @@ export default function Dashboard() {
setIsRecordingScore(true);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
@@ -215,8 +213,7 @@ export default function Dashboard() {
setIsClaimingReward(true);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await claimPezReward(peopleApi, selectedAccount.address, epochIndex, injector.signer);
if (result.success) {
@@ -332,9 +329,7 @@ export default function Dashboard() {
setRenouncingCitizenship(true);
try {
const { web3Enable, web3FromAddress } = await import('@pezkuwi/extension-dapp');
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
if (import.meta.env.DEV) console.log('Renouncing citizenship...');
+4 -6
View File
@@ -10,7 +10,7 @@ import { NftList } from '@/components/NftList';
import { Button } from '@/components/ui/button';
import { ArrowUpRight, ArrowDownRight, History, ArrowLeft, RefreshCw, Coins, Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
import { getSigner } from '@/lib/get-signer';
import { getPezRewards, recordTrustScore, claimPezReward, type PezRewardInfo } from '@pezkuwi/lib/scores';
interface Transaction {
@@ -29,7 +29,7 @@ interface Transaction {
const WalletDashboard: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount } = usePezkuwi();
const { api, isApiReady, peopleApi, isPeopleReady, selectedAccount, walletSource } = usePezkuwi();
const [isTransferModalOpen, setIsTransferModalOpen] = useState(false);
const [isReceiveModalOpen, setIsReceiveModalOpen] = useState(false);
const [isHistoryModalOpen, setIsHistoryModalOpen] = useState(false);
@@ -239,8 +239,7 @@ const WalletDashboard: React.FC = () => {
if (!peopleApi || !selectedAccount) return;
setIsRecordingScore(true);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await recordTrustScore(peopleApi, selectedAccount.address, injector.signer);
if (result.success) {
toast.success(t('wallet.trustScoreRecorded'));
@@ -260,8 +259,7 @@ const WalletDashboard: React.FC = () => {
if (!peopleApi || !selectedAccount) return;
setIsClaimingReward(true);
try {
await web3Enable('PezkuwiChain');
const injector = await web3FromAddress(selectedAccount.address);
const injector = await getSigner(selectedAccount.address, walletSource, peopleApi);
const result = await claimPezReward(peopleApi, selectedAccount.address, epochIndex, injector.signer);
if (result.success) {
const rewardInfo = pezRewards?.claimableRewards.find(r => r.epoch === epochIndex);