Last active
September 6, 2022 20:28
-
-
Save realabbas/5164f0c4a437a0313b6b171e43640ec5 to your computer and use it in GitHub Desktop.
Import and Export Wallet Feature implemented in Aries Bifold App (SSI Ecosystem powered by Hyperledger Aries and Aries Javascript Framework )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useAgent } from '@aries-framework/react-hooks' | |
import React, { useState } from 'react' | |
import { useTranslation } from 'react-i18next' | |
import { ActivityIndicator, Clipboard, StyleSheet, Text, TouchableOpacity, View } from 'react-native' | |
import { DirectoryPickerResponse, DocumentPickerResponse } from 'react-native-document-picker' | |
import { SafeAreaView } from 'react-native-safe-area-context' | |
import Toast from 'react-native-toast-message' | |
import Button, { ButtonType } from '../components/buttons/Button' | |
import { ToastType } from '../components/toast/BaseToast' | |
import { useStore } from '../contexts/store' | |
import { useTheme } from '../contexts/theme' | |
import { testIdWithKey } from '../utils/testable' | |
const RNFS = require('react-native-fs') | |
interface PinCreateProps { | |
setAuthenticated: React.Dispatch<React.SetStateAction<boolean>> | |
} | |
const ExportWallet: React.FC<PinCreateProps> = ({ setAuthenticated }) => { | |
const [loading, setLoading] = useState(false) | |
const [, dispatch] = useStore() | |
const { t } = useTranslation() | |
const { ColorPallet } = useTheme() | |
const { agent } = useAgent() | |
const [randomWords, setRandomWords] = useState('') | |
const [result, setResult] = React.useState< | |
Array<DocumentPickerResponse> | DirectoryPickerResponse | undefined | null | |
>() | |
const style = StyleSheet.create({ | |
container: { | |
backgroundColor: ColorPallet.brand.primaryBackground, | |
margin: 20, | |
flex: 1, | |
justifyContent: 'flex-end', | |
}, | |
root: { | |
flex: 1, | |
}, | |
row: { margin: 10 }, | |
rowText: { backgroundColor: '#343434', padding: 20 }, | |
text: { fontSize: 30, lineHeight: 50 }, | |
clipboard: { marginTop: 10 }, | |
generate: { marginBottom: 20 } | |
}) | |
React.useEffect(() => { | |
generateRandomWords() | |
}, []) | |
const copyToClipboard = () => { | |
Clipboard.setString(randomWords); | |
Toast.show({ | |
type: ToastType.Success, | |
text1: 'Notification', | |
text2: 'Copied to clipboard', | |
visibilityTime: 1500, | |
position: 'bottom', | |
}) | |
} | |
const keywords = [ | |
'diagram', | |
'clothes', | |
'jealous', | |
'trouble', | |
'undress', | |
'graphic', | |
'scatter', | |
'liberty', | |
'harvest', | |
'freedom', | |
'trolley', | |
'extinct', | |
'density', | |
'terrace', | |
'glacier', | |
'stomach', | |
'reptile', | |
'capital', | |
'gravity', | |
'recycle', | |
'lighter', | |
'drawing', | |
'country', | |
'support', | |
'collect', | |
'pyramid', | |
'compact', | |
'journal', | |
'arrange', | |
'relieve', | |
'elegant', | |
'limited', | |
'present', | |
'crevice', | |
'nervous', | |
'manager', | |
'recover', | |
'opposed', | |
'private', | |
'inspire', | |
'section', | |
'welcome', | |
'laborer', | |
'brother', | |
'referee', | |
'clique', | |
'height', | |
'ribbon', | |
'banana', | |
'injury', | |
'leader', | |
'redeem', | |
'orange', | |
'grudge', | |
'sample', | |
'ballot', | |
'avenue', | |
'ignore', | |
'deport', | |
'filter', | |
'linger', | |
'spring', | |
'single', | |
'energy', | |
'ground', | |
'master', | |
'pigeon', | |
'foster', | |
'origin', | |
'rocket', | |
'heaven', | |
'result', | |
'praise', | |
'elapse', | |
'studio', | |
'action', | |
'tiptoe', | |
'bucket', | |
'member', | |
'scream', | |
'spread', | |
'stress', | |
'latest', | |
'church', | |
'refund', | |
'bounce', | |
'suburb', | |
'staff', | |
'small', | |
'trait', | |
'arrow', | |
'young', | |
'wrist', | |
'glass', | |
'suite', | |
'sight', | |
'check', | |
'cower', | |
'stain', | |
'tease', | |
'cross', | |
'video', | |
'crude', | |
'smile', | |
'tower', | |
'enter', | |
'river', | |
'cycle', | |
'tiger', | |
'write', | |
'stake', | |
'world', | |
'bride', | |
'rumor', | |
'flock', | |
'eject', | |
'snarl', | |
'obese', | |
'sheet', | |
'glide', | |
'leash', | |
'motif', | |
'train', | |
'store', | |
'disco', | |
'tribe', | |
'other', | |
'jelly', | |
'cruel', | |
'right', | |
'leave', | |
'chair', | |
'round', | |
'smart', | |
'clean', | |
'quest', | |
'strip', | |
'owner', | |
'sense', | |
'entry', | |
'study', | |
'dash', | |
'goat', | |
'cake', | |
'sign', | |
'sigh', | |
'snub', | |
'bean', | |
'nest', | |
'bury', | |
'grip', | |
'rise', | |
'deer', | |
'mist', | |
'seed', | |
'ruin', | |
'feed', | |
'oven', | |
'mill', | |
'mine', | |
'jest', | |
'unit', | |
'plot', | |
'damn', | |
'crop', | |
'tidy', | |
'rest', | |
'gown', | |
'bare', | |
'harm', | |
'food', | |
'loan', | |
'list', | |
'deal', | |
'cage', | |
'duck', | |
'knit', | |
'draw', | |
'step', | |
'help', | |
'case', | |
'mold', | |
'bulb', | |
'acid', | |
'scan', | |
'wage', | |
'soft', | |
'wash', | |
'fold', | |
'sock', | |
'warn', | |
'lace', | |
'seat', | |
'pony', | |
'mole', | |
'form', | |
] | |
function getRandomInt(max: number) { | |
return Math.floor(Math.random() * max) | |
} | |
const generateRandomWords = () => { | |
const max = keywords.length - 1 | |
let paraphrase = '' | |
for (let i = 0; i < 15; i++) { | |
paraphrase += keywords[getRandomInt(max)] + ' ' | |
} | |
console.log("paraphrase", paraphrase.trim()) | |
setRandomWords(paraphrase.trim()) | |
} | |
const exportWallet = async () => { | |
const backupKey = randomWords | |
const random = Math.floor(Math.random() * 10000) | |
const backupWalletName = `backup-${random}` | |
const path = `${RNFS.ExternalStorageDirectoryPath}/${backupWalletName}` | |
console.log('newAgent.wallet', agent.wallet.export, path, agent.config.walletConfig) | |
agent.wallet | |
.export({ path: path, key: backupKey }) | |
.then((res) => { | |
Toast.show({ | |
type: ToastType.Success, | |
text1: 'Wallet exported successfully', | |
text2: path, | |
visibilityTime: 3000, | |
position: 'bottom', | |
}) | |
}) | |
.catch((err) => { | |
console.log('err', err) | |
}) | |
} | |
return ( | |
<SafeAreaView style={style.root}> | |
{loading ? <ActivityIndicator /> : null} | |
<View style={styles.row}> | |
<View style={styles.rowText}> | |
<Text style={styles.text}>{randomWords}</Text> | |
</View> | |
<TouchableOpacity activeOpacity={0.5} style={styles.clipboard} onPress={() => copyToClipboard()}> | |
<Text>Copy to clipboard</Text> | |
</TouchableOpacity> | |
</View> | |
<View style={[style.container]}> | |
<TouchableOpacity activeOpacity={0.5} style={styles.generate} onPress={() => generateRandomWords()}> | |
<Text>Generate Again</Text> | |
</TouchableOpacity> | |
<Button | |
styles={{}} | |
disabled={loading} | |
title={'Export wallet'} | |
accessibilityLabel={t('PinCreate.Create')} | |
testID={testIdWithKey('Create')} | |
buttonType={ButtonType.Primary} | |
onPress={()=> exportWallet()} | |
/> | |
</View> | |
</SafeAreaView> | |
) | |
} | |
export default ExportWallet |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Agent, | |
AutoAcceptCredential, | |
ConsoleLogger, | |
HttpOutboundTransport, | |
LogLevel, | |
MediatorPickupStrategy, | |
WsOutboundTransport | |
} from '@aries-framework/core' | |
import { useAgent } from '@aries-framework/react-hooks' | |
import { agentDependencies } from '@aries-framework/react-native' | |
import React, { useState } from 'react' | |
import { useTranslation } from 'react-i18next' | |
import { ActivityIndicator, Clipboard, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native' | |
import { Config } from 'react-native-config' | |
import DocumentPicker, { | |
DirectoryPickerResponse, | |
DocumentPickerResponse, | |
isInProgress | |
} from 'react-native-document-picker' | |
import { SafeAreaView } from 'react-native-safe-area-context' | |
import Toast from 'react-native-toast-message' | |
import indyLedgers from '../../configs/ledgers/indy' | |
import Button, { ButtonType } from '../components/buttons/Button' | |
import { ToastType } from '../components/toast/BaseToast' | |
import { DispatchAction } from '../contexts/reducers/store' | |
import { useStore } from '../contexts/store' | |
import { useTheme } from '../contexts/theme' | |
import { testIdWithKey } from '../utils/testable' | |
const RNFS = require('react-native-fs') | |
interface PinCreateProps { | |
setAuthenticated: React.Dispatch<React.SetStateAction<boolean>> | |
} | |
const ImportWallet: React.FC<PinCreateProps> = ({ setAuthenticated, setAgent }) => { | |
const [pin, setPin] = useState('') | |
const [pinTwo, setPinTwo] = useState('') | |
const [loading, setLoading] = useState(false) | |
const [, dispatch] = useStore() | |
const { t } = useTranslation() | |
const { ColorPallet } = useTheme() | |
const { agent } = useAgent() | |
const [result, setResult] = React.useState< | |
Array<DocumentPickerResponse> | DirectoryPickerResponse | undefined | null | |
>() | |
const style = StyleSheet.create({ | |
container: { | |
backgroundColor: ColorPallet.brand.primaryBackground, | |
margin: 20, | |
flex: 1, | |
justifyContent: 'flex-end', | |
}, | |
root: { | |
flex: 1, | |
}, | |
}) | |
const handleError = (err: unknown) => { | |
if (DocumentPicker.isCancel(err)) { | |
console.warn('cancelled') | |
// User cancelled the picker, exit any dialogs or menus and move on | |
} else if (isInProgress(err)) { | |
console.warn('multiple pickers were opened, only the last will be considered') | |
} else { | |
throw err | |
} | |
} | |
const [agentInitDone, setAgentInitDone] = useState(false) | |
const [initAgentInProcess, setInitAgentInProcess] = useState(false) | |
const [copiedText, setCopiedText] = useState('') | |
const [paraphrase, setParaphrase] = useState('') | |
const getPath = (fileName: string) => `${RNFS.ExternalStorageDirectoryPath}/${fileName}` | |
const importWallet = async (fileName: string) => { | |
// TODO: Show loading indicator here | |
dispatch({ type: DispatchAction.LOADING_ENABLED }) | |
//Flag to protect the init process from being duplicated | |
setInitAgentInProcess(true) | |
try { | |
const newAgent = new Agent( | |
{ | |
label: 'Aries Bifold', | |
mediatorConnectionsInvite: Config.MEDIATOR_URL, | |
mediatorPickupStrategy: MediatorPickupStrategy.Implicit, | |
// walletConfig: { id: 'wallet4', key: '123' }, | |
autoAcceptConnections: true, | |
autoAcceptCredentials: AutoAcceptCredential.ContentApproved, | |
logger: new ConsoleLogger(LogLevel.trace), | |
indyLedgers, | |
connectToIndyLedgersOnStartup: false, | |
}, | |
agentDependencies | |
) | |
console.log('getPath(path)', getPath(fileName)) | |
await newAgent.wallet | |
.import( | |
{ id: 'wallet4', key: '123' }, | |
{ | |
path: getPath(fileName), | |
key: paraphrase, | |
} | |
) | |
.then(async (res: Object) => { | |
await newAgent.wallet.initialize({ id: 'wallet4', key: '123' }) | |
const wsTransport = new WsOutboundTransport() | |
const httpTransport = new HttpOutboundTransport() | |
newAgent.registerOutboundTransport(wsTransport) | |
newAgent.registerOutboundTransport(httpTransport) | |
await newAgent.initialize() | |
setAgent(newAgent) // -> This will set the agent in the global provider | |
// setAgentInitDone(true) | |
// saveData(newAgent) | |
setLoading(false) | |
Toast.show({ | |
type: ToastType.Success, | |
text1: 'Wallet Imported Successfully', | |
text2: getPath(fileName), | |
visibilityTime: 3000, | |
position: 'bottom', | |
}) | |
dispatch({ type: DispatchAction.LOADING_DISABLED }) | |
dispatch({ | |
type: DispatchAction.DID_SHOW_IMPORT_WALLET, | |
}) | |
}) | |
.catch((err: Object) => { | |
console.log(err) | |
setLoading(false) | |
Toast.show({ | |
type: ToastType.Error, | |
text1: 'Error', | |
text2: 'Please use correct paraphrase', | |
visibilityTime: 3000, | |
position: 'bottom', | |
}) | |
}) | |
} catch (e: unknown) { | |
Toast.show({ | |
type: ToastType.Error, | |
text1: t('Global.Failure'), | |
text2: (e as Error)?.message || t('Error.Unknown'), | |
visibilityTime: 2000, | |
position: 'bottom', | |
}) | |
} | |
setInitAgentInProcess(false) | |
} | |
const fetchCopiedText = async () => { | |
const text = await Clipboard.getString() | |
setParaphrase(text) | |
} | |
const paraphraseInput = () => { | |
return ( | |
<View style={{ margin: 10 }}> | |
<View style={{ backgroundColor: '#343434', padding: 20 }}> | |
<TextInput | |
value={paraphrase} | |
onChangeText={(text) => { | |
setParaphrase(text) | |
}} | |
multiline | |
style={{ fontSize: 30, lineHeight: 50 }} | |
/> | |
</View> | |
<TouchableOpacity activeOpacity={0.5} style={{ marginTop: 10 }} onPress={() => fetchCopiedText()}> | |
<Text>Paste from clipboard</Text> | |
</TouchableOpacity> | |
</View> | |
) | |
} | |
return ( | |
<SafeAreaView style={style.root}> | |
{loading ? <ActivityIndicator /> : null} | |
{paraphraseInput()} | |
<View style={style.container}> | |
{result ? <Text style={{ marginBottom: 20 }}>Imported file: - {getPath(result[0].name)}</Text> : null} | |
<Button | |
styles={{}} | |
disabled={loading} | |
title={loading ? 'Importing Wallet from Backup File' : 'Import Backup File'} | |
accessibilityLabel={t('PinCreate.Create')} | |
testID={testIdWithKey('Create')} | |
buttonType={ButtonType.Primary} | |
onPress={async () => { | |
try { | |
if (paraphrase.split(' ').length === 15) { | |
setResult(null) | |
setLoading(true) | |
const pickerResult = await DocumentPicker.pickSingle({ | |
presentationStyle: 'fullScreen', | |
copyTo: 'cachesDirectory', | |
}) | |
console.log(pickerResult) | |
setResult([pickerResult]) | |
importWallet(pickerResult.name) | |
} else { | |
Toast.show({ | |
type: ToastType.Error, | |
text1: 'Please enter 15 letter paraphrase', | |
text2: '', | |
visibilityTime: 2000, | |
position: 'bottom', | |
}) | |
} | |
} catch (e) { | |
handleError(e) | |
} | |
}} | |
/> | |
</View> | |
</SafeAreaView> | |
) | |
} | |
export default ImportWallet |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Well these two are separate files, so you need to add these two flows in the navigator file of the BiFold App and then call these flows accordingly. The implementation remains the same