From d07daef8cf7a2bfb77ea8e266c1698ff4ae5e00b Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Fri, 26 Jun 2026 17:53:10 +0300 Subject: [PATCH] feat(android): support API 24 setup-free --- API.md | 6 + CHANGELOG.md | 11 + README.md | 12 +- android/build.gradle | 10 +- .../OmsClientReactNativeSdkModule.kt | 4 +- examples/expo-example/app.json | 10 +- examples/expo-example/package-lock.json | 35 +- examples/expo-example/package.json | 3 +- examples/expo-example/src/App.tsx | 431 ++++++++++++++++-- examples/sdk-example/android/build.gradle | 4 +- .../android/build.gradle | 4 +- package.json | 2 +- 12 files changed, 427 insertions(+), 105 deletions(-) diff --git a/API.md b/API.md index 8c80f6d..d71a57e 100644 --- a/API.md +++ b/API.md @@ -9,6 +9,12 @@ This document describes the public TypeScript API for npm install @0xsequence/oms-react-native-sdk ``` +## Native Requirements + +Android apps need `minSdk` 24 or newer, `compileSdk` 34 or newer, and Java 17 +compile options. The SDK does not require app-level core library desugaring or a +custom Kotlin version override. + ## Client ```ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 416cbc1..86228a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [0.1.0-alpha.4] — 2026-06-26 + +### Changed +- Lowered the Android OMS SDK requirement to `minSdk 24`. +- Aligned Android Kotlin, coroutine, and serialization versions with current + React Native and Expo defaults. + +### Fixed +- Removed the need for Expo and bare React Native apps to raise Android + `minSdk`, override Kotlin, or enable core library desugaring for OMS. + ## [0.1.0-alpha.3] — 2026-06-26 ### Added diff --git a/README.md b/README.md index d954eef..460b291 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ See [API.md](./API.md) for the public API surface and TypeScript shapes. ## Native SDK Dependencies The React Native SDK owns its native SDK dependencies. Android resolves -`io.github.0xsequence:oms-client-kotlin-sdk:0.1.0-alpha.3` from Maven, and iOS +`io.github.0xsequence:oms-client-kotlin-sdk:0.1.0-alpha.4` from Maven, and iOS resolves `oms-client-swift-sdk` `0.1.0-alpha.3` from CocoaPods. The React Native wrapper itself is distributed through npm. React Native @@ -159,8 +159,9 @@ on the underlying native SDKs. - Bare React Native apps are supported through normal React Native autolinking. - Expo apps must use a development build, Expo prebuild/EAS Build, or the bare workflow. Expo Go cannot load this SDK because it includes custom native code. -- Android apps need `minSdk 26`, `compileSdk 34` or newer, and Java 17 compile - options. +- Android apps need `minSdk 24`, `compileSdk 34` or newer, and Java 17 compile + options. No app-level core library desugaring or Kotlin version override is + required for OMS. - iOS apps need deployment target 15.0 or newer. - OIDC redirect auth requires the consuming app to configure its own URL scheme or app links. @@ -171,9 +172,8 @@ on the underlying native SDKs. - `examples/trails-actions-example` is the bare React Native demo for OMS wallet flow with Trails action resolution. - `examples/expo-example` is a standalone Expo development-build demo that uses - `expo-web-browser` and the published npm package. It is intentionally - excluded from the root Yarn workspace so it is not linked to the local SDK - source. Its dependency is not updated until this SDK version is published. + `expo-web-browser` and the npm package. It is intentionally excluded from the + root Yarn workspace so it is not linked to the local SDK source. ## Publishing diff --git a/android/build.gradle b/android/build.gradle index da3664c..360af7c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { ext.OmsClientReactNativeSdk = [ - kotlinVersion: "2.3.20", - minSdkVersion: 26, + kotlinVersion: "2.1.20", + minSdkVersion: 24, compileSdkVersion: 36, targetSdkVersion: 36, - omsClientKotlinSdkVersion: "0.1.0-alpha.3", - kotlinxCoroutinesVersion: "1.11.0", - kotlinxSerializationJsonVersion: "1.11.0" + omsClientKotlinSdkVersion: "0.1.0-alpha.4", + kotlinxCoroutinesVersion: "1.10.2", + kotlinxSerializationJsonVersion: "1.8.1" ] ext.getExtOrDefault = { prop -> diff --git a/android/src/main/java/com/omsclientreactnativesdk/OmsClientReactNativeSdkModule.kt b/android/src/main/java/com/omsclientreactnativesdk/OmsClientReactNativeSdkModule.kt index 3c21572..1183b61 100644 --- a/android/src/main/java/com/omsclientreactnativesdk/OmsClientReactNativeSdkModule.kt +++ b/android/src/main/java/com/omsclientreactnativesdk/OmsClientReactNativeSdkModule.kt @@ -98,7 +98,7 @@ class OmsClientReactNativeSdkModule(reactContext: ReactApplicationContext) : Arguments.createMap().apply { putString("clientId", clientId) putMap("session", sessionMap(event.session)) - putString("expiredAt", event.expiredAt.toString()) + putString("expiredAt", event.expiredAt) } ) } @@ -707,7 +707,7 @@ class OmsClientReactNativeSdkModule(reactContext: ReactApplicationContext) : private fun sessionMap(session: OMSClientSessionState?): WritableMap = Arguments.createMap().apply { putNullableString("walletAddress", session?.walletAddress) - putNullableString("expiresAt", session?.expiresAt?.toString()) + putNullableString("expiresAt", session?.expiresAt) putNullableString("loginType", session?.loginType?.name) putNullableString("sessionEmail", session?.sessionEmail) } diff --git a/examples/expo-example/app.json b/examples/expo-example/app.json index 6b7e2ed..440befb 100644 --- a/examples/expo-example/app.json +++ b/examples/expo-example/app.json @@ -14,15 +14,7 @@ "package": "com.sequence.oms.expoexample" }, "plugins": [ - "expo-web-browser", - [ - "expo-build-properties", - { - "android": { - "minSdkVersion": 26 - } - } - ] + "expo-web-browser" ] } } diff --git a/examples/expo-example/package-lock.json b/examples/expo-example/package-lock.json index 588b11e..02a5f37 100644 --- a/examples/expo-example/package-lock.json +++ b/examples/expo-example/package-lock.json @@ -8,9 +8,8 @@ "name": "oms-client-react-native-sdk-expo-example", "version": "0.0.1", "dependencies": { - "@0xsequence/oms-react-native-sdk": "0.1.0-alpha.2", + "@0xsequence/oms-react-native-sdk": "0.1.0-alpha.4", "expo": "~56.0.8", - "expo-build-properties": "~56.0.17", "expo-dev-client": "~56.0.19", "expo-web-browser": "~56.0.5", "react": "19.2.7", @@ -26,9 +25,9 @@ } }, "node_modules/@0xsequence/oms-react-native-sdk": { - "version": "0.1.0-alpha.2", - "resolved": "https://registry.npmjs.org/@0xsequence/oms-react-native-sdk/-/oms-react-native-sdk-0.1.0-alpha.2.tgz", - "integrity": "sha512-OWX/ibaIEd2D9NmINnaqxkIH9uwZjpVHRKAbEcDQvTpxvi1tPcRD6KwvSEMl+6cwB7z+qXxqgb24Pyxfl33sww==", + "version": "0.1.0-alpha.4", + "resolved": "https://registry.npmjs.org/@0xsequence/oms-react-native-sdk/-/oms-react-native-sdk-0.1.0-alpha.4.tgz", + "integrity": "sha512-V7ayhhFroKBOwps/sdFEX9+KqzfpM38DI0lgd5PF0YQHqvM5kiMGofGv2WHzPkbYi4vT1nf0/tgZNBhbHEmjDQ==", "license": "MIT", "workspaces": [ "examples/*", @@ -2749,32 +2748,6 @@ } } }, - "node_modules/expo-build-properties": { - "version": "56.0.17", - "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-56.0.17.tgz", - "integrity": "sha512-qt12qdaxV4FEeFH+X4tM4uoeKNytqJahpmb74VVV/cSKX47RzBRtCyEIAW+tgI6BUTZjnDtBZj0PTyBdlZbB6Q==", - "license": "MIT", - "dependencies": { - "@expo/schema-utils": "^56.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.6.0" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-build-properties/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/expo-dev-client": { "version": "56.0.19", "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-56.0.19.tgz", diff --git a/examples/expo-example/package.json b/examples/expo-example/package.json index 40c13f4..24c2e72 100644 --- a/examples/expo-example/package.json +++ b/examples/expo-example/package.json @@ -12,9 +12,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@0xsequence/oms-react-native-sdk": "0.1.0-alpha.2", + "@0xsequence/oms-react-native-sdk": "0.1.0-alpha.4", "expo": "~56.0.8", - "expo-build-properties": "~56.0.17", "expo-dev-client": "~56.0.19", "expo-web-browser": "~56.0.5", "react": "19.2.7", diff --git a/examples/expo-example/src/App.tsx b/examples/expo-example/src/App.tsx index 82bc8d8..a8aa648 100644 --- a/examples/expo-example/src/App.tsx +++ b/examples/expo-example/src/App.tsx @@ -19,19 +19,12 @@ import { View, } from 'react-native'; import { - completeEmailAuth, - configure, - getSupportedNetworks, - getSession, - sendTransaction, - handleOidcRedirectCallback, + OMSClient, OidcProviders, - signMessage, - signOut, - startEmailAuth, - startOidcRedirectAuth, - verifyMessageSignature, + type OmsClientSessionExpiredEvent, type OmsClientSessionState, + type OmsFeeOptionSelection, + type OmsFeeOptionWithBalance, type OmsNetwork, type OmsPendingWalletSelection, type OmsWallet, @@ -40,16 +33,13 @@ import { WebBrowser.maybeCompleteAuthSession(); -const DEMO_PUBLISHABLE_KEY = 'AQAAAAAAAAK2JvvZhWqZ51riasWBftkrVXE'; -const DEMO_PROJECT_ID = 'proj_014kg56dc0a75'; +const DEMO_PUBLISHABLE_KEY = + 'pk_dev_sdbx_01kqa06hyyetj_01kv5ceg4xefattzmm9fyx04ev'; const DEMO_OIDC_REDIRECT_URI = 'omsclientrndemo://auth/callback'; -const DEMO_ENVIRONMENT = { - apiRpcUrl: 'https://dev-api.sequence.app/rpc/API', - indexerUrlTemplate: 'https://dev-{value}-indexer.sequence.app/rpc/Indexer/', -}; const DEFAULT_TRANSACTION_TO = '0xE5E8B483FfC05967FcFed58cc98D053265af6D99'; const PREFERRED_NETWORK_ORDER = ['80002', '137']; +const DEFAULT_SESSION_LIFETIME_SECONDS = '604800'; const SIGNED_OUT_SESSION: OmsClientSessionState = { walletAddress: null, expiresAt: null, @@ -239,6 +229,77 @@ function WalletSelectionOption({ ); } +function FeeOptionPickerModal({ + options, + visible, + onCancel, + onSelect, +}: { + options: OmsFeeOptionWithBalance[]; + visible: boolean; + onCancel: () => void; + onSelect: (selection: OmsFeeOptionSelection) => void; +}) { + return ( + + + + + Fee Option + + + + `${option.selection.token}-${index}` + } + renderItem={({ item }) => { + const selectable = feeOptionIsSelectable(item); + return ( + onSelect(item.selection)} + style={({ pressed }) => [ + styles.feeOption, + !selectable && styles.buttonDisabled, + pressed && selectable && styles.buttonPressed, + ]} + > + + + {feeOptionTitle(item)} + + + {feeOptionSubtitle(item)} + + + {item.selection.token} + + + + {selectable ? 'Select' : 'Insufficient'} + + + ); + }} + style={styles.networkPickerList} + /> + + + + ); +} + function NetworkPickerModal({ networks, selectedChainId, @@ -304,6 +365,10 @@ function NetworkPickerModal({ } export default function App() { + const oms = useMemo( + () => new OMSClient({ publishableKey: DEMO_PUBLISHABLE_KEY }), + [] + ); const [networks, setNetworks] = useState([]); const [selectedChainId, setSelectedChainId] = useState('80002'); const [sdkReady, setSdkReady] = useState(false); @@ -314,6 +379,11 @@ export default function App() { const [code, setCode] = useState(''); const [authStatus, setAuthStatus] = useState('Waiting for sign-in.'); const [manualWalletSelection, setManualWalletSelection] = useState(false); + const [sessionLifetimeSeconds, setSessionLifetimeSeconds] = useState( + DEFAULT_SESSION_LIFETIME_SECONDS + ); + const [expiredSessionEvent, setExpiredSessionEvent] = + useState(null); const [pendingWalletSelection, setPendingWalletSelection] = useState(null); const [message, setMessage] = useState('test'); @@ -335,8 +405,14 @@ export default function App() { const [networkPickerVisible, setNetworkPickerVisible] = useState(false); const [logLines, setLogLines] = useState(['Ready.']); const [loadingAction, setLoadingAction] = useState(null); + const [feeOptionPickerOptions, setFeeOptionPickerOptions] = useState< + OmsFeeOptionWithBalance[] + >([]); const handledRedirectUrlsRef = useRef(new Set()); const handlingRedirectUrlRef = useRef(null); + const feeOptionSelectionResolverRef = useRef< + ((selection: OmsFeeOptionSelection | null) => void) | null + >(null); const selectedNetwork = useMemo( () => @@ -349,16 +425,67 @@ export default function App() { setLogLines((current) => [...current, messageToAppend].slice(-80)); }, []); + const resolveFeeOptionSelection = useCallback( + (selection: OmsFeeOptionSelection | null) => { + const resolver = feeOptionSelectionResolverRef.current; + feeOptionSelectionResolverRef.current = null; + setFeeOptionPickerOptions([]); + resolver?.(selection); + }, + [] + ); + + const selectFeeOption = useCallback( + async ( + feeOptions: OmsFeeOptionWithBalance[] + ): Promise => { + if (feeOptions.length === 0) { + appendLog('No fee options available.'); + return null; + } + + feeOptionSelectionResolverRef.current?.(null); + setFeeOptionPickerOptions(feeOptions); + appendLog(`Fee options available: ${feeOptions.length}`); + + return new Promise((resolve) => { + feeOptionSelectionResolverRef.current = resolve; + }); + }, + [appendLog] + ); + + const chooseFeeOption = useCallback( + (selection: OmsFeeOptionSelection) => { + appendLog(`Selected fee option: ${selection.token}`); + resolveFeeOptionSelection(selection); + }, + [appendLog, resolveFeeOptionSelection] + ); + + const cancelFeeOptionSelection = useCallback(() => { + appendLog('Fee option selection cancelled.'); + resolveFeeOptionSelection(null); + }, [appendLog, resolveFeeOptionSelection]); + + useEffect(() => { + return () => { + feeOptionSelectionResolverRef.current?.(null); + feeOptionSelectionResolverRef.current = null; + }; + }, []); + const refreshSession = useCallback(async () => { - const nextSession = await getSession(); + const nextSession = await oms.wallet.getSession(); setSession(nextSession); if (nextSession.walletAddress) { + setExpiredSessionEvent(null); setAuthStatus('Restored persisted wallet session'); setSignatureStatus('Signature status: ready to sign.'); setTransactionStatus('Transaction status: ready to send.'); } return nextSession; - }, []); + }, [oms]); const runAction = useCallback( async ( @@ -380,10 +507,49 @@ export default function App() { [appendLog] ); + const requestedSessionLifetimeSeconds = useCallback( + () => parseSessionLifetimeSeconds(sessionLifetimeSeconds), + [sessionLifetimeSeconds] + ); + + const clearExpiredSessionState = useCallback(() => { + setExpiredSessionEvent(null); + }, []); + + const handleSessionExpired = useCallback( + (event: OmsClientSessionExpiredEvent) => { + const emailHint = expiredSessionEmail(event); + + setExpiredSessionEvent(event); + setSession(SIGNED_OUT_SESSION); + setPendingWalletSelection(null); + setCode(''); + setAuthStage('email'); + setAuthStatus( + emailHint + ? `Wallet session expired. Sign in again as ${emailHint}.` + : 'Wallet session expired. Sign in again.' + ); + if (emailHint) { + setEmail(emailHint); + } + setLastSignedMessage(null); + setLastSignature(null); + setLastTransactionHash(null); + setSignatureStatus('Signature status: waiting for reauth.'); + setTransactionStatus('Transaction status: waiting for reauth.'); + appendLog( + `Wallet session expired at ${event.expiredAt}: wallet=${event.session.walletAddress ?? 'none'} email=${event.session.sessionEmail ?? 'none'}` + ); + }, + [appendLog] + ); + const activateWallet = useCallback( async (result: OmsWalletActivationResult) => { - const nextSession = await getSession(); + const nextSession = await oms.wallet.getSession(); const address = nextSession.walletAddress ?? result.walletAddress; + clearExpiredSessionState(); setPendingWalletSelection(null); setCode(''); setAuthStage('email'); @@ -397,7 +563,7 @@ export default function App() { setTransactionStatus('Transaction status: ready to send.'); appendLog(`Wallet ready: ${address}`); }, - [appendLog] + [appendLog, clearExpiredSessionState, oms] ); const finishOidcRedirectSignIn = useCallback( @@ -410,12 +576,15 @@ export default function App() { } handlingRedirectUrlRef.current = callbackUrl; + let callbackHandled = false; try { setAuthStatus('Completing Google redirect sign-in...'); - const result = await handleOidcRedirectCallback({ + const result = await oms.wallet.handleOidcRedirectCallback({ callbackUrl, walletSelection: manualWalletSelection ? 'manual' : 'automatic', + sessionLifetimeSeconds: requestedSessionLifetimeSeconds(), }); + callbackHandled = true; switch (result.type) { case 'completed': @@ -448,11 +617,20 @@ export default function App() { break; } } finally { - handledRedirectUrlsRef.current.add(callbackUrl); + if (callbackHandled) { + handledRedirectUrlsRef.current.add(callbackUrl); + } handlingRedirectUrlRef.current = null; } }, - [activateWallet, appendLog, manualWalletSelection, refreshSession] + [ + activateWallet, + appendLog, + manualWalletSelection, + oms, + refreshSession, + requestedSessionLifetimeSeconds, + ] ); const selectNetwork = useCallback( @@ -476,13 +654,7 @@ export default function App() { async function bootstrap() { await runAction('Initializing SDK', async () => { - await configure({ - publishableKey: DEMO_PUBLISHABLE_KEY, - projectId: DEMO_PROJECT_ID, - environment: DEMO_ENVIRONMENT, - }); - - const supportedNetworks = sortNetworks(await getSupportedNetworks()); + const supportedNetworks = sortNetworks(oms.supportedNetworks); if (disposed) return; setNetworks(supportedNetworks); @@ -502,11 +674,13 @@ export default function App() { return () => { disposed = true; }; - }, [appendLog, refreshSession, runAction]); + }, [appendLog, oms, refreshSession, runAction]); useEffect(() => { if (!sdkReady) return undefined; + const sessionExpiredSubscription = + oms.wallet.onSessionExpired(handleSessionExpired); const subscription = Linking.addEventListener('url', ({ url }) => { if (isDemoOidcRedirectUrl(url)) { runAction( @@ -539,8 +713,18 @@ export default function App() { appendLog(`!! ${describeError(error)}`); }); - return () => subscription.remove(); - }, [appendLog, finishOidcRedirectSignIn, runAction, sdkReady]); + return () => { + sessionExpiredSubscription.remove(); + subscription.remove(); + }; + }, [ + appendLog, + finishOidcRedirectSignIn, + handleSessionExpired, + oms, + runAction, + sdkReady, + ]); const walletAddress = session.walletAddress; const isSignedIn = walletAddress != null; @@ -550,13 +734,18 @@ export default function App() { runAction( 'Start email sign-in', async () => { - const normalizedEmail = requireText(email, 'Email'); + const normalizedEmail = email.trim(); setAuthStatus('Requesting email code...'); setPendingWalletSelection(null); - await startEmailAuth(normalizedEmail); + const emailForSignIn = + normalizedEmail || expiredSessionEmail(expiredSessionEvent); + if (!emailForSignIn) { + throw new Error('Email is required'); + } + await oms.wallet.startEmailAuth(emailForSignIn); setEmail(''); setAuthStage('code'); - setAuthStatus(`Code requested for ${normalizedEmail}`); + setAuthStatus(`Code requested for ${emailForSignIn}`); }, (error) => { setAuthStatus(`Email sign-in failed: ${describeError(error)}`); @@ -569,9 +758,10 @@ export default function App() { 'Confirm code and resolve wallet', async () => { setAuthStatus('Confirming code and resolving wallet...'); - const authResult = await completeEmailAuth({ + const authResult = await oms.wallet.completeEmailAuth({ code: requireText(code, 'Verification code'), walletSelection: manualWalletSelection ? 'manual' : 'automatic', + sessionLifetimeSeconds: requestedSessionLifetimeSeconds(), }); if (authResult.type === 'walletSelection') { @@ -600,9 +790,11 @@ export default function App() { async () => { setPendingWalletSelection(null); setAuthStatus('Opening Google redirect sign-in...'); - const started = await startOidcRedirectAuth({ + requestedSessionLifetimeSeconds(); + const started = await oms.wallet.startOidcRedirectAuth({ provider: OidcProviders.google(), redirectUri: DEMO_OIDC_REDIRECT_URI, + loginHint: expiredSessionEmail(expiredSessionEvent), }); appendLog(`Google redirect auth started: state=${started.state}`); @@ -629,7 +821,8 @@ export default function App() { const cancelCodeStep = () => { runAction('Cancel email code step', async () => { - await signOut(); + await oms.wallet.signOut(); + clearExpiredSessionState(); setSession(SIGNED_OUT_SESSION); setCode(''); setPendingWalletSelection(null); @@ -670,7 +863,8 @@ export default function App() { const logout = () => { runAction('Logout', async () => { - await signOut(); + await oms.wallet.signOut(); + clearExpiredSessionState(); setSession(SIGNED_OUT_SESSION); setAuthStage('email'); setPendingWalletSelection(null); @@ -691,7 +885,10 @@ export default function App() { const network = requireNetwork(selectedNetwork); const nextMessage = requireText(message, 'Message'); setSignatureStatus('Signature status: signing in progress...'); - const signature = await signMessage(network.chainId, nextMessage); + const signature = await oms.wallet.signMessage( + network.chainId, + nextMessage + ); setLastSignedMessage(nextMessage); setLastSignature(signature); setSignatureStatus('Signature status: signed. Ready to verify.'); @@ -711,7 +908,7 @@ export default function App() { const signedMessage = requireText(lastSignedMessage, 'Signed message'); const signature = requireText(lastSignature, 'Signature'); setSignatureStatus('Signature status: verification in progress...'); - const isValid = await verifyMessageSignature({ + const isValid = await oms.wallet.verifyMessageSignature({ chainId: network.chainId, message: signedMessage, signature, @@ -735,10 +932,11 @@ export default function App() { async () => { const network = requireNetwork(selectedNetwork); setTransactionStatus('Transaction status: sending in progress...'); - const txResult = await sendTransaction({ + const txResult = await oms.wallet.sendTransaction({ chainId: network.chainId, to: requireText(transactionTo, 'Transaction destination'), value: decimalToBaseUnits(transactionValue, 18), + selectFeeOption, }); setLastTransactionHash(txResult.txnHash); setTransactionStatus( @@ -782,7 +980,6 @@ export default function App() { Auth Demo OMS Client React Native SDK - Expo example {authStatus} + {expiredSessionEvent ? ( + + + + + + ) : null} {pendingWalletSelection ? ( Finish sign-in by selecting a wallet below. @@ -809,6 +1030,17 @@ export default function App() { setManualWalletSelection((current) => !current) } /> + + !current) } /> + Verification Code + 0} + /> setNetworkPickerVisible(false)} @@ -1058,6 +1302,68 @@ function requireText(value: string | null, label: string): string { return trimmed; } +function parseSessionLifetimeSeconds(value: string): number | null { + const trimmed = value.trim(); + if (!trimmed) { + return null; + } + + if (!/^\d+$/.test(trimmed)) { + throw new Error('Session lifetime seconds must be a positive whole number'); + } + + const parsed = Number(trimmed); + if (!Number.isSafeInteger(parsed) || parsed <= 0) { + throw new Error('Session lifetime seconds must be a positive whole number'); + } + + return parsed; +} + +function expiredSessionEmail( + event: OmsClientSessionExpiredEvent | null +): string | null { + const email = event?.session.sessionEmail?.trim(); + return email ? email : null; +} + +function feeOptionTitle(option: OmsFeeOptionWithBalance): string { + const symbol = feeOptionSymbol(option); + return `${symbol} fee`; +} + +function feeOptionSubtitle(option: OmsFeeOptionWithBalance): string { + const symbol = feeOptionSymbol(option); + const fee = `${option.feeOption.displayValue} ${symbol}`; + const available = option.available + ? `${option.available} ${symbol}` + : 'unknown'; + + return `Fee ${fee} · Available ${available}`; +} + +function feeOptionSymbol(option: OmsFeeOptionWithBalance): string { + return option.feeOption.token.symbol || option.selection.token; +} + +function feeOptionIsSelectable(option: OmsFeeOptionWithBalance): boolean { + const available = optionalBigInt(option.availableRaw); + const fee = optionalBigInt(option.feeOption.value); + return available == null || fee == null || available >= fee; +} + +function optionalBigInt(value: string | null | undefined): bigint | null { + if (!value) { + return null; + } + + try { + return BigInt(value); + } catch { + return null; + } +} + function formatLoginType( loginType: OmsClientSessionState['loginType'] ): string { @@ -1182,6 +1488,10 @@ const styles = StyleSheet.create({ fontWeight: '600', textTransform: 'uppercase', }, + fieldSeparator: { + backgroundColor: '#303644', + height: 1, + }, input: { backgroundColor: '#0B0D12', borderColor: '#303644', @@ -1357,6 +1667,37 @@ const styles = StyleSheet.create({ color: '#94A3B8', fontSize: 12, }, + feeOption: { + alignItems: 'center', + borderColor: '#303644', + borderRadius: 8, + borderWidth: 1, + flexDirection: 'row', + gap: 12, + justifyContent: 'space-between', + marginBottom: 10, + padding: 12, + }, + feeOptionText: { + flex: 1, + gap: 4, + }, + feeOptionTitle: { + color: '#F8FAFC', + fontSize: 15, + fontWeight: '700', + }, + feeOptionSubtitle: { + color: '#CBD5E1', + fontSize: 12, + lineHeight: 17, + }, + feeOptionToken: { + color: '#94A3B8', + fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace' }), + fontSize: 11, + lineHeight: 16, + }, status: { color: '#CBD5E1', fontSize: 13, diff --git a/examples/sdk-example/android/build.gradle b/examples/sdk-example/android/build.gradle index 57d4e85..fcaece1 100644 --- a/examples/sdk-example/android/build.gradle +++ b/examples/sdk-example/android/build.gradle @@ -1,11 +1,11 @@ buildscript { ext { buildToolsVersion = "36.0.0" - minSdkVersion = 26 + minSdkVersion = 24 compileSdkVersion = 36 targetSdkVersion = 36 ndkVersion = "27.1.12297006" - kotlinVersion = "2.3.20" + kotlinVersion = "2.1.20" } repositories { mavenLocal() diff --git a/examples/trails-actions-example/android/build.gradle b/examples/trails-actions-example/android/build.gradle index 57d4e85..fcaece1 100644 --- a/examples/trails-actions-example/android/build.gradle +++ b/examples/trails-actions-example/android/build.gradle @@ -1,11 +1,11 @@ buildscript { ext { buildToolsVersion = "36.0.0" - minSdkVersion = 26 + minSdkVersion = 24 compileSdkVersion = 36 targetSdkVersion = 36 ndkVersion = "27.1.12297006" - kotlinVersion = "2.3.20" + kotlinVersion = "2.1.20" } repositories { mavenLocal() diff --git a/package.json b/package.json index 23502b1..97f12d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/oms-react-native-sdk", - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "description": "React Native SDK for the OMS platform.", "homepage": "https://github.com/0xsequence/react-native-sdk", "main": "./lib/commonjs/index.js",