diff --git a/frontend/mobile/app.config.ts b/frontend/mobile/app.config.ts new file mode 100644 index 00000000..d6a4e965 --- /dev/null +++ b/frontend/mobile/app.config.ts @@ -0,0 +1,33 @@ +import { ExpoConfig, ConfigContext } from 'expo/config'; + +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + name: config.name ?? 'Veil', + slug: config.slug ?? 'veil-mobile', + extra: { + ...config.extra, + NEXT_PUBLIC_HORIZON_URL: process.env.NEXT_PUBLIC_HORIZON_URL || process.env.EXPO_PUBLIC_HORIZON_URL, + NEXT_PUBLIC_SOROBAN_RPC_URL: process.env.NEXT_PUBLIC_SOROBAN_RPC_URL || process.env.EXPO_PUBLIC_SOROBAN_RPC_URL, + NEXT_PUBLIC_RPC_URL: process.env.NEXT_PUBLIC_RPC_URL || process.env.EXPO_PUBLIC_RPC_URL, + NEXT_PUBLIC_FACTORY_CONTRACT_ID_TESTNET: + process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID_TESTNET || process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID_TESTNET, + NEXT_PUBLIC_FACTORY_CONTRACT_ID: + process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID || process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID, + NEXT_PUBLIC_MAINNET_RPC_URL: process.env.NEXT_PUBLIC_MAINNET_RPC_URL || process.env.EXPO_PUBLIC_MAINNET_RPC_URL, + NEXT_PUBLIC_FACTORY_CONTRACT_ID_MAINNET: + process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID_MAINNET || process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID_MAINNET, + NEXT_PUBLIC_NETWORK: process.env.NEXT_PUBLIC_NETWORK || process.env.EXPO_PUBLIC_NETWORK, + + EXPO_PUBLIC_HORIZON_URL: process.env.EXPO_PUBLIC_HORIZON_URL || process.env.NEXT_PUBLIC_HORIZON_URL, + EXPO_PUBLIC_SOROBAN_RPC_URL: process.env.EXPO_PUBLIC_SOROBAN_RPC_URL || process.env.NEXT_PUBLIC_SOROBAN_RPC_URL, + EXPO_PUBLIC_RPC_URL: process.env.EXPO_PUBLIC_RPC_URL || process.env.NEXT_PUBLIC_RPC_URL, + EXPO_PUBLIC_FACTORY_CONTRACT_ID_TESTNET: + process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID_TESTNET || process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID_TESTNET, + EXPO_PUBLIC_FACTORY_CONTRACT_ID: + process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID || process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID, + EXPO_PUBLIC_MAINNET_RPC_URL: process.env.EXPO_PUBLIC_MAINNET_RPC_URL || process.env.NEXT_PUBLIC_MAINNET_RPC_URL, + EXPO_PUBLIC_FACTORY_CONTRACT_ID_MAINNET: + process.env.EXPO_PUBLIC_FACTORY_CONTRACT_ID_MAINNET || process.env.NEXT_PUBLIC_FACTORY_CONTRACT_ID_MAINNET, + EXPO_PUBLIC_NETWORK: process.env.EXPO_PUBLIC_NETWORK || process.env.NEXT_PUBLIC_NETWORK, + }, +}); diff --git a/frontend/mobile/lib/network.ts b/frontend/mobile/lib/network.ts new file mode 100644 index 00000000..8cb85da9 --- /dev/null +++ b/frontend/mobile/lib/network.ts @@ -0,0 +1,74 @@ +import Constants from 'expo-constants' +import { Asset, Networks } from '@stellar/stellar-sdk' +import type { WalletConfig } from '@veil/sdk' + +export type VeilNetworkName = 'testnet' | 'mainnet' + +export type VeilNetwork = { + name: VeilNetworkName + displayName: string + networkPassphrase: string + horizonUrl: string + rpcUrl: string + factoryContractId: string + friendbotUrl: string | null +} + +const extra = Constants.expoConfig?.extra || (Constants as any).manifest?.extra || {} + +function getEnvVar(key: string, altKey?: string): string | undefined { + const val = extra[key] ?? (altKey ? extra[altKey] : undefined) + return typeof val === 'string' ? val : undefined +} + +export const NETWORKS: Record = { + testnet: { + name: 'testnet', + displayName: 'Stellar Testnet', + networkPassphrase: Networks.TESTNET, + horizonUrl: getEnvVar('NEXT_PUBLIC_HORIZON_URL', 'EXPO_PUBLIC_HORIZON_URL')?.trim() || 'https://horizon-testnet.stellar.org', + rpcUrl: + getEnvVar('NEXT_PUBLIC_SOROBAN_RPC_URL', 'EXPO_PUBLIC_SOROBAN_RPC_URL')?.trim() + || getEnvVar('NEXT_PUBLIC_RPC_URL', 'EXPO_PUBLIC_RPC_URL')?.trim() + || 'https://soroban-testnet.stellar.org', + factoryContractId: + getEnvVar('NEXT_PUBLIC_FACTORY_CONTRACT_ID_TESTNET', 'EXPO_PUBLIC_FACTORY_CONTRACT_ID_TESTNET')?.trim() + || getEnvVar('NEXT_PUBLIC_FACTORY_CONTRACT_ID', 'EXPO_PUBLIC_FACTORY_CONTRACT_ID')?.trim() + || '', + friendbotUrl: 'https://friendbot.stellar.org', + }, + mainnet: { + name: 'mainnet', + displayName: 'Stellar Mainnet', + networkPassphrase: Networks.PUBLIC, + horizonUrl: 'https://horizon.stellar.org', + rpcUrl: getEnvVar('NEXT_PUBLIC_MAINNET_RPC_URL', 'EXPO_PUBLIC_MAINNET_RPC_URL')?.trim() || '', + factoryContractId: getEnvVar('NEXT_PUBLIC_FACTORY_CONTRACT_ID_MAINNET', 'EXPO_PUBLIC_FACTORY_CONTRACT_ID_MAINNET')?.trim() || '', + friendbotUrl: null, + }, +} + +export function getNetwork(): VeilNetwork { + return getEnvVar('NEXT_PUBLIC_NETWORK', 'EXPO_PUBLIC_NETWORK') === 'mainnet' + ? NETWORKS.mainnet + : NETWORKS.testnet +} + +export const walletConfig: WalletConfig = { + factoryAddress: getNetwork().factoryContractId, + rpcUrl: getNetwork().rpcUrl, + networkPassphrase: getNetwork().networkPassphrase, +} + +export function getNativeAssetContractId(): string { + return Asset.native().contractId(getNetwork().networkPassphrase) +} + +export function buildFriendbotUrl(address: string): string | null { + const friendbotUrl = getNetwork().friendbotUrl + if (!friendbotUrl) return null + + const url = new URL(friendbotUrl) + url.searchParams.set('addr', address) + return url.toString() +} diff --git a/frontend/mobile/tests/network.test.ts b/frontend/mobile/tests/network.test.ts new file mode 100644 index 00000000..03de3b12 --- /dev/null +++ b/frontend/mobile/tests/network.test.ts @@ -0,0 +1,46 @@ +import assert from 'node:assert' +import { + NETWORKS, + getNetwork, + walletConfig, + getNativeAssetContractId, + buildFriendbotUrl, +} from '../lib/network' + +function testNetworkDefaults() { + const net = getNetwork() + assert.strictEqual(net.name, 'testnet') + assert.strictEqual(net.displayName, 'Stellar Testnet') + assert.strictEqual(net.horizonUrl, 'https://horizon-testnet.stellar.org') + assert.strictEqual(net.rpcUrl, 'https://soroban-testnet.stellar.org') + assert.strictEqual(net.friendbotUrl, 'https://friendbot.stellar.org') +} + +function testWalletConfig() { + assert.notStrictEqual(walletConfig.factoryAddress, undefined) + assert.strictEqual(walletConfig.rpcUrl, 'https://soroban-testnet.stellar.org') + assert.strictEqual(walletConfig.networkPassphrase, NETWORKS.testnet.networkPassphrase) +} + +function testGetNativeAssetContractId() { + const contractId = getNativeAssetContractId() + assert.strictEqual(typeof contractId, 'string') + assert.ok(contractId.length > 0) +} + +function testBuildFriendbotUrl() { + const testAddr = 'GD5DJ67Z7M4Y' + const url = buildFriendbotUrl(testAddr) + assert.strictEqual(url, `https://friendbot.stellar.org/?addr=${encodeURIComponent(testAddr)}`) + assert.strictEqual(NETWORKS.mainnet.friendbotUrl, null) +} + +function runAllTests() { + testNetworkDefaults() + testWalletConfig() + testGetNativeAssetContractId() + testBuildFriendbotUrl() + console.log('All mobile network tests passed successfully!') +} + +runAllTests() diff --git a/frontend/mobile/tsconfig.json b/frontend/mobile/tsconfig.json index f8a01f17..a8602e1a 100644 --- a/frontend/mobile/tsconfig.json +++ b/frontend/mobile/tsconfig.json @@ -2,13 +2,20 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, + "ignoreDeprecations": "6.0", + "baseUrl": ".", "paths": { "@/*": [ "./app/*" ], "@/assets/*": [ "./assets/*" - ] + ], + "@veil/sdk": [ + "../../sdk/src/useInvisibleWallet" + ], + "react": ["./node_modules/@types/react"], + "@stellar/stellar-sdk": ["./node_modules/@stellar/stellar-sdk"] } }, "include": [