diff --git a/__tests__/__snapshots__/History.snapshot.tsx.snap b/__tests__/__snapshots__/History.snapshot.tsx.snap index b1ffcf66d..c650bc7b1 100644 --- a/__tests__/__snapshots__/History.snapshot.tsx.snap +++ b/__tests__/__snapshots__/History.snapshot.tsx.snap @@ -225,6 +225,7 @@ exports[`Component Transactions - test Transactions - snapshot 1`] = ` "opacity": 1, } } + testID="header.drawmenu" > < color="rgb(216, 216, 216)" diff --git a/__tests__/__snapshots__/SyncReport.snapshot.tsx.snap b/__tests__/__snapshots__/SyncReport.snapshot.tsx.snap index 757800736..9eb3f30b3 100644 --- a/__tests__/__snapshots__/SyncReport.snapshot.tsx.snap +++ b/__tests__/__snapshots__/SyncReport.snapshot.tsx.snap @@ -355,7 +355,7 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` @@ -655,6 +655,7 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` "color": "rgb(28, 28, 30)", } } + testID="syncreport.syncednow" > 200000translated text50.00% @@ -699,6 +700,7 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` "color": "rgb(28, 28, 30)", } } + testID="syncreport.notyetsynced" > 100000translated text25.00% @@ -747,6 +749,7 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` "opacity": 1, } } + testID="syncreport.currentbatch" > translated text5translated text50 @@ -785,6 +788,7 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` "opacity": 1, } } + testID="syncreport.blocksperbatch" > 100 @@ -835,154 +839,10 @@ exports[`Component SyncReport - test Matches the snapshot SyncReport 1`] = ` 122 - - - - - - - - - - - + = ({ onItemSelected }) => { {translate('loadedapp.rescanwallet') as string} - onItemSelectedWrapper('Sync Report')} style={item}> + onItemSelectedWrapper('Sync Report')} style={item}> {translate('loadedapp.report') as string} diff --git a/app/rpc/RPC.ts b/app/rpc/RPC.ts index f087f7da6..6311d127d 100644 --- a/app/rpc/RPC.ts +++ b/app/rpc/RPC.ts @@ -54,7 +54,6 @@ export default class RPC { seconds_batch: number; batches: number; message: string; - process_end_block: number; constructor( fnSetSyncingStatusReport: (syncingStatusReport: SyncingStatusReportClass) => void, @@ -93,7 +92,6 @@ export default class RPC { this.seconds_batch = 0; this.batches = 0; this.message = ''; - this.process_end_block = -1; } static async rpc_setInterruptSyncAfterBatch(value: string): Promise { @@ -505,7 +503,6 @@ export default class RPC { this.seconds_batch = 0; this.batches = 0; this.message = this.translate('rpc.syncstart-message') as string; - this.process_end_block = fullRescan ? this.walletBirthday : this.lastWalletBlockHeight; // This is async, so when it is done, we finish the refresh. if (fullRescan) { @@ -551,7 +548,6 @@ export default class RPC { this.seconds_batch = 0; this.batches = 0; this.message = this.translate('rpc.syncstart-message') as string; - this.process_end_block = this.lastWalletBlockHeight; } this.prev_sync_id = ss.sync_id; } @@ -593,7 +589,10 @@ export default class RPC { const end_block: number = ss.end_block || 0; // lower - const total_general_blocks: number = this.lastServerBlockHeight - this.process_end_block; + // I want to know what was the first block of the current sync process + const process_end_block = end_block - batch_num * this.blocksPerBatch; + + const total_general_blocks: number = this.lastServerBlockHeight - process_end_block; //const progress_blocks: number = (synced_blocks + trial_decryptions_blocks + txn_scan_blocks) / 3; const progress_blocks: number = (synced_blocks + trial_decryptions_blocks + witnesses_updated) / 3; @@ -654,7 +653,7 @@ export default class RPC { secondsPerBatch: this.seconds_batch, percent: progress, message: this.message, - process_end_block: this.process_end_block, + process_end_block: process_end_block, lastBlockServer: this.lastServerBlockHeight, }; this.fnSetSyncingStatusReport(statusGeneral); @@ -735,7 +734,7 @@ export default class RPC { secondsPerBatch: this.seconds_batch, percent: progress, message: this.message, - process_end_block: this.process_end_block, + process_end_block: process_end_block, lastBlockServer: this.lastServerBlockHeight, }; this.fnSetSyncingStatusReport(statusBatch); @@ -774,7 +773,7 @@ export default class RPC { secondsPerBatch: this.seconds_batch, percent: progress, message: this.message, - process_end_block: this.process_end_block, + process_end_block: process_end_block, lastBlockServer: this.lastServerBlockHeight, }; this.fnSetSyncingStatusReport(statusSeconds); diff --git a/components/Components/DetailLine.tsx b/components/Components/DetailLine.tsx index 677641aac..48a3ae6fd 100644 --- a/components/Components/DetailLine.tsx +++ b/components/Components/DetailLine.tsx @@ -11,15 +11,20 @@ type DetailLineProps = { label: string; value?: string; children?: ReactNode; + testID?: string; }; -const DetailLine: React.FunctionComponent = ({ label, value, children }) => { +const DetailLine: React.FunctionComponent = ({ label, value, children, testID }) => { const { colors } = useTheme() as unknown as ThemeType; return ( {label} - {value && {value}} + {value && ( + + {value} + + )} {children} ); diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx index 62d6d41da..f3b32af9d 100644 --- a/components/Header/Header.tsx +++ b/components/Header/Header.tsx @@ -173,6 +173,7 @@ const Header: React.FunctionComponent = ({ {!noDrawMenu && ( diff --git a/components/SyncReport/SyncReport.tsx b/components/SyncReport/SyncReport.tsx index b336c4755..80d3b02ae 100644 --- a/components/SyncReport/SyncReport.tsx +++ b/components/SyncReport/SyncReport.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-native/no-inline-styles */ -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { View, ScrollView, SafeAreaView, Text } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { View, ScrollView, SafeAreaView, Text, ActivityIndicator } from 'react-native'; import { useTheme } from '@react-navigation/native'; import { ThemeType } from '../../app/types'; @@ -11,7 +11,6 @@ import moment from 'moment'; import 'moment/locale/es'; import RPC from '../../app/rpc'; import Header from '../Header'; -import CircularProgress from '../Components/CircularProgress'; type SyncReportProps = { closeModal: () => void; @@ -25,7 +24,6 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => const [points, setPoints] = useState([] as number[]); const [labels, setLabels] = useState([] as string[]); const [birthday_plus_1, setBirthday_plus_1] = useState(0); - const [count, setCount] = useState(1); moment.locale(language); useEffect(() => { @@ -46,88 +44,87 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => }, [syncingStatusReport.lastBlockServer]); useEffect(() => { + // for some reason when you run the sync process the first block you got (end_block) (yes `end_block`, this is not a mistake) + // is one more that the wallet height, if it's the first time you got the wallet birthday + 1 + // because of that I need, for the math to add 1 in the birthday. + // but in the screen I show the real wallet birthday. setBirthday_plus_1((walletSeed.birthday || 0) + 1); }, [walletSeed.birthday]); - useEffect(() => { - if (syncingStatusReport.secondsPerBatch >= 0) { - setCount(1); - } - }, [syncingStatusReport.secondsPerBatch]); - - const fCount = useCallback(() => { - if (count === 5) { - setCount(1); - } else { - setCount(count + 1); - } - }, [count]); - - useEffect(() => { - const inter = setInterval(fCount, 1000); - - return () => clearInterval(inter); - }, [fCount]); - useEffect(() => { (async () => await RPC.rpc_setInterruptSyncAfterBatch('false'))(); }, []); + /* + SERVER points: + - server_0 : first block of the server -> 0 + - server_1 : wallet's birthday + - server_2 : server last block + - server_3 : empty part of the server bar + */ + const server_1: number = birthday_plus_1 || 0; const server_2: number = - syncingStatusReport.process_end_block && birthday_plus_1 - ? syncingStatusReport.process_end_block - birthday_plus_1 || 0 - : syncingStatusReport.lastBlockWallet && birthday_plus_1 - ? syncingStatusReport.lastBlockWallet - birthday_plus_1 || 0 - : 0; - const server_3: number = - syncingStatusReport.lastBlockServer && syncingStatusReport.process_end_block - ? syncingStatusReport.lastBlockServer - syncingStatusReport.process_end_block || 0 - : syncingStatusReport.lastBlockServer && syncingStatusReport.lastBlockWallet - ? syncingStatusReport.lastBlockServer - syncingStatusReport.lastBlockWallet || 0 + syncingStatusReport.lastBlockServer && birthday_plus_1 + ? syncingStatusReport.lastBlockServer - birthday_plus_1 || 0 : 0; - const server_4: number = maxBlocks ? maxBlocks - server_1 - server_2 - server_3 || 0 : 0; + const server_3: number = maxBlocks ? maxBlocks - server_1 - server_2 : 0; + const server_1_percent: number = (server_1 * 100) / maxBlocks; + const server_2_percent: number = (server_2 * 100) / maxBlocks; + const server_3_percent: number = (server_3 * 100) / maxBlocks; + + /* + server_server : blocks of the server + server_wallet : blocks of the wallet + */ + const server_server: number = syncingStatusReport.lastBlockServer || 0; const server_wallet: number = syncingStatusReport.lastBlockServer && birthday_plus_1 ? syncingStatusReport.lastBlockServer - birthday_plus_1 || 0 : 0; + /* + WALLET points: + - wallet_0 : birthday of the wallet + - wallet_1 : first block of the sync process (end_block) + - wallet_2 : current block of the sync process + - wallet_3 : empty part of the wallet bar + */ + let wallet_1: number = syncingStatusReport.process_end_block && birthday_plus_1 ? syncingStatusReport.process_end_block - birthday_plus_1 || 0 - : syncingStatusReport.lastBlockWallet && birthday_plus_1 - ? syncingStatusReport.lastBlockWallet - birthday_plus_1 || 0 : 0; - let wallet_21: number = + let wallet_2: number = syncingStatusReport.currentBlock && syncingStatusReport.process_end_block ? syncingStatusReport.currentBlock - syncingStatusReport.process_end_block || 0 - : syncingStatusReport.currentBlock && syncingStatusReport.lastBlockWallet - ? syncingStatusReport.currentBlock - syncingStatusReport.lastBlockWallet || 0 : 0; + // It is really weird, but don't want any negative values in the UI. if (wallet_1 < 0) { wallet_1 = 0; } - if (wallet_21 < 0) { - wallet_21 = 0; + if (wallet_2 < 0) { + wallet_2 = 0; } + const wallet_3: number = syncingStatusReport.lastBlockServer && birthday_plus_1 - ? syncingStatusReport.lastBlockServer - birthday_plus_1 - wallet_1 - wallet_21 || 0 + ? syncingStatusReport.lastBlockServer - birthday_plus_1 - wallet_1 - wallet_2 || 0 : 0; - let wallet_old_synced: number = wallet_1; - let wallet_new_synced: number = wallet_21; - let wallet_for_synced: number = wallet_3; - let wallet_old_synced_percent: number = (wallet_old_synced * 100) / server_wallet; - let wallet_new_synced_percent: number = (wallet_new_synced * 100) / server_wallet; - if (wallet_new_synced_percent < 0.01 && wallet_new_synced_percent > 0) { - wallet_new_synced_percent = 0.01; + let wallet_old_synced_percent: number = (wallet_1 * 100) / server_wallet; + let wallet_new_synced_percent: number = (wallet_2 * 100) / server_wallet; + if (wallet_old_synced_percent < 0.01 && wallet_old_synced_percent > 0) { + wallet_old_synced_percent = 0.01; } if (wallet_old_synced_percent > 100) { wallet_old_synced_percent = 100; } + if (wallet_new_synced_percent < 0.01 && wallet_new_synced_percent > 0) { + wallet_new_synced_percent = 0.01; + } if (wallet_new_synced_percent > 100) { wallet_new_synced_percent = 100; } @@ -140,7 +137,7 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => // syncStatusReport.lastBlockWallet, // syncStatusReport.lastBlockServer, //); - //console.log('server', server_1, server_2, server_3, server_4); + //console.log('server', server_1, server_2, server_3); //console.log('leyends', server_server, server_wallet, server_sync); //console.log('wallet', wallet_old_synced, wallet_new_synced, wallet_for_synced); //console.log('wallet %', wallet_old_synced_percent, wallet_new_synced_percent, wallet_for_synced_percent); @@ -242,7 +239,7 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => borderBottomWidth: 2, marginBottom: 0, }}> - {server_1 >= 0 && ( + {server_1_percent >= 0 && ( = ({ closeModal }) => borderLeftColor: colors.primary, borderLeftWidth: 1, borderRightColor: 'blue', - borderRightWidth: server_1 > 0 ? 1 : 0, - width: ((server_1 * 100) / maxBlocks).toString() + '%', + borderRightWidth: server_1_percent > 0 ? 1 : 0, + width: server_1_percent.toString() + '%', }} /> )} - {server_2 + server_3 >= 0 && ( + {server_2_percent >= 0 && ( 0 ? 1 : 0, - width: (((server_2 + server_3) * 100) / maxBlocks).toString() + '%', + borderRightWidth: server_2_percent > 0 ? 1 : 0, + width: server_2_percent.toString() + '%', borderBottomColor: 'blue', borderBottomWidth: 5, }} /> )} - {server_4 >= 0 && ( + {server_3_percent >= 0 && ( )} @@ -385,49 +382,43 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => borderBottomWidth: 2, marginBottom: 0, }}> - {wallet_1 >= 0 && ( + {wallet_old_synced_percent >= 0 && ( 0 ? 1 : 0, + borderRightWidth: wallet_old_synced_percent > 0 ? 1 : 0, }} /> )} - {wallet_21 >= 0 && ( + {wallet_new_synced_percent >= 0 && ( 0 ? 1 : 0, + borderRightWidth: wallet_new_synced_percent > 0 ? 1 : 0, }} /> )} - {wallet_3 >= 0 && ( + {wallet_for_synced_percent >= 0 && ( )} - {wallet_old_synced > 0 && ( + {wallet_1 > 0 && ( = ({ closeModal }) => }} /> - {wallet_old_synced + - (translate('report.blocks') as string) + - wallet_old_synced_percent.toFixed(2) + - '%'} + {wallet_1 + (translate('report.blocks') as string) + wallet_old_synced_percent.toFixed(2) + '%'} )} - {wallet_new_synced > 0 && ( + {wallet_2 > 0 && ( = ({ closeModal }) => margin: 5, }} /> - - {wallet_new_synced + - (translate('report.blocks') as string) + - wallet_new_synced_percent.toFixed(2) + - '%'} + + {wallet_2 + (translate('report.blocks') as string) + wallet_new_synced_percent.toFixed(2) + '%'} )} - {wallet_for_synced > 0 && ( + {wallet_3 > 0 && ( = ({ closeModal }) => margin: 5, }} /> - - {wallet_for_synced + - (translate('report.blocks') as string) + - wallet_for_synced_percent.toFixed(2) + - '%'} + + {wallet_3 + (translate('report.blocks') as string) + wallet_for_synced_percent.toFixed(2) + '%'} )} @@ -525,6 +507,7 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => {syncingStatusReport.inProgress && syncingStatusReport.currentBatch > 0 && ( <> = ({ closeModal }) => } /> @@ -542,13 +526,7 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => label={translate('report.secondsperbatch') as string} value={syncingStatusReport.secondsPerBatch.toString()} /> - + )} diff --git a/e2e/README.md b/e2e/README.md index 11bdb8a67..1e0754dc2 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -29,5 +29,5 @@ check installed emulators with compare to the configuration aliases in `.detoxrs` pick a test called `e2e/TESTNAME.test.js` `yarn detox build TESTNAME -c CONFIGURATION` -`yarn detox tests TESTNAME -c CONFIGURATION` +`yarn detox test TESTNAME -c CONFIGURATION` diff --git a/e2e/sync_report.test.js b/e2e/sync_report.test.js new file mode 100644 index 000000000..fab194262 --- /dev/null +++ b/e2e/sync_report.test.js @@ -0,0 +1,70 @@ +const { log, device, by, element } = require('detox'); + +import { loadTestWallet } from "./loadTestWallet.js"; + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +describe('Renders Sync Report data (blocks & batches) correctly.', () => { + // i just pulled this seed out of thin air + it('loads a wallet', loadTestWallet); + + it('When App go to background & back to foreground -> Report Screen: blocks & batches are aligned', async () => { + await element(by.id('header.drawmenu')).tap(); + await element(by.id('menu.syncreport')).tap(); + + // waiting for starting the sync process + await waitFor(element(by.id('syncreport.currentbatch'))).toBeVisible().withTimeout(10000); + + // put the App in background + await device.sendToHome(); + // put the App to sleep because we need some progress in BS to reproduce the bug + await sleep(20000); + // put the App in foregroung again + await device.launchApp({ newInstance: false }); + + // waiting for starting the sync process again + await waitFor(element(by.id('syncreport.currentbatch'))).toBeVisible().withTimeout(10000); + + // getting current batch & total batches from the screen + const batches = element(by.id('syncreport.currentbatch')); + const batches_attributes = await batches.getAttributes(); + const batchNum = Number(batches_attributes.text.split(':')[1].split('of')[0]); + const batchesNum = Number(batches_attributes.text.split(':')[2]); + + // getting blocks now synced from the screen + const blockssyncednow = element(by.id('syncreport.syncednow')); + const blockssyncednow_attributes = await blockssyncednow.getAttributes(); + const blockssyncednowNum = Number(blockssyncednow_attributes.text.split(' ')[0]); + + // getting blocks not yet sync from the screen + const blocksnotyetsynced = element(by.id('syncreport.notyetsynced')); + const blocksnotyetsynced_attributes = await blocksnotyetsynced.getAttributes(); + const blocksnotyetsyncedNum = Number(blocksnotyetsynced_attributes.text.split(' ')[0]); + + // calculating total blocks in this sync process + const blockstotalNum = blockssyncednowNum + blocksnotyetsyncedNum; + + // getting blocks per batch or batch size from the screen + const batchsize = element(by.id('syncreport.blocksperbatch')); + const batchsize_attributes = await batchsize.getAttributes(); + const batchsizeNum = Number(batchsize_attributes.text); + + log.info('batches', batchNum); + log.info('total batches', batchesNum); + log.info('blocks sync now', blockssyncednowNum); + log.info('blocks not yet sync', blocksnotyetsyncedNum); + log.info('blocks total', blockstotalNum); + log.info('batch size:', batchsizeNum); + + // a couple of examples: + // batch: 1 -> means blocks between 0 and 100 + // batch: 33 -> means blocks between 3200 and 3300 + if (blockssyncednowNum < (batchNum * batchsizeNum) - batchsizeNum || blockssyncednowNum > (batchNum * batchsizeNum)) { + fail('The synced blocks are not align with the synced batches'); + } + + if (blockstotalNum < (batchesNum * batchsizeNum) - batchsizeNum || blockstotalNum > (batchesNum * batchsizeNum)) { + fail('The total blocks in this process are not align with the total of batches'); + } + }); +}); diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 556ed6491..7b994a8dc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -85,9 +85,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -96,13 +96,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.13", ] [[package]] @@ -1285,13 +1285,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1381,9 +1381,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libm" @@ -2296,9 +2296,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.6" +version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d097081ed288dfe45699b72f5b5d648e5f15d64d900c7080273baa20c16a6849" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ "bitflags", "errno", @@ -3492,6 +3492,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3775,7 +3784,7 @@ dependencies = [ [[package]] name = "zingo-memo" version = "0.1.0" -source = "git+https://github.com/zingolabs/zingolib?branch=dev#67a87f087f02952c2bae1e9489bb871576bfebeb" +source = "git+https://github.com/zingolabs/zingolib?branch=dev#05cabb7ea2ce4729ac782be64c01ccd7fc74d5c7" dependencies = [ "zcash_address", "zcash_client_backend", @@ -3787,7 +3796,7 @@ dependencies = [ [[package]] name = "zingoconfig" version = "0.1.0" -source = "git+https://github.com/zingolabs/zingolib?branch=dev#67a87f087f02952c2bae1e9489bb871576bfebeb" +source = "git+https://github.com/zingolabs/zingolib?branch=dev#05cabb7ea2ce4729ac782be64c01ccd7fc74d5c7" dependencies = [ "dirs", "http", @@ -3800,7 +3809,7 @@ dependencies = [ [[package]] name = "zingolib" version = "0.2.0" -source = "git+https://github.com/zingolabs/zingolib?branch=dev#67a87f087f02952c2bae1e9489bb871576bfebeb" +source = "git+https://github.com/zingolabs/zingolib?branch=dev#05cabb7ea2ce4729ac782be64c01ccd7fc74d5c7" dependencies = [ "base58", "base64 0.13.1",