React Native
The Ratio React Native SDK allows partners to embed and connect to Ratio’s application from their React Native application.
The React Native SDK provides a
RatioComponent
component that requires a few parameters (listed below). This component allows for a few customizations so that it can be placed anywhere within your app.The
RatioComponent
takes a child view that is wrapped using a <TouchableOpacity/>
component. This means that whatever view is passed into the RatioComponent
will be clickable. Our suggestion is to use the view as a custom "button" to allow users access Ratio services.To use the SDK you must first acquire a
clientId
and clientSecret
from the Ratio team. Learn how to request API keys here.Ratio will provide an API that you will be required to wrap in your own back end (see example below)
npm install @ratio.me/ratio-react-native-library
or
yarn add @ratio.me/ratio-react-native-library
Our library has a peer dependancy on the following
-
react-native-webview: "11.x"
- react-native-svg: "12.x || 13.x"
This means you must add
react-native-webview
and react-native-svg
as dependancies by running the followingnpm install react-native-webview --save
npm install react-native-svg --save
or
yarn add react-native-webview --save
yarn add react-native-svg --save
NOTE: it is important to make sure you use the
--save
flag, this will make sure the native code is autolinked. Read more about React Native Auto linking here1
import * as React from 'react';
2
import { useEffect, useState } from 'react';
3
4
import {
5
ActivityIndicator,
6
Alert,
7
Platform,
8
SafeAreaView,
9
StatusBar,
10
StyleSheet,
11
Text,
12
View,
13
} from 'react-native';
14
import Web3 from 'web3';
15
import {
16
RatioComponent,
17
RatioOrderStatus,
18
} from '@ratio.me/ratio-react-native-library';
19
import * as LocalAuthentication from 'expo-local-authentication';
20
import type { LocalAuthenticationResult } from 'expo-local-authentication';
21
22
const styles = StyleSheet.create({
23
AndroidSafeArea: {
24
flex: 1,
25
backgroundColor: 'white',
26
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
27
},
28
buttonWrapper: {
29
display: 'flex',
30
alignItems: 'center',
31
},
32
buyCryptoButton: {
33
backgroundColor: 'black',
34
width: 185,
35
height: 50,
36
display: 'flex',
37
alignItems: 'center',
38
justifyContent: 'center',
39
borderRadius: 14,
40
},
41
buyText: {
42
color: 'white',
43
},
44
});
45
46
const provider ='WEB3_PROVIDER_URL';
47
const privateKey =
48
'WALLET_PRIVATE_KEY';
49
const walletSigningAddress = 'SIGNING_ADDRESS';
50
const walletNetwork = 'WALLET_NETWORK';
51
const walletDepositAddress = 'DEPOSIT_ADDRESS';
52
53
const web3 = new Web3(new Web3.providers.HttpProvider(provider));
54
55
export default function App() {
56
const [loading, setLoading] = React.useState(false);
57
const [isBiometricSupported, setIsBiometricSupported] = useState(false);
58
const [ratioUser, setRatioUser] = useState(null)
59
60
useEffect(() => {
61
(async () => {
62
const compatible = await LocalAuthentication.hasHardwareAsync();
63
setIsBiometricSupported(compatible);
64
})();
65
});
66
67
const fetchSessionToken = async () => {
68
try {
69
let sessionTokenResponse = await fetch(
70
'https://your.api.com/clients/session',
71
{
72
method: 'POST',
73
body: JSON.stringify({
74
signingAddress: walletAddress,
75
depositAddress: walletAddress,
76
signingNetwork: walletNetwork,
77
}),
78
}
79
);
80
81
let data = await sessionTokenResponse.json();
82
return data.id;
83
} catch (e) {
84
console.error(e);
85
}
86
return null;
87
};
88
89
const fallBackToDefaultAuth = () => {};
90
const handleBiometricAuth = async () => {
91
const savedBiometrics = await LocalAuthentication.isEnrolledAsync();
92
if (!savedBiometrics) {
93
Alert.alert(
94
'Biometric record not found',
95
'Please verify your identity with your password',
96
[{ text: 'OK' }],
97
{ onDismiss: () => fallBackToDefaultAuth() }
98
);
99
return {
100
success: false,
101
error: 'no saved biometrics',
102
} as LocalAuthenticationResult;
103
}
104
const biometricAuth = await LocalAuthentication.authenticateAsync({
105
promptMessage: 'Login with Biometrics',
106
disableDeviceFallback: true,
107
cancelLabel: 'Cancel',
108
});
109
110
return biometricAuth;
111
};
112
113
return (
114
<SafeAreaView style={styles.AndroidSafeArea}>
115
{loading ? <ActivityIndicator /> : null}
116
<Text>
117
{isBiometricSupported
118
? 'Your device is compatible with Biometrics'
119
: 'Face or Fingerprint scanner is available on this device'}
120
</Text>
121
<View style={styles.buttonWrapper}>
122
<RatioComponent
123
fetchSessionToken={async () => {
124
return await fetchSessionToken();
125
}}
126
signingCallback={async (challenge: string) => {
127
let result = await handleBiometricAuth();
128
if (result.success) {
129
let sign = web3.eth.accounts.sign(challenge, privateKey);
130
131
return Promise.resolve({
132
signature: sign.signature,
133
});
134
} else {
135
return Promise.reject('failed biometrics');
136
}
137
}}
138
onPress={() => {
139
setLoading(true);
140
}}
141
onOpen={() => setLoading(false)}
142
onTransactionComplete={(orderStatus: RatioOrderStatus) => {
143
Alert.alert(orderStatus.status);
144
}}
145
onHelp={() => {
146
Alert.alert('Help button pressed');
147
}}
148
onAccountRecovery={() => {
149
Alert.alert('Account recovery button pressed');
150
}}
151
onError={(errorMessage: string) => {
152
setLoading(false);
153
Alert.alert(errorMessage);
154
}}
155
onClose={() => {}}
156
onLogin={(user: RatioUser)=> { setRatioUser(user) }}
157
>
158
{/* view used as visible 'button' to press */}
159
<View style={styles.buyCryptoButton}>
160
<Text style={styles.buyText}>Buy Crypto</Text>
161
</View>
162
</RatioComponent>
163
</View>
164
</SafeAreaView>
165
);
166
}
A function that is used to fetch session token that is generated from the API that is used to wrap the ratio
/v1/clients/session
(documentation here)This is an
async
function.The Ratio API uses client authentication which requires a
client_id
and client_secret
. It is highly recommended to implement this call in a secure API backend. This will prevent the need of shipping the clientSecret
with the client application.const fetchSessionToken = async () => {
try {
let sessionTokenResponse = await fetch(
'https://your.api.com/clients/session',
{
method: 'POST',
body: JSON.stringify({
signingAddress: walletAddress,
depositAddress: walletAddress,
signingNetwork: walletNetwork,
}),
}
);
let data = await sessionTokenResponse.json();
return data.id;
} catch (e) {
console.error(e);
}
return null;
};
<RatioComponent
fetchSessionToken={async () => {
return await fetchSessionToken();
}}/>
TYPE | REQUIRED |
---|---|
function | Yes |
Function that accepts a string which contains the
challenge
that is returned from the Ratio /v1/auth/cryptoWallet:start
call (documentation here)This is an
async
function that should return a promise. This will allow such asynchronous activities such as a biometrics check to happen during signing. Example using Web3.js library
<RatioComponent
signingCallback={async (challenge: string) => {
let sign = web3.eth.accounts.sign(challenge, privateKey);
return Promise.resolve({
signature: sign.signature,
});
}}/>
TYPE | REQUIRED |
---|---|
function | Yes |
A function that is called when the child view is pressed. This function is called before the SDK starts the authentication flow. Suggested uses include setting an ActivityIndicator or loading spinner to be visible.
Example
const [loading, setLoading] = useState(false)
<RatioComponent onPress={()=> {
setLoading(true)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called once the SDK has completed its authentication flow and before the modal is displayed. Suggested uses include setting an ActivityIndicator or loading spinner to be hidden
Example
const [loading, setLoading] = useState(false)
<RatioComponent onOpen={()=> {
setLoading(false)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called whenever a transaction is completed even if there was a failure.
As part of the data in the
RatioOrderStatus
object, you will receive the userId for the user that completed the transaction and the ActivityItem detailing the transaction. The userId and activityItem will not be returned if there was an error in processing the order.Example
<RatioComponent onTransactionComplete={(orderStatus: RatioOrderStatus)=> {
Alert.alert(orderStatus.status)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called whenever a help button is pressed from within the RatioComponent Modal WebView. This callback allows for custom handling for when the user needs help.
If not provided, the default behaviour is to open the default email client and draft an email to
[email protected]
Example
<RatioComponent onHelp={()=> {
Alert.alert(orderStatus.status)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called whenever the account recovery button is pressed from within the RatioComponent Modal WebView. This callback allows for custom handling for when the user needs help recovering their account.
If not provided, the default behaviour is to open the default email client and draft an email to
[email protected]
Example
<RatioComponent onAccountRecovery={()=> {
Alert.alert(orderStatus.status)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called whenever an error occurs within the Ratio component's authorization flow.
If not provided, the default behaviour is to show an Alert dialog with an error message
Example
<RatioComponent onError={(errorMessage: string)=> {
Alert.alert(errorMessage)
}}/>
TYPE | REQUIRED |
---|---|
function | No |
A function that is called after the Ratio Modal WebView. There is no default behaviour if not provided. As of writing this documentation this function will be called when the user presses "Return to wallet" in the application. It will then close the React Native modal and then call "onClose"
TYPE | REQUIRED |
---|---|
function | No |
A function that is called when the a user is fully authenticated. It is also called when the user's account has been created. The callback returns a
RatioUser
object that is described below.Example
<RatioComponent onLogin={(user: RatioUser)=> {
Alert.alert(user.firstName)
}}/>
export interface RatioKitSigningResult {
signature: string;
}
export interface RatioOrderStatus {
data: {userId: string, activity: ActivityItem};
status: 'success' | 'failure';
error: OrderError;
}
export interface OrderError {
errorId: string;
message: string;
statusCode: number;
}
export interface ActivityItem {
id: string;
createTime: string;
updateTime: string;
fiat: ActivityItemFiat;
crypto: ActivityItemCrypto;
metadata: any;
}
export interface ActivityItemFiat {
status: ActivityItemStatus;
currency: FiatCurrency;
amount: string;
direction: Direction;
fundingMethod: FundingMethod;
bankAccount: BankAccount;
}
export enum ActivityItemStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED'
}
export enum FiatCurrency {
USD = 'USD'
}
export enum Direction {
CREDIT = 'CREDIT',
DEBIT = 'DEBIT'
}
export enum FundingMethod {
ACH_ORIGINATED_STANDARD = 'ACH_ORIGINATED_STANDARD',
ACH_ORIGINATED_INSTANT = 'ACH_ORIGINATED_INSTANT',
ACH_RECEIVED = 'ACH_RECEIVED'
}
export interface BankAccount {
id: string;
createTime: string;
updateTime: string;
name: string;
mask: string;
linkStatus: LinkStatus;
verificationStatus: VerificationStatus;
}
export enum LinkStatus {
INACTIVE = 'INACTIVE',
ACTIVE = 'ACTIVE',
LOGIN_REQUIRED = 'LOGIN_REQUIRED'
}
export enum VerificationStatus {
IN_REVIEW = 'IN_REVIEW',
APPROVED = 'APPROVED',
DECLINED = 'DECLINED'
}
export interface ActivityItemCrypto {
status: ActivityItemStatus;
currency: string;
wallet: Wallet;
direction: Direction;
amount: string;
price: string;
fee: string;
transactionHash: string;
}
export interface Wallet {
id: string;
address: string;
createTime: string;
name: string;
network: string;
updateTime: string;
}
export interface RatioUser {
id: string;
createTime: string;
updateTime: string;
firstName: string;
middleName: string;
lastName: string;
email: string;
country: string;
phone: string;
preferredMfaMethod: TwoFactorMethod;
nationality: string;
occupation: string;
kyc: Kyc;
connectedBankAccounts: BankAccount[];
}
export enum TwoFactorMethod {
OTP_SMS = 'OTP_SMS',
TOTP = 'TOTP'
}
export interface BankAccount {
id: string;
createTime: string;
updateTime: string;
name: string;
mask: string;
linkStatus: LinkStatus;
verificationStatus: VerificationStatus;
}
export enum LinkStatus {
INACTIVE = 'INACTIVE',
ACTIVE = 'ACTIVE',
LOGIN_REQUIRED = 'LOGIN_REQUIRED'
}
export enum VerificationStatus {
IN_REVIEW = 'IN_REVIEW',
APPROVED = 'APPROVED',
DECLINED = 'DECLINED'
}
export type Kyc = {
createTime: string;
updateTime: string;
addressResult: KycResult;
dobResult: KycResult;
fraudResult: KycResult;
idvResult: KycResult;
};
export enum KycResult {
UNKNOWN = 'UNKNOWN',
NOT_STARTED = 'NOT_STARTED',
SUBMITTED = 'SUBMITTED',
IN_REVIEW = 'IN_REVIEW',
APPROVED = 'APPROVED',
DECLINED = 'DECLINED'
}
.png?alt=media&token=db49f912-1323-48f1-b89f-0e8e3ef073e8)
Last modified 11d ago