Skip to content

Commit 93bcc75

Browse files
release: desktop iota 2.0.5 #7994 from iotaledger/release/desktop-iota-2.0.5
Merge pull request #7994 from iotaledger/release/desktop-iota-2.0.5
2 parents 968a6c2 + 849e14e commit 93bcc75

File tree

105 files changed

+1846
-1250
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+1846
-1250
lines changed

docs/specifications/deep-links.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,21 @@ confirmation on behalf of the user.
2222

2323
## Scheme
2424

25-
The Firefly deep link scheme can be broken down to the following (simple) syntax:
25+
Our system incorporates two specific deeplink schemes—namely IOTA and SHIMMER. Breaking down the Firefly deep link scheme reveals the following simple syntax:
26+
27+
28+
**IOTA**
2629

2730
```
2831
iota[-<stage>]://<context>/<operation>[?param=<param>]
2932
```
3033

34+
**Shimmer**
35+
36+
```
37+
firefly[-<stage>]://<context>/<operation>[?param=<param>]
38+
```
39+
3140
The parameters are as follows:
3241

3342
- `stage` - indicates a specific stage of the app to target, options are:
@@ -40,15 +49,14 @@ The parameters are as follows:
4049
- `operation` - an operation within a specific context (see below for more detail)
4150
- `param` - query parameter(s) relevant for the specified operation
4251

43-
If you wish to target the production version, simply omit this from the prefix:
52+
To target the production version simply don't specify any stages, example for Shimmer:
4453

4554
```
4655
firefly://
4756
```
4857

49-
:::caution
50-
This deep link scheme is **NOT** compatible with Firefly V1, as that version of the application is in maintenance mode.
51-
:::
58+
This prefix is specifically meant for the production version of Firefly. You don't need to add anything else after ``firefly://``
59+
5260

5361
## Contexts
5462

@@ -103,7 +111,7 @@ This operation brings the user to the send confirmation popup:
103111
The deep link structure is as follows:
104112

105113
```
106-
firefly://wallet/sendConfirmation?address=<address>&amount=<amount>[&unit=<unit>][&assetId=<assetId>][&metadata=<metadata>][&tag=<tag>][&giftStorageDeposit=<true|false>][&disableToggleGift=<true|false>][&disableChangeExpiration=<true|false>][&surplus=<surplus>]
114+
firefly://wallet/sendConfirmation?address=<address>&amount=<amount>[&unit=<unit>][&assetId=<assetId>][&metadata=<metadata>][&tag=<tag>][&giftStorageDeposit=<true|false>][&disableToggleGift=<true|false>][&disableChangeExpiration=<true|false>][&surplus=<surplus>][&expiration=<expiration>]
107115
```
108116

109117
The following parameters are **required**:
@@ -115,23 +123,24 @@ The following parameters are **required**:
115123

116124
The following parameters are **optional**:
117125

118-
- `unit` - a specified denomination of the token to use, if applicable (default for IOTA is `Mi`, SMR is `SMR`)
126+
- `unit` - a specified denomination of the token to use, if applicable (default for IOTA is `micro`, SMR is `glow`)
119127
- `assetId` - the identifier of the asset to send, e.g. `4218` (IOTA), `4219` (SMR), or a native token ID (default is base token of the network, i.e. IOTA or SMR)
120128
- `metadata` - a string of text to embed as metadata in the transaction
121129
- `tag` - a string to tag the transaction for indexing purposes
122130
- `giftStorageDeposit` - gifts the tokens used in funding the storage deposit for a transaction
123131
- `disableToggleGift` - prevents the user from being able to toggle the option to gift the storage deposit
124132
- `disableChangeExpiration` - prevents the user from being able to change the expiration time of the transaction
125133
- `surplus` - send additional amounts of the base token when transferring native tokens
134+
- `expiration` - the expiration time of the transaction, e.g. `1w`, `2d`, `5h` or `10m`. Also accepts a UNIX timestamp in milliseconds.
126135

127136
Example:
128137

129-
[!button Click me!](firefly://wallet/sendForm?address=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=10&unit=Gi&giftStorageDeposit=true&surplus=1&metadata=Take%20my%20money)
138+
[!button Click me!](firefly://wallet/sendConfirmation?address=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=10&unit=Gi&giftStorageDeposit=true&surplus=1&metadata=Take%20my%20money&expiration=1h)
130139

131140
Source:
132141

133142
```
134-
firefly://wallet/sendConfirmation?address=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=10&unit=Gi&giftStorageDeposit=true&disableToggleGift=true&surplus=1&metadata=Take%20my%20money
143+
firefly://wallet/sendConfirmation?address=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=10&unit=Gi&giftStorageDeposit=true&disableToggleGift=true&surplus=1&metadata=Take%20my%20money&expiration=1h
135144
```
136145

137146
### Collectibles
-70.8 KB
Loading

docs/static/send-form-popup.png

-39.3 KB
Loading

packages/desktop/.sentrycliignore

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/desktop/components/modals/AccountActionsMenu.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
modal?.close()
3535
}
3636
37+
function onWithdrawFromL2Click(): void {
38+
openPopup({ id: PopupId.WithdrawFromL2 })
39+
modal?.close()
40+
}
41+
3742
function onVerifyAddressClick(): void {
3843
const ADDRESS_INDEX = 0
3944
checkOrConnectLedger(() => {
@@ -72,13 +77,16 @@
7277
<Modal bind:this={modal} {...$$restProps}>
7378
<account-actions-menu class="flex flex-col">
7479
<MenuItem icon={Icon.Doc} title={localize('actions.viewBalanceBreakdown')} onClick={onViewBalanceClick} />
75-
{#if $activeProfile?.network?.id === NetworkId.Iota}
80+
{#if $activeProfile?.network?.id === NetworkId.Iota || $activeProfile?.network?.id === NetworkId.IotaAlphanet}
7681
<MenuItem
7782
icon={Icon.Timer}
7883
title={localize('actions.viewAddressHistory')}
7984
onClick={onViewAddressHistoryClick}
8085
/>
8186
{/if}
87+
{#if $activeProfile?.network?.id === NetworkId.Shimmer || $activeProfile?.network?.id === NetworkId.Testnet}
88+
<MenuItem icon={Icon.Transfer} title={localize('actions.withdrawFromL2')} onClick={onWithdrawFromL2Click} />
89+
{/if}
8290
<MenuItem icon={Icon.Customize} title={localize('actions.customizeAcount')} onClick={onCustomiseAccountClick} />
8391
{#if $isActiveLedgerProfile}
8492
<MenuItem

packages/desktop/components/popups/AddressHistoryPopup.svelte

Lines changed: 144 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +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'
4-
import { truncateString } from '@core/utils'
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'
9+
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'
914
10-
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
39+
40+
function onCopyClick(): void {
41+
const addresses = knownAddresses.map((address) => address.address).join(',')
42+
setClipboard(addresses)
43+
}
1144
1245
onMount(() => {
13-
getSelectedAccount()
14-
?.addresses()
15-
.then((_addressList) => {
16-
addressList = _addressList?.sort((a, b) => a.keyIndex - b.keyIndex) ?? []
17-
})
18-
.catch((err) => {
19-
console.error(err)
20-
addressList = []
21-
})
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+
}
2267
})
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+
}
23139
</script>
24140

25141
<div class="flex flex-col space-y-6">
26142
<Text type={TextType.h3} fontWeight={FontWeight.semibold} lineHeight="6">
27143
{localize('popups.addressHistory.title')}
28144
</Text>
29-
<Text fontSize="15" color="gray-700" classes="text-left">{localize('popups.addressHistory.disclaimer')}</Text>
30-
{#if addressList}
31-
{#if addressList.length > 0}
145+
{#if knownAddresses}
146+
{#if knownAddresses.length > 0}
32147
<div class="w-full flex-col space-y-2 virtual-list-wrapper">
33-
<VirtualList items={addressList} let:item>
148+
<VirtualList items={knownAddresses} let:item>
34149
<div class="mb-1">
35150
<KeyValueBox
36151
isCopyable
@@ -58,6 +173,18 @@
58173
</div>
59174
{/if}
60175
</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>
61188

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

packages/desktop/components/popups/Popup.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import VestingCollectPopup from './VestingCollectPopup.svelte'
5858
import PayoutDetailsPopup from './PayoutDetailsPopup.svelte'
5959
import VestingRewardsFinderPopup from './VestingRewardsFinderPopup.svelte'
60+
import WithdrawFromL2Popup from './WithdrawFromL2Popup.svelte'
6061
6162
export let id: PopupId
6263
export let props: any
@@ -144,6 +145,7 @@
144145
[PopupId.VestingCollect]: VestingCollectPopup,
145146
[PopupId.PayoutDetails]: PayoutDetailsPopup,
146147
[PopupId.VestingRewardsFinder]: VestingRewardsFinderPopup,
148+
[PopupId.WithdrawFromL2]: WithdrawFromL2Popup,
147149
}
148150
149151
function onKey(event: KeyboardEvent): void {

0 commit comments

Comments
 (0)