From 9bbc7a8b985aa1aa8b3fc9a1ff0d345c9a4efbd6 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Wed, 30 Oct 2024 16:12:04 +0100 Subject: [PATCH 01/19] feat: fetch stardust basic and nft objects --- apps/core/src/constants/features.enum.ts | 4 +- .../components/sidebar/Sidebar.tsx | 4 +- .../app/(protected)/migrations/page.tsx | 81 ++++++++++++++++--- .../lib/constants/migration.constants.ts | 6 ++ 4 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 apps/wallet-dashboard/lib/constants/migration.constants.ts diff --git a/apps/core/src/constants/features.enum.ts b/apps/core/src/constants/features.enum.ts index 3670db4edc1..ec99f53b815 100644 --- a/apps/core/src/constants/features.enum.ts +++ b/apps/core/src/constants/features.enum.ts @@ -18,6 +18,6 @@ export enum Feature { NetworkOutageOverride = 'network-outage-override', ModuleSourceVerification = 'module-source-verification', WalletEffectsOnlySharedTransaction = 'wallet-effects-only-shared-transaction', - StardustMigration = 'migration', - SupplyIncreaseVesting = 'supply-increase-vesting', + WalletDashboardMigration = 'migration', + WalletDashboardSupplyIncreaseVesting = 'supply-increase-vesting', } diff --git a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx index 03742f9e8e4..bb2df88d1c7 100644 --- a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx +++ b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx @@ -9,8 +9,8 @@ import { useFeature } from '@growthbook/growthbook-react'; export function Sidebar() { const featureFlags = { - Migrations: useFeature(Feature.StardustMigration).value, - Vesting: useFeature(Feature.SupplyIncreaseVesting).value, + Migrations: useFeature(Feature.WalletDashboardMigration).value, + Vesting: useFeature(Feature.WalletDashboardSupplyIncreaseVesting).value, }; const filteredRoutes = PROTECTED_ROUTES.filter(({ title }) => { diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index 01f8a8fbb4f..3549f4d2080 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -2,24 +2,79 @@ // SPDX-License-Identifier: Apache-2.0 'use client'; -import { useFeature } from '@growthbook/growthbook-react'; -import { Feature } from '@iota/core'; -import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; +import { VirtualList } from '@/components'; +import { + STARDUST_BASIC_OUTPUT_TYPE, + STARDUST_NFT_OUTPUT_TYPE, +} from '@/lib/constants/migration.constants'; +import { useGetOwnedObjects } from '@iota/core'; +import { useCurrentAccount, useIotaClientContext } from '@iota/dapp-kit'; +import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; function MigrationDashboardPage(): JSX.Element { - const router = useRouter(); - const stardustMigrationEnabled = useFeature(Feature.StardustMigration).value; + const account = useCurrentAccount(); + const { network } = useIotaClientContext(); + const { explorer } = getNetwork(network); - useEffect(() => { - if (!stardustMigrationEnabled) { - router.push('/'); - } - }, [stardustMigrationEnabled, router]); + const { + data: basicOutputObjects, + fetchNextPage: fetchNextPageBasic, + hasNextPage: hasNextPageBasic, + isFetchingNextPage: isFetchingNextPageBasic, + } = useGetOwnedObjects(account?.address || '', { + StructType: STARDUST_BASIC_OUTPUT_TYPE, + }); + const { + data: nftOutputObjects, + fetchNextPage: fetchNextPageNft, + hasNextPage: hasNextPageNft, + isFetchingNextPage: isFetchingNextPageNft, + } = useGetOwnedObjects(account?.address || '', { + StructType: STARDUST_NFT_OUTPUT_TYPE, + }); + + const basicOutputs = + basicOutputObjects?.pages + .flatMap((page) => page.data) + .filter((asset) => asset.data && asset.data.objectId) + .map((response) => response.data!) ?? []; + + const nftOutputs = + nftOutputObjects?.pages + .flatMap((page) => page.data) + .filter((asset) => asset.data && asset.data.objectId) + .map((response) => response.data!) ?? []; + + const virtualItem = (asset: IotaObjectData): JSX.Element => ( + + {asset.objectId} + + ); return ( -
-

MIGRATIONS

+
+
+

Basic Outputs

+ 30} + render={virtualItem} + /> +
+
+

Nft Outputs

+ 30} + render={virtualItem} + /> +
); } diff --git a/apps/wallet-dashboard/lib/constants/migration.constants.ts b/apps/wallet-dashboard/lib/constants/migration.constants.ts new file mode 100644 index 00000000000..66e3d95b9c5 --- /dev/null +++ b/apps/wallet-dashboard/lib/constants/migration.constants.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 +export const STARDUST_PACKAGE_ID = + '000000000000000000000000000000000000000000000000000000000000107a'; +export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; +export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; From 1f67787d1e6f15d07c48dbd486983f19f2a487bf Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 31 Oct 2024 13:27:49 +0100 Subject: [PATCH 02/19] feat: organize stardust objects on migratable and unmigratable --- .../app/(protected)/migrations/page.tsx | 75 ++++++++++--------- apps/wallet-dashboard/lib/interfaces/index.ts | 1 + .../lib/interfaces/migration.interface.ts | 47 ++++++++++++ apps/wallet-dashboard/lib/utils/index.ts | 1 + apps/wallet-dashboard/lib/utils/migration.ts | 55 ++++++++++++++ 5 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 apps/wallet-dashboard/lib/interfaces/migration.interface.ts create mode 100644 apps/wallet-dashboard/lib/utils/migration.ts diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index 3549f4d2080..37d89caaec7 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -3,48 +3,43 @@ 'use client'; import { VirtualList } from '@/components'; +import { useGetCurrentEpochStartTimestamp } from '@/hooks'; import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, } from '@/lib/constants/migration.constants'; -import { useGetOwnedObjects } from '@iota/core'; +import { groupStardustObjectsByMigrationStatus } from '@/lib/utils'; +import { useGetAllOwnedObjects } from '@iota/core'; import { useCurrentAccount, useIotaClientContext } from '@iota/dapp-kit'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; function MigrationDashboardPage(): JSX.Element { const account = useCurrentAccount(); + const address = account?.address || ''; const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); + const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); - const { - data: basicOutputObjects, - fetchNextPage: fetchNextPageBasic, - hasNextPage: hasNextPageBasic, - isFetchingNextPage: isFetchingNextPageBasic, - } = useGetOwnedObjects(account?.address || '', { + const { data: basicOutputObjects } = useGetAllOwnedObjects(address, { StructType: STARDUST_BASIC_OUTPUT_TYPE, }); - const { - data: nftOutputObjects, - fetchNextPage: fetchNextPageNft, - hasNextPage: hasNextPageNft, - isFetchingNextPage: isFetchingNextPageNft, - } = useGetOwnedObjects(account?.address || '', { + const { data: nftOutputObjects } = useGetAllOwnedObjects(address, { StructType: STARDUST_NFT_OUTPUT_TYPE, }); - const basicOutputs = - basicOutputObjects?.pages - .flatMap((page) => page.data) - .filter((asset) => asset.data && asset.data.objectId) - .map((response) => response.data!) ?? []; - - const nftOutputs = - nftOutputObjects?.pages - .flatMap((page) => page.data) - .filter((asset) => asset.data && asset.data.objectId) - .map((response) => response.data!) ?? []; + const { migratable: migratableBasicOutputs, unmigratable: unmigratableBasicOutputs } = + groupStardustObjectsByMigrationStatus( + basicOutputObjects ?? [], + Number(currentEpochMs), + address, + ); + const { migratable: migratableNftOutputs, unmigratable: unmigratableNftOutputs } = + groupStardustObjectsByMigrationStatus( + nftOutputObjects ?? [], + Number(currentEpochMs), + address, + ); const virtualItem = (asset: IotaObjectData): JSX.Element => ( {asset.objectId} @@ -52,25 +47,35 @@ function MigrationDashboardPage(): JSX.Element { ); return ( -
+
+
+

Migratable Basic Outputs: {migratableBasicOutputs.length}

+ 30} + render={virtualItem} + /> +
+
+

Unmigratable Basic Outputs: {unmigratableBasicOutputs.length}

+ 30} + render={virtualItem} + /> +
-

Basic Outputs

+

Migratable NFT Outputs: {migratableNftOutputs.length}

30} render={virtualItem} />
-

Nft Outputs

+

Unmigratable NFT Outputs: {unmigratableNftOutputs.length}

30} render={virtualItem} /> diff --git a/apps/wallet-dashboard/lib/interfaces/index.ts b/apps/wallet-dashboard/lib/interfaces/index.ts index eeab2cb4f5c..679bc45212f 100644 --- a/apps/wallet-dashboard/lib/interfaces/index.ts +++ b/apps/wallet-dashboard/lib/interfaces/index.ts @@ -3,5 +3,6 @@ export * from './transactions.interface'; export * from './timelock.interface'; +export * from './migration.interface'; export * from './vesting.interface'; export * from './appRoute.interface'; diff --git a/apps/wallet-dashboard/lib/interfaces/migration.interface.ts b/apps/wallet-dashboard/lib/interfaces/migration.interface.ts new file mode 100644 index 00000000000..df35ae29112 --- /dev/null +++ b/apps/wallet-dashboard/lib/interfaces/migration.interface.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +type ExpirationUnlockCondition = { + owner: string; + return_address: string; + unix_time: number; +}; +type StorageDepositReturnUnlockCondition = { + return_address: string; + return_amount: string; +}; +type TimelockUnlockCondition = { + unix_time: number; +}; + +export type CommonOutputObject = { + id: { id: string }; + balance: string; + native_tokens: { + type: string; + fields: { id: { id: string }; size: string }; + }; +}; + +export interface CommonOutputObjectWithUc extends CommonOutputObject { + expiration_uc?: { + type: string; + fields: ExpirationUnlockCondition; + }; + storage_deposit_return_uc?: { + type: string; + fields: StorageDepositReturnUnlockCondition; + }; + timelock_uc?: { + type: string; + fields: TimelockUnlockCondition; + }; +} + +export interface BasicOutputObject extends CommonOutputObjectWithUc { + metadata?: number[]; + tag?: number[]; + sender?: string; +} + +export interface NftOutputObject extends CommonOutputObjectWithUc {} diff --git a/apps/wallet-dashboard/lib/utils/index.ts b/apps/wallet-dashboard/lib/utils/index.ts index 78b0f2eb9c5..27ddc5bc265 100644 --- a/apps/wallet-dashboard/lib/utils/index.ts +++ b/apps/wallet-dashboard/lib/utils/index.ts @@ -5,5 +5,6 @@ export * from './indexGenerator'; export * from './vesting'; export * from './time'; export * from './timelock'; +export * from './migration'; export * from './transaction'; export * from './growthbook'; diff --git a/apps/wallet-dashboard/lib/utils/migration.ts b/apps/wallet-dashboard/lib/utils/migration.ts new file mode 100644 index 00000000000..c069c266da9 --- /dev/null +++ b/apps/wallet-dashboard/lib/utils/migration.ts @@ -0,0 +1,55 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { IotaObjectData } from '@iota/iota-sdk/client'; +import { CommonOutputObjectWithUc } from '../interfaces/migration.interface'; + +export type StardustMigrationGroupedObjects = { + migratable: IotaObjectData[]; + unmigratable: IotaObjectData[]; +}; + +export function groupStardustObjectsByMigrationStatus( + stardustOutputObjects: IotaObjectData[], + epochTimestamp: number, + address: string, +): StardustMigrationGroupedObjects { + const migratable: IotaObjectData[] = []; + const unmigratable: IotaObjectData[] = []; + + const epochUnix = epochTimestamp / 1000; + + for (const outputObject of stardustOutputObjects) { + if (!outputObject) { + unmigratable.push(outputObject); + continue; + } + const outputObjectFields = ( + outputObject.content as unknown as { + fields: CommonOutputObjectWithUc; + } + ).fields; + + if (outputObjectFields.expiration_uc) { + const unlockableAddress = + outputObjectFields.expiration_uc.fields.unix_time <= epochUnix + ? outputObjectFields.expiration_uc.fields.return_address + : outputObjectFields.expiration_uc.fields.owner; + if (unlockableAddress !== address) { + unmigratable.push(outputObject); + continue; + } + } + if ( + outputObjectFields.timelock_uc && + outputObjectFields.timelock_uc.fields.unix_time > epochUnix + ) { + unmigratable.push(outputObject); + continue; + } + + migratable.push(outputObject); + } + + return { migratable, unmigratable }; +} From 2ea4ee13fe5afa77e4d581b0f2e118d4e28bc742 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 31 Oct 2024 13:36:30 +0100 Subject: [PATCH 03/19] fix: migration constants and variable nameing --- apps/wallet-dashboard/app/(protected)/vesting/page.tsx | 4 +++- apps/wallet-dashboard/lib/constants/migration.constants.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx index d04c672a705..d62ce5b4b5f 100644 --- a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx @@ -44,7 +44,9 @@ function VestingDashboardPage(): JSX.Element { const { data: timelockedStakedObjects } = useGetTimelockedStakedObjects(account?.address || ''); const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction(); - const supplyIncreaseVestingEnabled = useFeature(Feature.SupplyIncreaseVesting).value; + const supplyIncreaseVestingEnabled = useFeature( + Feature.WalletDashboardSupplyIncreaseVesting, + ).value; const timelockedMapped = mapTimelockObjects(timelockedObjects || []); const timelockedstakedMapped = formatDelegatedTimelockedStake(timelockedStakedObjects || []); diff --git a/apps/wallet-dashboard/lib/constants/migration.constants.ts b/apps/wallet-dashboard/lib/constants/migration.constants.ts index 66e3d95b9c5..41c57144881 100644 --- a/apps/wallet-dashboard/lib/constants/migration.constants.ts +++ b/apps/wallet-dashboard/lib/constants/migration.constants.ts @@ -3,4 +3,4 @@ export const STARDUST_PACKAGE_ID = '000000000000000000000000000000000000000000000000000000000000107a'; export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; -export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; +export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<0x2::iota::IOTA>`; From c7509fe7cc9cc870820a103c64f4745a036ba91c Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 31 Oct 2024 14:27:22 +0100 Subject: [PATCH 04/19] fix: variable naming --- apps/apps-backend/src/features/features.controller.ts | 4 ++-- apps/core/src/constants/features.enum.ts | 4 ++-- .../app/(protected)/components/sidebar/Sidebar.tsx | 4 ++-- apps/wallet-dashboard/app/(protected)/vesting/page.tsx | 4 +--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/apps-backend/src/features/features.controller.ts b/apps/apps-backend/src/features/features.controller.ts index e71e4b326a4..82bbafbe419 100644 --- a/apps/apps-backend/src/features/features.controller.ts +++ b/apps/apps-backend/src/features/features.controller.ts @@ -64,10 +64,10 @@ export class FeaturesController { [Feature.AccountFinder]: { defaultValue: false, }, - [Feature.WalletDashboardMigration]: { + [Feature.StardustMigration]: { defaultValue: false, }, - [Feature.WalletDashboardSupplyIncreaseVesting]: { + [Feature.SupplyIncreaseVesting]: { defaultValue: false, }, }, diff --git a/apps/core/src/constants/features.enum.ts b/apps/core/src/constants/features.enum.ts index ec99f53b815..3670db4edc1 100644 --- a/apps/core/src/constants/features.enum.ts +++ b/apps/core/src/constants/features.enum.ts @@ -18,6 +18,6 @@ export enum Feature { NetworkOutageOverride = 'network-outage-override', ModuleSourceVerification = 'module-source-verification', WalletEffectsOnlySharedTransaction = 'wallet-effects-only-shared-transaction', - WalletDashboardMigration = 'migration', - WalletDashboardSupplyIncreaseVesting = 'supply-increase-vesting', + StardustMigration = 'migration', + SupplyIncreaseVesting = 'supply-increase-vesting', } diff --git a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx index bb2df88d1c7..03742f9e8e4 100644 --- a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx +++ b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx @@ -9,8 +9,8 @@ import { useFeature } from '@growthbook/growthbook-react'; export function Sidebar() { const featureFlags = { - Migrations: useFeature(Feature.WalletDashboardMigration).value, - Vesting: useFeature(Feature.WalletDashboardSupplyIncreaseVesting).value, + Migrations: useFeature(Feature.StardustMigration).value, + Vesting: useFeature(Feature.SupplyIncreaseVesting).value, }; const filteredRoutes = PROTECTED_ROUTES.filter(({ title }) => { diff --git a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx index d62ce5b4b5f..d04c672a705 100644 --- a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx @@ -44,9 +44,7 @@ function VestingDashboardPage(): JSX.Element { const { data: timelockedStakedObjects } = useGetTimelockedStakedObjects(account?.address || ''); const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction(); - const supplyIncreaseVestingEnabled = useFeature( - Feature.WalletDashboardSupplyIncreaseVesting, - ).value; + const supplyIncreaseVestingEnabled = useFeature(Feature.SupplyIncreaseVesting).value; const timelockedMapped = mapTimelockObjects(timelockedObjects || []); const timelockedstakedMapped = formatDelegatedTimelockedStake(timelockedStakedObjects || []); From 35af4d30df51fceb6caf70b2bb5d58e21fcc377e Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Mon, 4 Nov 2024 10:56:39 +0100 Subject: [PATCH 05/19] feat:add migration popup --- .../app/(protected)/migrations/page.tsx | 50 ++++++++++- .../components/Popup/Popups/MigratePopup.tsx | 82 +++++++++++++++++++ .../hooks/useMigrationTransaction.ts | 32 ++++++++ 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx create mode 100644 apps/wallet-dashboard/hooks/useMigrationTransaction.ts diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index 37d89caaec7..567051ab9d2 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -3,19 +3,25 @@ 'use client'; import { VirtualList } from '@/components'; -import { useGetCurrentEpochStartTimestamp } from '@/hooks'; +import MigratePopup from '@/components/Popup/Popups/MigratePopup'; +import { useGetCurrentEpochStartTimestamp, usePopups } from '@/hooks'; import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, } from '@/lib/constants/migration.constants'; import { groupStardustObjectsByMigrationStatus } from '@/lib/utils'; +import { Button } from '@iota/apps-ui-kit'; import { useGetAllOwnedObjects } from '@iota/core'; -import { useCurrentAccount, useIotaClientContext } from '@iota/dapp-kit'; +import { useCurrentAccount, useIotaClient, useIotaClientContext } from '@iota/dapp-kit'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; +import { useQueryClient } from '@tanstack/react-query'; function MigrationDashboardPage(): JSX.Element { const account = useCurrentAccount(); const address = account?.address || ''; + const { openPopup, closePopup } = usePopups(); + const queryClient = useQueryClient(); + const iotaClient = useIotaClient(); const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); @@ -46,6 +52,45 @@ function MigrationDashboardPage(): JSX.Element {
); + function handleOnSuccess(digest: string): void { + iotaClient + .waitForTransaction({ + digest, + }) + .then(() => { + queryClient.invalidateQueries({ + queryKey: [ + 'get-all-owned-objects', + account?.address, + { + StructType: STARDUST_BASIC_OUTPUT_TYPE, + }, + ], + }); + queryClient.invalidateQueries({ + queryKey: [ + 'get-all-owned-objects', + account?.address, + { + StructType: STARDUST_NFT_OUTPUT_TYPE, + }, + ], + }); + }); + } + function openMigratePopup(): void { + const stardustOutputObjects = [...migratableBasicOutputs, ...migratableNftOutputs]; + if (stardustOutputObjects.length > 0) { + openPopup( + , + ); + } + } + return (
@@ -80,6 +125,7 @@ function MigrationDashboardPage(): JSX.Element { render={virtualItem} />
+
); } diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx new file mode 100644 index 00000000000..281f8985e92 --- /dev/null +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -0,0 +1,82 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import { VirtualList } from '@/components'; +import { + useCurrentAccount, + useIotaClientContext, + useSignAndExecuteTransaction, +} from '@iota/dapp-kit'; +import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; +import { useMigrationTransaction } from '@/hooks/useMigrationTransaction'; +import { Button } from '@iota/apps-ui-kit'; +import { useNotifications } from '@/hooks'; +import { NotificationType } from '@/stores/notificationStore'; + +interface MigratePopupProps { + stardustOutputObjects: IotaObjectData[]; + closePopup: () => void; + onSuccess?: (digest: string) => void; +} + +function MigratePopup({ + stardustOutputObjects, + closePopup, + onSuccess, +}: MigratePopupProps): JSX.Element { + const account = useCurrentAccount(); + const { addNotification } = useNotifications(); + const { data: migrateData } = useMigrationTransaction( + stardustOutputObjects, + account?.address || '', + ); + const { network } = useIotaClientContext(); + const { explorer } = getNetwork(network); + const { mutateAsync: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction(); + + async function handleMigrate(): Promise { + if (!migrateData) return; + signAndExecuteTransaction( + { + transaction: migrateData.transaction, + }, + { + onSuccess: (tx) => { + if (onSuccess) { + onSuccess(tx.digest); + } + }, + }, + ) + .then(() => { + closePopup(); + addNotification('Migration transaction has been sent'); + }) + .catch(() => { + addNotification('Migration transaction was not sent', NotificationType.Error); + }); + } + + const virtualItem = (asset: IotaObjectData): JSX.Element => ( + + {asset.objectId} + + ); + return ( +
+
+

Migratable Outputs: {stardustOutputObjects.length}

+ 30} + render={virtualItem} + /> +
+

Gas Fees: {migrateData?.gasBudget?.toString() || '--'}

+
+ ); +} + +export default MigratePopup; diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts new file mode 100644 index 00000000000..028af412aaf --- /dev/null +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useIotaClient } from '@iota/dapp-kit'; +import { IotaObjectData } from '@iota/iota-sdk/client'; +import { Transaction } from '@iota/iota-sdk/transactions'; +import { useQuery } from '@tanstack/react-query'; + +export function useMigrationTransaction( + stardustOutputObjects: IotaObjectData[], + senderAddress: string, +) { + const client = useIotaClient(); + return useQuery({ + // eslint-disable-next-line @tanstack/query/exhaustive-deps + queryKey: ['migration-transaction', senderAddress], + queryFn: async () => { + const transaction = new Transaction(); + transaction.setSender(senderAddress); + await transaction.build({ client }); + return transaction; + }, + enabled: !!stardustOutputObjects && !!senderAddress, + gcTime: 0, + select: (transaction) => { + return { + transaction, + gasBudget: transaction.getData().gasData.budget, + }; + }, + }); +} From 2539673dfb8d935d81a9f04865f0a218ea1b5364 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Mon, 4 Nov 2024 11:20:08 +0100 Subject: [PATCH 06/19] fix: move constants to core --- apps/core/src/constants/index.ts | 1 + .../lib => core/src}/constants/migration.constants.ts | 1 + apps/wallet-dashboard/app/(protected)/migrations/page.tsx | 6 +++--- apps/wallet-dashboard/lib/constants/index.ts | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) rename apps/{wallet-dashboard/lib => core/src}/constants/migration.constants.ts (99%) diff --git a/apps/core/src/constants/index.ts b/apps/core/src/constants/index.ts index 93409fc03e9..baca8bd0d9a 100644 --- a/apps/core/src/constants/index.ts +++ b/apps/core/src/constants/index.ts @@ -5,4 +5,5 @@ export * from './staking.constants'; export * from './recognizedPackages.constants'; export * from './coins.constants'; export * from './timelock.constants'; +export * from './migration.constants'; export * from './features.enum'; diff --git a/apps/wallet-dashboard/lib/constants/migration.constants.ts b/apps/core/src/constants/migration.constants.ts similarity index 99% rename from apps/wallet-dashboard/lib/constants/migration.constants.ts rename to apps/core/src/constants/migration.constants.ts index 41c57144881..6086a758e0f 100644 --- a/apps/wallet-dashboard/lib/constants/migration.constants.ts +++ b/apps/core/src/constants/migration.constants.ts @@ -1,5 +1,6 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 + export const STARDUST_PACKAGE_ID = '000000000000000000000000000000000000000000000000000000000000107a'; export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index 37d89caaec7..645497c4030 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -4,12 +4,12 @@ import { VirtualList } from '@/components'; import { useGetCurrentEpochStartTimestamp } from '@/hooks'; +import { groupStardustObjectsByMigrationStatus } from '@/lib/utils'; import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, -} from '@/lib/constants/migration.constants'; -import { groupStardustObjectsByMigrationStatus } from '@/lib/utils'; -import { useGetAllOwnedObjects } from '@iota/core'; + useGetAllOwnedObjects, +} from '@iota/core'; import { useCurrentAccount, useIotaClientContext } from '@iota/dapp-kit'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; diff --git a/apps/wallet-dashboard/lib/constants/index.ts b/apps/wallet-dashboard/lib/constants/index.ts index a7c8637513f..f24da8ee774 100644 --- a/apps/wallet-dashboard/lib/constants/index.ts +++ b/apps/wallet-dashboard/lib/constants/index.ts @@ -3,3 +3,4 @@ export * from './time.constants'; export * from './vesting.constants'; +export * from './routes.constants'; From 0e985ec7fa730e2ad57069901f489493e7c8cba4 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Mon, 4 Nov 2024 12:11:43 +0100 Subject: [PATCH 07/19] fix: evert adding routes to barrel --- apps/wallet-dashboard/lib/constants/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/wallet-dashboard/lib/constants/index.ts b/apps/wallet-dashboard/lib/constants/index.ts index f24da8ee774..a7c8637513f 100644 --- a/apps/wallet-dashboard/lib/constants/index.ts +++ b/apps/wallet-dashboard/lib/constants/index.ts @@ -3,4 +3,3 @@ export * from './time.constants'; export * from './vesting.constants'; -export * from './routes.constants'; From 00e36f3e62f51a19d6148d45377816ffeb2f0754 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Tue, 5 Nov 2024 13:22:42 +0100 Subject: [PATCH 08/19] feat: add migration ptb --- apps/core/src/constants/coins.constants.ts | 1 + .../core/src/constants/migration.constants.ts | 4 +- apps/core/src/utils/index.ts | 1 + .../migration/createMigrationTransaction.ts | 193 ++++++++++++++++++ apps/core/src/utils/migration/index.ts | 4 + .../components/sidebar/Sidebar.tsx | 2 +- .../app/(protected)/migrations/page.tsx | 6 +- .../components/Popup/Popups/MigratePopup.tsx | 58 ++++-- .../hooks/useMigrationTransaction.ts | 21 +- .../lib/interfaces/migration.interface.ts | 47 ----- apps/wallet-dashboard/lib/utils/migration.ts | 4 +- 11 files changed, 270 insertions(+), 71 deletions(-) create mode 100644 apps/core/src/utils/migration/createMigrationTransaction.ts create mode 100644 apps/core/src/utils/migration/index.ts diff --git a/apps/core/src/constants/coins.constants.ts b/apps/core/src/constants/coins.constants.ts index 11fba0c12a0..cec74c25229 100644 --- a/apps/core/src/constants/coins.constants.ts +++ b/apps/core/src/constants/coins.constants.ts @@ -3,3 +3,4 @@ export const COINS_QUERY_REFETCH_INTERVAL = 20_000; export const COINS_QUERY_STALE_TIME = 20_000; +export const IOTA_COIN_TYPE = '0x2::iota::IOTA'; diff --git a/apps/core/src/constants/migration.constants.ts b/apps/core/src/constants/migration.constants.ts index 6086a758e0f..c5604d12745 100644 --- a/apps/core/src/constants/migration.constants.ts +++ b/apps/core/src/constants/migration.constants.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - export const STARDUST_PACKAGE_ID = - '000000000000000000000000000000000000000000000000000000000000107a'; + // '000000000000000000000000000000000000000000000000000000000000107a'; + '0x261d8be760d975fd9c5a024443a2a4e031e4141868d831581c7a72cb808c549e'; export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<0x2::iota::IOTA>`; diff --git a/apps/core/src/utils/index.ts b/apps/core/src/utils/index.ts index 31dd96fa3bb..aa49acbf1e4 100644 --- a/apps/core/src/utils/index.ts +++ b/apps/core/src/utils/index.ts @@ -19,3 +19,4 @@ export * from './parseObjectDetails'; export * from './stake'; export * from './transaction'; export * from './validation'; +export * from './migration'; diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts new file mode 100644 index 00000000000..c9798cfe504 --- /dev/null +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -0,0 +1,193 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { IotaClient, IotaObjectData } from '@iota/iota-sdk/client'; +import { Transaction } from '@iota/iota-sdk/transactions'; +import { STARDUST_PACKAGE_ID } from '../../constants/migration.constants'; +import { IOTA_COIN_TYPE } from '../../constants/coins.constants'; + +type NestedResultType = { + $kind: 'NestedResult'; + NestedResult: [number, number]; +}; + +type ExpirationUnlockCondition = { + owner: string; + return_address: string; + unix_time: number; +}; +type StorageDepositReturnUnlockCondition = { + return_address: string; + return_amount: string; +}; +type TimelockUnlockCondition = { + unix_time: number; +}; + +export type CommonOutputObject = { + id: { id: string }; + balance: string; + native_tokens: { + type: string; + fields: { id: { id: string }; size: string }; + }; +}; + +export interface CommonOutputObjectWithUc extends CommonOutputObject { + expiration_uc?: { + type: string; + fields: ExpirationUnlockCondition; + }; + storage_deposit_return_uc?: { + type: string; + fields: StorageDepositReturnUnlockCondition; + }; + timelock_uc?: { + type: string; + fields: TimelockUnlockCondition; + }; +} + +export interface BasicOutputObject extends CommonOutputObjectWithUc { + metadata?: number[]; + tag?: number[]; + sender?: string; +} + +export interface NftOutputObject extends CommonOutputObjectWithUc {} + +export async function getNativeTokenTypesFromBag( + bagId: string, + client: IotaClient, +): Promise { + const nativeTokenDynamicFields = await client.getDynamicFields({ + parentId: bagId, + }); + const nativeTokenTypes: string[] = []; + for (const nativeToken of nativeTokenDynamicFields.data) { + nativeTokenTypes.push(nativeToken?.name?.value as string); + } + + return nativeTokenTypes; +} + +export async function createMigrationTransaction( + client: IotaClient, + address: string, + basicOutputs: IotaObjectData[] = [], + nftOutputs: IotaObjectData[] = [], +): Promise { + const ptb = new Transaction(); + + const coinsFromBasicOutputs: NestedResultType[] = []; + + // Basics + for (const basicOutputObject of basicOutputs) { + const basicOutputObjectId = basicOutputObject.objectId; + + const bagId = (basicOutputObject.content as unknown as { fields: BasicOutputObject }).fields + .native_tokens.fields.id.id; + const bagSize = (basicOutputObject.content as unknown as { fields: BasicOutputObject }) + .fields.native_tokens.fields.size; + // console.log('Bag Size:', bagSize, bagId); + const nativeTokenTypes: string[] = + Number(bagSize) > 0 ? await getNativeTokenTypesFromBag(bagId, client) : []; + console.log('Native Token Types:', bagId, Number(bagSize), nativeTokenTypes); + const migratableResult = ptb.moveCall({ + target: `${STARDUST_PACKAGE_ID}::basic_output::extract_assets`, + typeArguments: [IOTA_COIN_TYPE], + arguments: [ptb.object(basicOutputObjectId)], + }); + + // eslint-disable-next-line prefer-const + let [balance, nativeTokensBag] = migratableResult; + // let nativeTokensBag = initialNativeTokensBag; + + // Convert Balance in Coin + const [coin] = ptb.moveCall({ + target: '0x02::coin::from_balance', + typeArguments: [IOTA_COIN_TYPE], + arguments: [ptb.object(balance)], + }); + + coinsFromBasicOutputs.push(coin); + + for (const nativeTokenType of nativeTokenTypes) { + // console.log('Native Token Type:', nativeTokenType, JSON.stringify(basicOutputObject, null, 2)); + // Convert NativeTokenBag in Native token and sent to address + [nativeTokensBag] = ptb.moveCall({ + target: '0x107a::utilities::extract_and_send_to', + typeArguments: [nativeTokenType], + arguments: [ptb.object(nativeTokensBag), ptb.pure.address(address)], + }); + } + + ptb.moveCall({ + target: '0x02::bag::destroy_empty', // Destroy empty native tokens + arguments: [ptb.object(nativeTokensBag)], + }); + } + + const coinsFromNftOutputs: NestedResultType[] = []; + const nftsFromNftOutputs: NestedResultType[] = []; + + // NFTs + for (const nftOutputObject of nftOutputs) { + const nftOutputObjectId = nftOutputObject.objectId; + + const bagId = (nftOutputObject.content as unknown as { fields: NftOutputObject }).fields + .native_tokens.fields.id.id; + const bagSize = (nftOutputObject.content as unknown as { fields: BasicOutputObject }).fields + .native_tokens.fields.size; + + const nativeTokenTypes: string[] = + Number(bagSize) > 0 ? await getNativeTokenTypesFromBag(bagId, client) : []; + // console.log('Native Token Types NFT:', bagId, Number(bagSize), nativeTokenTypes); + const migratableResult = ptb.moveCall({ + target: `${STARDUST_PACKAGE_ID}::nft_output::extract_assets`, + typeArguments: [IOTA_COIN_TYPE], + arguments: [ptb.object(nftOutputObjectId)], + }); + + // eslint-disable-next-line prefer-const + let [balance, nativeTokensBag, nft] = migratableResult; + // let nativeTokensBag = initialNativeTokensBag; + nftsFromNftOutputs.push(nft); + + // Convert Balance in Coin + const [coin] = ptb.moveCall({ + target: '0x02::coin::from_balance', + typeArguments: [IOTA_COIN_TYPE], + arguments: [ptb.object(balance)], + }); + coinsFromNftOutputs.push(coin); + + for (const nativeTokenType of nativeTokenTypes) { + // Convert NativeTokenBag in Native token and sent to address + [nativeTokensBag] = ptb.moveCall({ + target: '0x107a::utilities::extract_and_send_to', + typeArguments: [nativeTokenType], + arguments: [ptb.object(nativeTokensBag), ptb.pure.address(address)], + }); + } + + ptb.moveCall({ + target: '0x02::bag::destroy_empty', // Destroy empty native tokens + arguments: [ptb.object(nativeTokensBag)], + }); + } + + const coinOne = coinsFromBasicOutputs.shift() || coinsFromNftOutputs.shift(); + const remainingCoins = [...coinsFromBasicOutputs, ...coinsFromNftOutputs]; + + if (coinOne) { + if (remainingCoins.length > 0) { + ptb.mergeCoins(coinOne, remainingCoins); + } + ptb.transferObjects([coinOne, ...nftsFromNftOutputs], ptb.pure.address(address)); + } else { + ptb.transferObjects([...nftsFromNftOutputs], ptb.pure.address(address)); + } + // console.log('Migration Transaction:', ptb); + return ptb; +} diff --git a/apps/core/src/utils/migration/index.ts b/apps/core/src/utils/migration/index.ts new file mode 100644 index 00000000000..140c87d1b7c --- /dev/null +++ b/apps/core/src/utils/migration/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './createMigrationTransaction'; diff --git a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx index 03742f9e8e4..cea73d253af 100644 --- a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx +++ b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx @@ -9,7 +9,7 @@ import { useFeature } from '@growthbook/growthbook-react'; export function Sidebar() { const featureFlags = { - Migrations: useFeature(Feature.StardustMigration).value, + Migrations: true, Vesting: useFeature(Feature.SupplyIncreaseVesting).value, }; diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index bd72b3ef486..bc897975e8b 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -12,6 +12,7 @@ import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, useGetAllOwnedObjects, + useGetObject, } from '@iota/core'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; import { useQueryClient } from '@tanstack/react-query'; @@ -78,12 +79,15 @@ function MigrationDashboardPage(): JSX.Element { }); }); } + const basicOutput = useGetObject('0x021f5bc6771079521dbdaf93eb3d578f8e38f051d3c43af4296294ffc650f439'); + console.log('Basic Output:', basicOutput); function openMigratePopup(): void { const stardustOutputObjects = [...migratableBasicOutputs, ...migratableNftOutputs]; if (stardustOutputObjects.length > 0) { openPopup( , diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx index 281f8985e92..f52ab2be842 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import React from 'react'; +import { useEffect } from 'react'; import { VirtualList } from '@/components'; import { useCurrentAccount, @@ -10,30 +11,43 @@ import { } from '@iota/dapp-kit'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; import { useMigrationTransaction } from '@/hooks/useMigrationTransaction'; -import { Button } from '@iota/apps-ui-kit'; +import { Button, InfoBox, InfoBoxStyle, InfoBoxType } from '@iota/apps-ui-kit'; import { useNotifications } from '@/hooks'; import { NotificationType } from '@/stores/notificationStore'; +import { Warning } from '@iota/ui-icons'; interface MigratePopupProps { - stardustOutputObjects: IotaObjectData[]; + basicOutputObjects?: IotaObjectData[]; + nftOutputObjects?: IotaObjectData[]; closePopup: () => void; onSuccess?: (digest: string) => void; } function MigratePopup({ - stardustOutputObjects, + basicOutputObjects, + nftOutputObjects, closePopup, onSuccess, }: MigratePopupProps): JSX.Element { const account = useCurrentAccount(); const { addNotification } = useNotifications(); - const { data: migrateData } = useMigrationTransaction( - stardustOutputObjects, - account?.address || '', - ); + const { + data: migrateData, + isPending, + isError, + error, + } = useMigrationTransaction(account?.address || '', basicOutputObjects, nftOutputObjects); + + useEffect(() => { + if (migrateData) { + console.log('Migration data has changed:', migrateData); + } + }, [migrateData]); + console.log("migrateData", migrateData, isError, error?.message); const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); - const { mutateAsync: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction(); + const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = + useSignAndExecuteTransaction(); async function handleMigrate(): Promise { if (!migrateData) return; @@ -57,7 +71,7 @@ function MigratePopup({ addNotification('Migration transaction was not sent', NotificationType.Error); }); } - + console.log("basicOutputObjects", basicOutputObjects); const virtualItem = (asset: IotaObjectData): JSX.Element => ( {asset.objectId} @@ -66,15 +80,35 @@ function MigratePopup({ return (
-

Migratable Outputs: {stardustOutputObjects.length}

+

Migratable Basic Outputs: {basicOutputObjects?.length}

+ 30} + render={virtualItem} + /> +
+
+

Migratable Nft Outputs: {nftOutputObjects?.length}

30} render={virtualItem} />

Gas Fees: {migrateData?.gasBudget?.toString() || '--'}

-
); } diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts index 028af412aaf..0713ce9f36f 100644 --- a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -3,24 +3,31 @@ import { useIotaClient } from '@iota/dapp-kit'; import { IotaObjectData } from '@iota/iota-sdk/client'; -import { Transaction } from '@iota/iota-sdk/transactions'; import { useQuery } from '@tanstack/react-query'; +import { createMigrationTransaction } from '@iota/core'; export function useMigrationTransaction( - stardustOutputObjects: IotaObjectData[], - senderAddress: string, + address: string, + basicOutputObjects?: IotaObjectData[], + nftOutputObjects?: IotaObjectData[], ) { const client = useIotaClient(); return useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps - queryKey: ['migration-transaction', senderAddress], + queryKey: ['migration-transaction', address], queryFn: async () => { - const transaction = new Transaction(); - transaction.setSender(senderAddress); + const transaction = await createMigrationTransaction( + client, + address, + basicOutputObjects, + nftOutputObjects, + ); + // transaction.setGasBudget(1000000000); + transaction.setSender(address); await transaction.build({ client }); return transaction; }, - enabled: !!stardustOutputObjects && !!senderAddress, + enabled: !!address, gcTime: 0, select: (transaction) => { return { diff --git a/apps/wallet-dashboard/lib/interfaces/migration.interface.ts b/apps/wallet-dashboard/lib/interfaces/migration.interface.ts index df35ae29112..e69de29bb2d 100644 --- a/apps/wallet-dashboard/lib/interfaces/migration.interface.ts +++ b/apps/wallet-dashboard/lib/interfaces/migration.interface.ts @@ -1,47 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -type ExpirationUnlockCondition = { - owner: string; - return_address: string; - unix_time: number; -}; -type StorageDepositReturnUnlockCondition = { - return_address: string; - return_amount: string; -}; -type TimelockUnlockCondition = { - unix_time: number; -}; - -export type CommonOutputObject = { - id: { id: string }; - balance: string; - native_tokens: { - type: string; - fields: { id: { id: string }; size: string }; - }; -}; - -export interface CommonOutputObjectWithUc extends CommonOutputObject { - expiration_uc?: { - type: string; - fields: ExpirationUnlockCondition; - }; - storage_deposit_return_uc?: { - type: string; - fields: StorageDepositReturnUnlockCondition; - }; - timelock_uc?: { - type: string; - fields: TimelockUnlockCondition; - }; -} - -export interface BasicOutputObject extends CommonOutputObjectWithUc { - metadata?: number[]; - tag?: number[]; - sender?: string; -} - -export interface NftOutputObject extends CommonOutputObjectWithUc {} diff --git a/apps/wallet-dashboard/lib/utils/migration.ts b/apps/wallet-dashboard/lib/utils/migration.ts index c069c266da9..b36bad690e9 100644 --- a/apps/wallet-dashboard/lib/utils/migration.ts +++ b/apps/wallet-dashboard/lib/utils/migration.ts @@ -1,8 +1,8 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { CommonOutputObjectWithUc } from '@iota/core'; import { IotaObjectData } from '@iota/iota-sdk/client'; -import { CommonOutputObjectWithUc } from '../interfaces/migration.interface'; export type StardustMigrationGroupedObjects = { migratable: IotaObjectData[]; @@ -31,6 +31,8 @@ export function groupStardustObjectsByMigrationStatus( ).fields; if (outputObjectFields.expiration_uc) { + unmigratable.push(outputObject); + continue; const unlockableAddress = outputObjectFields.expiration_uc.fields.unix_time <= epochUnix ? outputObjectFields.expiration_uc.fields.return_address From 5b1b904aa1b2c97b8e97f7aa7136313cd56a9873 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Tue, 5 Nov 2024 15:38:10 +0100 Subject: [PATCH 09/19] fix: remove leftover testing code --- apps/core/src/constants/migration.constants.ts | 3 +-- .../src/utils/migration/createMigrationTransaction.ts | 1 - .../app/(protected)/components/sidebar/Sidebar.tsx | 2 +- apps/wallet-dashboard/app/(protected)/migrations/page.tsx | 8 +++----- .../components/Popup/Popups/MigratePopup.tsx | 7 ------- apps/wallet-dashboard/lib/utils/migration.ts | 2 -- 6 files changed, 5 insertions(+), 18 deletions(-) diff --git a/apps/core/src/constants/migration.constants.ts b/apps/core/src/constants/migration.constants.ts index c5604d12745..41c57144881 100644 --- a/apps/core/src/constants/migration.constants.ts +++ b/apps/core/src/constants/migration.constants.ts @@ -1,7 +1,6 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 export const STARDUST_PACKAGE_ID = - // '000000000000000000000000000000000000000000000000000000000000107a'; - '0x261d8be760d975fd9c5a024443a2a4e031e4141868d831581c7a72cb808c549e'; + '000000000000000000000000000000000000000000000000000000000000107a'; export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<0x2::iota::IOTA>`; diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index c9798cfe504..b1f470c3fca 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -188,6 +188,5 @@ export async function createMigrationTransaction( } else { ptb.transferObjects([...nftsFromNftOutputs], ptb.pure.address(address)); } - // console.log('Migration Transaction:', ptb); return ptb; } diff --git a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx index cea73d253af..03742f9e8e4 100644 --- a/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx +++ b/apps/wallet-dashboard/app/(protected)/components/sidebar/Sidebar.tsx @@ -9,7 +9,7 @@ import { useFeature } from '@growthbook/growthbook-react'; export function Sidebar() { const featureFlags = { - Migrations: true, + Migrations: useFeature(Feature.StardustMigration).value, Vesting: useFeature(Feature.SupplyIncreaseVesting).value, }; diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index bc897975e8b..66e1c9925b6 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -12,7 +12,6 @@ import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, useGetAllOwnedObjects, - useGetObject, } from '@iota/core'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; import { useQueryClient } from '@tanstack/react-query'; @@ -79,15 +78,14 @@ function MigrationDashboardPage(): JSX.Element { }); }); } - const basicOutput = useGetObject('0x021f5bc6771079521dbdaf93eb3d578f8e38f051d3c43af4296294ffc650f439'); - console.log('Basic Output:', basicOutput); + function openMigratePopup(): void { const stardustOutputObjects = [...migratableBasicOutputs, ...migratableNftOutputs]; if (stardustOutputObjects.length > 0) { openPopup( , diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx index f52ab2be842..bf247e8bddc 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -38,12 +38,6 @@ function MigratePopup({ error, } = useMigrationTransaction(account?.address || '', basicOutputObjects, nftOutputObjects); - useEffect(() => { - if (migrateData) { - console.log('Migration data has changed:', migrateData); - } - }, [migrateData]); - console.log("migrateData", migrateData, isError, error?.message); const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = @@ -71,7 +65,6 @@ function MigratePopup({ addNotification('Migration transaction was not sent', NotificationType.Error); }); } - console.log("basicOutputObjects", basicOutputObjects); const virtualItem = (asset: IotaObjectData): JSX.Element => (
{asset.objectId} diff --git a/apps/wallet-dashboard/lib/utils/migration.ts b/apps/wallet-dashboard/lib/utils/migration.ts index b36bad690e9..f1551307e36 100644 --- a/apps/wallet-dashboard/lib/utils/migration.ts +++ b/apps/wallet-dashboard/lib/utils/migration.ts @@ -31,8 +31,6 @@ export function groupStardustObjectsByMigrationStatus( ).fields; if (outputObjectFields.expiration_uc) { - unmigratable.push(outputObject); - continue; const unlockableAddress = outputObjectFields.expiration_uc.fields.unix_time <= epochUnix ? outputObjectFields.expiration_uc.fields.return_address From cca2fc9a5b337f151d60d5b5e40173ae782da161 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Wed, 6 Nov 2024 09:20:55 +0100 Subject: [PATCH 10/19] fix: remove undefined object check --- apps/wallet-dashboard/lib/utils/migration.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/wallet-dashboard/lib/utils/migration.ts b/apps/wallet-dashboard/lib/utils/migration.ts index c069c266da9..e4a5cfa1f30 100644 --- a/apps/wallet-dashboard/lib/utils/migration.ts +++ b/apps/wallet-dashboard/lib/utils/migration.ts @@ -20,10 +20,6 @@ export function groupStardustObjectsByMigrationStatus( const epochUnix = epochTimestamp / 1000; for (const outputObject of stardustOutputObjects) { - if (!outputObject) { - unmigratable.push(outputObject); - continue; - } const outputObjectFields = ( outputObject.content as unknown as { fields: CommonOutputObjectWithUc; From bd6c0265196c7bfec5fe3d3afa436332d400a112 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Wed, 6 Nov 2024 14:42:58 +0100 Subject: [PATCH 11/19] fix: add schemas for migration transaction --- .../migration/createMigrationTransaction.ts | 172 ++++++++++-------- 1 file changed, 96 insertions(+), 76 deletions(-) diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index b1f470c3fca..8eb8b2c563d 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -5,56 +5,69 @@ import { IotaClient, IotaObjectData } from '@iota/iota-sdk/client'; import { Transaction } from '@iota/iota-sdk/transactions'; import { STARDUST_PACKAGE_ID } from '../../constants/migration.constants'; import { IOTA_COIN_TYPE } from '../../constants/coins.constants'; +import { z } from 'zod'; type NestedResultType = { $kind: 'NestedResult'; NestedResult: [number, number]; }; -type ExpirationUnlockCondition = { - owner: string; - return_address: string; - unix_time: number; -}; -type StorageDepositReturnUnlockCondition = { - return_address: string; - return_amount: string; -}; -type TimelockUnlockCondition = { - unix_time: number; -}; - -export type CommonOutputObject = { - id: { id: string }; - balance: string; - native_tokens: { - type: string; - fields: { id: { id: string }; size: string }; - }; -}; - -export interface CommonOutputObjectWithUc extends CommonOutputObject { - expiration_uc?: { - type: string; - fields: ExpirationUnlockCondition; - }; - storage_deposit_return_uc?: { - type: string; - fields: StorageDepositReturnUnlockCondition; - }; - timelock_uc?: { - type: string; - fields: TimelockUnlockCondition; - }; -} - -export interface BasicOutputObject extends CommonOutputObjectWithUc { - metadata?: number[]; - tag?: number[]; - sender?: string; -} - -export interface NftOutputObject extends CommonOutputObjectWithUc {} +const ExpirationUnlockConditionSchema = z.object({ + type: z.string(), + fields: z.object({ + owner: z.string(), + return_address: z.string(), + unix_time: z.number(), + }), +}); + +const StorageDepositReturnUnlockConditionSchema = z.object({ + type: z.string(), + fields: z.object({ + return_address: z.string(), + return_amount: z.string(), + }), +}); + +const TimelockUnlockConditionSchema = z.object({ + type: z.string(), + fields: z.object({ + unix_time: z.number(), + }), +}); + +const CommonOutputObjectSchema = z.object({ + id: z.object({ + id: z.string(), + }), + balance: z.string(), + native_tokens: z.object({ + type: z.string(), + fields: z.object({ + id: z.object({ + id: z.string(), + }), + size: z.string(), + }), + }), +}); + +const CommonOutputObjectWithUcSchema = CommonOutputObjectSchema.extend({ + expiration_uc: ExpirationUnlockConditionSchema.nullable().optional(), + storage_deposit_return_uc: StorageDepositReturnUnlockConditionSchema.nullable().optional(), + timelock_uc: TimelockUnlockConditionSchema.nullable().optional(), +}); + +const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ + metadata: z.array(z.number()).optional(), + tag: z.array(z.number()).optional(), + sender: z.string().optional(), +}); + +const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; + +type BasicOutputObject = z.infer; +type NftOutputObject = z.infer; export async function getNativeTokenTypesFromBag( bagId: string, @@ -71,6 +84,24 @@ export async function getNativeTokenTypesFromBag( return nativeTokenTypes; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateBasicOutputObject(outputObject: any): BasicOutputObject { + const result = BasicOutputObjectSchema.safeParse(outputObject.content); + if (!result.success) { + throw new Error('Invalid basic output object content'); + } + return result.data; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateNftOutputObject(outputObject: any): NftOutputObject { + const result = NftOutputObjectSchema.safeParse(outputObject.content.fields); + if (!result.success) { + throw new Error('Invalid nft output object content'); + } + return result.data; +} + export async function createMigrationTransaction( client: IotaClient, address: string, @@ -81,27 +112,23 @@ export async function createMigrationTransaction( const coinsFromBasicOutputs: NestedResultType[] = []; - // Basics + // Basic Outputs for (const basicOutputObject of basicOutputs) { - const basicOutputObjectId = basicOutputObject.objectId; - - const bagId = (basicOutputObject.content as unknown as { fields: BasicOutputObject }).fields - .native_tokens.fields.id.id; - const bagSize = (basicOutputObject.content as unknown as { fields: BasicOutputObject }) - .fields.native_tokens.fields.size; - // console.log('Bag Size:', bagSize, bagId); + const validatedOutputObject = validateBasicOutputObject(basicOutputObject); + const basicOutputObjectId = validatedOutputObject.id.id; + const bagId = validatedOutputObject.native_tokens.fields.id.id; + const bagSize = validatedOutputObject.native_tokens.fields.size; const nativeTokenTypes: string[] = Number(bagSize) > 0 ? await getNativeTokenTypesFromBag(bagId, client) : []; - console.log('Native Token Types:', bagId, Number(bagSize), nativeTokenTypes); + const migratableResult = ptb.moveCall({ target: `${STARDUST_PACKAGE_ID}::basic_output::extract_assets`, typeArguments: [IOTA_COIN_TYPE], arguments: [ptb.object(basicOutputObjectId)], }); - // eslint-disable-next-line prefer-const - let [balance, nativeTokensBag] = migratableResult; - // let nativeTokensBag = initialNativeTokensBag; + const balance = migratableResult[0]; + let nativeTokensBag = migratableResult[1]; // Convert Balance in Coin const [coin] = ptb.moveCall({ @@ -113,8 +140,6 @@ export async function createMigrationTransaction( coinsFromBasicOutputs.push(coin); for (const nativeTokenType of nativeTokenTypes) { - // console.log('Native Token Type:', nativeTokenType, JSON.stringify(basicOutputObject, null, 2)); - // Convert NativeTokenBag in Native token and sent to address [nativeTokensBag] = ptb.moveCall({ target: '0x107a::utilities::extract_and_send_to', typeArguments: [nativeTokenType], @@ -123,35 +148,33 @@ export async function createMigrationTransaction( } ptb.moveCall({ - target: '0x02::bag::destroy_empty', // Destroy empty native tokens + target: '0x02::bag::destroy_empty', arguments: [ptb.object(nativeTokensBag)], }); } + // NFT Outputs const coinsFromNftOutputs: NestedResultType[] = []; const nftsFromNftOutputs: NestedResultType[] = []; - // NFTs for (const nftOutputObject of nftOutputs) { - const nftOutputObjectId = nftOutputObject.objectId; - - const bagId = (nftOutputObject.content as unknown as { fields: NftOutputObject }).fields - .native_tokens.fields.id.id; - const bagSize = (nftOutputObject.content as unknown as { fields: BasicOutputObject }).fields - .native_tokens.fields.size; - + const validatedOutputObject = validateNftOutputObject(nftOutputObject); + const nftOutputObjectId = validatedOutputObject.id.id; + const bagId = validatedOutputObject.native_tokens.fields.id.id; + const bagSize = validatedOutputObject.native_tokens.fields.size; const nativeTokenTypes: string[] = Number(bagSize) > 0 ? await getNativeTokenTypesFromBag(bagId, client) : []; - // console.log('Native Token Types NFT:', bagId, Number(bagSize), nativeTokenTypes); + const migratableResult = ptb.moveCall({ target: `${STARDUST_PACKAGE_ID}::nft_output::extract_assets`, typeArguments: [IOTA_COIN_TYPE], arguments: [ptb.object(nftOutputObjectId)], }); - // eslint-disable-next-line prefer-const - let [balance, nativeTokensBag, nft] = migratableResult; - // let nativeTokensBag = initialNativeTokensBag; + const balance = migratableResult[0]; + let nativeTokensBag = migratableResult[1]; + const nft = migratableResult[2]; + nftsFromNftOutputs.push(nft); // Convert Balance in Coin @@ -163,7 +186,6 @@ export async function createMigrationTransaction( coinsFromNftOutputs.push(coin); for (const nativeTokenType of nativeTokenTypes) { - // Convert NativeTokenBag in Native token and sent to address [nativeTokensBag] = ptb.moveCall({ target: '0x107a::utilities::extract_and_send_to', typeArguments: [nativeTokenType], @@ -172,21 +194,19 @@ export async function createMigrationTransaction( } ptb.moveCall({ - target: '0x02::bag::destroy_empty', // Destroy empty native tokens + target: '0x02::bag::destroy_empty', arguments: [ptb.object(nativeTokensBag)], }); } const coinOne = coinsFromBasicOutputs.shift() || coinsFromNftOutputs.shift(); const remainingCoins = [...coinsFromBasicOutputs, ...coinsFromNftOutputs]; - if (coinOne) { if (remainingCoins.length > 0) { ptb.mergeCoins(coinOne, remainingCoins); } ptb.transferObjects([coinOne, ...nftsFromNftOutputs], ptb.pure.address(address)); - } else { - ptb.transferObjects([...nftsFromNftOutputs], ptb.pure.address(address)); } + return ptb; } From fd17081228e7533471f157d86ba70f5e5efd95fe Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 7 Nov 2024 10:35:03 +0100 Subject: [PATCH 12/19] fix: change migration popup params and add loader to button --- .../app/(protected)/migrations/page.tsx | 28 +++++----- .../components/Popup/Popups/MigratePopup.tsx | 54 +++++++++++++++---- .../hooks/useMigrationTransaction.ts | 11 ++-- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index bd72b3ef486..55ac40f5e99 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -46,6 +46,10 @@ function MigrationDashboardPage(): JSX.Element { Number(currentEpochMs), address, ); + + const hasMigratableObjects = + migratableBasicOutputs.length > 0 || migratableNftOutputs.length > 0; + const virtualItem = (asset: IotaObjectData): JSX.Element => ( {asset.objectId} @@ -61,7 +65,7 @@ function MigrationDashboardPage(): JSX.Element { queryClient.invalidateQueries({ queryKey: [ 'get-all-owned-objects', - account?.address, + address, { StructType: STARDUST_BASIC_OUTPUT_TYPE, }, @@ -70,7 +74,7 @@ function MigrationDashboardPage(): JSX.Element { queryClient.invalidateQueries({ queryKey: [ 'get-all-owned-objects', - account?.address, + address, { StructType: STARDUST_NFT_OUTPUT_TYPE, }, @@ -79,16 +83,14 @@ function MigrationDashboardPage(): JSX.Element { }); } function openMigratePopup(): void { - const stardustOutputObjects = [...migratableBasicOutputs, ...migratableNftOutputs]; - if (stardustOutputObjects.length > 0) { - openPopup( - , - ); - } + openPopup( + , + ); } return ( @@ -125,7 +127,7 @@ function MigrationDashboardPage(): JSX.Element { render={virtualItem} />
-
); } diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx index 281f8985e92..e11b66c190a 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -10,30 +10,37 @@ import { } from '@iota/dapp-kit'; import { getNetwork, IotaObjectData } from '@iota/iota-sdk/client'; import { useMigrationTransaction } from '@/hooks/useMigrationTransaction'; -import { Button } from '@iota/apps-ui-kit'; +import { Button, InfoBox, InfoBoxStyle, InfoBoxType } from '@iota/apps-ui-kit'; import { useNotifications } from '@/hooks'; import { NotificationType } from '@/stores/notificationStore'; +import { Loader, Warning } from '@iota/ui-icons'; interface MigratePopupProps { - stardustOutputObjects: IotaObjectData[]; + basicOutputObjects: IotaObjectData[]; + nftOutputObjects: IotaObjectData[]; closePopup: () => void; onSuccess?: (digest: string) => void; } function MigratePopup({ - stardustOutputObjects, + basicOutputObjects = [], + nftOutputObjects = [], closePopup, onSuccess, }: MigratePopupProps): JSX.Element { const account = useCurrentAccount(); const { addNotification } = useNotifications(); - const { data: migrateData } = useMigrationTransaction( - stardustOutputObjects, - account?.address || '', - ); + const { + data: migrateData, + isPending, + isError, + error, + } = useMigrationTransaction(account?.address || '', basicOutputObjects, nftOutputObjects); + const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); - const { mutateAsync: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction(); + const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = + useSignAndExecuteTransaction(); async function handleMigrate(): Promise { if (!migrateData) return; @@ -66,15 +73,40 @@ function MigratePopup({ return (
-

Migratable Outputs: {stardustOutputObjects.length}

+

Migratable Basic Outputs: {basicOutputObjects?.length}

30} + render={virtualItem} + /> +
+
+

Migratable Nft Outputs: {nftOutputObjects?.length}

+ 30} render={virtualItem} />

Gas Fees: {migrateData?.gasBudget?.toString() || '--'}

-
); } diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts index 028af412aaf..10f87cad28e 100644 --- a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -7,20 +7,21 @@ import { Transaction } from '@iota/iota-sdk/transactions'; import { useQuery } from '@tanstack/react-query'; export function useMigrationTransaction( - stardustOutputObjects: IotaObjectData[], - senderAddress: string, + address: string, + basicOutputObjects?: IotaObjectData[], + nftOutputObjects?: IotaObjectData[], ) { const client = useIotaClient(); return useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps - queryKey: ['migration-transaction', senderAddress], + queryKey: ['migration-transaction', address], queryFn: async () => { const transaction = new Transaction(); - transaction.setSender(senderAddress); + transaction.setSender(address); await transaction.build({ client }); return transaction; }, - enabled: !!stardustOutputObjects && !!senderAddress, + enabled: !!address, gcTime: 0, select: (transaction) => { return { From 0e7fe0953d63e81a2452d8491049e454166b17db Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 7 Nov 2024 11:21:40 +0100 Subject: [PATCH 13/19] remove migration inteface --- apps/wallet-dashboard/lib/interfaces/index.ts | 1 - apps/wallet-dashboard/lib/interfaces/migration.interface.ts | 0 2 files changed, 1 deletion(-) delete mode 100644 apps/wallet-dashboard/lib/interfaces/migration.interface.ts diff --git a/apps/wallet-dashboard/lib/interfaces/index.ts b/apps/wallet-dashboard/lib/interfaces/index.ts index 679bc45212f..eeab2cb4f5c 100644 --- a/apps/wallet-dashboard/lib/interfaces/index.ts +++ b/apps/wallet-dashboard/lib/interfaces/index.ts @@ -3,6 +3,5 @@ export * from './transactions.interface'; export * from './timelock.interface'; -export * from './migration.interface'; export * from './vesting.interface'; export * from './appRoute.interface'; diff --git a/apps/wallet-dashboard/lib/interfaces/migration.interface.ts b/apps/wallet-dashboard/lib/interfaces/migration.interface.ts deleted file mode 100644 index e69de29bb2d..00000000000 From b0b49707a02e35f2b7de3ea442d0b33731cd1fba Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 7 Nov 2024 11:27:45 +0100 Subject: [PATCH 14/19] fix: remove empty spaces and add iota coin type constant migration constants --- apps/core/src/constants/migration.constants.ts | 7 +++++-- apps/wallet-dashboard/app/(protected)/migrations/page.tsx | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/core/src/constants/migration.constants.ts b/apps/core/src/constants/migration.constants.ts index 41c57144881..e8539e5392e 100644 --- a/apps/core/src/constants/migration.constants.ts +++ b/apps/core/src/constants/migration.constants.ts @@ -1,6 +1,9 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 + +import { IOTA_COIN_TYPE } from './coins.constants'; + export const STARDUST_PACKAGE_ID = '000000000000000000000000000000000000000000000000000000000000107a'; -export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<0x2::iota::IOTA>`; -export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<0x2::iota::IOTA>`; +export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<${IOTA_COIN_TYPE}>`; +export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<${IOTA_COIN_TYPE}>`; diff --git a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx index d2f41c713e4..55ac40f5e99 100644 --- a/apps/wallet-dashboard/app/(protected)/migrations/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/migrations/page.tsx @@ -82,7 +82,6 @@ function MigrationDashboardPage(): JSX.Element { }); }); } - function openMigratePopup(): void { openPopup( Date: Thu, 7 Nov 2024 11:48:46 +0100 Subject: [PATCH 15/19] fix: exporting type and eslint --- .../migration/createMigrationTransaction.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index 8eb8b2c563d..a9a72a82b01 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -66,6 +66,7 @@ const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; +export type CommonOutputObjectWithUc = z.infer; type BasicOutputObject = z.infer; type NftOutputObject = z.infer; @@ -84,18 +85,22 @@ export async function getNativeTokenTypesFromBag( return nativeTokenTypes; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateBasicOutputObject(outputObject: any): BasicOutputObject { - const result = BasicOutputObjectSchema.safeParse(outputObject.content); +export function validateBasicOutputObject(outputObject: IotaObjectData): BasicOutputObject { + if (outputObject.content?.dataType !== 'moveObject') { + throw new Error('Invalid basic output object'); + } + const result = BasicOutputObjectSchema.safeParse(outputObject.content.fields); if (!result.success) { throw new Error('Invalid basic output object content'); } return result.data; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateNftOutputObject(outputObject: any): NftOutputObject { - const result = NftOutputObjectSchema.safeParse(outputObject.content.fields); +export function validateNftOutputObject(outputObject: IotaObjectData): NftOutputObject { + if (outputObject.content?.dataType !== 'moveObject') { + throw new Error('Invalid nft output object'); + } + const result = NftOutputObjectSchema.safeParse(outputObject?.content); if (!result.success) { throw new Error('Invalid nft output object content'); } From 29f4b712ac27f2d104eba2769943426a11e3a4a4 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Thu, 7 Nov 2024 13:29:59 +0100 Subject: [PATCH 16/19] fix: migration object schema --- .../src/utils/migration/createMigrationTransaction.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index a9a72a82b01..99d8fdb296d 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -59,9 +59,9 @@ const CommonOutputObjectWithUcSchema = CommonOutputObjectSchema.extend({ }); const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ - metadata: z.array(z.number()).optional(), - tag: z.array(z.number()).optional(), - sender: z.string().optional(), + metadata: z.array(z.number()).nullable().optional(), + tag: z.array(z.number()).nullable().optional(), + sender: z.string().nullable().optional(), }); const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; @@ -100,7 +100,7 @@ export function validateNftOutputObject(outputObject: IotaObjectData): NftOutput if (outputObject.content?.dataType !== 'moveObject') { throw new Error('Invalid nft output object'); } - const result = NftOutputObjectSchema.safeParse(outputObject?.content); + const result = NftOutputObjectSchema.safeParse(outputObject.content.fields); if (!result.success) { throw new Error('Invalid nft output object content'); } From 939597f4c812c2d714818a10488043c7c50f0174 Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Fri, 8 Nov 2024 09:48:39 +0100 Subject: [PATCH 17/19] fix: add missing exports --- .../src/utils/migration/createMigrationTransaction.ts | 10 ++++++++-- .../components/Popup/Popups/MigratePopup.tsx | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index 99d8fdb296d..9030c7fc3fa 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -66,9 +66,15 @@ const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; +export type ExpirationUnlockCondition = z.infer; +export type StorageDepositReturnUnlockCondition = z.infer< + typeof StorageDepositReturnUnlockConditionSchema +>; +export type TimelockUnlockCondition = z.infer; +export type CommonOutputObject = z.infer; export type CommonOutputObjectWithUc = z.infer; -type BasicOutputObject = z.infer; -type NftOutputObject = z.infer; +export type BasicOutputObject = z.infer; +export type NftOutputObject = z.infer; export async function getNativeTokenTypesFromBag( bagId: string, diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx index eb4d2a72356..fb2930c43df 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -105,6 +105,7 @@ function MigratePopup({ ) : null } + iconAfterText />
); From 7ead70e2054fdb4c94bbb5320c030f6350f578aa Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Fri, 8 Nov 2024 13:05:19 +0100 Subject: [PATCH 18/19] fix: remove IOTA_COIN_TYPE and use sdk constant IOTA_TYPE_ARG --- apps/core/src/constants/coins.constants.ts | 1 - apps/core/src/constants/migration.constants.ts | 6 +++--- .../src/utils/migration/createMigrationTransaction.ts | 10 +++++----- .../components/Popup/Popups/MigratePopup.tsx | 1 + 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/core/src/constants/coins.constants.ts b/apps/core/src/constants/coins.constants.ts index cec74c25229..11fba0c12a0 100644 --- a/apps/core/src/constants/coins.constants.ts +++ b/apps/core/src/constants/coins.constants.ts @@ -3,4 +3,3 @@ export const COINS_QUERY_REFETCH_INTERVAL = 20_000; export const COINS_QUERY_STALE_TIME = 20_000; -export const IOTA_COIN_TYPE = '0x2::iota::IOTA'; diff --git a/apps/core/src/constants/migration.constants.ts b/apps/core/src/constants/migration.constants.ts index e8539e5392e..45072d1d652 100644 --- a/apps/core/src/constants/migration.constants.ts +++ b/apps/core/src/constants/migration.constants.ts @@ -1,9 +1,9 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { IOTA_COIN_TYPE } from './coins.constants'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; export const STARDUST_PACKAGE_ID = '000000000000000000000000000000000000000000000000000000000000107a'; -export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<${IOTA_COIN_TYPE}>`; -export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<${IOTA_COIN_TYPE}>`; +export const STARDUST_BASIC_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::basic_output::BasicOutput<${IOTA_TYPE_ARG}>`; +export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<${IOTA_TYPE_ARG}>`; diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index 9030c7fc3fa..4cb43b1caea 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -4,8 +4,8 @@ import { IotaClient, IotaObjectData } from '@iota/iota-sdk/client'; import { Transaction } from '@iota/iota-sdk/transactions'; import { STARDUST_PACKAGE_ID } from '../../constants/migration.constants'; -import { IOTA_COIN_TYPE } from '../../constants/coins.constants'; import { z } from 'zod'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; type NestedResultType = { $kind: 'NestedResult'; @@ -134,7 +134,7 @@ export async function createMigrationTransaction( const migratableResult = ptb.moveCall({ target: `${STARDUST_PACKAGE_ID}::basic_output::extract_assets`, - typeArguments: [IOTA_COIN_TYPE], + typeArguments: [IOTA_TYPE_ARG], arguments: [ptb.object(basicOutputObjectId)], }); @@ -144,7 +144,7 @@ export async function createMigrationTransaction( // Convert Balance in Coin const [coin] = ptb.moveCall({ target: '0x02::coin::from_balance', - typeArguments: [IOTA_COIN_TYPE], + typeArguments: [IOTA_TYPE_ARG], arguments: [ptb.object(balance)], }); @@ -178,7 +178,7 @@ export async function createMigrationTransaction( const migratableResult = ptb.moveCall({ target: `${STARDUST_PACKAGE_ID}::nft_output::extract_assets`, - typeArguments: [IOTA_COIN_TYPE], + typeArguments: [IOTA_TYPE_ARG], arguments: [ptb.object(nftOutputObjectId)], }); @@ -191,7 +191,7 @@ export async function createMigrationTransaction( // Convert Balance in Coin const [coin] = ptb.moveCall({ target: '0x02::coin::from_balance', - typeArguments: [IOTA_COIN_TYPE], + typeArguments: [IOTA_TYPE_ARG], arguments: [ptb.object(balance)], }); coinsFromNftOutputs.push(coin); diff --git a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx index fb2930c43df..627fb860f3f 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/MigratePopup.tsx @@ -64,6 +64,7 @@ function MigratePopup({ addNotification('Migration transaction was not sent', NotificationType.Error); }); } + const virtualItem = (asset: IotaObjectData): JSX.Element => ( {asset.objectId} From 17832cfea88d575996fb0760e0c4fedd7be7bb6a Mon Sep 17 00:00:00 2001 From: Branko Bosnic Date: Tue, 12 Nov 2024 15:12:53 +0100 Subject: [PATCH 19/19] fix: move migration types to dedicated file and add literal strings for UC types --- .../migration/createMigrationTransaction.ts | 71 ++---------------- apps/core/src/utils/migration/index.ts | 1 + apps/core/src/utils/migration/types.ts | 73 +++++++++++++++++++ 3 files changed, 80 insertions(+), 65 deletions(-) create mode 100644 apps/core/src/utils/migration/types.ts diff --git a/apps/core/src/utils/migration/createMigrationTransaction.ts b/apps/core/src/utils/migration/createMigrationTransaction.ts index 4cb43b1caea..8519a950831 100644 --- a/apps/core/src/utils/migration/createMigrationTransaction.ts +++ b/apps/core/src/utils/migration/createMigrationTransaction.ts @@ -4,78 +4,19 @@ import { IotaClient, IotaObjectData } from '@iota/iota-sdk/client'; import { Transaction } from '@iota/iota-sdk/transactions'; import { STARDUST_PACKAGE_ID } from '../../constants/migration.constants'; -import { z } from 'zod'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { + BasicOutputObject, + BasicOutputObjectSchema, + NftOutputObject, + NftOutputObjectSchema, +} from './types'; type NestedResultType = { $kind: 'NestedResult'; NestedResult: [number, number]; }; -const ExpirationUnlockConditionSchema = z.object({ - type: z.string(), - fields: z.object({ - owner: z.string(), - return_address: z.string(), - unix_time: z.number(), - }), -}); - -const StorageDepositReturnUnlockConditionSchema = z.object({ - type: z.string(), - fields: z.object({ - return_address: z.string(), - return_amount: z.string(), - }), -}); - -const TimelockUnlockConditionSchema = z.object({ - type: z.string(), - fields: z.object({ - unix_time: z.number(), - }), -}); - -const CommonOutputObjectSchema = z.object({ - id: z.object({ - id: z.string(), - }), - balance: z.string(), - native_tokens: z.object({ - type: z.string(), - fields: z.object({ - id: z.object({ - id: z.string(), - }), - size: z.string(), - }), - }), -}); - -const CommonOutputObjectWithUcSchema = CommonOutputObjectSchema.extend({ - expiration_uc: ExpirationUnlockConditionSchema.nullable().optional(), - storage_deposit_return_uc: StorageDepositReturnUnlockConditionSchema.nullable().optional(), - timelock_uc: TimelockUnlockConditionSchema.nullable().optional(), -}); - -const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ - metadata: z.array(z.number()).nullable().optional(), - tag: z.array(z.number()).nullable().optional(), - sender: z.string().nullable().optional(), -}); - -const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; - -export type ExpirationUnlockCondition = z.infer; -export type StorageDepositReturnUnlockCondition = z.infer< - typeof StorageDepositReturnUnlockConditionSchema ->; -export type TimelockUnlockCondition = z.infer; -export type CommonOutputObject = z.infer; -export type CommonOutputObjectWithUc = z.infer; -export type BasicOutputObject = z.infer; -export type NftOutputObject = z.infer; - export async function getNativeTokenTypesFromBag( bagId: string, client: IotaClient, diff --git a/apps/core/src/utils/migration/index.ts b/apps/core/src/utils/migration/index.ts index 140c87d1b7c..1f09b64ac4a 100644 --- a/apps/core/src/utils/migration/index.ts +++ b/apps/core/src/utils/migration/index.ts @@ -2,3 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 export * from './createMigrationTransaction'; +export * from './types'; diff --git a/apps/core/src/utils/migration/types.ts b/apps/core/src/utils/migration/types.ts new file mode 100644 index 00000000000..6f078b149f6 --- /dev/null +++ b/apps/core/src/utils/migration/types.ts @@ -0,0 +1,73 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { z } from 'zod'; +import { STARDUST_PACKAGE_ID } from '../../constants'; + +const ExpirationUnlockConditionSchema = z.object({ + type: z.literal( + `${STARDUST_PACKAGE_ID}::expiration_unlock_condition::ExpirationUnlockCondition`, + ), + fields: z.object({ + owner: z.string(), + return_address: z.string(), + unix_time: z.number(), + }), +}); + +const StorageDepositReturnUnlockConditionSchema = z.object({ + type: z.literal( + `${STARDUST_PACKAGE_ID}::storage_deposit_return_unlock_condition::StorageDepositReturnUnlockCondition`, + ), + fields: z.object({ + return_address: z.string(), + return_amount: z.string(), + }), +}); + +const TimelockUnlockConditionSchema = z.object({ + type: z.literal(`${STARDUST_PACKAGE_ID}::timelock_unlock_condition::TimelockUnlockCondition`), + fields: z.object({ + unix_time: z.number(), + }), +}); + +const CommonOutputObjectSchema = z.object({ + id: z.object({ + id: z.string(), + }), + balance: z.string(), + native_tokens: z.object({ + type: z.literal('0x2::bag::Bag'), + fields: z.object({ + id: z.object({ + id: z.string(), + }), + size: z.string(), + }), + }), +}); + +const CommonOutputObjectWithUcSchema = CommonOutputObjectSchema.extend({ + expiration_uc: ExpirationUnlockConditionSchema.nullable().optional(), + storage_deposit_return_uc: StorageDepositReturnUnlockConditionSchema.nullable().optional(), + timelock_uc: TimelockUnlockConditionSchema.nullable().optional(), +}); + +export const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({ + metadata: z.array(z.number()).nullable().optional(), + tag: z.array(z.number()).nullable().optional(), + sender: z.string().nullable().optional(), +}); + +export const NftOutputObjectSchema = CommonOutputObjectWithUcSchema; + +export type ExpirationUnlockCondition = z.infer; +export type StorageDepositReturnUnlockCondition = z.infer< + typeof StorageDepositReturnUnlockConditionSchema +>; +export type TimelockUnlockCondition = z.infer; +export type CommonOutputObject = z.infer; +export type CommonOutputObjectWithUc = z.infer; +export type BasicOutputObject = z.infer; +export type NftOutputObject = z.infer;