Skip to content

Commit 0682960

Browse files
authored
Merge pull request #540 from rabsqueen/refactor/wallet-unification-520
Refactor/wallet unification 520
2 parents 3bac8c7 + aff6d41 commit 0682960

4 files changed

Lines changed: 161 additions & 98 deletions

File tree

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
// Update the import - use the correct way to access freighter
2-
declare global {
3-
interface Window {
4-
freighter?: any;
5-
}
6-
}
1+
/**
2+
* @file freighter.ts
3+
* @description Centralized Freighter wallet service leveraging the official `@stellar/freighter-api` package.
4+
*/
5+
6+
import {
7+
isConnected as freighterIsConnected,
8+
getAddress as freighterGetAddress,
9+
signTransaction as freighterSignTransaction,
10+
requestAccess as freighterRequestAccess,
11+
getNetwork as freighterGetNetwork
12+
} from '@stellar/freighter-api';
713

814
export interface FreighterWallet {
915
isConnected: () => Promise<boolean>;
@@ -17,75 +23,115 @@ export class FreighterService {
1723

1824
private constructor() {}
1925

26+
/**
27+
* Retrieves the singleton instance of the FreighterService.
28+
* @returns {FreighterService} The singleton service instance.
29+
*/
2030
public static getInstance(): FreighterService {
2131
if (!FreighterService.instance) {
2232
FreighterService.instance = new FreighterService();
2333
}
2434
return FreighterService.instance;
2535
}
2636

27-
private async getFreighter(): Promise<any> {
28-
// Check if freighter is available
29-
if (typeof window === 'undefined') {
30-
throw new Error('Window is not defined');
31-
}
32-
33-
// Freighter injects itself into the window object
34-
if (!window.freighter) {
35-
throw new Error('Freighter wallet is not installed');
36-
}
37-
38-
return window.freighter;
39-
}
40-
37+
/**
38+
* Checks if the Freighter wallet extension is installed and accessible.
39+
* @returns {Promise<boolean>} True if connected/available.
40+
*/
4141
async isInstalled(): Promise<boolean> {
4242
try {
4343
if (typeof window === 'undefined') return false;
44-
return !!window.freighter;
44+
const response = await freighterIsConnected();
45+
46+
if (typeof response === 'object' && response !== null && 'isConnected' in response) {
47+
return Boolean((response as any).isConnected);
48+
}
49+
return Boolean(response);
4550
} catch (error) {
4651
return false;
4752
}
4853
}
4954

55+
/**
56+
* Requests access to the Freighter wallet and retrieves the user's public address.
57+
* @returns {Promise<string>} The public key address.
58+
*/
5059
async connect(): Promise<string> {
5160
try {
52-
const freighter = await this.getFreighter();
53-
54-
// Enable freighter
55-
await freighter.enable();
61+
const connected = await this.isInstalled();
62+
if (!connected) {
63+
await freighterRequestAccess();
64+
}
5665

57-
// Get public key
58-
const publicKey = await freighter.getPublicKey();
66+
const response = await freighterGetAddress();
67+
let publicKey = '';
68+
69+
if (typeof response === 'object' && response !== null && 'address' in response) {
70+
publicKey = String((response as any).address || '');
71+
} else {
72+
publicKey = String(response || '');
73+
}
74+
75+
if (!publicKey) {
76+
throw new Error('No public key returned from Freighter wallet.');
77+
}
5978

6079
return publicKey;
6180
} catch (error: any) {
62-
throw new Error(`Failed to connect to Freighter: ${error.message}`);
81+
throw new Error(`Failed to connect to Freighter: ${error.message || error}`);
6382
}
6483
}
6584

85+
/**
86+
* Retrieves the current Stellar network from Freighter.
87+
* @returns {Promise<string>} The lowercase network identifier.
88+
*/
6689
async getNetwork(): Promise<string> {
6790
try {
68-
const freighter = await this.getFreighter();
69-
const network = await freighter.getNetwork();
70-
return network.toLowerCase(); // Convert to lowercase for consistency
91+
const response = await freighterGetNetwork();
92+
let networkStr = '';
93+
94+
if (typeof response === 'object' && response !== null) {
95+
networkStr = String((response as any).network || (response as any).id || '');
96+
} else {
97+
networkStr = String(response || '');
98+
}
99+
100+
return (networkStr || 'testnet').toLowerCase();
71101
} catch (error) {
72102
throw new Error('Failed to get network from Freighter');
73103
}
74104
}
75105

106+
/**
107+
* Signs a transaction XDR string using Freighter.
108+
* @param {string} xdr - The transaction XDR payload.
109+
* @returns {Promise<string>} Signed transaction XDR.
110+
*/
76111
async signTransaction(xdr: string): Promise<string> {
77112
try {
78-
const freighter = await this.getFreighter();
79113
const network = await this.getNetwork();
80-
81-
const signedXdr = await freighter.signTransaction(xdr, {
82-
network,
83-
accountToSign: await freighter.getPublicKey(),
84-
});
85-
86-
return signedXdr;
114+
const addressResp = await freighterGetAddress();
115+
let publicKey = '';
116+
117+
if (typeof addressResp === 'object' && addressResp !== null && 'address' in addressResp) {
118+
publicKey = String((addressResp as any).address || '');
119+
} else {
120+
publicKey = String(addressResp || '');
121+
}
122+
123+
// Cast options parameter as any to prevent strict package signature mismatches
124+
const signedResponse = await freighterSignTransaction(xdr, {
125+
network: network.toUpperCase().includes('PUBLIC') ? 'PUBLIC' : 'TESTNET',
126+
accountToSign: publicKey,
127+
} as any);
128+
129+
if (typeof signedResponse === 'object' && signedResponse !== null && 'signedTxXdr' in signedResponse) {
130+
return String((signedResponse as any).signedTxXdr);
131+
}
132+
return String(signedResponse);
87133
} catch (error: any) {
88-
throw new Error(`Failed to sign transaction: ${error.message}`);
134+
throw new Error(`Failed to sign transaction: ${error.message || error}`);
89135
}
90136
}
91137
}

apps/frontend/app/services/wallet/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export interface IWalletService {
2323
}
2424

2525
export class WalletServiceFactory {
26+
/**
27+
* Retrieves the singleton service instance for the specified wallet type.
28+
* @param {WalletType} walletType - The target wallet provider identifier.
29+
* @returns {IWalletService} The matching wallet service implementation.
30+
*/
2631
static getService(walletType: WalletType): IWalletService {
2732
switch (walletType) {
2833
case WalletType.FREIGHTER:
@@ -36,6 +41,10 @@ export class WalletServiceFactory {
3641
}
3742
}
3843

44+
/**
45+
* Scans and returns all available wallet providers installed in the client environment.
46+
* @returns {Promise<WalletType[]>} List of available wallet types.
47+
*/
3948
static async getAvailableWallets(): Promise<WalletType[]> {
4049
const availableWallets: WalletType[] = [];
4150

@@ -49,7 +58,7 @@ export class WalletServiceFactory {
4958
// Freighter not available
5059
}
5160

52-
// Albedo is always available as it's a web-based wallet
61+
// Albedo is always available as a web-based wallet
5362
availableWallets.push(WalletType.ALBEDO);
5463

5564
// Check for Lobstr
@@ -64,4 +73,4 @@ export class WalletServiceFactory {
6473

6574
return availableWallets;
6675
}
67-
}
76+
}

0 commit comments

Comments
 (0)