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",