From 4f85c457d63da9005fb964ce8ba93152b21da288 Mon Sep 17 00:00:00 2001 From: Branko Date: Wed, 29 Jan 2025 17:33:29 +0100 Subject: [PATCH 01/27] feat: add max tx byte size to migration tx --- .../dialogs/migration/MigrationDialog.tsx | 24 +++++++++++++++---- .../migration/views/ConfirmMigrationView.tsx | 11 +++++++++ .../hooks/useMigrationTransaction.ts | 14 +++++++++-- .../src/transactions/Transaction.ts | 1 + .../src/transactions/json-rpc-resolver.ts | 1 + 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index e3e9cd19541..65be9c86da7 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit'; import { IotaObjectData } from '@iota/iota-sdk/client'; import { useMigrationTransaction } from '@/hooks/useMigrationTransaction'; @@ -31,6 +31,9 @@ export function MigrationDialog({ isTimelocked, }: MigrationDialogProps): JSX.Element { const account = useCurrentAccount(); + const [basicOutputs, setBasicOutputs] = useState(basicOutputObjects); + const [nftOutputs, setNftOutputs] = useState(nftOutputObjects); + const [isPartialMigration, setIsPartialMigration] = useState(false); const [txDigest, setTxDigest] = useState(''); const [view, setView] = useState(MigrationDialogView.Confirmation); @@ -38,11 +41,23 @@ export function MigrationDialog({ data: migrateData, isPending: isMigrationPending, isError: isMigrationError, - } = useMigrationTransaction(account?.address || '', basicOutputObjects, nftOutputObjects); + } = useMigrationTransaction(account?.address || '', basicOutputs, nftOutputs); const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = useSignAndExecuteTransaction(); + useEffect(() => { + if (isMigrationError) { + const newBasicOutputs = basicOutputs.slice(0, -1); + const newNftOutputs = nftOutputs.slice(0, -1); + + setBasicOutputs(newBasicOutputs); + setNftOutputs(newNftOutputs); + setIsPartialMigration(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMigrationError]); + async function handleMigrate(): Promise { if (!migrateData) return; signAndExecuteTransaction( @@ -69,14 +84,15 @@ export function MigrationDialog({ {view === MigrationDialogView.Confirmation && ( )} diff --git a/apps/wallet-dashboard/components/dialogs/migration/views/ConfirmMigrationView.tsx b/apps/wallet-dashboard/components/dialogs/migration/views/ConfirmMigrationView.tsx index d0109dd04cb..b4e2b53d43a 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/views/ConfirmMigrationView.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/views/ConfirmMigrationView.tsx @@ -39,6 +39,7 @@ interface ConfirmMigrationViewProps { | undefined; isMigrationPending: boolean; isMigrationError: boolean; + isPartialMigration: boolean; isSendingTransaction: boolean; } @@ -51,6 +52,7 @@ export function ConfirmMigrationView({ migrateData, isMigrationPending, isMigrationError, + isPartialMigration, isSendingTransaction, }: ConfirmMigrationViewProps): JSX.Element { const account = useCurrentAccount(); @@ -129,6 +131,15 @@ export function ConfirmMigrationView({ icon={} /> )} + {isPartialMigration && !isLoading && ( + } + /> + )} {isLoading ? ( <> diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts index b36c5b991fb..04a5c61ed4d 100644 --- a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -12,10 +12,20 @@ export function useMigrationTransaction( nftOutputObjects?: IotaObjectData[], ) { const client = useIotaClient(); + const basicOutputObjectsIds = basicOutputObjects?.map((o) => o.objectId) || []; + const nftOutputObjectsIds = nftOutputObjects?.map((o) => o.objectId) || []; + return useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps - queryKey: ['migration-transaction', address], + queryKey: ['migration-transaction', address, basicOutputObjectsIds, nftOutputObjectsIds], queryFn: async () => { + const config = await client.getProtocolConfig(); + const max_tx_size_bytes = config.attributes['max_tx_size_bytes']; + const maxTxSizeBytes = + max_tx_size_bytes && 'u64' in max_tx_size_bytes + ? Number(max_tx_size_bytes?.u64) + : Infinity; + const transaction = await createMigrationTransaction( client, address, @@ -23,7 +33,7 @@ export function useMigrationTransaction( nftOutputObjects, ); transaction.setSender(address); - await transaction.build({ client }); + await transaction.build({ client, maxSizeBytes: maxTxSizeBytes }); return transaction; }, enabled: !!address, diff --git a/sdk/typescript/src/transactions/Transaction.ts b/sdk/typescript/src/transactions/Transaction.ts index aaa263f4a86..382fcdea3e7 100644 --- a/sdk/typescript/src/transactions/Transaction.ts +++ b/sdk/typescript/src/transactions/Transaction.ts @@ -525,6 +525,7 @@ export class Transaction { await this.prepareForSerialization(options); await this.#prepareBuild(options); return this.#data.build({ + maxSizeBytes: options.maxSizeBytes, onlyTransactionKind: options.onlyTransactionKind, }); } diff --git a/sdk/typescript/src/transactions/json-rpc-resolver.ts b/sdk/typescript/src/transactions/json-rpc-resolver.ts index 6d958f63f17..519c5ec1830 100644 --- a/sdk/typescript/src/transactions/json-rpc-resolver.ts +++ b/sdk/typescript/src/transactions/json-rpc-resolver.ts @@ -24,6 +24,7 @@ const MAX_GAS = 50_000_000_000; export interface BuildTransactionOptions { client?: IotaClient; onlyTransactionKind?: boolean; + maxSizeBytes?: number; } export interface SerializeTransactionOptions extends BuildTransactionOptions { From e04f3f59ed262b7fd7aac7d2cd6353e46a988257 Mon Sep 17 00:00:00 2001 From: Branko Date: Thu, 30 Jan 2025 07:14:31 +0100 Subject: [PATCH 02/27] fix: add reduction step constant --- .../components/dialogs/migration/MigrationDialog.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index 65be9c86da7..35fa5c7f107 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -11,6 +11,9 @@ import { TransactionDialogView } from '../TransactionDialog'; import { MigrationDialogView } from './enums'; import { ConfirmMigrationView } from './views'; +// Number of objects to reduce on every attempt +const REDUCTION_STEP_SIZE = 1; + interface MigrationDialogProps { handleClose: () => void; basicOutputObjects: IotaObjectData[] | undefined; @@ -41,15 +44,16 @@ export function MigrationDialog({ data: migrateData, isPending: isMigrationPending, isError: isMigrationError, + error, } = useMigrationTransaction(account?.address || '', basicOutputs, nftOutputs); - + console.log(isMigrationError, error); const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = useSignAndExecuteTransaction(); useEffect(() => { if (isMigrationError) { - const newBasicOutputs = basicOutputs.slice(0, -1); - const newNftOutputs = nftOutputs.slice(0, -1); + const newBasicOutputs = basicOutputs.slice(0, -REDUCTION_STEP_SIZE); + const newNftOutputs = nftOutputs.slice(0, -REDUCTION_STEP_SIZE); setBasicOutputs(newBasicOutputs); setNftOutputs(newNftOutputs); From 7da32e764629ef8ae2e92c285bd32bddaca72b2d Mon Sep 17 00:00:00 2001 From: Branko Date: Thu, 30 Jan 2025 07:33:07 +0100 Subject: [PATCH 03/27] fix: change reduction step size --- .../components/dialogs/migration/MigrationDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index 35fa5c7f107..5eec777439d 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -12,7 +12,7 @@ import { MigrationDialogView } from './enums'; import { ConfirmMigrationView } from './views'; // Number of objects to reduce on every attempt -const REDUCTION_STEP_SIZE = 1; +const REDUCTION_STEP_SIZE = 5; interface MigrationDialogProps { handleClose: () => void; From b836a5bbad34c3f7c34ede1ef399ef0170cbc210 Mon Sep 17 00:00:00 2001 From: Branko Date: Fri, 31 Jan 2025 12:16:32 +0100 Subject: [PATCH 04/27] fix: catch max size bytes error --- .../components/dialogs/migration/MigrationDialog.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index fb0a1cc6dca..cf89552201f 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -14,7 +14,7 @@ import { ampli } from '@/lib/utils/analytics'; // Number of objects to reduce on every attempt const REDUCTION_STEP_SIZE = 5; - +const MAX_SIZE_BYTES_ERROR = 'Attempting to serialize to BCS, but buffer does not have enough size'; interface MigrationDialogProps { handleClose: () => void; basicOutputObjects: IotaObjectData[] | undefined; @@ -45,12 +45,13 @@ export function MigrationDialog({ data: migrateData, isPending: isMigrationPending, isError: isMigrationError, + error, } = useMigrationTransaction(account?.address || '', basicOutputs, nftOutputs); const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } = useSignAndExecuteTransaction(); useEffect(() => { - if (isMigrationError) { + if (isMigrationError && error?.message.includes(MAX_SIZE_BYTES_ERROR)) { const newBasicOutputs = basicOutputs.slice(0, -REDUCTION_STEP_SIZE); const newNftOutputs = nftOutputs.slice(0, -REDUCTION_STEP_SIZE); From 37ccc3cb2410d86e7fff9043f57c678edea85ef9 Mon Sep 17 00:00:00 2001 From: Branko Date: Fri, 31 Jan 2025 12:32:17 +0100 Subject: [PATCH 05/27] feat: add changeset --- .changeset/late-laws-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/late-laws-rest.md diff --git a/.changeset/late-laws-rest.md b/.changeset/late-laws-rest.md new file mode 100644 index 00000000000..73b06e25780 --- /dev/null +++ b/.changeset/late-laws-rest.md @@ -0,0 +1,5 @@ +--- +'@iota/iota-sdk': minor +--- + +Exposed maxSizeBytes in BuildTransactionOptions interface: Added the maxSizeBytes option to the BuildTransactionOptions interface to allow specifying the maximum size of the transaction in bytes during the build process. From 7a94600a3017153ff6bf16e816b4a9c92988e31b Mon Sep 17 00:00:00 2001 From: Branko Date: Fri, 31 Jan 2025 14:40:15 +0100 Subject: [PATCH 06/27] feat: add useMaxTransactionSizeBytes hook --- apps/core/src/hooks/index.ts | 1 + .../src/hooks/useMaxTransactionSizeBytes.ts | 21 +++++++++++++++++++ .../hooks/useMigrationTransaction.ts | 10 ++------- 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 apps/core/src/hooks/useMaxTransactionSizeBytes.ts diff --git a/apps/core/src/hooks/index.ts b/apps/core/src/hooks/index.ts index bfa63a73173..f8bdff3e653 100644 --- a/apps/core/src/hooks/index.ts +++ b/apps/core/src/hooks/index.ts @@ -55,6 +55,7 @@ export * from './useFeatureEnabledByNetwork'; export * from './useGetAllStardustSharedObjects'; export * from './useGetStardustSharedBasicObjects'; export * from './useGetStardustSharedNftObjects'; +export * from './useMaxTransactionSizeBytes'; export * from './stake'; export * from './ui'; diff --git a/apps/core/src/hooks/useMaxTransactionSizeBytes.ts b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts new file mode 100644 index 00000000000..f8b229889ef --- /dev/null +++ b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useIotaClient } from '@iota/dapp-kit'; +import { useQuery } from '@tanstack/react-query'; + +export function useMaxTransactionSizeBytes() { + const client = useIotaClient(); + + return useQuery({ + queryKey: ['protocol-config-max-tx-size-bytes'], + queryFn: async () => { + const config = await client.getProtocolConfig(); + const max_tx_size_bytes = config.attributes['max_tx_size_bytes']; + return max_tx_size_bytes && 'u64' in max_tx_size_bytes + ? Number(max_tx_size_bytes.u64) + : Infinity; + }, + enabled: !!client, + }); +} diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts index 90880b77548..0f02effcf51 100644 --- a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -4,7 +4,7 @@ import { useIotaClient } from '@iota/dapp-kit'; import { IotaObjectData } from '@iota/iota-sdk/client'; import { useQuery } from '@tanstack/react-query'; -import { createMigrationTransaction } from '@iota/core'; +import { createMigrationTransaction, useMaxTransactionSizeBytes } from '@iota/core'; export function useMigrationTransaction( address: string, @@ -14,18 +14,12 @@ export function useMigrationTransaction( const client = useIotaClient(); const basicOutputObjectsIds = basicOutputObjects.map(({ objectId }) => objectId); const nftOutputObjectsIds = nftOutputObjects.map(({ objectId }) => objectId); + const { data: maxTxSizeBytes = Infinity } = useMaxTransactionSizeBytes(); return useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps queryKey: ['migration-transaction', address, basicOutputObjectsIds, nftOutputObjectsIds], queryFn: async () => { - const config = await client.getProtocolConfig(); - const max_tx_size_bytes = config.attributes['max_tx_size_bytes']; - const maxTxSizeBytes = - max_tx_size_bytes && 'u64' in max_tx_size_bytes - ? Number(max_tx_size_bytes?.u64) - : Infinity; - const transaction = await createMigrationTransaction( client, address, From abe13541a4e3f128ac4911f892dcf3ab93859080 Mon Sep 17 00:00:00 2001 From: Branko Date: Fri, 31 Jan 2025 16:34:06 +0100 Subject: [PATCH 07/27] fix: move max size bytes error message to hook --- apps/core/src/hooks/useMaxTransactionSizeBytes.ts | 3 +++ .../components/dialogs/migration/MigrationDialog.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/core/src/hooks/useMaxTransactionSizeBytes.ts b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts index f8b229889ef..6ab1c9afc4e 100644 --- a/apps/core/src/hooks/useMaxTransactionSizeBytes.ts +++ b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts @@ -4,6 +4,9 @@ import { useIotaClient } from '@iota/dapp-kit'; import { useQuery } from '@tanstack/react-query'; +export const MAX_SIZE_BYTES_ERROR = + 'Attempting to serialize to BCS, but buffer does not have enough size'; + export function useMaxTransactionSizeBytes() { const client = useIotaClient(); diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index cf89552201f..d63d6af9ddf 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -11,10 +11,10 @@ import { TransactionDialogView } from '../TransactionDialog'; import { MigrationDialogView } from './enums'; import { ConfirmMigrationView } from './views'; import { ampli } from '@/lib/utils/analytics'; +import { MAX_SIZE_BYTES_ERROR } from '@iota/core'; // Number of objects to reduce on every attempt const REDUCTION_STEP_SIZE = 5; -const MAX_SIZE_BYTES_ERROR = 'Attempting to serialize to BCS, but buffer does not have enough size'; interface MigrationDialogProps { handleClose: () => void; basicOutputObjects: IotaObjectData[] | undefined; From d14dd210690decfb07eb6174f492658c30f145d7 Mon Sep 17 00:00:00 2001 From: Branko Date: Mon, 3 Feb 2025 10:36:39 +0100 Subject: [PATCH 08/27] fix: code improvements --- .../dialogs/migration/MigrationDialog.tsx | 16 ++++++++-------- .../hooks/useMigrationTransaction.ts | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index d63d6af9ddf..11190668bd0 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit'; import { IotaObjectData } from '@iota/iota-sdk/client'; import { useMigrationTransaction } from '@/hooks/useMigrationTransaction'; @@ -15,6 +15,7 @@ import { MAX_SIZE_BYTES_ERROR } from '@iota/core'; // Number of objects to reduce on every attempt const REDUCTION_STEP_SIZE = 5; + interface MigrationDialogProps { handleClose: () => void; basicOutputObjects: IotaObjectData[] | undefined; @@ -38,6 +39,7 @@ export function MigrationDialog({ const [basicOutputs, setBasicOutputs] = useState(basicOutputObjects); const [nftOutputs, setNftOutputs] = useState(nftOutputObjects); const [isPartialMigration, setIsPartialMigration] = useState(false); + const reductionSize = useRef(0); const [txDigest, setTxDigest] = useState(null); const [view, setView] = useState(MigrationDialogView.Confirmation); @@ -52,11 +54,9 @@ export function MigrationDialog({ useEffect(() => { if (isMigrationError && error?.message.includes(MAX_SIZE_BYTES_ERROR)) { - const newBasicOutputs = basicOutputs.slice(0, -REDUCTION_STEP_SIZE); - const newNftOutputs = nftOutputs.slice(0, -REDUCTION_STEP_SIZE); - - setBasicOutputs(newBasicOutputs); - setNftOutputs(newNftOutputs); + reductionSize.current += REDUCTION_STEP_SIZE; + setBasicOutputs(basicOutputObjects.slice(0, -reductionSize.current)); + setNftOutputs(nftOutputObjects.slice(0, -reductionSize.current)); setIsPartialMigration(true); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -74,8 +74,8 @@ export function MigrationDialog({ setTxDigest(tx.digest); setView(MigrationDialogView.TransactionDetails); ampli.migration({ - basicOutputObjects: basicOutputObjects.length, - nftOutputObjects: nftOutputObjects.length, + basicOutputObjects: basicOutputs.length, + nftOutputObjects: nftOutputs.length, }); }, }, diff --git a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts index 0f02effcf51..bfdbf0d3acb 100644 --- a/apps/wallet-dashboard/hooks/useMigrationTransaction.ts +++ b/apps/wallet-dashboard/hooks/useMigrationTransaction.ts @@ -14,7 +14,7 @@ export function useMigrationTransaction( const client = useIotaClient(); const basicOutputObjectsIds = basicOutputObjects.map(({ objectId }) => objectId); const nftOutputObjectsIds = nftOutputObjects.map(({ objectId }) => objectId); - const { data: maxTxSizeBytes = Infinity } = useMaxTransactionSizeBytes(); + const { data: maxSizeBytes = Infinity } = useMaxTransactionSizeBytes(); return useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps @@ -27,7 +27,7 @@ export function useMigrationTransaction( nftOutputObjects, ); transaction.setSender(address); - await transaction.build({ client, maxSizeBytes: maxTxSizeBytes }); + await transaction.build({ client, maxSizeBytes }); return transaction; }, enabled: !!address, From fa37e12b2541b82c36ad92c4e85ec9839fede645 Mon Sep 17 00:00:00 2001 From: Panteleymonchuk Date: Mon, 3 Feb 2025 11:37:58 +0200 Subject: [PATCH 09/27] feat(dashboard): determine max ptb for unstake timelocked objects. --- .../useUnlockTimelockedObjectsTransaction.ts | 2 +- .../useGetSupplyIncreaseVestingObjects.ts | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts index 6e97751de56..a0c7e3a2c71 100644 --- a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts +++ b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts @@ -14,7 +14,7 @@ export function useUnlockTimelockedObjectsTransaction(address: string, objectIds queryFn: async () => { const transaction = createUnlockTimelockedObjectsTransaction({ address, objectIds }); transaction.setSender(address); - await transaction.build({ client }); + await transaction.build({ client, maxSizeBytes: 32 }); return transaction; }, enabled: !!address && !!objectIds, diff --git a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts index e086333e4ce..0359249bda1 100644 --- a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts +++ b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts @@ -1,6 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { useRef, useEffect, useState } from 'react'; import { useGetCurrentEpochStartTimestamp } from '@/hooks'; import { SupplyIncreaseVestingPayout, @@ -44,6 +45,9 @@ interface SupplyIncreaseVestingObject { } export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncreaseVestingObject { + const limitUnlockObjects = useRef(null); + const [isDeterminingLimitInProgress, setIsDeterminingInProgress] = useState(); + const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); const { data: timelockedObjects, refetch: refetchGetAllOwnedObjects } = useGetAllOwnedObjects( @@ -91,9 +95,9 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre ); const supplyIncreaseVestingUnlockedObjectIds: string[] = supplyIncreaseVestingUnlocked.map((unlockedObject) => unlockedObject.id.id) || []; - const { data: unlockAllSupplyIncreaseVesting } = useUnlockTimelockedObjectsTransaction( + const { data: unlockAllSupplyIncreaseVesting, error } = useUnlockTimelockedObjectsTransaction( address || '', - supplyIncreaseVestingUnlockedObjectIds, + determineMaxSupplyIncreaseVestingUnlockedObjects(), ); const isSupplyIncreaseVestingScheduleEmpty = @@ -108,6 +112,33 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre refetchGetAllOwnedObjects(); } + function determineMaxSupplyIncreaseVestingUnlockedObjects() { + if (isDeterminingLimitInProgress && limitUnlockObjects.current) { + return supplyIncreaseVestingUnlockedObjectIds.slice(0, limitUnlockObjects.current); + } + return supplyIncreaseVestingUnlockedObjectIds; + } + + useEffect(() => { + if (!supplyIncreaseVestingUnlocked.length) return; + limitUnlockObjects.current = supplyIncreaseVestingUnlocked.length; + }, [supplyIncreaseVestingUnlocked?.length]); + + useEffect(() => { + if ( + error?.message?.includes( + 'Attempting to serialize to BCS, but buffer does not have enough size.', + ) && + limitUnlockObjects.current !== null + ) { + setIsDeterminingInProgress(true); + let nextLimit = limitUnlockObjects.current - 5; + nextLimit = nextLimit > 0 ? nextLimit : 0; + + limitUnlockObjects.current = nextLimit; + } + }, [error?.message, supplyIncreaseVestingUnlocked]); + return { nextPayout, lastPayout, From f59bbdb60afa862fb2a0a28ed1980869e398e062 Mon Sep 17 00:00:00 2001 From: Panteleymonchuk Date: Mon, 3 Feb 2025 17:50:17 +0200 Subject: [PATCH 10/27] feat: refactor unlock timelocked objects transaction and improve supply increase vesting logic --- .../useUnlockTimelockedObjectsTransaction.ts | 2 +- .../useGetSupplyIncreaseVestingObjects.ts | 101 ++++++++++++------ 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts index a0c7e3a2c71..6e97751de56 100644 --- a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts +++ b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts @@ -14,7 +14,7 @@ export function useUnlockTimelockedObjectsTransaction(address: string, objectIds queryFn: async () => { const transaction = createUnlockTimelockedObjectsTransaction({ address, objectIds }); transaction.setSender(address); - await transaction.build({ client, maxSizeBytes: 32 }); + await transaction.build({ client }); return transaction; }, enabled: !!address && !!objectIds, diff --git a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts index 0359249bda1..e23360bc6f8 100644 --- a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts +++ b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts @@ -1,7 +1,12 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { useRef, useEffect, useState } from 'react'; +import { + // useRef, + useEffect, + useState, + useMemo, +} from 'react'; import { useGetCurrentEpochStartTimestamp } from '@/hooks'; import { SupplyIncreaseVestingPayout, @@ -45,8 +50,8 @@ interface SupplyIncreaseVestingObject { } export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncreaseVestingObject { - const limitUnlockObjects = useRef(null); - const [isDeterminingLimitInProgress, setIsDeterminingInProgress] = useState(); + // const limitUnlockObjects = useRef(null); + // const [isDeterminingLimitInProgress, setIsDeterminingInProgress] = useState(); const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); @@ -95,10 +100,16 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre ); const supplyIncreaseVestingUnlockedObjectIds: string[] = supplyIncreaseVestingUnlocked.map((unlockedObject) => unlockedObject.id.id) || []; - const { data: unlockAllSupplyIncreaseVesting, error } = useUnlockTimelockedObjectsTransaction( - address || '', - determineMaxSupplyIncreaseVestingUnlockedObjects(), + const { determinedUnlockedTimelockObjects } = useDetermining( + supplyIncreaseVestingUnlockedObjectIds, ); + const { + data: unlockAllSupplyIncreaseVesting, + // error, + isPending, + } = useUnlockTimelockedObjectsTransaction(address || '', determinedUnlockedTimelockObjects); + + console.log('isPending', isPending); const isSupplyIncreaseVestingScheduleEmpty = !supplyIncreaseVestingSchedule.totalVested && @@ -112,32 +123,20 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre refetchGetAllOwnedObjects(); } - function determineMaxSupplyIncreaseVestingUnlockedObjects() { - if (isDeterminingLimitInProgress && limitUnlockObjects.current) { - return supplyIncreaseVestingUnlockedObjectIds.slice(0, limitUnlockObjects.current); - } - return supplyIncreaseVestingUnlockedObjectIds; - } - - useEffect(() => { - if (!supplyIncreaseVestingUnlocked.length) return; - limitUnlockObjects.current = supplyIncreaseVestingUnlocked.length; - }, [supplyIncreaseVestingUnlocked?.length]); - - useEffect(() => { - if ( - error?.message?.includes( - 'Attempting to serialize to BCS, but buffer does not have enough size.', - ) && - limitUnlockObjects.current !== null - ) { - setIsDeterminingInProgress(true); - let nextLimit = limitUnlockObjects.current - 5; - nextLimit = nextLimit > 0 ? nextLimit : 0; - - limitUnlockObjects.current = nextLimit; - } - }, [error?.message, supplyIncreaseVestingUnlocked]); + // useEffect(() => { + // if ( + // && + // limitUnlockObjects.current !== null + // ) { + // setIsDeterminingInProgress(true); + // let nextLimit = limitUnlockObjects.current - 5; + // nextLimit = nextLimit > 0 ? nextLimit : 0; + + // limitUnlockObjects.current = nextLimit; + // } else { + // console.log('there is no error'); + // } + // }, [error?.message, supplyIncreaseVestingUnlocked]); return { nextPayout, @@ -152,3 +151,41 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre isSupplyIncreaseVestingScheduleEmpty, }; } + +function useDetermining(supplyIncreaseVestingUnlockedObjectIds: string[]) { + const [maxLimit, setMaxLimit] = useState(); + const [status, setStatus] = useState<'firstAttempt' | 'idle'>('idle'); + + useEffect(() => { + if (!supplyIncreaseVestingUnlockedObjectIds.length) { + return; + } + + setMaxLimit(supplyIncreaseVestingUnlockedObjectIds.length); + setStatus('firstAttempt'); + }, [supplyIncreaseVestingUnlockedObjectIds]); + + const determinedUnlockedTimelockObjects = useMemo(() => { + if (status === 'idle' || status === 'firstAttempt') { + return supplyIncreaseVestingUnlockedObjectIds; + } + return supplyIncreaseVestingUnlockedObjectIds.slice(0, maxLimit); + }, [status, supplyIncreaseVestingUnlockedObjectIds, maxLimit]); + + console.log('status', status); + + const handleLimitError = (error?: Error) => { + const hasMessage = error?.message?.includes( + 'Attempting to serialize to BCS, but buffer does not have enough size.', + ); + + if (hasMessage) { + console.log('--- has message'); + } + }; + + return { + handleLimitError, + determinedUnlockedTimelockObjects, + }; +} From ddb02364d745340f05d29311e50c45c593f1fd96 Mon Sep 17 00:00:00 2001 From: Panteleymonchuk Date: Mon, 3 Feb 2025 19:17:54 +0200 Subject: [PATCH 11/27] feat: enhance unlock timelocked objects transaction handling with dynamic size adjustment --- .../useUnlockTimelockedObjectsTransaction.ts | 64 +++++++++++++++---- .../useGetSupplyIncreaseVestingObjects.ts | 6 +- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts index 6e97751de56..c8b181a369c 100644 --- a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts +++ b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts @@ -4,25 +4,65 @@ import { useIotaClient } from '@iota/dapp-kit'; import { createUnlockTimelockedObjectsTransaction } from '../utils'; import { useQuery } from '@tanstack/react-query'; +import { Transaction } from '@iota/iota-sdk/transactions'; + +interface UnlockResult { + transactionBlock: Transaction; + validCount: number; +} export function useUnlockTimelockedObjectsTransaction(address: string, objectIds: string[]) { const client = useIotaClient(); return useQuery({ - // eslint-disable-next-line @tanstack/query/exhaustive-deps queryKey: ['unlock-timelocked-objects', address, objectIds], - queryFn: async () => { - const transaction = createUnlockTimelockedObjectsTransaction({ address, objectIds }); - transaction.setSender(address); - await transaction.build({ client }); - return transaction; + queryFn: async (): Promise => { + // Start with the full list. + let currentLimit = objectIds.length; + let isMaxSizeError = false; + + // Loop until we either succeed or run out of objects. + do { + // Use only the first currentLimit objectIds. + const currentObjectIds = objectIds.slice(0, currentLimit); + + // Create a transaction for the current subset. + const transaction = createUnlockTimelockedObjectsTransaction({ + address, + objectIds: currentObjectIds, + }); + + transaction.setSender(address); + + try { + await transaction.build({ client, maxSizeBytes: 32 }); + isMaxSizeError = false; + return { + transactionBlock: transaction, + validCount: currentLimit, + }; + } catch (e: unknown) { + if ( + e instanceof Error && + e.message.includes( + 'Attempting to serialize to BCS, but buffer does not have enough size.', + ) + ) { + isMaxSizeError = true; + // Reduce the currentLimit by one and try again. + currentLimit -= 1; + } else { + // If it's any other error, rethrow it. + throw e; + } + } + } while (isMaxSizeError && currentLimit > 0); + + // If we have reduced to zero, no valid transaction can be built. + throw new Error('Unable to build transaction with any object count.'); }, - enabled: !!address && !!objectIds, + enabled: !!address && objectIds.length > 0, gcTime: 0, - select: (transaction) => { - return { - transactionBlock: transaction, - }; - }, + // You could use select here if you want to massage the returned data further. }); } diff --git a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts index e23360bc6f8..cd96dcbba87 100644 --- a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts +++ b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts @@ -105,11 +105,11 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre ); const { data: unlockAllSupplyIncreaseVesting, - // error, + error, isPending, } = useUnlockTimelockedObjectsTransaction(address || '', determinedUnlockedTimelockObjects); - console.log('isPending', isPending); + console.log('isPending timelocked', unlockAllSupplyIncreaseVesting, isPending, error); const isSupplyIncreaseVestingScheduleEmpty = !supplyIncreaseVestingSchedule.totalVested && @@ -172,7 +172,7 @@ function useDetermining(supplyIncreaseVestingUnlockedObjectIds: string[]) { return supplyIncreaseVestingUnlockedObjectIds.slice(0, maxLimit); }, [status, supplyIncreaseVestingUnlockedObjectIds, maxLimit]); - console.log('status', status); + // console.log('status', status); const handleLimitError = (error?: Error) => { const hasMessage = error?.message?.includes( From 5d3c93314e49cc0cce05ab2cfa57d918d1e9e096 Mon Sep 17 00:00:00 2001 From: Panteleymonchuk Date: Mon, 3 Feb 2025 20:06:58 +0200 Subject: [PATCH 12/27] feat: implement optimal limit search for unlocking timelocked objects and update related types --- .../useUnlockTimelockedObjectsTransaction.ts | 88 ++++++++++++------- .../app/(protected)/vesting/page.tsx | 14 ++- .../useGetSupplyIncreaseVestingObjects.ts | 80 +---------------- 3 files changed, 73 insertions(+), 109 deletions(-) diff --git a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts index c8b181a369c..f04f7be2b87 100644 --- a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts +++ b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts @@ -5,64 +5,88 @@ import { useIotaClient } from '@iota/dapp-kit'; import { createUnlockTimelockedObjectsTransaction } from '../utils'; import { useQuery } from '@tanstack/react-query'; import { Transaction } from '@iota/iota-sdk/transactions'; +import { useMaxTransactionSizeBytes } from './useMaxTransactionSizeBytes'; -interface UnlockResult { +export interface UnlockAllSupplyIncrease { transactionBlock: Transaction; + isMaxSizeReached: boolean; validCount: number; } export function useUnlockTimelockedObjectsTransaction(address: string, objectIds: string[]) { const client = useIotaClient(); + const { data: maxTxSizeBytes = Infinity } = useMaxTransactionSizeBytes(); return useQuery({ queryKey: ['unlock-timelocked-objects', address, objectIds], - queryFn: async (): Promise => { - // Start with the full list. + queryFn: async (): Promise => { + let low = 0; + let high = objectIds.length; let currentLimit = objectIds.length; - let isMaxSizeError = false; + let transaction: Transaction = {} as Transaction; + let isSearchingOptimalLimit = false; - // Loop until we either succeed or run out of objects. - do { - // Use only the first currentLimit objectIds. - const currentObjectIds = objectIds.slice(0, currentLimit); - - // Create a transaction for the current subset. - const transaction = createUnlockTimelockedObjectsTransaction({ + // first attempt + try { + transaction = createUnlockTimelockedObjectsTransaction({ address, - objectIds: currentObjectIds, + objectIds: objectIds, }); transaction.setSender(address); + await transaction.build({ client, maxSizeBytes: maxTxSizeBytes }); + + return { + transactionBlock: transaction, + isMaxSizeReached: false, + validCount: objectIds.length, + }; + } catch (e: unknown) { + if (isAttemptError(e)) { + isSearchingOptimalLimit = true; + console.info('Error max size. Start to search optimal count.'); + } else { + throw e; + } + } + // if first attempt failed start to find optimal limit + while (isSearchingOptimalLimit && low <= high) { try { - await transaction.build({ client, maxSizeBytes: 32 }); - isMaxSizeError = false; - return { - transactionBlock: transaction, - validCount: currentLimit, - }; + currentLimit = Math.ceil((low + high) / 2); + const currentObjectIds = objectIds.slice(0, currentLimit); + + transaction = createUnlockTimelockedObjectsTransaction({ + address, + objectIds: currentObjectIds, + }); + transaction.setSender(address); + await transaction.build({ client, maxSizeBytes: maxTxSizeBytes }); + + low = currentLimit + 1; } catch (e: unknown) { - if ( - e instanceof Error && - e.message.includes( - 'Attempting to serialize to BCS, but buffer does not have enough size.', - ) - ) { - isMaxSizeError = true; - // Reduce the currentLimit by one and try again. - currentLimit -= 1; + if (isAttemptError(e)) { + high = currentLimit - 1; } else { - // If it's any other error, rethrow it. throw e; } } - } while (isMaxSizeError && currentLimit > 0); + } - // If we have reduced to zero, no valid transaction can be built. - throw new Error('Unable to build transaction with any object count.'); + return { + transactionBlock: transaction, + isMaxSizeReached: true, + validCount: currentLimit, + }; }, enabled: !!address && objectIds.length > 0, gcTime: 0, - // You could use select here if you want to massage the returned data further. }); } + +function isAttemptError(e: unknown) { + return ( + e instanceof Error && + e.message.includes('Attempting to serialize to BCS, but buffer does not have enough size.') + ); +} diff --git a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx index 639be12171a..cec9d26b46c 100644 --- a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx @@ -35,6 +35,9 @@ import { LoadingIndicator, LabelText, LabelTextSize, + InfoBox, + InfoBoxStyle, + InfoBoxType, } from '@iota/apps-ui-kit'; import { Theme, @@ -52,7 +55,7 @@ import { } from '@iota/dapp-kit'; import { IotaValidatorSummary } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Calendar, StarHex } from '@iota/apps-ui-icons'; +import { Calendar, StarHex, Warning } from '@iota/apps-ui-icons'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { StakedTimelockObject } from '@/components'; @@ -267,6 +270,15 @@ export default function VestingDashboardPage(): JSX.Element { } /> + {unlockAllSupplyIncreaseVesting?.isMaxSizeReached ? ( + } + /> + ) : null} void; isSupplyIncreaseVestingScheduleEmpty: boolean; } export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncreaseVestingObject { - // const limitUnlockObjects = useRef(null); - // const [isDeterminingLimitInProgress, setIsDeterminingInProgress] = useState(); - const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); const { data: timelockedObjects, refetch: refetchGetAllOwnedObjects } = useGetAllOwnedObjects( @@ -100,16 +87,10 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre ); const supplyIncreaseVestingUnlockedObjectIds: string[] = supplyIncreaseVestingUnlocked.map((unlockedObject) => unlockedObject.id.id) || []; - const { determinedUnlockedTimelockObjects } = useDetermining( + const { data: unlockAllSupplyIncreaseVesting } = useUnlockTimelockedObjectsTransaction( + address || '', supplyIncreaseVestingUnlockedObjectIds, ); - const { - data: unlockAllSupplyIncreaseVesting, - error, - isPending, - } = useUnlockTimelockedObjectsTransaction(address || '', determinedUnlockedTimelockObjects); - - console.log('isPending timelocked', unlockAllSupplyIncreaseVesting, isPending, error); const isSupplyIncreaseVestingScheduleEmpty = !supplyIncreaseVestingSchedule.totalVested && @@ -123,21 +104,6 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre refetchGetAllOwnedObjects(); } - // useEffect(() => { - // if ( - // && - // limitUnlockObjects.current !== null - // ) { - // setIsDeterminingInProgress(true); - // let nextLimit = limitUnlockObjects.current - 5; - // nextLimit = nextLimit > 0 ? nextLimit : 0; - - // limitUnlockObjects.current = nextLimit; - // } else { - // console.log('there is no error'); - // } - // }, [error?.message, supplyIncreaseVestingUnlocked]); - return { nextPayout, lastPayout, @@ -151,41 +117,3 @@ export function useGetSupplyIncreaseVestingObjects(address: string): SupplyIncre isSupplyIncreaseVestingScheduleEmpty, }; } - -function useDetermining(supplyIncreaseVestingUnlockedObjectIds: string[]) { - const [maxLimit, setMaxLimit] = useState(); - const [status, setStatus] = useState<'firstAttempt' | 'idle'>('idle'); - - useEffect(() => { - if (!supplyIncreaseVestingUnlockedObjectIds.length) { - return; - } - - setMaxLimit(supplyIncreaseVestingUnlockedObjectIds.length); - setStatus('firstAttempt'); - }, [supplyIncreaseVestingUnlockedObjectIds]); - - const determinedUnlockedTimelockObjects = useMemo(() => { - if (status === 'idle' || status === 'firstAttempt') { - return supplyIncreaseVestingUnlockedObjectIds; - } - return supplyIncreaseVestingUnlockedObjectIds.slice(0, maxLimit); - }, [status, supplyIncreaseVestingUnlockedObjectIds, maxLimit]); - - // console.log('status', status); - - const handleLimitError = (error?: Error) => { - const hasMessage = error?.message?.includes( - 'Attempting to serialize to BCS, but buffer does not have enough size.', - ); - - if (hasMessage) { - console.log('--- has message'); - } - }; - - return { - handleLimitError, - determinedUnlockedTimelockObjects, - }; -} From 6e006ae4e9c3835dd05bbc4547bc236f253638e6 Mon Sep 17 00:00:00 2001 From: Branko Date: Tue, 4 Feb 2025 11:34:52 +0100 Subject: [PATCH 13/27] fix: add catch for SizeLimitExceeded error code --- apps/core/src/hooks/useMaxTransactionSizeBytes.ts | 3 +-- .../components/dialogs/migration/MigrationDialog.tsx | 6 +++--- sdk/bcs/src/writer.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/core/src/hooks/useMaxTransactionSizeBytes.ts b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts index 6ab1c9afc4e..7e966bdc785 100644 --- a/apps/core/src/hooks/useMaxTransactionSizeBytes.ts +++ b/apps/core/src/hooks/useMaxTransactionSizeBytes.ts @@ -4,8 +4,7 @@ import { useIotaClient } from '@iota/dapp-kit'; import { useQuery } from '@tanstack/react-query'; -export const MAX_SIZE_BYTES_ERROR = - 'Attempting to serialize to BCS, but buffer does not have enough size'; +export const SIZE_LIMIT_EXCEEDED = 'SizeLimitExceeded'; export function useMaxTransactionSizeBytes() { const client = useIotaClient(); diff --git a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx index 11190668bd0..b8de49c7755 100644 --- a/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx +++ b/apps/wallet-dashboard/components/dialogs/migration/MigrationDialog.tsx @@ -11,10 +11,10 @@ import { TransactionDialogView } from '../TransactionDialog'; import { MigrationDialogView } from './enums'; import { ConfirmMigrationView } from './views'; import { ampli } from '@/lib/utils/analytics'; -import { MAX_SIZE_BYTES_ERROR } from '@iota/core'; +import { SIZE_LIMIT_EXCEEDED } from '@iota/core'; // Number of objects to reduce on every attempt -const REDUCTION_STEP_SIZE = 5; +const REDUCTION_STEP_SIZE = 25; interface MigrationDialogProps { handleClose: () => void; @@ -53,7 +53,7 @@ export function MigrationDialog({ useSignAndExecuteTransaction(); useEffect(() => { - if (isMigrationError && error?.message.includes(MAX_SIZE_BYTES_ERROR)) { + if (isMigrationError && error?.message.includes(SIZE_LIMIT_EXCEEDED)) { reductionSize.current += REDUCTION_STEP_SIZE; setBasicOutputs(basicOutputObjects.slice(0, -reductionSize.current)); setNftOutputs(nftOutputObjects.slice(0, -reductionSize.current)); diff --git a/sdk/bcs/src/writer.ts b/sdk/bcs/src/writer.ts index ebfab244882..84c5b293407 100644 --- a/sdk/bcs/src/writer.ts +++ b/sdk/bcs/src/writer.ts @@ -57,7 +57,7 @@ export class BcsWriter { const nextSize = Math.min(this.maxSize, this.size + this.allocateSize); if (requiredSize > nextSize) { throw new Error( - `Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`, + `SizeLimitExceeded: Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`, ); } From 2a8a1d3cc50409afd94caf99b69a2545f5d495cb Mon Sep 17 00:00:00 2001 From: Panteleymonchuk Date: Tue, 4 Feb 2025 14:08:54 +0200 Subject: [PATCH 14/27] feat: improve transaction handling for unstaking timelocked objects with optimal size search and error handling --- .../useUnlockTimelockedObjectsTransaction.ts | 13 ++-- .../views/UnstakeTimelockedObjectsView.tsx | 16 ++++ .../hooks/useNewUnstakeTransaction.ts | 74 +++++++++++++++++-- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts index f04f7be2b87..9ab7ade9561 100644 --- a/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts +++ b/apps/core/src/hooks/useUnlockTimelockedObjectsTransaction.ts @@ -5,7 +5,7 @@ import { useIotaClient } from '@iota/dapp-kit'; import { createUnlockTimelockedObjectsTransaction } from '../utils'; import { useQuery } from '@tanstack/react-query'; import { Transaction } from '@iota/iota-sdk/transactions'; -import { useMaxTransactionSizeBytes } from './useMaxTransactionSizeBytes'; +import { MAX_SIZE_BYTES_ERROR, useMaxTransactionSizeBytes } from './useMaxTransactionSizeBytes'; export interface UnlockAllSupplyIncrease { transactionBlock: Transaction; @@ -42,7 +42,7 @@ export function useUnlockTimelockedObjectsTransaction(address: string, objectIds validCount: objectIds.length, }; } catch (e: unknown) { - if (isAttemptError(e)) { + if (e instanceof Error && isAttemptError(e)) { isSearchingOptimalLimit = true; console.info('Error max size. Start to search optimal count.'); } else { @@ -65,7 +65,7 @@ export function useUnlockTimelockedObjectsTransaction(address: string, objectIds low = currentLimit + 1; } catch (e: unknown) { - if (isAttemptError(e)) { + if (e instanceof Error && isAttemptError(e)) { high = currentLimit - 1; } else { throw e; @@ -84,9 +84,6 @@ export function useUnlockTimelockedObjectsTransaction(address: string, objectIds }); } -function isAttemptError(e: unknown) { - return ( - e instanceof Error && - e.message.includes('Attempting to serialize to BCS, but buffer does not have enough size.') - ); +function isAttemptError(e: Error) { + return e.message.includes(MAX_SIZE_BYTES_ERROR); } diff --git a/apps/wallet-dashboard/components/dialogs/unstake/views/UnstakeTimelockedObjectsView.tsx b/apps/wallet-dashboard/components/dialogs/unstake/views/UnstakeTimelockedObjectsView.tsx index 2bfcc7a1fad..ea009bdd549 100644 --- a/apps/wallet-dashboard/components/dialogs/unstake/views/UnstakeTimelockedObjectsView.tsx +++ b/apps/wallet-dashboard/components/dialogs/unstake/views/UnstakeTimelockedObjectsView.tsx @@ -21,11 +21,15 @@ import { Header, ButtonType, Button, + InfoBox, + InfoBoxStyle, + InfoBoxType, } from '@iota/apps-ui-kit'; import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit'; import { IotaSignAndExecuteTransactionOutput } from '@iota/wallet-standard'; import toast from 'react-hot-toast'; import { ampli } from '@/lib/utils/analytics'; +import { Warning } from '@iota/apps-ui-icons'; interface UnstakeTimelockedObjectsViewProps { onClose: () => void; @@ -50,6 +54,7 @@ export function UnstakeTimelockedObjectsView({ activeAddress, timelockedStakedIotaIds, ); + const { mutateAsync: signAndExecuteTransaction, isPending: isTransactionPending } = useSignAndExecuteTransaction(); @@ -151,6 +156,17 @@ export function UnstakeTimelockedObjectsView({ + {unstakeData?.isMaxSizeReached ? ( +
+ } + /> +
+ ) : null}