Skip to content

Commit 5b9cfd9

Browse files
Merge remote-tracking branch 'origin/develop' into feat/copy-addresses-to-clipboard
2 parents 024c4c0 + 6ee934c commit 5b9cfd9

21 files changed

+1219
-1070
lines changed

Diff for: packages/desktop/components/modals/AccountActionsMenu.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
<Modal bind:this={modal} {...$$restProps}>
7373
<account-actions-menu class="flex flex-col">
7474
<MenuItem icon={Icon.Doc} title={localize('actions.viewBalanceBreakdown')} onClick={onViewBalanceClick} />
75-
{#if $activeProfile?.network?.id === NetworkId.Iota}
75+
{#if $activeProfile?.network?.id === NetworkId.Iota || $activeProfile?.network?.id === NetworkId.IotaAlphanet}
7676
<MenuItem
7777
icon={Icon.Timer}
7878
title={localize('actions.viewAddressHistory')}

Diff for: packages/desktop/components/popups/AddressHistoryPopup.svelte

+139-24
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,151 @@
11
<script lang="ts">
2-
import { getSelectedAccount } from '@core/account'
2+
import { selectedAccount } from '@core/account'
3+
import { handleError } from '@core/error/handlers/handleError'
34
import { localize } from '@core/i18n'
5+
import { CHRONICLE_ADDRESS_HISTORY_ROUTE, CHRONICLE_URLS } from '@core/network/constants/chronicle-urls.constant'
6+
import { fetchWithTimeout } from '@core/nfts'
7+
import { checkActiveProfileAuth, getActiveProfile, updateAccountPersistedDataOnActiveProfile } from '@core/profile'
8+
import { getProfileManager } from '@core/profile-manager/stores'
49
import { setClipboard, truncateString } from '@core/utils'
510
import { AccountAddress } from '@iota/sdk/out/types'
611
import VirtualList from '@sveltejs/svelte-virtual-list'
7-
import { FontWeight, KeyValueBox, Spinner, Text, TextType } from 'shared/components'
12+
import { Button, FontWeight, KeyValueBox, Spinner, Text, TextType } from 'shared/components'
813
import { onMount } from 'svelte'
9-
import { Icon } from '@ui'
10-
import { Icon as IconEnum } from '@auxiliary/icon'
1114
12-
let addressList: AccountAddress[] | undefined = undefined
15+
interface AddressHistory {
16+
address: string
17+
items: [
18+
{
19+
milestoneIndex: number
20+
milestoneTimestamp: number
21+
outputId: string
22+
isSpent: boolean
23+
}
24+
]
25+
}
26+
27+
const activeProfile = getActiveProfile()
28+
const ADDRESS_GAP_LIMIT = 20
29+
30+
let knownAddresses: AccountAddress[] = []
31+
32+
$: accountIndex = $selectedAccount?.index
33+
$: network = activeProfile?.network?.id
34+
35+
let searchURL: string
36+
let searchAddressStartIndex = 0
37+
let currentSearchGap = 0
38+
let isBusy = false
1339
1440
function onCopyClick(): void {
15-
const addresses = addressList.map((address) => address.address).join(',')
41+
const addresses = knownAddresses.map((address) => address.address).join(',')
1642
setClipboard(addresses)
1743
}
1844
1945
onMount(() => {
20-
getSelectedAccount()
21-
?.addresses()
22-
.then((_addressList) => {
23-
addressList = _addressList?.sort((a, b) => a.keyIndex - b.keyIndex) ?? []
24-
})
25-
.catch((err) => {
26-
console.error(err)
27-
addressList = []
28-
})
46+
knownAddresses = $selectedAccount?.knownAddresses
47+
if (!knownAddresses?.length) {
48+
isBusy = true
49+
$selectedAccount
50+
.addresses()
51+
.then((_knownAddresses) => {
52+
knownAddresses = sortAddresses(_knownAddresses)
53+
updateAccountPersistedDataOnActiveProfile(accountIndex, { knownAddresses })
54+
isBusy = false
55+
})
56+
.finally(() => {
57+
isBusy = false
58+
})
59+
}
60+
61+
if (CHRONICLE_URLS[network] && CHRONICLE_URLS[network].length > 0) {
62+
const chronicleRoot = CHRONICLE_URLS[network][0]
63+
searchURL = `${chronicleRoot}${CHRONICLE_ADDRESS_HISTORY_ROUTE}`
64+
} else {
65+
throw new Error(localize('popups.addressHistory.errorNoChronicle'))
66+
}
2967
})
68+
69+
async function isAddressWithHistory(address: string): Promise<boolean> {
70+
try {
71+
const response = await fetchWithTimeout(`${searchURL}${address}`, 3, { method: 'GET' })
72+
const addressHistory: AddressHistory = await response.json()
73+
return addressHistory?.items?.length > 0
74+
} catch (err) {
75+
throw new Error(localize('popups.addressHistory.errorFailedFetch'))
76+
}
77+
}
78+
79+
async function generateNextUnknownAddress(): Promise<[string, number]> {
80+
let nextUnknownAddress: string
81+
try {
82+
do {
83+
nextUnknownAddress = await getProfileManager().generateEd25519Address(
84+
accountIndex,
85+
searchAddressStartIndex
86+
)
87+
88+
searchAddressStartIndex++
89+
} while (knownAddresses.map((accountAddress) => accountAddress.address).includes(nextUnknownAddress))
90+
} catch (err) {
91+
throw new Error(localize('popups.addressHistory.errorFailedGenerate'))
92+
}
93+
94+
return [nextUnknownAddress, searchAddressStartIndex - 1]
95+
}
96+
97+
async function search(): Promise<void> {
98+
currentSearchGap = 0
99+
const tmpKnownAddresses = [...knownAddresses]
100+
while (currentSearchGap < ADDRESS_GAP_LIMIT) {
101+
const [nextAddressToCheck, addressIndex] = await generateNextUnknownAddress()
102+
if (!nextAddressToCheck) {
103+
isBusy = false
104+
break
105+
}
106+
107+
const hasHistory = await isAddressWithHistory(nextAddressToCheck)
108+
if (hasHistory) {
109+
const accountAddress: AccountAddress = {
110+
address: nextAddressToCheck,
111+
keyIndex: addressIndex,
112+
internal: false,
113+
used: true,
114+
}
115+
116+
tmpKnownAddresses.push(accountAddress)
117+
} else {
118+
currentSearchGap++
119+
}
120+
}
121+
knownAddresses = sortAddresses(tmpKnownAddresses)
122+
updateAccountPersistedDataOnActiveProfile(accountIndex, { knownAddresses })
123+
}
124+
125+
async function handleSearchClick(): Promise<void> {
126+
isBusy = true
127+
try {
128+
await checkActiveProfileAuth(search, { stronghold: true, ledger: true })
129+
} catch (err) {
130+
handleError(err)
131+
} finally {
132+
isBusy = false
133+
}
134+
}
135+
136+
function sortAddresses(addresses: AccountAddress[] = []): AccountAddress[] {
137+
return addresses.sort((a, b) => a.keyIndex - b.keyIndex)
138+
}
30139
</script>
31140

32141
<div class="flex flex-col space-y-6">
33142
<Text type={TextType.h3} fontWeight={FontWeight.semibold} lineHeight="6">
34143
{localize('popups.addressHistory.title')}
35144
</Text>
36-
<div class="flex w-full items-center justify-between">
37-
<Text fontSize="15" color="gray-700" classes="text-left">{localize('popups.addressHistory.disclaimer')}</Text>
38-
<button on:click={onCopyClick} class="text-gray-500 dark:text-gray-100 p2" type="button">
39-
<Icon icon={IconEnum.Copy} />
40-
</button>
41-
</div>
42-
{#if addressList}
43-
{#if addressList.length > 0}
145+
{#if knownAddresses}
146+
{#if knownAddresses.length > 0}
44147
<div class="w-full flex-col space-y-2 virtual-list-wrapper">
45-
<VirtualList items={addressList} let:item>
148+
<VirtualList items={knownAddresses} let:item>
46149
<div class="mb-1">
47150
<KeyValueBox
48151
isCopyable
@@ -70,6 +173,18 @@
70173
</div>
71174
{/if}
72175
</div>
176+
<div class="flex flex-row flex-nowrap w-full space-x-4 mt-6">
177+
<div class="flex w-full justify-center pt-8 space-x-4">
178+
<Button outline classes="w-1/2" onClick={onCopyClick}>{localize('actions.copy')}</Button>
179+
<Button
180+
classes="w-1/2"
181+
onClick={handleSearchClick}
182+
disabled={isBusy}
183+
{isBusy}
184+
busyMessage={localize('actions.searching')}>{localize('actions.search')}</Button
185+
>
186+
</div>
187+
</div>
73188

74189
<style lang="scss">
75190
.virtual-list-wrapper :global(svelte-virtual-list-viewport) {

Diff for: packages/desktop/components/popups/send/SendConfirmationPopup.svelte

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
let minimumStorageDeposit = 0
6060
let visibleSurplus: number | undefined = undefined
6161
62+
let isPreparingOutput = false
63+
6264
$: expirationTimePicker?.setNull(giftStorageDeposit)
6365
6466
$: isBaseTokenTransfer =
@@ -101,6 +103,8 @@
101103
})
102104
103105
async function rebuildTransactionOutput(): Promise<void> {
106+
isPreparingOutput = true
107+
104108
updateNewTransactionDetails({
105109
type: transactionType,
106110
expirationDate,
@@ -126,6 +130,8 @@
126130
}
127131
} catch (err) {
128132
handleError(err)
133+
} finally {
134+
isPreparingOutput = false
129135
}
130136
}
131137
@@ -261,9 +267,12 @@
261267
classes="w-full"
262268
onClick={onConfirmClick}
263269
disabled={isTransferring ||
270+
isPreparingOutput ||
264271
(layer2Parameters?.networkAddress && !$newTransactionDetails?.layer2Parameters?.gasBudget)}
265272
isBusy={isTransferring ||
273+
isPreparingOutput ||
266274
(layer2Parameters?.networkAddress && !$newTransactionDetails?.layer2Parameters?.gasBudget)}
275+
busyMessage={isPreparingOutput ? 'Preparing' : ''}
267276
>
268277
{localize('actions.send')}
269278
</Button>

Diff for: packages/desktop/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "desktop",
33
"productName": "Firefly Shimmer",
4-
"version": "2.1.9",
4+
"version": "2.1.10",
55
"description": "Official wallet application of Shimmer",
66
"main": "public/build/main.js",
77
"repository": "[email protected]:iotaledger/firefly.git",

Diff for: packages/desktop/views/dashboard/vesting/Vesting.svelte

+1-11
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,10 @@
4141
let modal: Modal
4242
let timeUntilNextPayout = DEFAULT_EMPTY_VALUE_STRING
4343
let minRequiredStorageDeposit: number | null
44-
let hasOutputsToConsolidate = false
4544
4645
$: ({ baseCoin } = $selectedAccountAssets[$activeProfile?.network?.id])
4746
$: hasTransactionInProgress =
4847
$selectedAccount?.isTransferring || $selectedAccount.hasConsolidatingOutputsTransactionInProgress
49-
$: $selectedAccount, areOutputsReadyForConsolidation()
5048
$: vestingOverview = [
5149
{
5250
title: localize('views.vesting.overview.unlocked'),
@@ -71,20 +69,12 @@
7169
$selectedAccountVestingUnclaimedFunds > 0 &&
7270
!hasTransactionInProgress &&
7371
minRequiredStorageDeposit !== null &&
74-
$selectedAccount?.balances?.baseCoin?.available > minRequiredStorageDeposit &&
75-
hasOutputsToConsolidate
72+
$selectedAccount?.balances?.baseCoin?.available > minRequiredStorageDeposit
7673
7774
onMount(() => {
7875
getMinRequiredStorageDeposit()
7976
})
8077
81-
function areOutputsReadyForConsolidation(): void {
82-
$selectedAccount
83-
.prepareConsolidateOutputs({ force: false, outputThreshold: 2 })
84-
.then(() => (hasOutputsToConsolidate = true))
85-
.catch(() => (hasOutputsToConsolidate = false))
86-
}
87-
8878
function getMinRequiredStorageDeposit() {
8979
getRequiredStorageDepositForMinimalBasicOutput()
9080
.then((deposit) => (minRequiredStorageDeposit = deposit))

0 commit comments

Comments
 (0)