diff --git a/packages/mobile-aelf/app.json b/packages/mobile-aelf/app.json
index 450db7b60..e4a3342c2 100644
--- a/packages/mobile-aelf/app.json
+++ b/packages/mobile-aelf/app.json
@@ -8,6 +8,11 @@
"ios": {
"googleServicesFile": "./GoogleService-Info.plist"
},
- "plugins": ["@react-native-firebase/app", "@react-native-firebase/perf", "@react-native-firebase/crashlytics"]
+ "plugins": [
+ "@react-native-firebase/app",
+ "@react-native-firebase/perf",
+ "@react-native-firebase/crashlytics",
+ "react-native-cloud-storage"
+ ]
}
}
diff --git a/packages/mobile-aelf/js/assets/image/svgs.js b/packages/mobile-aelf/js/assets/image/svgs.js
index b1ede2b79..2f23a2713 100644
--- a/packages/mobile-aelf/js/assets/image/svgs.js
+++ b/packages/mobile-aelf/js/assets/image/svgs.js
@@ -1,2 +1,2 @@
/* eslint-disable prettier/prettier */
-export default {'Contract':'\n','ELF':'\n','ETransfer':'\n','ETransferLogo':'\n','accessory':'\n','activity-fail':'\n','activity-mined':'\n','activity-pending':'\n','activity':'\n','add-contact':'\n','add-contact1':'','add-tab':'\n','add':'\n','add1':'\n','add2':'\n','add3':'\n','add4':'\n','album':'\n','allowance-delete':'','app-blue-logo':'\n','app-logo-new':'\n','apple-icon':'\n','apple-white':'\n','apple':'\n','arrow-down-thin':'\n','arrow-left':'\n','arrow-right-thin':'\n','arrow-right':'\n','awaken-swap-round':'\n','awaken-swap':'\n','awakenLogo':'\n','bingoGame':'\n','block-back':'\n','book-mark-fill':'\n','book-mark':'\n','bookmark':'\n','bookmarked':'\n','buy':'\n','buy1':'\n','buy2':'\n','camera':'\n','chain_Logos':'\n','chain_side':'\n','change':'\n','chat-add-contact':'\n','chat-add-member':'\n','chat-add':'\n','chat-added':'\n','chat-album':'\n','chat-block':'cg\n','chat-bookmark':'\n','chat-camera':'\n','chat-chat':'\n','chat-create-group':'\n','chat-delete-emoji':'\n','chat-delete':'\n','chat-emoji':'\n','chat-file':'\n','chat-find-more':'\n','chat-group-avatar-header':'\n','chat-group-avatar-small-logo':'\n','chat-group-avatar':'\n','chat-group-info':'\n','chat-keyboard':'\n','chat-leave-group':'\n','chat-mute':'\n','chat-new-chat':'\n','chat-pin':'\n','chat-profile':'\n','chat-qr-code':'\n','chat-remove-member':'\n','chat-reply':'\n','chat-report':'\n','chat-reshutter':'\n','chat-robot':'\n','chat-scroll-to-bottom':'\n','chat-send':'\n','chat-shutter':'\n','chat-tab':'\n','chat-transfer':'\n','chat-unmute':'\n','chat-unpin':'\n','chat-unsupported-channel':'\n','check-circle':'\n','check-false':'\n','check-true':'\n','check':'\n','checkUpdate':'\n','checkbox-Checked':'\n','checkbox-new':'\n','checkbox':'\n','checked':'\n','checked_circle':'\n','chevron-right':'','chevron_down':'\n','chevron_right':'\n','chevron_right2':'\n','clear':'\n','clear1':'\n','clear2':'\n','clear3':'\n','clear4':'\n','clock':'\n','close-red-packet':'\n','close':'\n','close1':'\n','close2':'\n','close3':'\n','close4':'\n','close_thick':'\n','collect':'\n','collected':'\n','contact':'\n','contact2':'\n','copy-checked':'\n','copy-pre':'\n','copy-thin':'\n','copy':'\n','copy1':'\n','copy3':'\n','copy_v2':'\n','crypto-box-with-border':'\n','crypto-gift':'\n','default_record':'\n','delete-image2':'\n','delete-img':'\n','delete-mint':'\n','delete':'\n','deposit-receive':'\n','deposit':'\n','deposit1':'\n','depositMain':'\n','deposit_status_failed':'\n','deposit_status_processing':'\n','deposit_status_success':'\n','desk-mac':'\n','desk-win':'\n','direction-arrow-right':'\n','direction-right':'\n','direction-up':'\n','direction_down':'\n','disabled_visible':'\n','discord':'\n','discover':'\n','discover_close':'\n','down-arrow':'\n','drag':'\n','eBridge':'\n','eBridgeFavIcon':'\n','eBridgeLogo':'\n','edit':'\n','edit1':'','edit_thin':'\n','edit_token':'\n','elf-icon':'\n','email-login':'\n','email-white':'\n','email':'\n','error':'\n','explore':'\n','external':'','eye':'\n','eyeClosed':'\n','face-id':'\n','facebook-icon':'\n','facebook-white':'\n','facebook':'\n','fail':'\n','faucet':'\n','favorite-disable':'\n','favorite-unselected':'\n','favorite':'\n','filter-top':'\n','filter':'\n','finish':'\n','forest':'\n','free-mint-entry':'\n','gear':'\n','gift-box-close':'\n','gift-box-open':'\n','gift-thin':'\n','gift':'\n','gift_thin':'\n','google-drive':'\n','google-icon':'\n','google':'\n','grid':'\n','guardian-apple':'\n','guardian-email':'\n','guardian-facebook':'\n','guardian-google':'\n','guardian-phone':'\n','guardian':'\n','guardians-telegram':'\n','guardians-x':'\n','help-gray':'\n','help-white':'\n','help':'\n','history':'\n','home':'\n','homepage':'\n','httpWarn':'\n','httpsLock':'\n','iCloud':'\n','image-loading':'\n','import':'\n','info-white':'\n','info':'\n','inport':'\n','inviteFriend':'\n','keyboard_arrow_down':'\n','left-arrow-v2':'\n','left-arrow':'\n','lock-gift':'\n','lock':'\n','lock_security':'\n','logo-icon-text':'\n','logo-icon':'\n','logo-text':'\n','logout':'\n','luckiest':'\n','mainnet':'\n','master1':'\n','master2':'\n','master3':'\n','master4':'\n','master5':'\n','master6':'\n','mintFail':'\n','minted':'\n','more-circle':'\n','more-info':'\n','more-vertical':'\n','more':'\n','more_normal':'\n','more_selected':'\n','more_verti':'\n','my-avatar':'\n','my-pin':'\n','my':'\n','my_about':'\n','my_auto_lock':'\n','my_biometric':'\n','my_change':'\n','my_connect':'\n','my_contact':'\n','my_device':'\n','my_guardians':'\n','my_help':'\n','my_mail_thin':'\n','my_pin':'\n','my_referral':'\n','my_token allowance':'\n','my_transaction_limit':'\n','my_update':'\n','no-bookmarks':'\n','no-data-detail':'\n','no-data-nft':'\n','no-data':'\n','no-message':'\n','no-records':'\n','no-result':'\n','officialGroup':'\n','paste':'\n','phone-Android':'\n','phone-iOS':'\n','phone':'\n','photo':'\n','pin-list-icon':'\n','pin-message':'\n','privacy-policy':'\n','private key':'\n','profile':'\n','qrCode-white':'\n','question-mark':'\n','question-mark2':'\n','question':'\n','receive':'\n','receive1':'\n','recovery phrase':'\n','red-delete':'\n','red-packet-opened':'\n','red-packet':'\n','referral':'\n','refresh':'\n','refresh1':'\n','reload':'\n','remove':'','right-arrow':'\n','right-arrow2':'\n','rows':'\n','scan-frame':'\n','scan-square':'\n','scan':'\n','search':'\n','selected':'\n','selected2':'\n','selected3':'\n','selected4':'\n','selected5':'\n','sell':'\n','send-red-packet-button':'\n','send-small':'\n','send-thin':'\n','send':'\n','send1':'\n','setting':'\n','setting2':'\n','settings':'\n','settingsAboutUs':'\n','settingsAddressBook':'\n','settingsHelp':'\n','settingsNetworks':'\n','settingsSecurity':'\n','settingsSetting':'\n','share-gift':'\n','share-thin':'\n','share':'\n','share2':'\n','sideChain':'\n','side_chain':'\n','social-recovery':'\n','solid-down-arrow':'\n','sort-asc':'\n','sort-desc':'\n','star':'\n','success':'\n','suggest-add':'\n','suggest-circle':'\n','suggest-close':'\n','swap-arrow':'\n','swap-thin':'\n','swap':'\n','switch':'\n','telegram-blue':'\n','telegram-mono':'\n','telegram-white':'\n','telegram':'\n','terms':'\n','testnet':'\n','third-party-gate-io':'\n','time':'\n','token_list_item_right':'\n','touch-id':'\n','trade-small':'\n','trade':'\n','transfer-preview':'\n','transfer-receive':'\n','transfer-send':'\n','transfer':'\n','trend':'\n','tune':'\n','twitter-icon':'\n','twitter-white':'\n','twitter':'\n','unselected':'\n','up-arrow':'\n','upload-avatar-button':'\n','upload':'\n','usdt-icon':'\n','vector-right':'\n','visa':'\n','visibility_lock':'\n','wallet-gray':'\n','wallet-security':'\n','wallet':'\n','wallet_fill':'\n','warning-fill':'\n','warning':'\n','warning1':'\n','warning2':'\n','warning3':'\n','warning_v2':'\n','withdraw':'\n','zklogin_watermark':'\n'};
+export default {'Chain=AELF Main':'\n','Chain=AELF Side':'\n','Chain=Arbitrum':'\n','Chain=Avalanche':'\n','Chain=Base':'\n','Chain=Binance':'\n','Chain=Ethereum':'\n','Chain=Flow':'\n','Chain=Moonbeam':'\n','Chain=Optimism':'\n','Chain=Polkadot':'\n','Chain=Polygon':'\n','Chain=Solana':'\n','Chain=TON':'\n','Chain=Testnet':'\n','Chain=Tron':'\n','Contract':'\n','ELF':'\n','ETransfer':'\n','ETransferLogo':'\n','accessory':'\n','activity-fail':'\n','activity-mined':'\n','activity-pending':'\n','activity':'\n','add-contact':'\n','add-contact1':'','add-tab':'\n','add':'\n','add1':'\n','add2':'\n','add3':'\n','add4':'\n','album':'\n','allowance-delete':'','app-blue-logo':'\n','app-logo-new':'\n','apple-icon':'\n','apple-white':'\n','apple':'\n','arrow-down-thin':'\n','arrow-left':'\n','arrow-right-thin':'\n','arrow-right':'\n','awaken-swap-round':'\n','awaken-swap':'\n','awakenLogo':'\n','bingoGame':'\n','block-back':'\n','book-mark-fill':'\n','book-mark':'\n','bookmark':'\n','bookmarked':'\n','buy':'\n','buy1':'\n','buy2':'\n','camera':'\n','chain_Logos':'\n','chain_side':'\n','change':'\n','chat-add-contact':'\n','chat-add-member':'\n','chat-add':'\n','chat-added':'\n','chat-album':'\n','chat-block':'cg\n','chat-bookmark':'\n','chat-camera':'\n','chat-chat':'\n','chat-create-group':'\n','chat-delete-emoji':'\n','chat-delete':'\n','chat-emoji':'\n','chat-file':'\n','chat-find-more':'\n','chat-group-avatar-header':'\n','chat-group-avatar-small-logo':'\n','chat-group-avatar':'\n','chat-group-info':'\n','chat-keyboard':'\n','chat-leave-group':'\n','chat-mute':'\n','chat-new-chat':'\n','chat-pin':'\n','chat-profile':'\n','chat-qr-code':'\n','chat-remove-member':'\n','chat-reply':'\n','chat-report':'\n','chat-reshutter':'\n','chat-robot':'\n','chat-scroll-to-bottom':'\n','chat-send':'\n','chat-shutter':'\n','chat-tab':'\n','chat-transfer':'\n','chat-unmute':'\n','chat-unpin':'\n','chat-unsupported-channel':'\n','check-circle':'\n','check-false':'\n','check-true':'\n','check':'\n','checkUpdate':'\n','checkbox-Checked':'\n','checkbox-new':'\n','checkbox':'\n','checked':'\n','checked_circle':'\n','chevron-right':'','chevron_down':'\n','chevron_right':'\n','chevron_right2':'\n','clear':'\n','clear1':'\n','clear2':'\n','clear3':'\n','clear4':'\n','clock':'\n','close-red-packet':'\n','close':'\n','close1':'\n','close2':'\n','close3':'\n','close4':'\n','close_thick':'\n','collect':'\n','collected':'\n','contact':'\n','contact2':'\n','copy-checked':'\n','copy-pre':'\n','copy-thin':'\n','copy':'\n','copy1':'\n','copy3':'\n','copy_v2':'\n','crypto-box-with-border':'\n','crypto-gift':'\n','default_record':'\n','delete-image2':'\n','delete-img':'\n','delete-mint':'\n','delete':'\n','deposit-receive':'\n','deposit':'\n','deposit1':'\n','depositMain':'\n','deposit_status_failed':'\n','deposit_status_processing':'\n','deposit_status_success':'\n','desk-mac':'\n','desk-win':'\n','direction-arrow-right':'\n','direction-right':'\n','direction-up':'\n','direction_down':'\n','disabled_visible':'\n','discord':'\n','discover':'\n','discover_close':'\n','down-arrow':'\n','drag':'\n','eBridge':'\n','eBridgeFavIcon':'\n','eBridgeLogo':'\n','edit':'\n','edit1':'','edit_thin':'\n','edit_token':'\n','elf-icon':'\n','email-login':'\n','email-white':'\n','email':'\n','error':'\n','explore':'\n','external':'','eye':'\n','eyeClosed':'\n','face-id':'\n','facebook-icon':'\n','facebook-white':'\n','facebook':'\n','fail':'\n','faucet':'\n','favorite-disable':'\n','favorite-unselected':'\n','favorite':'\n','filter-top':'\n','filter':'\n','finish':'\n','forest':'\n','free-mint-entry':'\n','gear':'\n','gift-box-close':'\n','gift-box-open':'\n','gift-thin':'\n','gift':'\n','gift_thin':'\n','google-drive':'\n','google-icon':'\n','google':'\n','grid':'\n','guardian-apple':'\n','guardian-email':'\n','guardian-facebook':'\n','guardian-google':'\n','guardian-phone':'\n','guardian':'\n','guardians-telegram':'\n','guardians-x':'\n','help-gray':'\n','help-white':'\n','help':'\n','history':'\n','home':'\n','homepage':'\n','httpWarn':'\n','httpsLock':'\n','iCloud':'\n','image-loading':'\n','import':'\n','info-white':'\n','info':'\n','inport':'\n','inviteFriend':'\n','keyboard_arrow_down':'\n','left-arrow-v2':'\n','left-arrow':'\n','lock-gift':'\n','lock':'\n','lock_security':'\n','logo-icon-text':'\n','logo-icon':'\n','logo-text':'\n','logout':'\n','luckiest':'\n','mainnet':'\n','master1':'\n','master2':'\n','master3':'\n','master4':'\n','master5':'\n','master6':'\n','mintFail':'\n','minted':'\n','more-circle':'\n','more-info':'\n','more-vertical':'\n','more':'\n','more_normal':'\n','more_selected':'\n','more_verti':'\n','my-avatar':'\n','my-pin':'\n','my':'\n','my_about':'\n','my_auto_lock':'\n','my_biometric':'\n','my_change':'\n','my_connect':'\n','my_contact':'\n','my_device':'\n','my_guardians':'\n','my_help':'\n','my_mail_thin':'\n','my_pin':'\n','my_referral':'\n','my_token allowance':'\n','my_transaction_limit':'\n','my_update':'\n','no-bookmarks':'\n','no-data-detail':'\n','no-data-nft':'\n','no-data':'\n','no-message':'\n','no-records':'\n','no-result':'\n','officialGroup':'\n','paste':'\n','phone-Android':'\n','phone-iOS':'\n','phone':'\n','photo':'\n','pin-list-icon':'\n','pin-message':'\n','privacy-policy':'\n','private key':'\n','profile':'\n','qrCode-white':'\n','question-mark':'\n','question-mark2':'\n','question':'\n','receive':'\n','receive1':'\n','recovery phrase':'\n','red-delete':'\n','red-packet-opened':'\n','red-packet':'\n','referral':'\n','refresh':'\n','refresh1':'\n','reload':'\n','remove':'','right-arrow':'\n','right-arrow2':'\n','rows':'\n','scan-frame':'\n','scan-square':'\n','scan':'\n','search':'\n','selected':'\n','selected2':'\n','selected3':'\n','selected4':'\n','selected5':'\n','sell':'\n','send-red-packet-button':'\n','send-small':'\n','send-thin':'\n','send':'\n','send1':'\n','setting':'\n','setting2':'\n','settings':'\n','settingsAboutUs':'\n','settingsAddressBook':'\n','settingsHelp':'\n','settingsNetworks':'\n','settingsSecurity':'\n','settingsSetting':'\n','share-gift':'\n','share-thin':'\n','share':'\n','share2':'\n','sideChain':'\n','side_chain':'\n','social-recovery':'\n','solid-down-arrow':'\n','sort-asc':'\n','sort-desc':'\n','star':'\n','success':'\n','suggest-add':'\n','suggest-circle':'\n','suggest-close':'\n','swap-arrow':'\n','swap-thin':'\n','swap':'\n','switch':'\n','telegram-blue':'\n','telegram-mono':'\n','telegram-white':'\n','telegram':'\n','terms':'\n','testnet':'\n','third-party-gate-io':'\n','time':'\n','token_list_item_right':'\n','touch-id':'\n','trade-small':'\n','trade':'\n','transfer-preview':'\n','transfer-receive':'\n','transfer-send':'\n','transfer':'\n','trend':'\n','tune':'\n','twitter-icon':'\n','twitter-white':'\n','twitter':'\n','unselected':'\n','up-arrow':'\n','upload-avatar-button':'\n','upload':'\n','usdt-icon':'\n','vector-right':'\n','visa':'\n','visibility':'\n','visibility_lock':'\n','visibility_off':'\n','wallet-gray':'\n','wallet-security':'\n','wallet':'\n','wallet_fill':'\n','warning-fill':'\n','warning':'\n','warning1':'\n','warning2':'\n','warning3':'\n','warning_v2':'\n','withdraw':'\n','zklogin_watermark':'\n'};
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Main.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Main.svg
new file mode 100644
index 000000000..144f39b26
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Main.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Side.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Side.svg
new file mode 100644
index 000000000..3a8f3ae0e
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=AELF Side.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Arbitrum.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Arbitrum.svg
new file mode 100644
index 000000000..93298f9cd
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Arbitrum.svg
@@ -0,0 +1,16 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Avalanche.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Avalanche.svg
new file mode 100644
index 000000000..6c987a09d
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Avalanche.svg
@@ -0,0 +1,12 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Base.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Base.svg
new file mode 100644
index 000000000..45f96343c
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Base.svg
@@ -0,0 +1,15 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Binance.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Binance.svg
new file mode 100644
index 000000000..6a050aea1
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Binance.svg
@@ -0,0 +1,14 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Ethereum.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Ethereum.svg
new file mode 100644
index 000000000..972eff085
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Ethereum.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Flow.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Flow.svg
new file mode 100644
index 000000000..42820dcd7
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Flow.svg
@@ -0,0 +1,14 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Moonbeam.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Moonbeam.svg
new file mode 100644
index 000000000..48c04d11f
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Moonbeam.svg
@@ -0,0 +1,24 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Optimism.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Optimism.svg
new file mode 100644
index 000000000..7760b489e
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Optimism.svg
@@ -0,0 +1,12 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Polkadot.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Polkadot.svg
new file mode 100644
index 000000000..7e9be784c
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Polkadot.svg
@@ -0,0 +1,21 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Polygon.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Polygon.svg
new file mode 100644
index 000000000..3b223586f
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Polygon.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Solana.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Solana.svg
new file mode 100644
index 000000000..f243f2d8b
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Solana.svg
@@ -0,0 +1,30 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=TON.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=TON.svg
new file mode 100644
index 000000000..b256265d4
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=TON.svg
@@ -0,0 +1,16 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Testnet.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Testnet.svg
new file mode 100644
index 000000000..3fe6ae948
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Testnet.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/Chain=Tron.svg b/packages/mobile-aelf/js/assets/image/svgs/Chain=Tron.svg
new file mode 100644
index 000000000..1d58a77d4
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/Chain=Tron.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/visibility.svg b/packages/mobile-aelf/js/assets/image/svgs/visibility.svg
new file mode 100644
index 000000000..17786bc32
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/visibility.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/mobile-aelf/js/assets/image/svgs/visibility_off.svg b/packages/mobile-aelf/js/assets/image/svgs/visibility_off.svg
new file mode 100644
index 000000000..02399c112
--- /dev/null
+++ b/packages/mobile-aelf/js/assets/image/svgs/visibility_off.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/mobile-aelf/js/pages/Home/HomeTab/index.tsx b/packages/mobile-aelf/js/pages/Home/HomeTab/index.tsx
index da131720a..ca9079bba 100644
--- a/packages/mobile-aelf/js/pages/Home/HomeTab/index.tsx
+++ b/packages/mobile-aelf/js/pages/Home/HomeTab/index.tsx
@@ -190,6 +190,12 @@ const HomeTab: React.FC = ({ _ }) => {
style={{ marginTop: 40 }}>
Manual Backup
+ navigationService.push('CloudBackup')} style={{ marginTop: 40 }}>
+ CloudBackup
+
+ navigationService.push('CloudBackupDev')} style={{ marginTop: 10 }}>
+ CloudBackupDev
+
navigationService.push('Referral')} style={{ marginTop: 20 }}>
Referral
diff --git a/packages/mobile-aelf/js/pages/Login/CloudBackup/cases.tsx b/packages/mobile-aelf/js/pages/Login/CloudBackup/cases.tsx
new file mode 100644
index 000000000..68d08dc2e
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/CloudBackup/cases.tsx
@@ -0,0 +1,308 @@
+// https://github.com/kuatsu/react-native-cloud-storage/blob/master/example/src/views/Home.tsx
+import React, { useCallback, useEffect, useState } from 'react';
+import { Text, View } from 'react-native';
+import PageContainer from 'components/PageContainer';
+import { isIOS } from '@portkey-wallet/utils/mobile/device';
+import aes from '@portkey-wallet/utils/aes';
+import { makeStyles } from '@rneui/themed';
+import { pTd } from 'utils/unit';
+import fonts from 'assets/theme/fonts';
+import Svg from 'components/Svg';
+import Touchable from 'components/Touchable';
+import CommonButton from 'components/CommonButton';
+import GStyles from 'mobile-did/js/assets/theme/GStyles';
+import CommonInput from 'mobile-did/js/components/CommonInput';
+import CheckBox from 'components/CheckBox';
+import { OfficialWebsite } from '@portkey-wallet/constants/constants-ca/network';
+import navigationService from 'utils/navigationService';
+import { useCurrentWallet } from '@portkey-wallet/hooks/hooks-eoa/wallet';
+import { useCredentials } from 'hooks/store';
+import { useCloudStorage } from './useCloudStorage';
+import CommonToast from 'components/CommonToast';
+import { getPasswords, passwordShowFormat } from './index';
+
+export default function CloudBackupCases() {
+ const styles = getStyles();
+ // const { theme } = useTheme();
+ // const [copied, setCopied] = useState(false);
+ //
+ // // const currentAccount = useCurrentAccount();
+ const currentWallet = useCurrentWallet();
+ const credentials = useCredentials();
+ const [password, setPassword] = useState('');
+ const [passwordShow, setPasswordShow] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [confirmPasswordShow, setConfirmPasswordShow] = useState('');
+ const [secureTextEntry, setSecureTextEntry] = useState(true);
+ const [confirmSecureTextEntry, setConfirmSecureTextEntry] = useState(true);
+ const [errorMessage, setErrorMessage] = useState('');
+
+ const [isChecked, setIsChecked] = useState(false);
+ const onClickCheckBox = useCallback(() => {
+ setIsChecked(!isChecked);
+ }, [isChecked]);
+
+ useEffect(() => {
+ console.log('cloud backup - currentWallet: ', currentWallet, credentials);
+ }, [credentials, currentWallet]);
+
+ const {
+ // cloudStorage,
+ readFile,
+ handleCreateDirectory,
+ handleDeleteDirectory,
+ handleListContents,
+ handleCreateFile,
+ loading,
+ } = useCloudStorage();
+
+ return (
+
+
+
+ handleCreateDirectory
+
+ {
+ if (!currentWallet) {
+ CommonToast.fail('Can not found wallet');
+ return;
+ }
+ readFile(currentWallet.key);
+ }}
+ style={{ marginTop: 10 }}>
+ readFile
+
+ handleDeleteDirectory(true)}
+ style={{ marginTop: 10 }}>
+ handleDeleteDirectory
+
+
+ handleListContents/Wallet List in Cloud
+
+ {
+ if (!currentWallet) {
+ CommonToast.fail('Can not found wallet');
+ return;
+ }
+ handleCreateFile({
+ filename: currentWallet.key,
+ // TODO, encrypt before create
+ input: JSON.stringify(currentWallet),
+ });
+ }}
+ style={{ marginTop: 10 }}>
+ handleCreateFile
+
+
+ Create password
+
+ This password will secure your seed phrase in the cloud. We cannot reset it if you lose it, so please keep it
+ safe.
+
+
+ {
+ const { newPassword, passwordShow: _passwordShow } = getPasswords(value, password, secureTextEntry);
+ setPassword(newPassword);
+ setPasswordShow(_passwordShow);
+ if (confirmPassword !== newPassword) {
+ setErrorMessage('Not match, please try again.');
+ } else {
+ setErrorMessage('');
+ }
+ }}
+ rightIcon={
+
+ {
+ setPassword('');
+ setPasswordShow('');
+ }}>
+
+
+ {
+ const newSecureTextEntry = !secureTextEntry;
+ setSecureTextEntry(newSecureTextEntry);
+ setPasswordShow(passwordShowFormat(newSecureTextEntry, password));
+ }}>
+
+
+
+ }
+ />
+ {
+ const { newPassword, passwordShow: _passwordShow } = getPasswords(
+ value,
+ confirmPassword,
+ confirmSecureTextEntry,
+ );
+ setConfirmPassword(newPassword);
+ setConfirmPasswordShow(_passwordShow);
+ if (password !== newPassword) {
+ setErrorMessage('Not match, please try again.');
+ } else {
+ setErrorMessage('');
+ }
+ }}
+ rightIcon={
+
+ {
+ setConfirmPassword('');
+ setConfirmPasswordShow('');
+ }}>
+
+
+ {
+ const newSecureTextEntry = !confirmSecureTextEntry;
+ setConfirmSecureTextEntry(newSecureTextEntry);
+ setConfirmPasswordShow(passwordShowFormat(newSecureTextEntry, confirmPassword));
+ }}>
+
+
+
+ }
+ />
+
+
+
+
+
+
+ onClickCheckBox()} boxStyle={styles.checkBox} />
+
+
+ I understand that if I lose my password, I will not be able to access my backup, which could result in the
+ loss of all my assets. I agree to{' '}
+ {
+ navigationService.navigate('ViewOnWebView', {
+ title: 'Terms of Service',
+ url: `${OfficialWebsite}/terms-of-service`,
+ });
+ }}>
+ terms
+ {' '}
+ and{' '}
+ {
+ navigationService.navigate('ViewOnWebView', {
+ title: 'Privacy Policy',
+ url: `${OfficialWebsite}/privacy-policy`,
+ });
+ }}>
+ privacy policy
+ {' '}
+ for using aelf wallet.
+
+
+ {
+ // TODO:
+ // if directory exists, will not create again.
+ await handleCreateDirectory();
+ if (!currentWallet) {
+ CommonToast.fail('Can not found wallet');
+ return;
+ }
+ await handleCreateFile({
+ filename: currentWallet.key,
+ // TODO, encrypt before create
+ input: aes.encrypt(JSON.stringify(currentWallet), password),
+ });
+ }}>
+ Continue
+
+
+
+ );
+}
+
+const getStyles = makeStyles(theme => ({
+ containerStyles: {
+ backgroundColor: theme.colors.bgBase1,
+ justifyContent: 'space-between',
+ },
+ title: {
+ marginTop: pTd(24),
+ fontSize: pTd(32),
+ ...fonts.BGMediumFont,
+ },
+ desc: {
+ color: theme.colors.textBase2,
+ marginTop: pTd(16),
+ fontSize: pTd(14),
+ },
+ inputContainer: {
+ marginTop: pTd(24),
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ inputLabel: {
+ paddingLeft: 0,
+ },
+ understandContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ checkBox: {
+ backgroundColor: theme.colors.bgBase1,
+ marginRight: pTd(12),
+ },
+ understandText: {
+ flex: 1,
+ fontSize: pTd(14),
+ lineHeight: pTd(14) * 1.4,
+ },
+ link: {
+ fontSize: pTd(14),
+ color: theme.colors.textBrand3,
+ },
+ continueButton: {
+ marginTop: pTd(24),
+ marginBottom: pTd(16),
+ },
+}));
diff --git a/packages/mobile-aelf/js/pages/Login/CloudBackup/index.tsx b/packages/mobile-aelf/js/pages/Login/CloudBackup/index.tsx
new file mode 100644
index 000000000..35b962b40
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/CloudBackup/index.tsx
@@ -0,0 +1,309 @@
+// https://github.com/kuatsu/react-native-cloud-storage/blob/master/example/src/views/Home.tsx
+import React, { useCallback, useState } from 'react';
+import { Text, View } from 'react-native';
+import PageContainer from 'components/PageContainer';
+import { isIOS } from '@portkey-wallet/utils/mobile/device';
+import aes from '@portkey-wallet/utils/aes';
+import { makeStyles } from '@rneui/themed';
+import { pTd } from 'utils/unit';
+import fonts from 'assets/theme/fonts';
+import Svg from 'components/Svg';
+import Touchable from 'components/Touchable';
+import CommonButton from 'components/CommonButton';
+import GStyles from 'mobile-did/js/assets/theme/GStyles';
+import CommonInput from 'mobile-did/js/components/CommonInput';
+import CheckBox from 'components/CheckBox';
+import { OfficialWebsite } from '@portkey-wallet/constants/constants-ca/network';
+import navigationService from 'utils/navigationService';
+import { useCurrentWallet } from '@portkey-wallet/hooks/hooks-eoa/wallet';
+// import { useCredentials } from 'hooks/store';
+import { useCloudStorage } from './useCloudStorage';
+import CommonToast from 'components/CommonToast';
+import { useCredentials } from 'hooks/store';
+import { TWalletInfo } from '@portkey-wallet/types/types-eoa/wallet';
+
+function generateDots(length: number) {
+ if (length < 0) {
+ throw new Error('Length must be a non-negative number');
+ }
+ return '•'.repeat(length);
+}
+
+export function passwordShowFormat(isSecure: boolean, password: string) {
+ return isSecure ? generateDots(password.length) : password;
+}
+
+export function getPasswords(newValue: string, prePassword: string, isSecure: boolean) {
+ const _password = newValue.trim();
+ const _passwordLength = _password.length;
+ const prePasswordLength = prePassword.length;
+ // let newPassword = _password ? prePassword.slice(0, _passwordLength - 1) + _password[_passwordLength - 1] : '';
+ let newPassword = prePassword.slice(0, prePasswordLength - 1);
+ if (!_password) {
+ newPassword = '';
+ } else if (prePasswordLength < _passwordLength) {
+ newPassword = prePassword.slice(0, _passwordLength - 1) + _password[_passwordLength - 1];
+ }
+ return {
+ newPassword,
+ passwordShow: passwordShowFormat(isSecure, newPassword),
+ };
+}
+
+export default function CloudBackup() {
+ const styles = getStyles();
+ // const { theme } = useTheme();
+ // const [copied, setCopied] = useState(false);
+ //
+ // // const currentAccount = useCurrentAccount();
+ const currentWallet = useCurrentWallet();
+ const credentials = useCredentials();
+ const [password, setPassword] = useState('');
+ const [passwordShow, setPasswordShow] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [confirmPasswordShow, setConfirmPasswordShow] = useState('');
+ const [secureTextEntry, setSecureTextEntry] = useState(true);
+ const [confirmSecureTextEntry, setConfirmSecureTextEntry] = useState(true);
+ const [errorMessage, setErrorMessage] = useState('');
+
+ const [isChecked, setIsChecked] = useState(false);
+ const onClickCheckBox = useCallback(() => {
+ setIsChecked(!isChecked);
+ }, [isChecked]);
+
+ const { handleCreateDirectory, handleCreateFile, loading } = useCloudStorage();
+
+ return (
+
+
+ Create password
+
+ This password will secure your seed phrase in the cloud. We cannot reset it if you lose it, so please keep it
+ safe.
+
+
+ {
+ const { newPassword, passwordShow: _passwordShow } = getPasswords(value, password, secureTextEntry);
+ setPassword(newPassword);
+ setPasswordShow(_passwordShow);
+ if (confirmPassword !== newPassword) {
+ setErrorMessage('Not match, please try again.');
+ } else {
+ setErrorMessage('');
+ }
+ }}
+ rightIcon={
+
+ {
+ setPassword('');
+ setPasswordShow('');
+ }}>
+
+
+ {
+ const newSecureTextEntry = !secureTextEntry;
+ setSecureTextEntry(newSecureTextEntry);
+ setPasswordShow(passwordShowFormat(newSecureTextEntry, password));
+ }}>
+
+
+
+ }
+ />
+ {
+ const { newPassword, passwordShow: _passwordShow } = getPasswords(
+ value,
+ confirmPassword,
+ confirmSecureTextEntry,
+ );
+ setConfirmPassword(newPassword);
+ setConfirmPasswordShow(_passwordShow);
+ if (password !== newPassword) {
+ setErrorMessage('Not match, please try again.');
+ } else {
+ setErrorMessage('');
+ }
+ }}
+ rightIcon={
+
+ {
+ setConfirmPassword('');
+ setConfirmPasswordShow('');
+ }}>
+
+
+ {
+ const newSecureTextEntry = !confirmSecureTextEntry;
+ setConfirmSecureTextEntry(newSecureTextEntry);
+ setConfirmPasswordShow(passwordShowFormat(newSecureTextEntry, confirmPassword));
+ }}>
+
+
+
+ }
+ />
+
+
+
+
+
+
+ onClickCheckBox()} boxStyle={styles.checkBox} />
+
+
+ I understand that if I lose my password, I will not be able to access my backup, which could result in the
+ loss of all my assets. I agree to{' '}
+ {
+ navigationService.navigate('ViewOnWebView', {
+ title: 'Terms of Service',
+ url: `${OfficialWebsite}/terms-of-service`,
+ });
+ }}>
+ terms
+ {' '}
+ and{' '}
+ {
+ navigationService.navigate('ViewOnWebView', {
+ title: 'Privacy Policy',
+ url: `${OfficialWebsite}/privacy-policy`,
+ });
+ }}>
+ privacy policy
+ {' '}
+ for using aelf wallet.
+
+
+ {
+ console.log('currentWallet: ', currentWallet);
+ if (!currentWallet) {
+ CommonToast.fail('Can not found wallet');
+ return;
+ }
+ const _currentWallet: TWalletInfo = JSON.parse(JSON.stringify(currentWallet));
+ if (!credentials?.pin) {
+ // almost impossible
+ CommonToast.fail('Wallet is locked');
+ return;
+ }
+ const { pin } = credentials;
+ if (_currentWallet.AESEncryptMnemonic) {
+ const result = aes.decrypt(_currentWallet.AESEncryptMnemonic, pin);
+ if (!result) {
+ CommonToast.fail('Decrypt failed');
+ }
+ _currentWallet.AESEncryptMnemonic = aes.encrypt(result as string, password);
+ }
+ _currentWallet.accountList.map(account => {
+ const result = aes.decrypt(account.AESEncryptPrivateKey, pin);
+ if (!result) {
+ CommonToast.fail('Decrypt failed');
+ }
+ account.AESEncryptPrivateKey = aes.encrypt(result as string, password);
+ });
+ console.log('_currentWallet: ', _currentWallet, currentWallet);
+ // if directory exists, will not create again.
+ await handleCreateDirectory();
+
+ await handleCreateFile({
+ filename: _currentWallet.key,
+ input: JSON.stringify({
+ updateTime: Date.now(),
+ wallet: aes.encrypt(JSON.stringify(_currentWallet), password),
+ }),
+ });
+ navigationService.navigate('Home');
+ CommonToast.success('Backup completed');
+ }}>
+ Continue
+
+
+
+ );
+}
+
+const getStyles = makeStyles(theme => ({
+ containerStyles: {
+ backgroundColor: theme.colors.bgBase1,
+ justifyContent: 'space-between',
+ },
+ title: {
+ marginTop: pTd(24),
+ fontSize: pTd(32),
+ ...fonts.BGMediumFont,
+ },
+ desc: {
+ color: theme.colors.textBase2,
+ marginTop: pTd(16),
+ fontSize: pTd(14),
+ },
+ inputContainer: {
+ marginTop: pTd(24),
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ inputLabel: {
+ paddingLeft: 0,
+ },
+ understandContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ checkBox: {
+ backgroundColor: theme.colors.bgBase1,
+ marginRight: pTd(12),
+ },
+ understandText: {
+ flex: 1,
+ fontSize: pTd(14),
+ lineHeight: pTd(14) * 1.4,
+ },
+ link: {
+ fontSize: pTd(14),
+ color: theme.colors.textBrand3,
+ },
+ continueButton: {
+ marginTop: pTd(24),
+ marginBottom: pTd(16),
+ },
+}));
diff --git a/packages/mobile-aelf/js/pages/Login/CloudBackup/useCloudStorage.tsx b/packages/mobile-aelf/js/pages/Login/CloudBackup/useCloudStorage.tsx
new file mode 100644
index 000000000..ff743388e
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/CloudBackup/useCloudStorage.tsx
@@ -0,0 +1,191 @@
+import { useState, useEffect, useMemo, useCallback } from 'react';
+import {
+ CloudStorage,
+ CloudStorageError,
+ CloudStorageErrorCode,
+ // type CloudStorageFileStat,
+ CloudStorageProvider,
+ // CloudStorageScope,
+ useIsCloudAvailable,
+} from 'react-native-cloud-storage';
+import CommonToast from '../../../components/CommonToast';
+
+function commonCloudStorageError(e: any) {
+ console.warn('commonCloudStorageError', e);
+ if (e instanceof CloudStorageError) {
+ if (e.code === CloudStorageErrorCode.READ_ERROR) {
+ CommonToast.fail('No backup found');
+ } else {
+ CommonToast.fail(e.code);
+ }
+ } else {
+ CommonToast.fail('Something went wrong. Please try backing up later.');
+ }
+}
+
+export const useCloudStorage = () => {
+ // const [provider, setProvider] = useState(CloudStorage.getDefaultProvider());
+ const [provider] = useState(CloudStorage.getDefaultProvider());
+ // const [scope, setScope] = useState(CloudStorageScope.AppData);
+ // const [parentDirectory, setParentDirectory] = useState('/portkey-eoa/wallet');
+ const [parentDirectory] = useState('/portkey-eoa/wallet');
+ const [isParentDirectoryExist, setIsParentDirectoryExist] = useState();
+ // const [filename, setFilename] = useState('test.txt');
+ // const [stats, setStats] = useState(null);
+ // const [input, setInput] = useState('');
+ // const [appendInput, setAppendInput] = useState('');
+ // const [accessToken, setAccessToken] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ const cloudStorage = useMemo(() => {
+ return new CloudStorage(
+ provider,
+ provider === CloudStorageProvider.GoogleDrive ? { strictFilenames: true } : undefined,
+ );
+ }, [provider]);
+ const cloudAvailable = useIsCloudAvailable(cloudStorage);
+
+ const isDirectoryExists = useCallback(async () => {
+ setLoading(true);
+ try {
+ const exists = await cloudStorage.exists(parentDirectory);
+ console.log('useCloudStorage - isDirectoryExists: ', exists);
+ setIsParentDirectoryExist(exists);
+ } catch (e) {
+ // console.warn(e);
+ setIsParentDirectoryExist(false);
+ commonCloudStorageError(e);
+ } finally {
+ setLoading(false);
+ }
+ }, [cloudStorage, parentDirectory]);
+
+ const readFile = useCallback(
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ async (filename: string) => {
+ setLoading(true);
+ try {
+ const newStats = await cloudStorage.stat(parentDirectory + '/' + filename);
+ // setStats(newStats);
+ console.log('File stats', newStats);
+ if (newStats.isDirectory()) {
+ return;
+ }
+ const fileContent = await cloudStorage.readFile(parentDirectory + '/' + filename);
+ console.log('File content', fileContent);
+ return fileContent;
+ } catch (e) {
+ // console.log('readFile: ', e);
+ commonCloudStorageError(e);
+ return false;
+ // if (e instanceof CloudStorageError) {
+ // // TODO: return to page use, show Toast.
+ // if (e.code === CloudStorageErrorCode.FILE_NOT_FOUND) {
+ // // setStats(null);
+ // // setInput('');
+ // CommonToast.fail(e.code);
+ // } else {
+ // console.warn('Native storage error', e.code, e.message);
+ // }
+ // } else {
+ // console.warn('Unknown error', e);
+ // }
+ } finally {
+ setLoading(false);
+ }
+ },
+ [cloudStorage, parentDirectory],
+ );
+
+ const handleCreateFile = useCallback(
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ async ({ filename, input }: { filename: string; input: string }) => {
+ setLoading(true);
+ try {
+ await cloudStorage.writeFile(parentDirectory + '/' + filename, input);
+ readFile(filename);
+ } catch (e) {
+ // console.warn(e);
+ commonCloudStorageError(e);
+ } finally {
+ setLoading(false);
+ }
+ },
+ [cloudStorage, parentDirectory, readFile],
+ );
+
+ const handleCreateDirectory = useCallback(async () => {
+ console.log('useCloudStorage - handleCreateDirectory: ', 'isParentDirectoryExist: ', isParentDirectoryExist);
+ if (isParentDirectoryExist) {
+ console.log('useCloudStorage - handleCreateDirectory: ', 'skip');
+ return;
+ }
+ console.log('useCloudStorage - handleCreateDirectory: ', 'create');
+ setLoading(true);
+ try {
+ await cloudStorage.mkdir(parentDirectory);
+ await isDirectoryExists();
+ // readFile(filename);
+ } catch (e) {
+ // console.warn(e);
+ commonCloudStorageError(e);
+ } finally {
+ setLoading(false);
+ }
+ }, [cloudStorage, isDirectoryExists, isParentDirectoryExist, parentDirectory]);
+
+ // List parent wallet/ privateKey Wallet addresses.
+ const handleListContents = useCallback(async () => {
+ setLoading(true);
+ try {
+ const contents = await cloudStorage.readdir(parentDirectory);
+ console.log('useCloudStorage - Directory contents', contents.length, contents.map(c => `• ${c}`).join('\n'));
+ return contents;
+ } catch (e) {
+ // console.warn(e);
+ commonCloudStorageError(e);
+ return false;
+ } finally {
+ setLoading(false);
+ }
+ }, [cloudStorage, parentDirectory]);
+
+ const handleDeleteDirectory = async (recursive?: boolean) => {
+ if (recursive === undefined) {
+ handleDeleteDirectory(false);
+ // Alert.alert('Delete directory', 'Do you want to delete the directory and all its contents (recursively)?', [
+ // { text: 'Cancel', style: 'cancel' },
+ // { text: 'Directory only', onPress: () => handleDeleteDirectory(false) },
+ // { text: 'Recursively', onPress: () => handleDeleteDirectory(true) },
+ // ]);
+ } else {
+ setLoading(true);
+ try {
+ await cloudStorage.rmdir(parentDirectory, { recursive });
+ await isDirectoryExists();
+ // setStats(null);
+ // setInput('');
+ } catch (e) {
+ // console.warn(e);
+ commonCloudStorageError(e);
+ } finally {
+ setLoading(false);
+ }
+ }
+ };
+
+ useEffect(() => {
+ isDirectoryExists();
+ }, [isDirectoryExists]);
+
+ return {
+ loading,
+ cloudStorage,
+ cloudAvailable,
+ handleCreateDirectory,
+ handleDeleteDirectory,
+ handleListContents,
+ handleCreateFile,
+ readFile,
+ };
+};
diff --git a/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/Decrypt/index.tsx b/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/Decrypt/index.tsx
new file mode 100644
index 000000000..1e0dff0b3
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/Decrypt/index.tsx
@@ -0,0 +1,149 @@
+// https://github.com/kuatsu/react-native-cloud-storage/blob/master/example/src/views/Home.tsx
+import React, { useState } from 'react';
+import { Text, View } from 'react-native';
+import PageContainer from 'components/PageContainer';
+import { isIOS } from '@portkey-wallet/utils/mobile/device';
+import aes from '@portkey-wallet/utils/aes';
+import { makeStyles } from '@rneui/themed';
+import { pTd } from 'utils/unit';
+import fonts from 'assets/theme/fonts';
+import Svg from 'components/Svg';
+import Touchable from 'components/Touchable';
+import CommonButton from 'components/CommonButton';
+import GStyles from 'mobile-did/js/assets/theme/GStyles';
+import CommonInput from 'mobile-did/js/components/CommonInput';
+import CommonToast from 'components/CommonToast';
+import { getPasswords, passwordShowFormat } from '../../../CloudBackup';
+import useRouterParams from '@portkey-wallet/hooks/useRouterParams';
+import { TWalletInfo } from '@portkey-wallet/types/types-eoa/wallet';
+import { useImportWallet } from '../../../hooks/useImportWallet';
+
+export default function DecryptByPassword() {
+ const styles = getStyles();
+
+ const { walletInCloud } = useRouterParams<{
+ walletInCloud: string;
+ }>();
+
+ console.log('walletInCloud: ', walletInCloud);
+ const [password, setPassword] = useState('');
+ const [passwordShow, setPasswordShow] = useState('');
+ const [secureTextEntry, setSecureTextEntry] = useState(true);
+ const { importWalletByPrivateKey, importWalletByMnemonic } = useImportWallet();
+
+ return (
+
+
+ Password
+ Enter the password that protects your seed phrase in the cloud.
+
+ {
+ const { newPassword, passwordShow: _passwordShow } = getPasswords(value, password, secureTextEntry);
+ setPassword(newPassword);
+ setPasswordShow(_passwordShow);
+ }}
+ rightIcon={
+
+ {
+ setPassword('');
+ setPasswordShow('');
+ }}>
+
+
+ {
+ const newSecureTextEntry = !secureTextEntry;
+ setSecureTextEntry(newSecureTextEntry);
+ setPasswordShow(passwordShowFormat(newSecureTextEntry, password));
+ }}>
+
+
+
+ }
+ />
+
+
+
+
+ {
+ const result = aes.decrypt(walletInCloud, password);
+ if (!result) {
+ CommonToast.fail('Password error');
+ return;
+ }
+ console.log('decrypt result: ', result, JSON.parse(result));
+ const wallet: TWalletInfo = JSON.parse(result);
+ if (wallet.AESEncryptMnemonic) {
+ const mnemonic = aes.decrypt(wallet.AESEncryptMnemonic, password);
+ if (!mnemonic) {
+ CommonToast.fail('Password error');
+ return;
+ }
+ console.log('mnemonic: ', mnemonic);
+ importWalletByMnemonic(mnemonic);
+ } else {
+ const privateKey = aes.decrypt(wallet.accountList[0].AESEncryptPrivateKey, password);
+ if (!privateKey) {
+ CommonToast.fail('Password error');
+ return;
+ }
+ console.log('privateKey: ', privateKey);
+ importWalletByPrivateKey(privateKey);
+ }
+ }}>
+ Continue
+
+
+
+ );
+}
+
+const getStyles = makeStyles(theme => ({
+ containerStyles: {
+ backgroundColor: theme.colors.bgBase1,
+ justifyContent: 'space-between',
+ },
+ title: {
+ marginTop: pTd(24),
+ fontSize: pTd(32),
+ ...fonts.BGMediumFont,
+ },
+ desc: {
+ color: theme.colors.textBase2,
+ marginTop: pTd(16),
+ fontSize: pTd(14),
+ },
+ inputContainer: {
+ marginTop: pTd(24),
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ inputLabel: {
+ paddingLeft: 0,
+ },
+ continueButton: {
+ marginTop: pTd(24),
+ marginBottom: pTd(16),
+ },
+}));
diff --git a/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/index.tsx b/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/index.tsx
new file mode 100644
index 000000000..56a94517e
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/ImportWallet/ImportByCloud/index.tsx
@@ -0,0 +1,323 @@
+import React, { useEffect, useState } from 'react';
+import { Text, View } from 'react-native';
+import PageContainer from 'components/PageContainer';
+import { isIOS } from '@portkey-wallet/utils/mobile/device';
+import { makeStyles } from '@rneui/themed';
+import { pTd } from 'utils/unit';
+import { useCardStyles, useWalletCommonStyles } from '../../styles';
+import CommonAvatar from 'components/CommonAvatar';
+import navigationService from 'utils/navigationService';
+import Touchable from 'components/Touchable';
+import fonts from 'assets/theme/fonts';
+import { useCloudStorage } from '../../CloudBackup/useCloudStorage';
+// import LottieLoading from 'components/LottieLoading';
+import { addressFormat, formatStr2EllipsisStr } from '@portkey-wallet/utils';
+import dayjs from 'dayjs';
+import ActionSheet from 'components/ActionSheet';
+import * as Clipboard from 'expo-clipboard';
+import CommonToast from 'components/CommonToast';
+import { ChainId } from '@portkey-wallet/types';
+import { IconName } from 'components/Svg';
+
+interface IAddressInfo {
+ address: string;
+ addressShow: string;
+ info?: {
+ updateTime: string;
+ wallet: string;
+ };
+}
+
+const modalAddressInfo: {
+ chain: ChainId;
+ name: string;
+ icon: IconName;
+ addressFormatted: string;
+ addressShow: string;
+}[] = [
+ {
+ chain: 'tDVV',
+ icon: 'Chain=AELF Side',
+ name: 'aelf dAppChain',
+ addressFormatted: '',
+ addressShow: '',
+ },
+ {
+ chain: 'AELF',
+ icon: 'Chain=AELF Main',
+ name: 'aelf MainChain',
+ addressFormatted: '',
+ addressShow: '',
+ },
+];
+
+export default function ImportByCloud() {
+ const styles = getStyles();
+ const addressCardStyles = getAddressCardStyles();
+ const commonStyles = useWalletCommonStyles();
+ const cardStyles = useCardStyles();
+
+ const { handleListContents, readFile } = useCloudStorage();
+
+ const [addressesInfo, setAddressesInfo] = useState([]);
+ const [addresses, setAddresses] = useState([]);
+
+ useEffect(() => {
+ const getAddressList = async () => {
+ const addressList = await handleListContents();
+ console.log('address list', addressList);
+ if (addressList) {
+ const _addressesInfo = addressList.map(item => {
+ return {
+ address: item,
+ addressShow: formatStr2EllipsisStr(addressFormat(item), 8),
+ // addressShow: addressFormat(item),
+ info: {
+ updateTime: '',
+ wallet: '',
+ },
+ };
+ });
+ setAddressesInfo(_addressesInfo);
+ setAddresses(addressList);
+ // getAddressInfo(addressList, _addressesInfo);
+ }
+ };
+ getAddressList();
+ }, [handleListContents, readFile]);
+
+ useEffect(() => {
+ if (addresses.length === 0) {
+ return;
+ }
+ const getAddressInfo = async (addressList: string[]) => {
+ const promiseList = [];
+ for (const address of addressList) {
+ promiseList.push(readFile(address));
+ }
+ const _addressesInfo = JSON.parse(JSON.stringify(addressesInfo)) as IAddressInfo[];
+ await Promise.all(promiseList).then(results => {
+ results.forEach((result, index) => {
+ const address = addressList[index];
+ const _index = _addressesInfo.findIndex(item => item.address === address);
+ if (result && _index >= 0) {
+ console.log('addressInfo result ', result, _index);
+ _addressesInfo[_index].info = JSON.parse(result);
+ }
+ });
+ });
+ setAddressesInfo(_addressesInfo);
+ };
+ getAddressInfo(addresses);
+ }, [addresses, readFile]);
+
+ return (
+
+ Choose backup
+ Select the backup you wish to import.
+
+ {/*{loading ? : null}*/}
+ {addressesInfo.map((item, index) => {
+ return (
+ {
+ if (!item.info?.wallet) {
+ return;
+ }
+ navigationService.push('ImportByCloudDecrypt', {
+ walletInCloud: item.info?.wallet,
+ });
+ }}>
+
+
+ {
+ const addressesShowInfo = modalAddressInfo.map(_addressInfo => {
+ const addressFormatted = addressFormat(item.address, _addressInfo.chain);
+ return {
+ ..._addressInfo,
+ addressFormatted,
+ addressShow: formatStr2EllipsisStr(addressFormatted, 8),
+ };
+ });
+ ActionSheet.alert({
+ isCloseShow: false,
+ title: (
+
+ Multichain addresses
+
+ {addressesShowInfo.map((addressShowInfo, addressesShowInfoIndex) => {
+ return (
+
+
+
+
+ {addressShowInfo.name}
+ {addressShowInfo.addressShow}
+
+
+ {
+ const isCopy = await Clipboard.setStringAsync(addressShowInfo.addressFormatted);
+ if (isCopy) {
+ CommonToast.success('Copied');
+ }
+ }}>
+
+
+
+ );
+ })}
+
+
+ ),
+ buttonGroupDirection: 'column',
+ buttons: [],
+ });
+ }}>
+ Multichain
+
+
+ {item.addressShow}
+
+ {item.info?.updateTime ? 'Added on ' + dayjs(item.info.updateTime).format('MMM DD, YYYY') : ' '}
+
+
+
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+const getStyles = makeStyles(theme => ({
+ rightContainer: {
+ justifyContent: 'center',
+ },
+ icon: {
+ // marginRight: pTd(12),
+ backgroundColor: 'transparent',
+ },
+ marginBottom40: {
+ marginBottom: pTd(40),
+ },
+ marginVertical16: {
+ marginVertical: pTd(8),
+ },
+ tagContainer: {
+ flexDirection: 'row',
+ borderColor: theme.colors.borderBase1,
+ borderRadius: pTd(4),
+ borderWidth: pTd(1),
+ // paddingHorizontal: pTd(4),
+ // paddingVertical: pTd(6),
+ width: pTd(90),
+ height: pTd(24),
+ backgroundColor: theme.colors.bgBase1,
+ alignItems: 'center',
+ // justifyContent: 'center',
+ },
+ tagText: {
+ ...fonts.SGMediumFont,
+ color: theme.colors.textBase1,
+ fontSize: pTd(12),
+ lineHeight: pTd(12) * 1.4,
+ marginLeft: pTd(6),
+ marginRight: pTd(4),
+ },
+ tagIcon: {
+ marginRight: pTd(16),
+ backgroundColor: 'transparent',
+ },
+ title: {
+ ...fonts.SGMediumFont,
+ color: theme.colors.textBase1,
+ fontSize: pTd(16),
+ fontWeight: 'bold',
+ lineHeight: pTd(16) * 1.4,
+ marginTop: pTd(8),
+ },
+ subtitle: {
+ color: theme.colors.textBase1Opacity07,
+ fontSize: pTd(14),
+ lineHeight: pTd(14) * 1.4,
+ marginTop: pTd(12),
+ },
+}));
+const getAddressCardStyles = makeStyles(theme => ({
+ header: {
+ ...fonts.BGMediumFont,
+ color: theme.colors.textBase1,
+ fontSize: pTd(20),
+ lineHeight: pTd(16) * 1.2,
+ },
+ cardContainer: {
+ flexDirection: 'column',
+ },
+ card: {
+ flexDirection: 'row',
+ paddingVertical: pTd(16),
+ // paddingHorizontal: pTd(12),
+ justifyContent: 'space-between',
+ width: '100%',
+ marginTop: pTd(12),
+ },
+ info: {
+ flexDirection: 'row',
+ },
+ chainIcon: {
+ marginRight: pTd(12),
+ },
+ title: {
+ color: theme.colors.textBase1,
+ fontSize: pTd(16),
+ lineHeight: pTd(16) * 1.4,
+ // marginTop: pTd(8),
+ },
+ subtitle: {
+ color: theme.colors.textBase1Opacity07,
+ fontSize: pTd(14),
+ lineHeight: pTd(14) * 1.4,
+ marginTop: pTd(5),
+ width: pTd(250),
+ },
+}));
diff --git a/packages/mobile-aelf/js/pages/Login/ImportWallet/PrivateKey/index.tsx b/packages/mobile-aelf/js/pages/Login/ImportWallet/PrivateKey/index.tsx
index 9d20cedbc..065aa32b4 100644
--- a/packages/mobile-aelf/js/pages/Login/ImportWallet/PrivateKey/index.tsx
+++ b/packages/mobile-aelf/js/pages/Login/ImportWallet/PrivateKey/index.tsx
@@ -6,9 +6,7 @@ import Touchable from 'components/Touchable';
import CommonButton from 'components/CommonButton';
import Svg from 'components/Svg';
import * as Clipboard from 'expo-clipboard';
-import navigationService from 'utils/navigationService';
-import { SetBiometricsTypeEnum } from 'pages/Pin/SetBiometrics';
-import { authenticationReady } from '@portkey-wallet/utils/mobile/authentication';
+import { useImportWallet } from '../../hooks/useImportWallet';
export default function RecoveryPhrase() {
const styles = getStyles();
@@ -64,20 +62,7 @@ export default function RecoveryPhrase() {
);
}, [onClear, styles.button, styles.buttonText, theme.colors.iconBase2]);
- const importWalletByPrivateKey = useCallback(async () => {
- const isReady = await authenticationReady();
- if (isReady) {
- navigationService.push('SetBiometrics', {
- type: SetBiometricsTypeEnum.create,
- privateKey: inputText.trim(),
- });
- return;
- }
-
- navigationService.navigate('SetPin', {
- privateKey: inputText.trim(),
- });
- }, [inputText]);
+ const { importWalletByPrivateKey } = useImportWallet();
return (
@@ -96,7 +81,7 @@ export default function RecoveryPhrase() {
style={styles.importButton}
disabledStyle={styles.importButtonDisable}
disabled={!isPrivateKeyValid}
- onPress={importWalletByPrivateKey}>
+ onPress={() => importWalletByPrivateKey(inputText)}>
Import
diff --git a/packages/mobile-aelf/js/pages/Login/ImportWallet/RecoveryPhrase/index.tsx b/packages/mobile-aelf/js/pages/Login/ImportWallet/RecoveryPhrase/index.tsx
index 5b386d68d..06fdccc4c 100644
--- a/packages/mobile-aelf/js/pages/Login/ImportWallet/RecoveryPhrase/index.tsx
+++ b/packages/mobile-aelf/js/pages/Login/ImportWallet/RecoveryPhrase/index.tsx
@@ -9,9 +9,7 @@ import CommonButton from 'components/CommonButton';
import CommonToast from 'components/CommonToast';
import * as bip39 from 'bip39';
import * as Clipboard from 'expo-clipboard';
-import { authenticationReady } from '@portkey-wallet/utils/mobile/authentication';
-import navigationService from 'utils/navigationService';
-import { SetBiometricsTypeEnum } from 'pages/Pin/SetBiometrics';
+import { useImportWallet } from '../../hooks/useImportWallet';
const MnemonicsWordCount = 12;
let invalidMnemonicsToastTimer: NodeJS.Timeout;
@@ -84,20 +82,7 @@ export default function RecoveryPhrase() {
);
}, [onClear, styles.button, styles.buttonText, theme.colors.iconBase2]);
- const importWalletByMnemonic = useCallback(async () => {
- const isReady = await authenticationReady();
- if (isReady) {
- navigationService.push('SetBiometrics', {
- type: SetBiometricsTypeEnum.create,
- mnemonics: mnemonics.join(' '),
- });
- return;
- }
-
- navigationService.navigate('SetPin', {
- mnemonics: mnemonics.join(' '),
- });
- }, [mnemonics]);
+ const { importWalletByMnemonic } = useImportWallet();
return (
@@ -130,7 +115,7 @@ export default function RecoveryPhrase() {
style={styles.importButton}
disabledStyle={styles.importButtonDisable}
disabled={!isMnemonicsValid}
- onPress={importWalletByMnemonic}>
+ onPress={() => importWalletByMnemonic(mnemonics)}>
Import
diff --git a/packages/mobile-aelf/js/pages/Login/WalletImportTypeSelect/index.tsx b/packages/mobile-aelf/js/pages/Login/WalletImportTypeSelect/index.tsx
index 89b6afb53..82502bba6 100644
--- a/packages/mobile-aelf/js/pages/Login/WalletImportTypeSelect/index.tsx
+++ b/packages/mobile-aelf/js/pages/Login/WalletImportTypeSelect/index.tsx
@@ -24,12 +24,14 @@ const ListItem: {
isAndroid: true,
svgName: 'google-drive',
title: 'Google Drive',
+ importType: 'google',
subTitle: 'Import your seed phrase from Google Drive.',
},
{
isIOS: true,
localImage: iCloudImage,
title: 'iCloud',
+ importType: 'iCloud',
subTitle: 'Import your seed phrase from iCloud.',
},
{
@@ -75,9 +77,15 @@ export default function WalletImportTypeSelect() {
if (!item.importType) {
return;
}
- navigationService.push('ImportWallet', {
- importType: item.importType,
- });
+ if (item.isAndroid || item.isIOS) {
+ navigationService.push('ImportByCloud', {
+ importType: item.importType,
+ });
+ } else {
+ navigationService.push('ImportWallet', {
+ importType: item.importType,
+ });
+ }
}}>
{/* TODO: iCloud, google drive loading */}
diff --git a/packages/mobile-aelf/js/pages/Login/hooks/useImportWallet.tsx b/packages/mobile-aelf/js/pages/Login/hooks/useImportWallet.tsx
new file mode 100644
index 000000000..f257f3ec9
--- /dev/null
+++ b/packages/mobile-aelf/js/pages/Login/hooks/useImportWallet.tsx
@@ -0,0 +1,44 @@
+import { useCallback } from 'react';
+import navigationService from 'utils/navigationService';
+import { SetBiometricsTypeEnum } from 'pages/Pin/SetBiometrics';
+import { authenticationReady } from '@portkey-wallet/utils/mobile/authentication';
+
+function formatMnemonics(mnemonics: string[] | string) {
+ return typeof mnemonics === 'string' ? mnemonics.trim() : mnemonics.join(' ');
+}
+export const useImportWallet = () => {
+ const importWalletByMnemonic = useCallback(async (mnemonics: string[] | string) => {
+ const isReady = await authenticationReady();
+ if (isReady) {
+ navigationService.push('SetBiometrics', {
+ type: SetBiometricsTypeEnum.create,
+ mnemonics: formatMnemonics(mnemonics),
+ });
+ return;
+ }
+
+ navigationService.navigate('SetPin', {
+ mnemonics: formatMnemonics(mnemonics),
+ });
+ }, []);
+
+ const importWalletByPrivateKey = useCallback(async (privateKey: string) => {
+ const isReady = await authenticationReady();
+ if (isReady) {
+ navigationService.push('SetBiometrics', {
+ type: SetBiometricsTypeEnum.create,
+ privateKey: privateKey.trim(),
+ });
+ return;
+ }
+
+ navigationService.navigate('SetPin', {
+ privateKey: privateKey.trim(),
+ });
+ }, []);
+
+ return {
+ importWalletByMnemonic,
+ importWalletByPrivateKey,
+ };
+};
diff --git a/packages/mobile-aelf/js/pages/Login/index.ts b/packages/mobile-aelf/js/pages/Login/index.ts
index 8701f93d2..b01d0929a 100644
--- a/packages/mobile-aelf/js/pages/Login/index.ts
+++ b/packages/mobile-aelf/js/pages/Login/index.ts
@@ -10,6 +10,10 @@ import ConfirmBackup from './ConfirmBackup';
import ManualBackup from './ManualBackup';
import ManualBackupSuccess from './ManualBackup/Success';
import WalletImportTypeSelect from './WalletImportTypeSelect';
+import ImportByCloud from './ImportWallet/ImportByCloud';
+import ImportByCloudDecrypt from './ImportWallet/ImportByCloud/Decrypt';
+import CloudBackup from './CloudBackup';
+import CloudBackupDev from './CloudBackup/cases';
const stackNav = [
{ name: 'LoginEmail', component: LoginEmail },
@@ -21,8 +25,12 @@ const stackNav = [
{ name: 'PrepareWallet', component: PrepareWallet, options: { gestureEnabled: false } },
{ name: 'ImportWallet', component: ImportWallet },
{ name: 'WalletImportTypeSelect', component: WalletImportTypeSelect },
+ { name: 'ImportByCloud', component: ImportByCloud },
+ { name: 'ImportByCloudDecrypt', component: ImportByCloudDecrypt },
{ name: 'ConfirmBackup', component: ConfirmBackup },
{ name: 'ManualBackup', component: ManualBackup },
+ { name: 'CloudBackup', component: CloudBackup },
+ { name: 'CloudBackupDev', component: CloudBackupDev },
{ name: 'ManualBackupSuccess', component: ManualBackupSuccess },
] as const;
diff --git a/packages/mobile-aelf/package.json b/packages/mobile-aelf/package.json
index 316920c80..39414a20b 100644
--- a/packages/mobile-aelf/package.json
+++ b/packages/mobile-aelf/package.json
@@ -129,7 +129,7 @@
"react": "^18.3.1",
"react-native": "^0.75.0",
"react-native-background-timer": "^2.4.1",
- "react-native-cloud-storage": "^2.2.1",
+ "react-native-cloud-storage": "^2.2.2",
"react-native-config": "^1.4.6",
"react-native-crypto": "^2.2.0",
"react-native-device-info": "^10.11.0",