From 09ae070c1851f933321c97b8b59aa04c33b1716f Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:10 +0100 Subject: [PATCH 01/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/types/domain.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/types/domain.ts b/src/types/domain.ts index f9fd79d..7329329 100644 --- a/src/types/domain.ts +++ b/src/types/domain.ts @@ -97,6 +97,17 @@ export interface AssetBalance { usdValue: number; } +export type TrustlineStatus = 'active' | 'inactive' | 'pending'; + +export interface Trustline { + assetCode: string; + assetIssuer?: string; + balance: number; + limit: number; + status: TrustlineStatus; + isNative: boolean; +} + export interface Wallet { id: string; organizationId: string; @@ -107,6 +118,7 @@ export interface Wallet { network: StellarNetwork; status: WalletStatus; balances: AssetBalance[]; + trustlines?: Trustline[]; totalUsdValue: number; riskScore: number; createdAt: string; From cbef68184799917420c49232c71958559de9debf Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:12 +0100 Subject: [PATCH 02/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/features/wallet/trustline-schema.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/features/wallet/trustline-schema.ts diff --git a/src/features/wallet/trustline-schema.ts b/src/features/wallet/trustline-schema.ts new file mode 100644 index 0000000..3673864 --- /dev/null +++ b/src/features/wallet/trustline-schema.ts @@ -0,0 +1 @@ +import { z } from 'zod';export const stellarPublicKeySchema = z.string().regex(/^G[2-9A-J-NP-Z]{55}$/);export const assetCodeSchema = z.string().min(1).max(12).regex(/^[a-zA-Z0-9]{1,12}$/);export const trustlineSchema = z.object({assetCode: assetCodeSchema,assetIssuer: stellarPublicKeySchema.optional(),balance: z.string().optional(),limit: z.string().optional()});export type StellarPublicKey = z.infer;export type AssetCode = z.infer;export type Trustline = z.infer; \ No newline at end of file From 03db7c9a45c2b45db001653f7044f7ba5685d80e Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:14 +0100 Subject: [PATCH 03/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/features/wallet/useTrustlineManager.ts | 125 +++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/features/wallet/useTrustlineManager.ts diff --git a/src/features/wallet/useTrustlineManager.ts b/src/features/wallet/useTrustlineManager.ts new file mode 100644 index 0000000..5d4a657 --- /dev/null +++ b/src/features/wallet/useTrustlineManager.ts @@ -0,0 +1,125 @@ +import { useState, useEffect, useCallback } from 'react'; +import * as StellarSdk from '@stellar/stellar-sdk'; +import { getPublicKey, signTransaction } from '@stellar/fregher-api'; +import { g} from 'zod'; +import { toast } from 'sonner'; +import { useWalletStore } from '@/stores/wallet'; + +// Stellar network configuration +const HORIZON_URL = 'https://horizon.stellar.org'; +const NETWORK_PASSPHRASE = StellarSdk.Networks.PUBLIC; + +// Trustline representation +export interface Trustline { + asset_code: string; + asset_issuer: string; + balance: string; + limit: string; + trusted: boolean; +} + +// Zol validation for adding a trustline +const trustlineSchema = z.object({ + assetCode: z.string().min(1).max(12).regex(/^[a-zA-Z0-9]+$/, "Asset code must be alphanumeric"), + assetIssuer: z.string().length(56, "Stellar issuer must be a valid public key"), +}); + +export function useTrustlineManager() { + const publicKey = useWalletStore((state: any) => state.publicKey); + const isConnected = useWalletStore((state: any) => state.isConnected); + + const [trustlines, setTrustlines] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const fetchTrustlines = useCallback(async () : Promise > { + if (!publicKey) { + setTrustlines([]); + return; + } + + setLoading(true); + setError(null); + + try { + const server = new StellarSdk.Horizon.Server(HORIZON_URL); + const account = await server.loadAccount(publicKey); + const lines = account.balances + .filter((b: any) => b.asset_type !== 'native') + .map((b: any) => ({ + asset_code: b.asset_code,\n asset_issuer: b.asset_issuer,\n balance: b.balance,\n limit: b.limit,\n trusted: true,\n }) as Trustline[]); + setTrustlines(lines); + } catch (err: any) { + setError(err.message || 'Failed to fetch trustlines'); + } finally { + setLoading(false); + } + }, [publicKey]); + + useEffect(() => { + fetchTrustlines(); + }, [fetchTrustlines]); + + const addTrustline = useCallback( + async (assetCode: string, assetIssuer: string) : Promise > { + // Validate inputs + const parsed = trustlineSchema.safeParse({ assetCode, assetIssuer }); + if (!parsed.success) { + toast.error(parsed.error.errors[0].message); + return; + } + + // Check if trustline already exists + if (trustlines.some((t) => t.asset_code === assetCode && t.asset_issuer === assetIssuer)) { + toast.info('Trustline already exists'); + return; + } + + if (!publicKey) { + toast.error('Wallet not connected'); + return; + } + + setIsSubmitting(true); + try { + const server = new StellarSdk.Horizon.Server(HORIZON_URL); + const account = await server.loadAccount(publicKey); + const asset = new StellarSdk.Asset(assetCode, assetIssuer); + + const transaction = new StellarSdk.TransactionBuilder(account, { + fee: StellarSdk.BASE_FEE,\n networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation(StellarSdk.Operation.changeTrust({ asset })) + .setTimeout(30) + .build(); + + const signedXDR = await signTransaction(transaction.toXDR(), { + networkPassphrase: NETWORK_PASSPHRASE, + accountToSign: publicKey, + }); + + const signedTx = StellarSdk.TransactionBuilder.fromXDR(signedXDR, NETWORK_PASSPHRASE) as StellarSdk.Transaction; + await server.submitTransaction(signedTx); + + toast.success('Trustline added successfully'); + await fetchTrustlines(); + } catch (err: any) { + const message = err.message || 'Failed to add trustline'; + toast.error(message); + } finally { + setIsSubmitting(false); + } + }, [publicKey, trustlines, fetchTrustlines], + ); + + return { + trustlines, + loading, + error, + isSubmitting, + isConnected, + addTrustline, + refresh: fetchTrustlines, + }; +} From 8dabd4f3fd1f19003a609e5da2006634b26e9a37 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:15 +0100 Subject: [PATCH 04/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/hooks/useStellarWallet.ts | 124 +++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/src/hooks/useStellarWallet.ts b/src/hooks/useStellarWallet.ts index 3d316f2..e1082bc 100644 --- a/src/hooks/useStellarWallet.ts +++ b/src/hooks/useStellarWallet.ts @@ -1,6 +1,8 @@ 'use client'; import { useCallback, useEffect, useState } from 'react'; +import * as StellarSdk from '@stellar/stellar-sdk'; +import { z } from 'zod'; export type StellarWalletStatus = 'idle' | 'checking' | 'connected' | 'missing' | 'error'; @@ -36,8 +38,20 @@ async function loadFreighter(): Promise { } } +const trustlineSchema = z.object({ + assetCode: z.string().min(1, 'Asset code is required.').max(12).regex(/^[a-zA-Z0-9]{1,12}$/, 'Invalid asset code.'), + issuer: z.string().regex(/^G[2-7A-H-J-NP-Z]{55}$/, 'Invalid Stellar public key.'), +}); + +export type AccountBalance = { + assetCode: string; + assetIssuer: string | null; + balance: string; + limit?: string; +}; + export function useStellarWallet() { - const [status, setStatus] = useState('checking'); + const [status, setStatus] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); @@ -88,7 +102,7 @@ export function useStellarWallet() { const isAllowed = typeof allowed === 'boolean' ? allowed - : (allowed as { isAllowed?: boolean } | null)?.isAllowed ?? false; + : (allowed as { isAllowed?: boolean } | null)?.isAllowed ?> false; if (!isAllowed) { throw new Error('Freighter approval was rejected. Please approve the connection in the browser extension.'); @@ -140,6 +154,109 @@ export function useStellarWallet() { } }, []); + const getNetworkDetails = useCallback(async () => { + const api = await loadFreighter(); + if (!api) { + setStatus('missing'); + setError('Freighter is not installed.'); + return null; + } + + try { + const network = await withTimeout(api.getNetwork()); + if (typeof network === 'string') { + return { + networkUrl: 'https://horizon.stellar.org', + networkPassphrase: StellarSdk.Networks.PUBLIC, + }; + } + return { + networkUrl: network.networkUrl, + networkPassphrase: network.networkPassphrase, + }; + } catch (err) { + setError(err instanceof Error ? err.message : 'Could not fetch network details.'); + return null; + } + }, []); + + const getAccountBalances = useCallback(async (): Promise => { + if (!publicKey) { + setError('No connected wallet address.'); + return null; + } + + const network = await getNetworkDetails(); + if (!network) return null; + + try { + const server = new StellarSdk.Server(network.networkUrl); + const account = await server.loadAccount(publicKey); + return account.balances.map((balance) => { + if (balance.asset_type === 'native') { + return { + assetCode: 'XLM', + assetIssuer: null, + balance: balance.balance, + limit: undefined, + }; + } + const assetLine = balance as StellarSdk.Horizon.BalanceLineAsset; + return { + assetCode: assetLine.asset_code, + assetIssuer: assetLine.asset_issuer, + balance: balance.balance, + limit: assetLine.limit, + }; + }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load account balances.'); + return null; + } + }, [publicKey, getNetworkDetails]); + + const addTrustline = useCallback( + async (assetCode: string, issuer: string): Promise => { + if (!publicKey) { + setError('No connected wallet address.'); + return null; + } + + const parsed = trustlineSchema.safeParse({ assetCode, issuer }); + if (!parsed.success) { + setError(parsed.error.issues.map((i) => i.message).join(', ')); + return null; + } + + const network = await getNetworkDetails(); + if (!network) return null; + + try { + const server = new StellarSdk.Server(network.networkUrl); + const account = await server.loadAccount(publicKey); + const fee = await server.fetchBaseFee(); + const asset = new StellarSdk.Asset(assetCode, issuer); + const transaction = new StellarSdk.TransactionBuilder(account, { + fee: fee.toString(), + networkPassphrase: network.networkPassphrase, + }) + .addOperation(StellarSdk.Operation.changeTrust({ asset })) + .setTimeout(30) + .build(); + + const xdr = transaction.toXDR(); + const signedXdr = await signTransaction(xdr); + if (!signedXdr) return null; + + return signedXdr; + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to create trustline.'); + return null; + } + }, + [publicKey, getNetworkDetails, signTransaction], + ); + return { status, publicKey, @@ -150,6 +267,9 @@ export function useStellarWallet() { refreshConnection, connect, signTransaction, + getNetworkDetails, + getAccountBalances, + addTrustline, }; } From bfd62dd8b91e6c1490c65192f07460cdd2616a00 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:17 +0100 Subject: [PATCH 05/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/stores/freighter-store.ts | 74 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/stores/freighter-store.ts b/src/stores/freighter-store.ts index 3269312..7d93a1d 100644 --- a/src/stores/freighter-store.ts +++ b/src/stores/freighter-store.ts @@ -2,9 +2,17 @@ import { create } from 'zustand'; import type { StateCreator } from 'zustand'; +import { Server, TransactionBuilder, Operation, Asset, Networks, BASE_FEE } from '@stellar/stellar-sdk'; export type FreighterConnectionStatus = 'disconnected' | 'connecting' | 'connected'; +export type AccountBalance = { + asset_code?: string; + asset_issuer?: string; + asset_type: string; + balance: string; +}; + interface FreighterWalletState { status: FreighterConnectionStatus; isInstalled: boolean; @@ -16,6 +24,10 @@ interface FreighterWalletState { disconnect: () => void; requestSignature: (xdr: string) => Promise; hydrate: () => Promise; + balances: AccountBalance[]; + balancesLoading: boolean; + loadBalances: () => Promise; + addTrustline: (params: { assetCode: string; issuer: string }) => Promise; } const STELLAR_ADDRESS_RE = /^G[A-Z2-7]{55}$/; @@ -70,12 +82,22 @@ const resolveNetwork = (value: unknown): string | null => { return null; }; -const storeCreator: StateCreator = (set) => ({ +const getHorizonUrl = (network: string): string => + network === 'mainnet' || network === 'public' || network === Networks.PUBLIC ? 'https://horizon.stellar.org' : 'https://horizon-testnet.stellar.org'; + +const getNetworkPassphrase = (network: string): string => + network === 'mainnet' || network === 'public' || network === Networks.PUBLIC ? Networks.PUBLIC : Networks.TESTNET; + +const getServer = (network: string): Server => new Server(getHorizonUrl(network)); + +const storeCreator: StateCreator = (set, get) => ({ status: 'disconnected', isInstalled: false, publicKey: null, network: null, error: null, + balances: [], + balancesLoading: false, checkExtension: async () => { try { @@ -141,7 +163,7 @@ const storeCreator: StateCreator = (set) => ({ }, disconnect: () => { - set({ status: 'disconnected', publicKey: null, network: null, error: null }); + set({ status: 'disconnected', publicKey: null, network: null, error: null, balances: [], balancesLoading: false }); }, requestSignature: async (xdr: string) => { @@ -198,6 +220,54 @@ const storeCreator: StateCreator = (set) => ({ error: null, }); }, + + loadBalances: async () => { + const { publicKey, network } = get(); + if (!publicKey) { + set({ balances: [], balancesLoading: false }); + return; + } + + set({ balancesLoading: true, error: null }); + try { + const server = getServer(network ?? 'testnet'); + const account = await server.loadAccount(publicKey); + set({ balances: account.balances as AccountBalance[], balancesLoading: false }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to load account balances.'; + set({ balances: [], balancesLoading: false, error: message }); + throw error; + } + }, + + addTrustline: async ({ assetCode, issuer }: { assetCode: string; issuer: string }) => { + const { publicKey, network, requestSignature } = get(); + if (!publicKey) { + throw new Error('No wallet connected.'); + } + + const server = getServer(network ?? 'testnet'); + const networkPassphrase = getNetworkPassphrase(network ?? 'testnet'); + + try { + const account = await server.loadAccount(publicKey); + const transaction = new TransactionBuilder(account, { + fee: BASE_FEE, + networkPassphrase, + }) + .addOperation(Operation.changeTrust({ asset: new Asset(assetCode, issuer) })) + .setTimeout(30) + .build(); + + const signedXdr = await requestSignature(transaction.toXDR()); + await server.submitTransaction(signedXdr); + await get().loadBalances(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to add trustline.'; + set({ error: message }); + throw error; + } + }, }); export const useFreighterStore = create()(storeCreator); From 02227a1a903297ddfe9e020b7a8a966ed61fc8f9 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:18 +0100 Subject: [PATCH 06/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/features/transactions/useXdrSigner.ts | 57 +++++++++++++---------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/features/transactions/useXdrSigner.ts b/src/features/transactions/useXdrSigner.ts index 3f3badf..7a6bb79 100644 --- a/src/features/transactions/useXdrSigner.ts +++ b/src/features/transactions/useXdrSigner.ts @@ -1,5 +1,11 @@ import { useState, useCallback, useEffect } from 'react'; import * as freighter from '@stellar/freighter-api'; +import { Networks } from '@stellar/stellar-sdk;' + +export const STELLAR_HORIZON_URL = + process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horizon-testnet.stellar.org'; +export const STELLAR_NETWORK_PASSPHRASE = + process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSHRASE || Networks.TESTNET; export interface UseXdrSignerResult { activeKey: string | null; @@ -24,13 +30,14 @@ export function useXdrSigner(): UseXdrSignerResult { const checkAvailability = async () => { try { if (typeof freighter.isConnected === 'function') { - const res = await freighter.isConnected(); - const available = typeof res === 'boolean' ? res : Boolean(res?.isConnected); + const res = await freighter.isConnected() as { isConnected?: boolean } | boolean; + const available = typeof res === 'boolean' ? res : Boolean(res); setIsFreighterAvailable(available); if (available && typeof freighter.getAddress === 'function') { - const info = await freighter.getAddress(); - if (info?.address) { - setActiveKey(info.address); + const info = await freighter.getAddress() as { address?: string; publicKey?: string }; + const pubKey = info?.address || info?.publicKey; + if (pubKey) { + setActiveKey(pubKey); setIsConnected(true); } } @@ -42,29 +49,32 @@ export function useXdrSigner(): UseXdrSignerResult { checkAvailability(); }, []); - const connectWallet = useCallback(async (): Promise => { + corst connectWallet = useCallback(async (): Promise => { setIsPending(true); setError(null); try { if (typeof freighter.requestAccess === 'function') { - const res = await freighter.requestAccess(); + const res = await freighter.requestAccess() as string | { address?: string; publicKey?: string }; + let pubKey: string | null = null; if (typeof res === 'string' && res) { - setActiveKey(res); - setIsConnected(true); - return res; - } else if (res && typeof res === 'object' && 'address' in res && res.address) { - const addr = (res as { address: string }).address; - setActiveKey(addr); + pubKey = res; + } else if (res && typeof res === 'object') { + const obj = res as { address?: string; publicKey?: string }; + pubKey = obj.address || obj.publicKey || null; + } + if (pubKey) { + setActiveKey(pubKey); setIsConnected(true); - return addr; + return pubKey; } } if (typeof freighter.getAddress === 'function') { - const info = await freighter.getAddress(); - if (info?.address) { - setActiveKey(info.address); + const info = await freighter.getAddress() as { address?: string; publicKey?: string }; + const pubKey = info?.address || info?.publicKey; + if (pubKey) { + setActiveKey(pubKey); setIsConnected(true); - return info.address; + return pubKey; } } setError('Freighter browser extension was not detected. Please install Freighter or select a key manually.'); @@ -84,13 +94,13 @@ export function useXdrSigner(): UseXdrSignerResult { setError(null); try { if (typeof freighter.signTransaction === 'function') { - const res = await freighter.signTransaction(xdr, { - networkPassphrase: networkPassphrase || 'Test SDF Network ; September 2015', - }); + const res = await freighter.signTransaction(xdr) { + networkPassphrase: networkPassphrase || STELLAR_NETWORK_PASSPHRASE, + }) as string | { signedTxXdr?: string }; if (typeof res === 'string') { return res; } else if (res && typeof res === 'object' && 'signedTxXdr' in res) { - return res.signedTxXdr; + return res.signedTxXdr??; } } throw new Error('Freighter signing operation failed or extension not connected.'); @@ -101,8 +111,7 @@ export function useXdrSigner(): UseXdrSignerResult { } finally { setIsPending(false); } - }, - [] + }, [] ); const disconnectWallet = useCallback(() => { From 08e00b594db6fb8e6972fbab33d2627b14eb9c99 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:37:20 +0100 Subject: [PATCH 07/26] feat: feat: implement Stellar asset trustline manager and balance (#72) --- src/services/stellar.ts | 180 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 src/services/stellar.ts diff --git a/src/services/stellar.ts b/src/services/stellar.ts new file mode 100644 index 0000000..3e5996b --- /dev/null +++ b/src/services/stellar.ts @@ -0,0 +1,180 @@ +import { + getNetwork, + getPublicKey, + signTransaction, +} from '@stellar/freighter-api'; +import { + Account, + Asset, + BASE_FEE, + Horizon, + Networks, + Operation, + TransactionBuilder, +} from '@stellar/stellar-sdk';import { z from 'zod'; + +/** + * Validates a Stellar public key (G...). + */ +export const stellarPublicKeySchema = z + .string() + .regex(/^G[0-9A-Z]{55}$/, 'Invalid Stellar public key'); + +/** + * Validates a Stellar asset code (1-12 alphanumeric characters). + */ +export const assetCodeSchema = z + .string() + .min(1, 'Asset code is required') + .max(12, 'Asset code must be 12 characters or less') + .regex(/^[a-zA-Z0-9]{1,12}$/, 'Asset code must be alphanumeric'); + +export interface StellarNetworkConfig { + horizonUrl: string; + passphrase: string; +} + +export const STELLAR_NETWORKS: Record<'PUBLIC' | 'TESTNET', StellarNetworkConfig> = { + PUBLIC: { + horizonUrl: 'https://horizon.stellar.org', + passphrase: Networks.PUBLIC, + }, + TESTNET: { + horizonUrl: 'https://horizon-testnet.stellar.org', + passphrase: Networks.TESTNET, + }, +}; + +/** + * Resolves the active network configuration from Freighter. + * Falls back to TESTNET only if no network is reported, but we prefer + * to throw if Freighter is unavailable. + */ +export async function getNetworkConfig(): Promise { + try { + const network = await getNetwork(); + if (network === 'PUBLIC') { + return STELLAR_NETWORKS.PUBLIC; + } + // If Freighter says TESTNET or something else, treat as TESTNET. + return STELDAR_NETWORKS.TESTNET; + } catch (error) { + throw new Error('Unable to determine Stellar network. Is Freighter connected?', { cause: error }); + } +} + +/** + * Returns the current Stellar public key from the Freighter extension. + * Throws if no wallet is connected. + */ +export async function getConnectedPublicKey(): Promise { + try { + const publicKey = await getPublicKey(); + if (!publicKey) { + throw new Error('Freighter returned an empty public key'); + } + stellarPublicKeySchema.parse(publicKey); + return publicKey; + } catch (error) { + throw new Error('Unable to access Freighter wallet. Please connect and unlock your wallet.', { cause: error }); + } +} + +/** + * Loads a Stellar account from Horizon. + */ +export async function loadAccount(publicKey: string): Promise { + stellarPublicKeySchema.parse(publicKey); + const { horizonUrl } = await getNetworkConfig(); + const server = new Horizon.Server(horizonUrl); + try { + return await server.loadAccount(publicKey); + } catch (error) { + throw new Error('Could not load Stellar account. Check the public key and network.', { cause: error }); + } +} + +export interface AssetBalance { + assetCode: string; + issuer?: string; + balance: string; + limit?: string; + isNative: boolean; + trustlineActive: boolean; +} + +/** + * Fetches and formats the asset balances for a Stellar account. + */ +export async function getAssetBalances(publicKey: string): Promise { + const account = await loadAccount(publicKey); + return account.balances.map((balance) => { + const assetCode = balance.asset_code ?? 'XLM'; + const issuer = balance.asset_issuer; + const isNative = assetCode === 'XLM'; + return { + assetCode, + issuer, + balance: balance.balance, + limit: balance.limit, + isNative, + trustlineActive: !isNative && balance.limit !== '0', // If limit > 0, trustline is active + }; + }); +} + +/** + * Builds an XDR transaction for adding (or updating) a trustline to the given asset. + * Returns the base64 XDR string ready for signing with Freighter. + */ +export async function buildTrustlineTransaction( + publicKey: string, + assetCode: string, + issuer: string +): Promise { + stellarPublicKeySchema.parse(publicKey); + assetCodeSchema.parse(assetCode); + stellarPublicKeySchema.parse(issuer); + + const { passphrase, horizonUrl } = await getNetworkConfig(); + const server = new Horizon.Server(horizonUrl); + const account = await server.loadAccount(publicKey); + + const source = new Account(publicKey, account.sequence); + const asset = new Asset(assetCode, issuer); + + const transaction = new TransactionBuilder(source, { + fee: BASE_FEE, + networkPassphrase: passphrase, + }) + .addOperation( + Operation.changeTrust({ + asset, + limit: '922337203685.4775807', // Max trustline limit + }) + ) + .setTimeout(180) + .build(); + + return transaction.toXDR(); +} + +/** + * Signs an XDR transaction with Freighter and submits it to the network. + * Returns the transaction hash. + */ +export async function signAndSubmitTransaction(xdr: string): Promise { + const { passphrase, horizonUrl } = await getNetworkConfig(); + const server = new Horizon.Server(horizonUrl); + + try { + const signedXdr = await signTransaction(xdr, { + networkPassphrase: passphrase, + }); + const transaction = TransactionBuilder.fromXDR(signedXdr, passphrase); + const result = await server.submitTransaction(transaction); + return result.hash; + } catch (error) { + throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? error.message : String(error)} , { cause: error }); + } +} From c5986feb3e8ebcce6921f6429dedfd8a8d0cf404 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:28 +0100 Subject: [PATCH 08/26] fix(ci): resolve failing checks for #72 --- src/types/domain.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/types/domain.ts b/src/types/domain.ts index 7329329..61991fe 100644 --- a/src/types/domain.ts +++ b/src/types/domain.ts @@ -2,6 +2,9 @@ export type Asset = 'XLM' | 'USDC' | string; +export type StellarAddress = string; +export type AssetCode = string; + export type StellarNetwork = 'testnet' | 'public'; // --------------------------------------------------------------------------- @@ -100,8 +103,8 @@ export interface AssetBalance { export type TrustlineStatus = 'active' | 'inactive' | 'pending'; export interface Trustline { - assetCode: string; - assetIssuer?: string; + assetCode: AssetCode; + assetIssuer?: StellarAddress; balance: number; limit: number; status: TrustlineStatus; @@ -113,7 +116,7 @@ export interface Wallet { organizationId: string; agentId?: string; name: string; - stellarAddress: string; + stellarAddress: StellarAddress; walletType: WalletType; network: StellarNetwork; status: WalletStatus; From ef9b484e00c797f3fb884d5edeeb0a1f996b58bd Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:30 +0100 Subject: [PATCH 09/26] fix(ci): resolve failing checks for #72 --- src/hooks/useStellarWallet.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/useStellarWallet.ts b/src/hooks/useStellarWallet.ts index e1082bc..95e494f 100644 --- a/src/hooks/useStellarWallet.ts +++ b/src/hooks/useStellarWallet.ts @@ -51,7 +51,7 @@ export type AccountBalance = { }; export function useStellarWallet() { - const [status, setStatus] = useState('checking'); const [publicKey, setPublicKey] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); @@ -73,10 +73,13 @@ export function useStellarWallet() { const nextKey = typeof address === 'string' ? address : address?.address ?? null; setPublicKey(nextKey); setError(null); + } else { + setPublicKey(null); } return Boolean(connected); } catch (err) { setStatus('error'); + setPublicKey(null); setError(err instanceof Error ? err.message : 'Could not connect to the Freighter wallet.'); return false; } @@ -102,7 +105,7 @@ export function useStellarWallet() { const isAllowed = typeof allowed === 'boolean' ? allowed - : (allowed as { isAllowed?: boolean } | null)?.isAllowed ?> false; + : (allowed as { isAllowed?: boolean } | null)?.isAllowed ?? false; if (!isAllowed) { throw new Error('Freighter approval was rejected. Please approve the connection in the browser extension.'); @@ -248,7 +251,8 @@ export function useStellarWallet() { const signedXdr = await signTransaction(xdr); if (!signedXdr) return null; - return signedXdr; + const result = await server.submitTransaction(signedXdr); + return result.hash; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create trustline.'); return null; From 0ba683bd58f34647859956047d5bee956415c8a3 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:32 +0100 Subject: [PATCH 10/26] fix(ci): resolve failing checks for #72 --- src/stores/freighter-store.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/stores/freighter-store.ts b/src/stores/freighter-store.ts index 7d93a1d..354f9bb 100644 --- a/src/stores/freighter-store.ts +++ b/src/stores/freighter-store.ts @@ -3,6 +3,7 @@ import { create } from 'zustand'; import type { StateCreator } from 'zustand'; import { Server, TransactionBuilder, Operation, Asset, Networks, BASE_FEE } from '@stellar/stellar-sdk'; +import { z } from 'zod'; export type FreighterConnectionStatus = 'disconnected' | 'connecting' | 'connected'; @@ -11,6 +12,10 @@ export type AccountBalance = { asset_issuer?: string; asset_type: string; balance: string; + limit?: string; + is_authorized?: boolean; + is_authorized_to_maintain_liabilities?: boolean; + is_clawback_enabled?: boolean; }; interface FreighterWalletState { @@ -31,6 +36,10 @@ interface FreighterWalletState { } const STELLAR_ADDRESS_RE = /^G[A-Z2-7]{55}$/; +const ASSET_CODE_RE = /^[A-Za-z0-9]{1,12}$/; + +export const stellarPublicKeySchema = z.string().regex(STELLAR_ADDRESS_RE); +export const assetCodeSchema = z.string().regex(ASSET_CODE_RE); const withTimeout = async (promise: Promise, ms = 15000): Promise => { let timeoutId: ReturnType | undefined; @@ -63,7 +72,7 @@ const getFreighter = async () => }; const isValidStellarAddress = (value: string | null | undefined): value is string => - typeof value === 'string' && STELLAR_ADDRESS_RE.test(value); + typeof value === 'string' && stellarPublicKeySchema.safeParse(value).success; const resolveWalletAddress = (value: unknown): string | null => { if (typeof value === 'string') return isValidStellarAddress(value) ? value : null; @@ -83,10 +92,10 @@ const resolveNetwork = (value: unknown): string | null => { }; const getHorizonUrl = (network: string): string => - network === 'mainnet' || network === 'public' || network === Networks.PUBLIC ? 'https://horizon.stellar.org' : 'https://horizon-testnet.stellar.org'; + network === 'mainnet' || network === 'public' || network === 'PUBLIC' || network === Networks.PUBLIC ? 'https://horizon.stellar.org' : 'https://horizon-testnet.stellar.org'; const getNetworkPassphrase = (network: string): string => - network === 'mainnet' || network === 'public' || network === Networks.PUBLIC ? Networks.PUBLIC : Networks.TESTNET; + network === 'mainnet' || network === 'public' || network === 'PUBLIC' || network === Networks.PUBLIC ? Networks.PUBLIC : Networks.TESTNET; const getServer = (network: string): Server => new Server(getHorizonUrl(network)); @@ -192,7 +201,7 @@ const storeCreator: StateCreator = (set, get) => ({ return signedXdr; } catch (error) { const message = error instanceof Error ? error.message : 'Signature request failed.'; - set({ error: message, status: 'disconnected' }); + set({ error: message }); throw error; } }, @@ -250,6 +259,12 @@ const storeCreator: StateCreator = (set, get) => ({ const networkPassphrase = getNetworkPassphrase(network ?? 'testnet'); try { + const assetCodeResult = assetCodeSchema.safeParse(assetCode); + const issuerResult = stellarPublicKeySchema.safeParse(issuer); + if (!assetCodeResult.success || !issuerResult.success) { + throw new Error('Invalid asset code or issuer address.'); + } + const account = await server.loadAccount(publicKey); const transaction = new TransactionBuilder(account, { fee: BASE_FEE, From 837b8a5fc446496358fbed67d6fee631f7af70fc Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:34 +0100 Subject: [PATCH 11/26] fix(ci): resolve failing checks for #72 --- src/features/wallet/useTrustlineManager.ts | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/features/wallet/useTrustlineManager.ts b/src/features/wallet/useTrustlineManager.ts index 5d4a657..c9ebaff 100644 --- a/src/features/wallet/useTrustlineManager.ts +++ b/src/features/wallet/useTrustlineManager.ts @@ -1,12 +1,12 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, effect, useCallback } from 'react'; import * as StellarSdk from '@stellar/stellar-sdk'; -import { getPublicKey, signTransaction } from '@stellar/fregher-api'; -import { g} from 'zod'; +import { signTransaction } from '@stellar/freighter-api'; +import { z } from 'zod'; import { toast } from 'sonner'; -import { useWalletStore } from '@/stores/wallet'; +import { useWalletStore } from '/stores/wallet'; // Stellar network configuration -const HORIZON_URL = 'https://horizon.stellar.org'; +const HORIZON_URL = 'https://horyzon.stellar.org'; const NETWORK_PASSPHRASE = StellarSdk.Networks.PUBLIC; // Trustline representation @@ -18,7 +18,7 @@ export interface Trustline { trusted: boolean; } -// Zol validation for adding a trustline +// Zod validation for adding a trustline const trustlineSchema = z.object({ assetCode: z.string().min(1).max(12).regex(/^[a-zA-Z0-9]+$/, "Asset code must be alphanumeric"), assetIssuer: z.string().length(56, "Stellar issuer must be a valid public key"), @@ -33,7 +33,7 @@ export function useTrustlineManager() { const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); - const fetchTrustlines = useCallback(async () : Promise > { + const fetchTrustlines = useCallback(async (): Promise => { if (!publicKey) { setTrustlines([]); return; @@ -45,10 +45,15 @@ export function useTrustlineManager() { try { const server = new StellarSdk.Horizon.Server(HORIZON_URL); const account = await server.loadAccount(publicKey); - const lines = account.balances + const lines: Trustline[] = account.balances .filter((b: any) => b.asset_type !== 'native') - .map((b: any) => ({ - asset_code: b.asset_code,\n asset_issuer: b.asset_issuer,\n balance: b.balance,\n limit: b.limit,\n trusted: true,\n }) as Trustline[]); + .map((b: any): Trustline => ({ + asset_code: b.asset_code, + asset_issuer: b.asset_issuer, + balance: b.balance, + limit: b.limit, + trusted: true, + })); setTrustlines(lines); } catch (err: any) { setError(err.message || 'Failed to fetch trustlines'); @@ -62,7 +67,7 @@ export function useTrustlineManager() { }, [fetchTrustlines]); const addTrustline = useCallback( - async (assetCode: string, assetIssuer: string) : Promise > { + async (assetCode: string, assetIssuer: string): Promise => { // Validate inputs const parsed = trustlineSchema.safeParse({ assetCode, assetIssuer }); if (!parsed.success) { @@ -88,14 +93,15 @@ export function useTrustlineManager() { const asset = new StellarSdk.Asset(assetCode, assetIssuer); const transaction = new StellarSdk.TransactionBuilder(account, { - fee: StellarSdk.BASE_FEE,\n networkPassphrase: NETWORK_PASSPHRASE, + fee: StellarSdk.BASE_FEE, + networkPassphrase: NETWORK_PASSPHRASE, }) .addOperation(StellarSdk.Operation.changeTrust({ asset })) .setTimeout(30) .build(); const signedXDR = await signTransaction(transaction.toXDR(), { - networkPassphrase: NETWORK_PASSPHRASE, + networkPassphrase: NETWORK_PASSHPRASE, accountToSign: publicKey, }); @@ -110,7 +116,8 @@ export function useTrustlineManager() { } finally { setIsSubmitting(false); } - }, [publicKey, trustlines, fetchTrustlines], + }, + [publicKey, trustlines, fetchTrustlines], ); return { From efff901c644bbcc7cb5869bc129724e8bbe744ec Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:36 +0100 Subject: [PATCH 12/26] fix(ci): resolve failing checks for #72 --- src/features/transactions/useXdrSigner.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/features/transactions/useXdrSigner.ts b/src/features/transactions/useXdrSigner.ts index 7a6bb79..38a227a 100644 --- a/src/features/transactions/useXdrSigner.ts +++ b/src/features/transactions/useXdrSigner.ts @@ -1,13 +1,13 @@ import { useState, useCallback, useEffect } from 'react'; -import * as freighter from '@stellar/freighter-api'; -import { Networks } from '@stellar/stellar-sdk;' +import * freighter from '@stellar/freighter-api'; +import { Networks } from '@stellar/stellar-sdk'; export const STELLAR_HORIZON_URL = process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horizon-testnet.stellar.org'; export const STELLAR_NETWORK_PASSPHRASE = - process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSHRASE || Networks.TESTNET; + process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSHARE || Networks.TESTNET; -export interface UseXdrSignerResult { +export interface UseXdrnSignerResult { activeKey: string | null; isConnected: boolean; isPending: boolean; @@ -19,7 +19,7 @@ export interface UseXdrSignerResult { setActiveKey: (key: string | null) => void; } -export function useXdrSigner(): UseXdrSignerResult { +export function useXdrSigner(): UseXdrnSignerResult { const [activeKey, setActiveKey] = useState(null); const [isConnected, setIsConnected] = useState(false); const [isPending, setIsPending] = useState(false); @@ -49,7 +49,7 @@ export function useXdrSigner(): UseXdrSignerResult { checkAvailability(); }, []); - corst connectWallet = useCallback(async (): Promise => { + const connectWallet = useCallback(async (): Promise => { setIsPending(true); setError(null); try { @@ -94,13 +94,13 @@ export function useXdrSigner(): UseXdrSignerResult { setError(null); try { if (typeof freighter.signTransaction === 'function') { - const res = await freighter.signTransaction(xdr) { + const res = await freighter.signTransaction(xdr, { networkPassphrase: networkPassphrase || STELLAR_NETWORK_PASSPHRASE, }) as string | { signedTxXdr?: string }; if (typeof res === 'string') { return res; } else if (res && typeof res === 'object' && 'signedTxXdr' in res) { - return res.signedTxXdr??; + return res.signedTxXdr ?? null; } } throw new Error('Freighter signing operation failed or extension not connected.'); @@ -111,7 +111,8 @@ export function useXdrSigner(): UseXdrSignerResult { } finally { setIsPending(false); } - }, [] + }, + [] ); const disconnectWallet = useCallback(() => { From 20e622c991e4cce57ec2e4d0bf95d4d3c4a72fee Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 11:58:37 +0100 Subject: [PATCH 13/26] fix(ci): resolve failing checks for #72 --- src/services/stellar.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/stellar.ts b/src/services/stellar.ts index 3e5996b..49a5cc6 100644 --- a/src/services/stellar.ts +++ b/src/services/stellar.ts @@ -11,7 +11,8 @@ import { Networks, Operation, TransactionBuilder, -} from '@stellar/stellar-sdk';import { z from 'zod'; +} from '@stellar/stellar-sdk'; +import { z } from 'zod'; /** * Validates a Stellar public key (G...). @@ -57,7 +58,7 @@ export async function getNetworkConfig(): Promise { return STELLAR_NETWORKS.PUBLIC; } // If Freighter says TESTNET or something else, treat as TESTNET. - return STELDAR_NETWORKS.TESTNET; + return STELLAR_NETWORKS.TESTNET; } catch (error) { throw new Error('Unable to determine Stellar network. Is Freighter connected?', { cause: error }); } @@ -175,6 +176,6 @@ export async function signAndSubmitTransaction(xdr: string): Promise { const result = await server.submitTransaction(transaction); return result.hash; } catch (error) { - throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? error.message : String(error)} , { cause: error }); + throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } } From 9410eb6b8fe96ec1892af91900c69d7ccd28cc18 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:15 +0100 Subject: [PATCH 14/26] fix(ci): resolve failing checks for #72 --- src/types/domain.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/types/domain.ts b/src/types/domain.ts index 61991fe..e840864 100644 --- a/src/types/domain.ts +++ b/src/types/domain.ts @@ -96,6 +96,7 @@ export type WalletStatus = 'active' | 'frozen' | 'paused' | 'archived'; export interface AssetBalance { asset: Asset; + assetIssuer?: StellarAddress; balance: number; usdValue: number; } @@ -111,6 +112,18 @@ export interface Trustline { isNative: boolean; } +export interface StellarAsset { + assetCode: AssetCode; + assetIssuer?: StellarAddress; + isNative: boolean; +} + +export interface TrustlineRequest { + assetCode: AssetCode; + assetIssuer: StellarAddress; + limit: number; +} + export interface Wallet { id: string; organizationId: string; From 88e43b8381200c587783ce4a4c04fd3e5960ab08 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:17 +0100 Subject: [PATCH 15/26] fix(ci): resolve failing checks for #72 --- src/hooks/useStellarWallet.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/hooks/useStellarWallet.ts b/src/hooks/useStellarWallet.ts index 95e494f..5e3b27b 100644 --- a/src/hooks/useStellarWallet.ts +++ b/src/hooks/useStellarWallet.ts @@ -127,7 +127,7 @@ export function useStellarWallet() { } }, []); - const signTransaction = useCallback(async (xdr: string) => { + const signTransaction = useCallback(async (xdr: string, networkPassphrase?: string) => { const api = await loadFreighter(); if (!api) { setStatus('missing'); @@ -139,7 +139,9 @@ export function useStellarWallet() { setError(null); try { - const response = await withTimeout(api.signTransaction(xdr)); + const response = networkPassphrase + ? await withTimeout(api.signTransaction(xdr, { networkPassphrase })) + : await withTimeout(api.signTransaction(xdr)); const signature = typeof response === 'string' ? response @@ -168,9 +170,11 @@ export function useStellarWallet() { try { const network = await withTimeout(api.getNetwork()); if (typeof network === 'string') { + const networkName = network.toUpperCase(); + const isTestnet = networkName === 'TESTNET' || networkName.includes('TESTNET'); return { - networkUrl: 'https://horizon.stellar.org', - networkPassphrase: StellarSdk.Networks.PUBLIC, + networkUrl: isTestnet ? 'https://horizon-testnet.stellar.org' : 'https://horizon.stellar.org', + networkPassphrase: isTestnet ? StellarSdk.Networks.TESTNET : StellarSdk.Networks.PUBLIC, }; } return { @@ -248,7 +252,7 @@ export function useStellarWallet() { .build(); const xdr = transaction.toXDR(); - const signedXdr = await signTransaction(xdr); + const signedXdr = await signTransaction(xdr, network.networkPassphrase); if (!signedXdr) return null; const result = await server.submitTransaction(signedXdr); From 91c80515ac09fb2fe08a6729c7e7148098754e6f Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:18 +0100 Subject: [PATCH 16/26] fix(ci): resolve failing checks for #72 --- src/stores/freighter-store.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/stores/freighter-store.ts b/src/stores/freighter-store.ts index 354f9bb..bc4766a 100644 --- a/src/stores/freighter-store.ts +++ b/src/stores/freighter-store.ts @@ -124,9 +124,9 @@ const storeCreator: StateCreator = (set, get) => ({ connect: async () => { set({ status: 'connecting', error: null }); + const installed = typeof window !== 'undefined' && !!(window as typeof window & { freighter?: unknown }).freighter; try { const freighter = await getFreighter(); - const installed = typeof window !== 'undefined' && !!(window as typeof window & { freighter?: unknown }).freighter; if (!installed && typeof freighter.isConnected !== 'function') { set({ isInstalled: false, status: 'disconnected', error: 'Freighter extension is not installed.' }); return null; @@ -162,7 +162,7 @@ const storeCreator: StateCreator = (set, get) => ({ const message = error instanceof Error ? error.message : 'Unable to connect to the Freighter wallet.'; set({ status: 'disconnected', - isInstalled: false, + isInstalled: installed, publicKey: null, network: null, error: message, @@ -207,13 +207,14 @@ const storeCreator: StateCreator = (set, get) => ({ }, hydrate: async () => { + await get().checkExtension(); const freighter = await getFreighter(); const walletAddress = resolveWalletAddress( typeof freighter.getAddress === 'function' ? await withTimeout(freighter.getAddress()).catch(() => null) : null, ); if (!walletAddress) { - set({ status: 'disconnected', publicKey: null, network: null, isInstalled: false, error: null }); + set({ status: 'disconnected', publicKey: null, network: null, error: null }); return; } @@ -276,7 +277,11 @@ const storeCreator: StateCreator = (set, get) => ({ const signedXdr = await requestSignature(transaction.toXDR()); await server.submitTransaction(signedXdr); - await get().loadBalances(); + try { + await get().loadBalances(); + } catch { + // Balance refresh is best-effort; the trustline transaction succeeded. + } } catch (error) { const message = error instanceof Error ? error.message : 'Failed to add trustline.'; set({ error: message }); From 804cf61635a7e2a851a62e6387258e16742ccd55 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:20 +0100 Subject: [PATCH 17/26] fix(ci): resolve failing checks for #72 From 30aded07106b126c33eca93f0db07afee62bd564 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:21 +0100 Subject: [PATCH 18/26] fix(ci): resolve failing checks for #72 --- src/features/wallet/useTrustlineManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/wallet/useTrustlineManager.ts b/src/features/wallet/useTrustlineManager.ts index c9ebaff..687ac37 100644 --- a/src/features/wallet/useTrustlineManager.ts +++ b/src/features/wallet/useTrustlineManager.ts @@ -1,12 +1,12 @@ -import { useState, effect, useCallback } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import * as StellarSdk from '@stellar/stellar-sdk'; import { signTransaction } from '@stellar/freighter-api'; -import { z } from 'zod'; +import { Z } from 'zod'; import { toast } from 'sonner'; import { useWalletStore } from '/stores/wallet'; // Stellar network configuration -const HORIZON_URL = 'https://horyzon.stellar.org'; +const HORIZON_URL = 'https://horizon.stellar.org'; const NETWORK_PASSPHRASE = StellarSdk.Networks.PUBLIC; // Trustline representation @@ -101,7 +101,7 @@ export function useTrustlineManager() { .build(); const signedXDR = await signTransaction(transaction.toXDR(), { - networkPassphrase: NETWORK_PASSHPRASE, + networkPassphrase: NETWORK_PASSPHRASE, accountToSign: publicKey, }); From 047faa4bc82842313c4748b1ece83512f45cdad9 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:22 +0100 Subject: [PATCH 19/26] fix(ci): resolve failing checks for #72 --- src/features/transactions/useXdrSigner.ts | 122 +++++++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/src/features/transactions/useXdrSigner.ts b/src/features/transactions/useXdrSigner.ts index 38a227a..2f1c7e1 100644 --- a/src/features/transactions/useXdrSigner.ts +++ b/src/features/transactions/useXdrSigner.ts @@ -1,12 +1,24 @@ import { useState, useCallback, useEffect } from 'react'; -import * freighter from '@stellar/freighter-api'; -import { Networks } from '@stellar/stellar-sdk'; +import * as freighter from '@stellar/freighter-api'; +import { Account, Asset, Networks, Operation, TransactionBuilder } from '@stellar/stellar-sdk'; export const STELLAR_HORIZON_URL = - process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horizon-testnet.stellar.org'; + process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horyzon-testnet.stellar.org'; export const STELLAR_NETWORK_PASSPHRASE = process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSHARE || Networks.TESTNET; +export interface Trustline { + assetCode: string; + issuer: string; + balance: string; + limit?: string; +} + +export interface AddTrustlineResult { + signedXdr: string; + hash: string; +} + export interface UseXdrnSignerResult { activeKey: string | null; isConnected: boolean; @@ -17,6 +29,17 @@ export interface UseXdrnSignerResult { signXdr: (xdr: string, networkPassphrase?: string) => Promise; disconnectWallet: () => void; setActiveKey: (key: string | null) => void; + getAccountBalances: (publicKey?: string) => Promise; + buildTrustlineXdr: (assetCode: string, issuer: string, limit?: string, publicKey?: string) => Promise; + addTrustline: (assetCode: string, issuer: string, limit?: string, publicKey?: string) => Promise; +} + +async function fetchAccount(publicKey: string): Promise { + const res = await fetch(`${STELLAR_HORIZON_URL}/accounts/${publicKey}`); + if (!res.ok) { + throw new Error(`Failed to fetch account from Horizon: ${res.statusText}`); + } + return res.json(); } export function useXdrSigner(): UseXdrnSignerResult { @@ -115,6 +138,96 @@ export function useXdrSigner(): UseXdrnSignerResult { [] ); + const getAccountBalances = useCallback(async (publicKey?: string): Promise => { + const key = publicKey || activeKey; + if (!key) { + setError('Wallet is not connected. Cannot fetch balances.'); + return []; + } + try { + const account = await fetchAccount(key); + const balances = account.balances || []; + return balances.map((balance: any) => ({ + assetCode: balance.asset_code || 'XLM', + issuer: balance.asset_issuer || '', + balance: balance.balance, + limit: balance.limit, + })); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : 'Failed to fetch account balances.'; + setError(msg); + return []; + } + }, [activeKey]); + + const buildTrustlineXdr = useCallback( + async (assetCode: string, issuer: string, limit?: string, publicKey?: string): Promise => { + const key = publicKey || activeKey; + if (!key) { + setError('Wallet is not connected. Cannot build trustline transaction.'); + return null; + } + try { + const account = await fetchAccount(key); + const asset = new Asset(assetCode, issuer); + const accountObj = new Account(key, account.sequence); + const transaction = new TransactionBuilder(accountObj, { + fee: '100', + networkPassphrase: STELLAR_NETWORK_PASSHRASE, + }) + .addOperation(Operation.changeTrust({ asset, limit: limit || undefined })) + .setTimeout(180) + .build(); + return transaction.toXDR(); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : 'Failed to build trustline transaction.'; + setError(msg); + return null; + } + }, + [activeKey] + ); + + const addTrustline = useCallback( + async (assetCode: string, issuer: string, limit?: string, publicKey?: string): Promise => { + const key = publicKey || activeKey; + if (!key) { + setError('Wallet is not connected. Cannot add trustline.'); + return null; + } + try { + const xdr = await buildTrustlineXdr(assetCode, issuer, limit, key); + if (!xdr) { + return null; + } + const signedXdr = await signXdr(xdr); + if (!signedXdr) { + return null; + } + + const body = new URLSearchParams({ tx: signedXdr }); + const res = await fetch(`${STELL@R_HORIZON_URL}/transactions`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); + + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Transaction submission failed (${res.status}): ${errorText}`); + } + + const data = await res.json(); + return { signedXdr, hash: data.hash }; + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : 'Failed to add trustline.'; + setError(msg); + return null; + } + }, + [activeKey, buildTrustlineXdr, signXdr] + ); + const disconnectWallet = useCallback(() => { setActiveKey(null); setIsConnected(false); @@ -131,5 +244,8 @@ export function useXdrSigner(): UseXdrnSignerResult { signXdr, disconnectWallet, setActiveKey, + getAccountBalances, + buildTrustlineXdr, + addTrustline, }; } From ddb7b320c06d0aa8c7cef58f160aa55759607159 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:20:24 +0100 Subject: [PATCH 20/26] fix(ci): resolve failing checks for #72 --- src/services/stellar.ts | 70 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/services/stellar.ts b/src/services/stellar.ts index 49a5cc6..b617315 100644 --- a/src/services/stellar.ts +++ b/src/services/stellar.ts @@ -57,7 +57,6 @@ export async function getNetworkConfig(): Promise { if (network === 'PUBLIC') { return STELLAR_NETWORKS.PUBLIC; } - // If Freighter says TESTNET or something else, treat as TESTNET. return STELLAR_NETWORKS.TESTNET; } catch (error) { throw new Error('Unable to determine Stellar network. Is Freighter connected?', { cause: error }); @@ -119,7 +118,70 @@ export async function getAssetBalances(publicKey: string): Promise 0, trustline is active + trustlineActive: !isNative && balance.limit !== '0', + }; + }); +} + +export interface SupportedAsset { + assetCode: string; + issuer: string; +} + +export interface SupportedAssetStatus extends SupportedAsset { + balance: string; + limit?: string; + trustlineActive: boolean; +} + +const SUPPORTED_ASSATS_PUBLIC: SupportedAsset[] = [ + { assetCode: 'USDC', issuer: `G${'A'.repeat(55)}` }, + { assetCode: 'USDT', issuer: `G${'B'.repeat(55)}` }, +]; + +const SUPPORTED_ASSETS_TESTNET: SupportedAsset[] = [ + { assetCode: 'USDC', issuer: `G${'C'.repeat(55)}` }, + { assetCode: 'USDT', issuer: `G${'D'.repeat(55)}` }, +]; + +/** + * Returns the supported assets for a given network passphrase. + */ +export function getSupportedAssetsForNetwork(passphrase: string): SupportedAsset[] { + return passphrase === Networks.PUBLIC ? SUPPORTED_ASSETS_PUBLIC : SUPPORTED_ASSATS_TESTNET; +} + +export async function getSupportedAssetStatuses(publicKey: string): Promise { + stellarPublicKeySchema.parse(publicKey); + + const networkConfig = await getNetworkConfig(); + const supportedAssets = getSupportedAssetsForNetwork.networkConfig.passphrase); + + const account = await loadAccount(publicKey); + const balanceMap = new Map(); + + for (const balance of account.balances) { + if ('asset_code' in balance && balance.asset_code && 'asset_issuer' in balance && balance.asset_issuer) { + const assetBalance = balance as Horizon.BalanceLineAsset; + balanceMap.set(`${assetBalance.asset_code}:${assetBalance.asset_issuer}`, assetBalance); + } + } + + return supportedAssets.map((asset) => { + const balance = balanceMap.get(`${asset.assetCode}:${asset.issuer}`); + if (balance) { + return { + ...asset, + balance: balance.balance, + limit: balance.limit, + trustlineActive: true, + }; + } + return { + ...asset, + balance: '0', + limit: '0', + trustlineActive: false, }; }); } @@ -151,7 +213,7 @@ export async function buildTrustlineTransaction( .addOperation( Operation.changeTrust({ asset, - limit: '922337203685.4775807', // Max trustline limit + limit: '922337203685.4775807', }) ) .setTimeout(180) @@ -178,4 +240,4 @@ export async function signAndSubmitTransaction(xdr: string): Promise { } catch (error) { throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } -} +} \ No newline at end of file From 14142970788dc00884c4bcd4589ea4e26cc821e7 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:43:51 +0100 Subject: [PATCH 21/26] fix(ci): resolve failing checks for #72 --- src/types/domain.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/types/domain.ts b/src/types/domain.ts index e840864..3b386a6 100644 --- a/src/types/domain.ts +++ b/src/types/domain.ts @@ -1,10 +1,20 @@ /** Core domain models mirroring the Astroid API entities (PRD Doc 5 / Doc 6). */ +import { z } from 'zod'; + export type Asset = 'XLM' | 'USDC' | string; export type StellarAddress = string; export type AssetCode = string; +export const stellarAddressSchema = z + .string() + .regex(/^G[A-Z2-7]{55}$/, 'Invalid Stellar public key'); + +export const assetCodeSchema = z + .string() + .regex(/^[A-Za-z0-9]{1,12}$/, 'Invalid asset code'); + export type StellarNetwork = 'testnet' | 'public'; // --------------------------------------------------------------------------- From 0ef2df2258ee6753d9ba8650c00d15166ab9fbff Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:44:00 +0100 Subject: [PATCH 22/26] fix(ci): resolve failing checks for #72 --- src/hooks/useStellarWallet.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/hooks/useStellarWallet.ts b/src/hooks/useStellarWallet.ts index 5e3b27b..8fc7fac 100644 --- a/src/hooks/useStellarWallet.ts +++ b/src/hooks/useStellarWallet.ts @@ -55,6 +55,8 @@ export function useStellarWallet() { const [publicKey, setPublicKey] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [balances, setBalances] = useState([]); + const [isBalancesLoading, setIsBalancesLoading] = useState(false); const refreshConnection = useCallback(async () => { const api = await loadFreighter(); @@ -75,6 +77,7 @@ export function useStellarWallet() { setError(null); } else { setPublicKey(null); + setBalances([]); } return Boolean(connected); } catch (err) { @@ -115,6 +118,9 @@ export function useStellarWallet() { const nextKey = typeof address === 'string' ? address : address?.address ?? null; setPublicKey(nextKey); + if (!nextKey) { + setBalances([]); + } setStatus(nextKey ? 'connected' : 'idle'); return nextKey; } catch (err) { @@ -199,7 +205,7 @@ export function useStellarWallet() { try { const server = new StellarSdk.Server(network.networkUrl); const account = await server.loadAccount(publicKey); - return account.balances.map((balance) => { + const nextBalances = account.balances.map((balance) => { if (balance.asset_type === 'native') { return { assetCode: 'XLM', @@ -216,12 +222,23 @@ export function useStellarWallet() { limit: assetLine.limit, }; }); + setBalances(nextBalances); + return nextBalances; } catch (err) { + setBalances([]); setError(err instanceof Error ? err.message : 'Failed to load account balances.'); return null; } }, [publicKey, getNetworkDetails]); + useEffect(() => { + if (publicKey) { + void getAccountBalances(); + } else { + setBalances([]); + } + }, [publicKey, getAccountBalances]); + const addTrustline = useCallback( async (assetCode: string, issuer: string): Promise => { if (!publicKey) { @@ -265,6 +282,15 @@ export function useStellarWallet() { [publicKey, getNetworkDetails, signTransaction], ); + const refreshBalances = useCallback(async (): Promise => { + setIsBalancesLoading(true); + try { + return await getAccountBalances(); + } finally { + setIsBalancesLoading(false); + } + }, [getAccountBalances]); + return { status, publicKey, @@ -277,6 +303,9 @@ export function useStellarWallet() { signTransaction, getNetworkDetails, getAccountBalances, + refreshBalances, + balances, + isBalancesLoading, addTrustline, }; } From 03ccdf353b4e8a8f046ce026a58b8c596dbbe99d Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:44:05 +0100 Subject: [PATCH 23/26] fix(ci): resolve failing checks for #72 --- src/stores/freighter-store.ts | 43 +++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/stores/freighter-store.ts b/src/stores/freighter-store.ts index bc4766a..3dbe80a 100644 --- a/src/stores/freighter-store.ts +++ b/src/stores/freighter-store.ts @@ -67,7 +67,9 @@ const getFreighter = async () => isConnected?: () => Promise; setAllowed?: () => Promise; getAddress?: () => Promise; - getNetwork?: () => Promise; + getPublicKey?: () => Promise; + getNetwork?: () => Promise; + getNetworkDetails?: () => Promise<{ network?: string; networkPassphrase?: string }>; signTransaction?: (xdr: string, options?: Record) => Promise; }; @@ -140,13 +142,23 @@ const storeCreator: StateCreator = (set, get) => ({ } } - const addressResult = typeof freighter.getAddress === 'function' ? await withTimeout(freighter.getAddress()) : null; + const addressResult = + typeof freighter.getPublicKey === 'function' + ? await withTimeout(freighter.getPublicKey()) + : typeof freighter.getAddress === 'function' + ? await withTimeout(freighter.getAddress()) + : null; const publicKey = resolveWalletAddress(addressResult); if (!publicKey) { throw new Error('Freighter did not return a valid Stellar public key.'); } - const networkResult = typeof freighter.getNetwork === 'function' ? await withTimeout(freighter.getNetwork()) : null; + const networkResult = + typeof freighter.getNetwork === 'function' + ? await withTimeout(freighter.getNetwork()) + : typeof freighter.getNetworkDetails === 'function' + ? await withTimeout(freighter.getNetworkDetails()) + : null; const network = resolveNetwork(networkResult) ?? 'testnet'; set({ @@ -186,7 +198,10 @@ const storeCreator: StateCreator = (set, get) => ({ throw new Error('Freighter does not expose transaction signing support.'); } - const response = await withTimeout(freighter.signTransaction(xdr)); + const { network } = get(); + const response = await withTimeout( + freighter.signTransaction(xdr, { networkPassphrase: getNetworkPassphrase(network ?? 'testnet') }), + ); const signedXdr = typeof response === 'string' ? response @@ -210,7 +225,11 @@ const storeCreator: StateCreator = (set, get) => ({ await get().checkExtension(); const freighter = await getFreighter(); const walletAddress = resolveWalletAddress( - typeof freighter.getAddress === 'function' ? await withTimeout(freighter.getAddress()).catch(() => null) : null, + typeof freighter.getPublicKey === 'function' + ? await withTimeout(freighter.getPublicKey()).catch(() => null) + : typeof freighter.getAddress === 'function' + ? await withTimeout(freighter.getAddress()).catch(() => null) + : null, ); if (!walletAddress) { @@ -219,7 +238,11 @@ const storeCreator: StateCreator = (set, get) => ({ } const network = resolveNetwork( - typeof freighter.getNetwork === 'function' ? await withTimeout(freighter.getNetwork()).catch(() => null) : null, + typeof freighter.getNetwork === 'function' + ? await withTimeout(freighter.getNetwork()).catch(() => null) + : typeof freighter.getNetworkDetails === 'function' + ? await withTimeout(freighter.getNetworkDetails()).catch(() => null) + : null, ); set({ @@ -251,9 +274,11 @@ const storeCreator: StateCreator = (set, get) => ({ }, addTrustline: async ({ assetCode, issuer }: { assetCode: string; issuer: string }) => { - const { publicKey, network, requestSignature } = get(); - if (!publicKey) { - throw new Error('No wallet connected.'); + const { status, publicKey, network, requestSignature } = get(); + if (status !== 'connected' || !publicKey) { + const message = 'No wallet connected.'; + set({ error: message }); + throw new Error(message); } const server = getServer(network ?? 'testnet'); From 31aa7f09532d4afac45e96122a127ce50d6d5bcb Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:44:06 +0100 Subject: [PATCH 24/26] fix(ci): resolve failing checks for #72 --- src/features/transactions/useXdrSigner.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/transactions/useXdrSigner.ts b/src/features/transactions/useXdrSigner.ts index 2f1c7e1..b52ac04 100644 --- a/src/features/transactions/useXdrSigner.ts +++ b/src/features/transactions/useXdrSigner.ts @@ -3,9 +3,9 @@ import * as freighter from '@stellar/freighter-api'; import { Account, Asset, Networks, Operation, TransactionBuilder } from '@stellar/stellar-sdk'; export const STELLAR_HORIZON_URL = - process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horyzon-testnet.stellar.org'; + process.env.NEXT_PUBLIC_STELLAR_HORIZON_URL || 'https://horizon-testnet.stellar.org'; export const STELLAR_NETWORK_PASSPHRASE = - process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSHARE || Networks.TESTNET; + process.env.NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE || Networks.TESTNET; export interface Trustline { assetCode: string; @@ -19,7 +19,7 @@ export interface AddTrustlineResult { hash: string; } -export interface UseXdrnSignerResult { +export interface UseXdrSignerResult { activeKey: string | null; isConnected: boolean; isPending: boolean; @@ -42,7 +42,7 @@ async function fetchAccount(publicKey: string): Promise { return res.json(); } -export function useXdrSigner(): UseXdrnSignerResult { +export function useXdrSigner(): UseXdrSignerResult { const [activeKey, setActiveKey] = useState(null); const [isConnected, setIsConnected] = useState(false); const [isPending, setIsPending] = useState(false); @@ -173,7 +173,7 @@ export function useXdrSigner(): UseXdrnSignerResult { const accountObj = new Account(key, account.sequence); const transaction = new TransactionBuilder(accountObj, { fee: '100', - networkPassphrase: STELLAR_NETWORK_PASSHRASE, + networkPassphrase: STELLAR_NETWORK_PASSPHRASE, }) .addOperation(Operation.changeTrust({ asset, limit: limit || undefined })) .setTimeout(180) @@ -206,7 +206,7 @@ export function useXdrSigner(): UseXdrnSignerResult { } const body = new URLSearchParams({ tx: signedXdr }); - const res = await fetch(`${STELL@R_HORIZON_URL}/transactions`, { + const res = await fetch(`${STELLAR_HORIZON_URL}/transactions`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString(), From fb89feb9424a92704ce89fa3a646a1c657c6dd40 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:44:08 +0100 Subject: [PATCH 25/26] fix(ci): resolve failing checks for #72 --- src/services/stellar.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/services/stellar.ts b/src/services/stellar.ts index b617315..baadf82 100644 --- a/src/services/stellar.ts +++ b/src/services/stellar.ts @@ -59,7 +59,7 @@ export async function getNetworkConfig(): Promise { } return STELLAR_NETWORKS.TESTNET; } catch (error) { - throw new Error('Unable to determine Stellar network. Is Freighter connected?', { cause: error }); + throw new Error("Unable to determine Stellar network. Is Freighter connected?", { cause: error }); } } @@ -76,7 +76,7 @@ export async function getConnectedPublicKey(): Promise { stellarPublicKeySchema.parse(publicKey); return publicKey; } catch (error) { - throw new Error('Unable to access Freighter wallet. Please connect and unlock your wallet.', { cause: error }); + throw new Error("Unable to access Freighter wallet. Please connect and unlock your wallet.", { cause: error }); } } @@ -134,28 +134,28 @@ export interface SupportedAssetStatus extends SupportedAsset { trustlineActive: boolean; } -const SUPPORTED_ASSATS_PUBLIC: SupportedAsset[] = [ - { assetCode: 'USDC', issuer: `G${'A'.repeat(55)}` }, - { assetCode: 'USDT', issuer: `G${'B'.repeat(55)}` }, +const SUPPORTED_ASSETS_PUBLIC: SupportedAsset[] = [ + { assetCode: 'USDC', issuer: g@${'A'.repeat(55)} }, + { assetCode: 'USDT', issuer: g@${'B'.repeat(55)} }, ]; const SUPPORTED_ASSETS_TESTNET: SupportedAsset[] = [ - { assetCode: 'USDC', issuer: `G${'C'.repeat(55)}` }, - { assetCode: 'USDT', issuer: `G${'D'.repeat(55)}` }, + { assetCode: 'USDC', issuer: g@${'C'.repeat(55)} }, + { assetCode: 'USDT', issuer: g@${'D'.repeat(55)} }, ]; /** * Returns the supported assets for a given network passphrase. */ export function getSupportedAssetsForNetwork(passphrase: string): SupportedAsset[] { - return passphrase === Networks.PUBLIC ? SUPPORTED_ASSETS_PUBLIC : SUPPORTED_ASSATS_TESTNET; + return passphrase === Networks.PUBLIC ? SUPPORTED_ASSETS_PUBLIC : SUPPORTED_ASSETS_TESTNET; } export async function getSupportedAssetStatuses(publicKey: string): Promise { stellarPublicKeySchema.parse(publicKey); const networkConfig = await getNetworkConfig(); - const supportedAssets = getSupportedAssetsForNetwork.networkConfig.passphrase); + const supportedAssets = getSupportedAssetsForNetwork(networkConfig.passphrase); const account = await loadAccount(publicKey); const balanceMap = new Map(); @@ -194,13 +194,13 @@ export async function buildTrustlineTransaction( publicKey: string, assetCode: string, issuer: string -): Promise { +|): Promise { stellarPublicKeySchema.parse(publicKey); assetCodeSchema.parse(assetCode); stellarPublicKeySchema.parse(issuer); const { passphrase, horizonUrl } = await getNetworkConfig(); - const server = new Horizon.Server(horizonUrl); + const server = new Horizon.Server"horizonUrl"); const account = await server.loadAccount(publicKey); const source = new Account(publicKey, account.sequence); @@ -238,6 +238,6 @@ export async function signAndSubmitTransaction(xdr: string): Promise { const result = await server.submitTransaction(transaction); return result.hash; } catch (error) { - throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); + throw new Error(`Transaction signing or submission failed: ${error instanceof Error ? err.message : String(error)}`, { cause: error }); } } \ No newline at end of file From af236f7fa8fcf9a5e00e6f2f3d8ceb32e23ac289 Mon Sep 17 00:00:00 2001 From: "DEV.Timmmyturnner" Date: Mon, 31 Aug 2026 12:44:10 +0100 Subject: [PATCH 26/26] fix(ci): resolve failing checks for #72 --- src/features/wallet/useTrustlineManager.ts | 115 ++++++--------------- 1 file changed, 34 insertions(+), 81 deletions(-) diff --git a/src/features/wallet/useTrustlineManager.ts b/src/features/wallet/useTrustlineManager.ts index 687ac37..d65dc4b 100644 --- a/src/features/wallet/useTrustlineManager.ts +++ b/src/features/wallet/useTrustlineManager.ts @@ -1,41 +1,25 @@ import { useState, useEffect, useCallback } from 'react'; -import * as StellarSdk from '@stellar/stellar-sdk'; -import { signTransaction } from '@stellar/freighter-api'; -import { Z } from 'zod'; import { toast } from 'sonner'; import { useWalletStore } from '/stores/wallet'; - -// Stellar network configuration -const HORIZON_URL = 'https://horizon.stellar.org'; -const NETWORK_PASSPHRASE = StellarSdk.Networks.PUBLIC; - -// Trustline representation -export interface Trustline { - asset_code: string; - asset_issuer: string; - balance: string; - limit: string; - trusted: boolean; -} - -// Zod validation for adding a trustline -const trustlineSchema = z.object({ - assetCode: z.string().min(1).max(12).regex(/^[a-zA-Z0-9]+$/, "Asset code must be alphanumeric"), - assetIssuer: z.string().length(56, "Stellar issuer must be a valid public key"), -}); +import { + getSupportedAssetStatuses, + buildTrustlineTransaction, + signAndSubmitTransaction, + SupportedAssetStatus, +} from '../../services/stellar'; export function useTrustlineManager() { const publicKey = useWalletStore((state: any) => state.publicKey); const isConnected = useWalletStore((state: any) => state.isConnected); - const [trustlines, setTrustlines] = useState([]); + const [assets, setAssets] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); - const fetchTrustlines = useCallback(async (): Promise => { + const fetchAssets = useCallback(async () => { if (!publicKey) { - setTrustlines([]); + setAssets([]); return; } @@ -43,90 +27,59 @@ export function useTrustlineManager() { setError(null); try { - const server = new StellarSdk.Horizon.Server(HORIZON_URL); - const account = await server.loadAccount(publicKey); - const lines: Trustline[] = account.balances - .filter((b: any) => b.asset_type !== 'native') - .map((b: any): Trustline => ({ - asset_code: b.asset_code, - asset_issuer: b.asset_issuer, - balance: b.balance, - limit: b.limit, - trusted: true, - })); - setTrustlines(lines); - } catch (err: any) { - setError(err.message || 'Failed to fetch trustlines'); + const statuses = await getSupportedAssetStatuses(publicKey); + setAssets(statuses); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to fetch asset balances'; + setError(message); + toast.error(message); } finally { setLoading(false); } }, [publicKey]); - useEffect(() => { - fetchTrustlines(); - }, [fetchTrustlines]); + useEffect(() { + fetchAssets(); + }, [fetchAssets]); const addTrustline = useCallback( - async (assetCode: string, assetIssuer: string): Promise => { - // Validate inputs - const parsed = trustlineSchema.safeParse({ assetCode, assetIssuer }); - if (!parsed.success) { - toast.error(parsed.error.errors[0].message); + async (assetCode: string, assetIssuer: string) => { + if (!publicKey) { + toast.error('Wallet not connected'); return; } - // Check if trustline already exists - if (trustlines.some((t) => t.asset_code === assetCode && t.asset_issuer === assetIssuer)) { + const existing = assets.find( + (a) => a.assetCode === assetCode && a.issuer === assetIssuer + ); + if (existing?.trustlineActive) { toast.info('Trustline already exists'); return; } - if (!publicKey) { - toast.error('Wallet not connected'); - return; - } - setIsSubmitting(true); try { - const server = new StellarSdk.Horizon.Server(HORIZON_URL); - const account = await server.loadAccount(publicKey); - const asset = new StellarSdk.Asset(assetCode, assetIssuer); - - const transaction = new StellarSdk.TransactionBuilder(account, { - fee: StellarSdk.BASE_FEE, - networkPassphrase: NETWORK_PASSPHRASE, - }) - .addOperation(StellarSdk.Operation.changeTrust({ asset })) - .setTimeout(30) - .build(); - - const signedXDR = await signTransaction(transaction.toXDR(), { - networkPassphrase: NETWORK_PASSPHRASE, - accountToSign: publicKey, - }); - - const signedTx = StellarSdk.TransactionBuilder.fromXDR(signedXDR, NETWORK_PASSPHRASE) as StellarSdk.Transaction; - await server.submitTransaction(signedTx); - + const xdr = await buildTrustlineTransaction(publicKey, assetCode, assetIssuer); + await signAndSubmitTransaction(xdr); toast.success('Trustline added successfully'); - await fetchTrustlines(); - } catch (err: any) { - const message = err.message || 'Failed to add trustline'; + await fetchAssets(); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to add trustline'; toast.error(message); } finally { setIsSubmitting(false); } }, - [publicKey, trustlines, fetchTrustlines], + [publicKey, assets, fetchAssets] ); return { - trustlines, + assets, loading, error, isSubmitting, isConnected, addTrustline, - refresh: fetchTrustlines, + refresh: fetchAssets, }; -} +} \ No newline at end of file