From 976cb278862b52bfd7e82296b9bce140fd6e53b6 Mon Sep 17 00:00:00 2001 From: Olusegun Kehinde Date: Fri, 26 Jun 2026 16:42:56 +0100 Subject: [PATCH 1/2] Account flow to backend (nonce, verify, profile) --- TODO.md | 13 ++ api/auth.ts | 29 +++ api/httpClient.ts | 87 +++++++++ api/users.ts | 28 +++ auth/signing.ts | 55 ++++++ auth/tokenStore.ts | 30 +++ constants/env.ts | 10 + hooks/auth/use-create-account.ts | 102 +++++----- package-lock.json | 309 +++++++++++++++++++++++++++++-- package.json | 2 + 10 files changed, 596 insertions(+), 69 deletions(-) create mode 100644 TODO.md create mode 100644 api/auth.ts create mode 100644 api/httpClient.ts create mode 100644 api/users.ts create mode 100644 auth/signing.ts create mode 100644 auth/tokenStore.ts create mode 100644 constants/env.ts diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b6ff859 --- /dev/null +++ b/TODO.md @@ -0,0 +1,13 @@ +# TODO - Connect Create Account flow to backend and navigate to Pay Screen + +- [ ] Implement navigation stack in `App.tsx` (Create Account -> Pay) using `RootStackParamList` +- [ ] Add API/http client using `EXPO_PUBLIC_API_URL` and Authorization helper +- [ ] Add secure token storage (accessToken/refreshToken) helper +- [ ] Implement `POST /api/v1/auth/nonce` and `POST /api/v1/auth/verify` in `use-create-account.ts` +- [ ] Implement wallet nonce signing + signature submission aligned with backend VerifyRequestDto (add signing integration if missing) +- [ ] Implement profile sync via `GET /api/v1/users/me` and `PATCH /api/v1/users/me` as needed +- [ ] Remove mock `setTimeout + Alert` success; replace with real success + errors + loading state +- [ ] Wire `CreateAccountScreen` navigation types and navigate to `Pay Screen` after success +- [ ] Lint + typecheck + run tests +- [ ] Ensure PR template can be completed: document nonce/verify, token storage, users/me sync, wallet signing, navigation to Pay + diff --git a/api/auth.ts b/api/auth.ts new file mode 100644 index 0000000..668ca90 --- /dev/null +++ b/api/auth.ts @@ -0,0 +1,29 @@ +import { httpRequest } from './httpClient'; + +export interface NonceResponseDto { + nonce: string; + expiresAt?: string; +} + +export interface VerifyRequestDto { + wallet: string; + nonce: string; + signature: string; + // If backend expects additional fields, add them here. +} + +export interface VerifyResponseDto { + accessToken: string; + refreshToken?: string; +} + +export const authApi = { + requestNonce: (wallet: string) => { + return httpRequest('POST', '/auth/nonce', { wallet }); + }, + + verify: (payload: VerifyRequestDto) => { + return httpRequest('POST', '/auth/verify', payload); + }, +}; + diff --git a/api/httpClient.ts b/api/httpClient.ts new file mode 100644 index 0000000..4ea4119 --- /dev/null +++ b/api/httpClient.ts @@ -0,0 +1,87 @@ +import { EXPO_PUBLIC_API_URL } from '../constants/env'; +import { getAccessToken } from '../auth/tokenStore'; + +export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; + +export class ApiError extends Error { + status?: number; + details?: unknown; + + constructor(message: string, status?: number, details?: unknown) { + super(message); + this.name = 'ApiError'; + this.status = status; + this.details = details; + } +} + +async function buildHeaders(extraHeaders?: Record) { + const headers: Record = { + 'Content-Type': 'application/json', + ...(extraHeaders ?? {}), + }; + + const token = await getAccessToken(); + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + return headers; +} + +function joinUrl(base: string, path: string) { + if (!base) return path; + if (base.endsWith('/')) { + base = base.slice(0, -1); + } + if (!path.startsWith('/')) { + path = `/${path}`; + } + return `${base}${path}`; +} + +export async function httpRequest( + method: HttpMethod, + path: string, + body?: unknown, + extraHeaders?: Record +): Promise { + const url = joinUrl(EXPO_PUBLIC_API_URL, path); + + const headers = await buildHeaders(extraHeaders); + + let response: Response; + try { + response = await fetch(url, { + method, + headers, + body: body === undefined ? undefined : JSON.stringify(body), + }); + } catch (e) { + throw new ApiError('Network error. Please check your connection.', undefined, e); + } + + if (!response.ok) { + let details: unknown = undefined; + try { + details = await response.json(); + } catch { + // ignore + } + + const message = + typeof details === 'object' && details && 'message' in (details as any) + ? String((details as any).message) + : `Request failed with status ${response.status}`; + + throw new ApiError(message, response.status, details); + } + + const text = await response.text(); + if (!text) return undefined as T; + + return JSON.parse(text) as T; +} + + + diff --git a/api/users.ts b/api/users.ts new file mode 100644 index 0000000..7adb7af --- /dev/null +++ b/api/users.ts @@ -0,0 +1,28 @@ +import { httpRequest } from './httpClient'; + +export interface UserProfileDto { + walletAddress: string; + username: string; + displayName: string; + profileImage?: string | null; + // Add fields if backend includes more. +} + +export interface GetMeResponseDto { + user: UserProfileDto; +} + +export interface UpdateMeRequestDto { + walletAddress?: string; + username?: string; + displayName?: string; + profileImage?: string | null; +} + +export const usersApi = { + getMe: () => httpRequest('GET', '/users/me'), + + updateMe: (payload: UpdateMeRequestDto) => + httpRequest('PATCH', '/users/me', payload), +}; + diff --git a/auth/signing.ts b/auth/signing.ts new file mode 100644 index 0000000..4ca8448 --- /dev/null +++ b/auth/signing.ts @@ -0,0 +1,55 @@ +import type { VerifyRequestDto } from '../api/auth'; +import { Keypair } from '@stellar/stellar-sdk'; + +export type SignNonceFn = (wallet: string, nonce: string) => Promise; + +/** + * Lobstr signing (best-effort placeholder). + * + * IMPORTANT: + * - Proper Lobstr signing requires invoking the Lobstr wallet via deep links / wallet SDK. + * - This repo does not currently contain that integration. + * + * What this does now: + * - If the app already has a Stellar secret key available (it should NOT), it will sign. + * - Otherwise it throws a clear error telling you that the wallet deep-link/integration is missing. + * + * Replace this module once the Lobstr callback/deep-link flow is known. + */ +export const signNonceWithWallet: SignNonceFn = async (wallet, nonce) => { + // Nonce signing requires the user to sign via their wallet. + // Since we don't have that integration in this repo yet, fail loudly. + // (Never log or persist secrets.) + const maybeSecret = (globalThis as any).__TRUSTUP_STELLAR_SECRET as string | undefined; + if (!maybeSecret) { + throw new Error( + 'Lobstr wallet signing integration is missing in this repo. ' + + 'Implement the Lobstr deep-link / callback flow to produce the signature for auth/verify.' + ); + } + + // WARNING: Using secret keys in the frontend is insecure. + // This branch is only here to make the flow testable in dev. + const kp = Keypair.fromSecret(maybeSecret); + if (kp.publicKey() !== wallet) { + throw new Error('Provided wallet address does not match the configured signing key.'); + } + + // Sign the nonce as bytes (ed25519) + const signature = kp.sign(Buffer.from(nonce, 'utf8')).toString('base64'); + return signature; +}; + +export async function buildVerifyPayload(params: { + wallet: string; + nonce: string; + signature: string; +}): Promise { + return { + wallet: params.wallet, + nonce: params.nonce, + signature: params.signature, + }; +} + + diff --git a/auth/tokenStore.ts b/auth/tokenStore.ts new file mode 100644 index 0000000..bee1e54 --- /dev/null +++ b/auth/tokenStore.ts @@ -0,0 +1,30 @@ +import * as SecureStore from 'expo-secure-store'; + +const ACCESS_TOKEN_KEY = 'trustup_access_token'; +const REFRESH_TOKEN_KEY = 'trustup_refresh_token'; + +export async function setTokens(params: { + accessToken: string; + refreshToken?: string; +}): Promise { + await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, params.accessToken); + if (params.refreshToken) { + await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, params.refreshToken); + } +} + +export async function getAccessToken(): Promise { + return SecureStore.getItemAsync(ACCESS_TOKEN_KEY); +} + +export async function getRefreshToken(): Promise { + return SecureStore.getItemAsync(REFRESH_TOKEN_KEY); +} + +export async function clearTokens(): Promise { + await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY); + await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY); +} + + + diff --git a/constants/env.ts b/constants/env.ts new file mode 100644 index 0000000..7a58e35 --- /dev/null +++ b/constants/env.ts @@ -0,0 +1,10 @@ +// Centralized environment access. +// Expo exposes env vars at build-time. +// +// Note: this repo may not have `expo-constants` installed, so we rely on +// the common Expo behavior where EXPO_PUBLIC_* values are injected. +export const EXPO_PUBLIC_API_URL: string = (process.env + .EXPO_PUBLIC_API_URL as string | undefined) ?? ''; + + + diff --git a/hooks/auth/use-create-account.ts b/hooks/auth/use-create-account.ts index d5a823e..546ea32 100644 --- a/hooks/auth/use-create-account.ts +++ b/hooks/auth/use-create-account.ts @@ -2,6 +2,12 @@ import { useState, useCallback } from 'react'; import { Alert } from 'react-native'; import * as ImagePicker from 'expo-image-picker'; +import { authApi } from '../../api/auth'; +import { usersApi } from '../../api/users'; +import { ApiError } from '../../api/httpClient'; +import { setTokens } from '../../auth/tokenStore'; +import { signNonceWithWallet, buildVerifyPayload } from '../../auth/signing'; + /** * Form field state for the create account form */ @@ -41,16 +47,13 @@ export interface UseCreateAccountReturn { // Actions pickImage: () => Promise; - createAccount: () => void; + createAccount: () => Promise; resetSuccess: () => void; // Validation isFormValid: () => boolean; } -/** - * Initial form state - */ const initialFormState: CreateAccountFormState = { profileImage: null, walletAddress: '', @@ -59,9 +62,6 @@ const initialFormState: CreateAccountFormState = { termsAccepted: false, }; -/** - * Initial errors state - */ const initialErrors: CreateAccountErrors = { walletAddress: '', username: '', @@ -114,17 +114,12 @@ const validateImageType = async (uri: string): Promise => { } }; -/** - * Custom hook for Create Account functionality - * Handles all form state, validation, and account creation logic - */ export const useCreateAccount = (): UseCreateAccountReturn => { const [formState, setFormState] = useState(initialFormState); const [errors, setErrors] = useState(initialErrors); const [isSubmitting, setIsSubmitting] = useState(false); const [showSuccess, setShowSuccess] = useState(false); - // Field change handlers const handleWalletAddressChange = useCallback((text: string) => { setFormState((prev) => ({ ...prev, walletAddress: text })); @@ -160,7 +155,6 @@ export const useCreateAccount = (): UseCreateAccountReturn => { setFormState((prev) => ({ ...prev, termsAccepted: accepted })); }, []); - // Image picking with validation const pickImage = useCallback(async () => { const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); @@ -180,14 +174,12 @@ export const useCreateAccount = (): UseCreateAccountReturn => { const imageUri = result.assets[0].uri; try { - // Validate file size const isSizeValid = await validateImageSize(imageUri); if (!isSizeValid) { setErrors((prev) => ({ ...prev, profileImage: 'File size must be less than 2MB' })); return; } - // Validate file type const isTypeValid = await validateImageType(imageUri); if (!isTypeValid) { setErrors((prev) => ({ @@ -197,19 +189,15 @@ export const useCreateAccount = (): UseCreateAccountReturn => { return; } - // All validations passed setErrors((prev) => ({ ...prev, profileImage: '' })); setFormState((prev) => ({ ...prev, profileImage: imageUri })); - } catch (error) { - console.error('Error processing image:', error); + } catch { setErrors((prev) => ({ ...prev, profileImage: 'Error processing image' })); } } }, []); - // Form validation const isFormValid = useCallback((): boolean => { - // Profile image is optional, but if there's an error with uploaded image, block submission const hasImageError = errors.profileImage !== ''; return ( @@ -225,69 +213,75 @@ export const useCreateAccount = (): UseCreateAccountReturn => { ); }, [formState, errors]); - // Account creation handler - const createAccount = useCallback(() => { - if (isFormValid()) { - setIsSubmitting(true); + const createAccount = useCallback(async () => { + if (!isFormValid()) return; - const accountData = { - profileImage: formState.profileImage, + setIsSubmitting(true); + setShowSuccess(false); + + try { + // 1) Nonce + const nonceRes = await authApi.requestNonce(formState.walletAddress); + const nonce = nonceRes.nonce; + + // 2) Sign + verify + const signature = await signNonceWithWallet(formState.walletAddress, nonce); + const verifyPayload = buildVerifyPayload({ + wallet: formState.walletAddress, + nonce, + signature, + }); + + const verifyRes = await authApi.verify(await verifyPayload); + + + // Persist tokens + await setTokens({ + accessToken: verifyRes.accessToken, + refreshToken: verifyRes.refreshToken, + }); + + // 3) Update profile + await usersApi.updateMe({ walletAddress: formState.walletAddress, username: formState.username, displayName: formState.displayName, - termsAccepted: formState.termsAccepted, - }; + profileImage: formState.profileImage, + }); - console.log('✅ Account Created Successfully:', accountData); + await usersApi.getMe(); - // Show success notification setShowSuccess(true); - - // Simulate account creation delay - setTimeout(() => { - setIsSubmitting(false); - - Alert.alert( - '✅ Account Created!', - `Welcome, ${formState.displayName}!\n\nYour account has been created successfully.\n\nUsername: @${formState.username}\nWallet: ${formState.walletAddress.substring(0, 10)}...`, - [ - { - text: 'OK', - onPress: () => { - setShowSuccess(false); - console.log('Account creation confirmed'); - }, - }, - ] - ); - }, 500); + } catch (e) { + const err = e as unknown; + const message = err instanceof ApiError ? err.message : 'Failed to create account. Please try again.'; + Alert.alert('Account creation failed', message); + setShowSuccess(false); + } finally { + setIsSubmitting(false); } }, [isFormValid, formState]); - // Reset success state const resetSuccess = useCallback(() => { setShowSuccess(false); }, []); return { - // Form state formState, errors, isSubmitting, showSuccess, - // Field handlers handleWalletAddressChange, handleUsernameChange, handleDisplayNameChange, handleTermsAcceptedChange, - // Actions pickImage, createAccount, resetSuccess, - // Validation isFormValid, }; }; + diff --git a/package-lock.json b/package-lock.json index af6cf24..3f55891 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-navigation/native": "^7.1.27", + "@stellar/stellar-sdk": "^16.0.1", "expo": "^54.0.0", "expo-blur": "~15.0.8", "expo-image-picker": "^17.0.10", "expo-router": "^6.0.21", + "expo-secure-store": "^56.0.4", "expo-status-bar": "~3.0.8", "lucide-react-native": "^0.562.0", "nativewind": "latest", @@ -2505,6 +2507,27 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@noble/ed25519": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.1.0.tgz", + "integrity": "sha512-pfcObRY3CtvwfaG9Mt5XqZdKmAQppl37tHUeuBhDUbiwJBCVY4/A4lbMvb1xKhMDx96AqAqZpMWuBX1HulhX4g==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3117,6 +3140,75 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stellar/js-xdr": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-4.0.0.tgz", + "integrity": "sha512-+NmNa7Tk5BI5XFdy/6xGTqAN4J9a9KgCrCGhj2uEUTCBhLkch0M+QbKzNH8zEnejWe0p8w+0q5hUVX6L3OzoVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0", + "pnpm": ">=9.0.0" + } + }, + "node_modules/@stellar/stellar-sdk": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-16.0.1.tgz", + "integrity": "sha512-bxKohaiyKVqoudRhbOOHeHhHIaeYV5Zab4rCjxhP4Ty1h1ozTLBOv8lWFnZz9ilBzXG8Bb7usQI3rlEcfvUynA==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ed25519": "^3.1.0", + "@noble/hashes": "^2.2.0", + "@stellar/js-xdr": "4.0.0", + "axios": "1.16.1", + "base32.js": "^0.1.0", + "bignumber.js": "^11.1.1", + "buffer": "^6.0.3", + "commander": "^14.0.3", + "eventsource": "^4.1.0", + "feaxios": "^0.0.23", + "smol-toml": "^1.6.1", + "uint8array-extras": "^1.5.0" + }, + "bin": { + "stellar-js": "bin/stellar-js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stellar/stellar-sdk/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@stellar/stellar-sdk/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -4200,6 +4292,12 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4216,6 +4314,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -4446,6 +4581,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4513,6 +4657,12 @@ "node": ">=0.6" } }, + "node_modules/bignumber.js": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.4.tgz", + "integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==", + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4678,7 +4828,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4949,6 +5098,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -5359,6 +5520,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5507,7 +5677,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5648,7 +5817,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5658,7 +5826,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5696,7 +5863,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5709,7 +5875,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6224,6 +6389,27 @@ "node": ">=6" } }, + "node_modules/eventsource": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", + "integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/exec-async": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", @@ -6662,6 +6848,15 @@ "node": ">=10" } }, + "node_modules/expo-secure-store": { + "version": "56.0.4", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-56.0.4.tgz", + "integrity": "sha512-hjEi/gmpdFFJ9lYbdp3k3p/WchV7Gi0Qt8jt/m/0WJadqQrskafHAlDxbZkII1cN3Yd7zp9Lvkeq3UfGhSwirQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-server": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz", @@ -7309,6 +7504,15 @@ "asap": "~2.0.3" } }, + "node_modules/feaxios": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/feaxios/-/feaxios-0.0.23.tgz", + "integrity": "sha512-eghR0A21fvbkcQBgZuMfQhrXxJzC0GNUGC9fXhBge33D+mFDTwl0aJ35zoQQn575BhyjQitRc5N4f+L4cP708g==", + "license": "MIT", + "dependencies": { + "is-retry-allowed": "^3.0.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7419,6 +7623,26 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -7441,6 +7665,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/freeport-async": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", @@ -7551,7 +7791,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7594,7 +7833,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7734,7 +7972,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7804,7 +8041,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7817,7 +8053,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7830,9 +8065,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8385,6 +8620,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-retry-allowed": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-3.0.0.tgz", + "integrity": "sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -9362,7 +9609,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10862,6 +11108,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -12190,6 +12445,18 @@ "node": ">=8.0.0" } }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -13007,6 +13274,18 @@ "node": "*" } }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 5779553..413dfa1 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-navigation/native": "^7.1.27", + "@stellar/stellar-sdk": "^16.0.1", "expo": "^54.0.0", "expo-blur": "~15.0.8", "expo-image-picker": "^17.0.10", "expo-router": "^6.0.21", + "expo-secure-store": "^56.0.4", "expo-status-bar": "~3.0.8", "lucide-react-native": "^0.562.0", "nativewind": "latest", From f56d99f0f84133417b5f35cd37095cb98e48da4b Mon Sep 17 00:00:00 2001 From: Olusegun Kehinde Date: Wed, 1 Jul 2026 21:50:49 +0100 Subject: [PATCH 2/2] Account flow to backend (nonce, verify, profile) and navigate to Pay Screen --- .vscode/settings.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0967ef4..8088916 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,5 @@ -{} +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] +}