Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet-dashboard): update migration portfolio to include shared objects #4974

Merged
merged 22 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9bd0183
feat(core): add StardustIndexerClient
panteleymonchuk Jan 22, 2025
8ca684e
feat(dashboard): add stardust objects from indexer.
panteleymonchuk Jan 22, 2025
e4e7c69
feat(core): enhance StardustIndexerClient with basic output mapping a…
panteleymonchuk Jan 22, 2025
e8439d7
Merge branch 'develop' into tooling-wallet/add-objects-from-indexer
cpl121 Jan 23, 2025
8884242
feat(dashboard): add NFT resolved outputs retrieval and mapping funct…
panteleymonchuk Jan 23, 2025
adedf67
Merge remote-tracking branch 'origin/develop' into tooling-wallet/add…
panteleymonchuk Jan 23, 2025
01975e8
refactor(core, dashboard): unify output types and enhance mapping fun…
panteleymonchuk Jan 23, 2025
6c9c84a
feat(wallet-dashboard, core): enhance Stardust output handling and ad…
panteleymonchuk Jan 24, 2025
88f9c02
Merge remote-tracking branch 'origin/develop' into tooling-wallet/add…
panteleymonchuk Jan 24, 2025
47bebe0
Merge branch 'develop' into tooling-wallet/add-objects-from-indexer
panteleymonchuk Jan 27, 2025
f176c64
fix(core, dashboard): pr fixes
panteleymonchuk Jan 27, 2025
3e002b4
feat(sdk): add metadata configuration for stardustIndexer in .env.def…
panteleymonchuk Jan 28, 2025
cf895b3
Merge branch 'develop' into tooling-wallet/add-objects-from-indexer
panteleymonchuk Jan 28, 2025
026efe8
refactor(core): rename limit parameter to page_size in StardustIndexe…
panteleymonchuk Jan 28, 2025
4c523eb
refactor(core): update PageParams interface and adjust pagination par…
panteleymonchuk Jan 28, 2025
926413d
Merge remote-tracking branch 'origin/develop' into tooling-wallet/add…
panteleymonchuk Jan 28, 2025
b4c3ede
refactor(core): update return_amount
panteleymonchuk Jan 28, 2025
303563e
refactor(core): update StardustIndexerClient methods and rename hook …
panteleymonchuk Jan 28, 2025
9bdab66
refactor(dashboard): remove unused variables in useGetAllStardustShar…
panteleymonchuk Jan 28, 2025
234c459
fix: add missing 0x to stardust package id
begonaalvarezd Jan 28, 2025
3d50207
refactor(core, dashboard): update Stardust package ID format, improve…
panteleymonchuk Jan 28, 2025
642600d
Merge remote-tracking branch 'origin/develop' into tooling-wallet/add…
panteleymonchuk Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions apps/core/src/api/StardustIndexerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { StardustIndexerOutput } from '../utils';

export class StardustIndexerClient {
private baseUrl: string;

constructor(baseUrl?: string) {
if (!baseUrl) {
throw new Error('Base URL for IndexerAPI is required.');
}
this.baseUrl = baseUrl;
}

private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(options?.headers || {}),
},
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`API Error: ${response.status} ${response.statusText} - ${errorText}`);
}

return response.json();
}

public async getBasicResolvedOutputs(address: string): Promise<StardustIndexerOutput[]> {
return this.request(`/basic/resolved/${address}`);
}

public async getNftResolvedOutputs(address: string): Promise<StardustIndexerOutput[]> {
return this.request(`/nft/resolved/${address}`);
}
}
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions apps/core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

export * from './SentryHttpTransport';
export * from './StardustIndexerClient';
3 changes: 3 additions & 0 deletions apps/core/src/constants/migration.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ export const STARDUST_PACKAGE_ID =
'000000000000000000000000000000000000000000000000000000000000107a';
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
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}>`;
export const STARDUST_EXPIRATION_UNLOCK_CONDITION_TYPE = `${STARDUST_PACKAGE_ID}::expiration_unlock_condition::ExpirationUnlockCondition`;
export const STARDUST_STORAGE_DEPOSIT_RETURN_UC_TYPE = `${STARDUST_PACKAGE_ID}::storage_deposit_return_unlock_condition::StorageDepositReturnUnlockCondition`;
export const STARDUST_TIMELOCK_TYPE = `${STARDUST_PACKAGE_ID}::timelock_unlock_condition::TimelockUnlockCondition`;
41 changes: 41 additions & 0 deletions apps/core/src/contexts/StardustIndexerClientContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useContext, createContext, useState, useEffect } from 'react';
import { StardustIndexerClient } from '../';
import { getNetwork } from '@iota/iota-sdk/client';

type StardustIndexerClientContextType = {
stardustIndexerClient: StardustIndexerClient | null;
};

export const StardustIndexerClientContext = createContext<StardustIndexerClientContextType | null>(
null,
);

export function useStardustIndexerClientContext(): StardustIndexerClientContextType {
const context = useContext(StardustIndexerClientContext);
if (!context) {
throw new Error('useStardustIndexerClient must be used within a StardustClientProvider');
}
return context;
}

export function useStardustIndexerClient(network?: string) {
const [stardustIndexerClient, setStardustIndexerClient] =
useState<StardustIndexerClient | null>(null);

const { stardustIndexer } = getNetwork(network || '');

useEffect(() => {
if (!stardustIndexer) {
setStardustIndexerClient(null);
} else {
setStardustIndexerClient(new StardustIndexerClient(stardustIndexer));
}
}, [stardustIndexer]);
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved

return {
stardustIndexerClient,
};
}
1 change: 1 addition & 0 deletions apps/core/src/contexts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from './ThemeContext';
export * from './HiddenAssetsProvider';
export * from './StardustIndexerClientContext';
15 changes: 13 additions & 2 deletions apps/core/src/hooks/useGetAllOwnedObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ import { useQuery } from '@tanstack/react-query';

const MAX_OBJECTS_PER_REQ = 10;

export function useGetAllOwnedObjects(address: string, filter?: IotaObjectDataFilter) {
export function useGetAllOwnedObjects(
address: string,
filter?: IotaObjectDataFilter,
mixinObjects?: () => Promise<IotaObjectData[]>,
) {
const client = useIotaClient();

return useQuery({
queryKey: ['get-all-owned-objects', address, filter],
queryKey: ['get-all-owned-objects', address, filter, mixinObjects],
queryFn: async () => {
let cursor: string | undefined | null = null;
const allData: IotaObjectData[] = [];

if (mixinObjects) {
const dependencyMixedObjects = await mixinObjects();
allData.push(...dependencyMixedObjects);
}
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved

// keep fetching until cursor is null or undefined
do {
const { data: objectResponse, nextCursor } = await client.getOwnedObjects({
Expand Down
1 change: 1 addition & 0 deletions apps/core/src/utils/migration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from './createMigrationTransaction';
export * from './types';
export * from './mapStardustIndexerOutputs';
77 changes: 77 additions & 0 deletions apps/core/src/utils/migration/mapStardustIndexerOutputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
STARDUST_BASIC_OUTPUT_TYPE,
STARDUST_EXPIRATION_UNLOCK_CONDITION_TYPE,
STARDUST_NFT_OUTPUT_TYPE,
STARDUST_STORAGE_DEPOSIT_RETURN_UC_TYPE,
STARDUST_TIMELOCK_TYPE,
} from '../../constants';
import { StardustIndexerOutput } from './types';

export function mapStardustBasicOutputs(output: StardustIndexerOutput) {
return mapStardustOutput(output, STARDUST_BASIC_OUTPUT_TYPE);
}

export function mapStardustNftOutputs(output: StardustIndexerOutput) {
return mapStardustOutput(output, STARDUST_NFT_OUTPUT_TYPE);
}

function mapStardustOutput(output: StardustIndexerOutput, type: string) {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
return {
objectId: output.id,
digest: '',
version: '',
type: type,
content: {
dataType: 'moveObject' as const,
type: STARDUST_NFT_OUTPUT_TYPE,
fields: {
balance: output.balance.value,
expiration_uc: output.expiration
? {
type: STARDUST_EXPIRATION_UNLOCK_CONDITION_TYPE,
fields: {
owner: output.expiration.owner,
return_address: output.expiration.return_address,
unix_time: output.expiration.unix_time,
},
}
: null,
id: {
id: output.id,
},
metadata: [],
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
native_tokens: {
type: '0x2::bag::Bag',
fields: {
id: {
id: output.native_tokens.id,
},
size: output.native_tokens.size,
},
},
sender: output.sender,
storage_deposit_return_uc: output.storage_deposit_return
? {
type: STARDUST_STORAGE_DEPOSIT_RETURN_UC_TYPE,
fields: {
return_address: output.storage_deposit_return.return_address,
return_amount: output.storage_deposit_return.return_address,
},
}
: null,
tag: output.tag,
timelock_uc: output.timelock
? {
fields: {
unix_time: output.timelock.unix_time,
},
type: STARDUST_TIMELOCK_TYPE,
}
: null,
},
},
};
}
39 changes: 35 additions & 4 deletions apps/core/src/utils/migration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// SPDX-License-Identifier: Apache-2.0

import { z } from 'zod';
import { STARDUST_PACKAGE_ID } from '../../constants';
import { STARDUST_EXPIRATION_UNLOCK_CONDITION_TYPE, STARDUST_PACKAGE_ID } from '../../constants';

const ExpirationUnlockConditionSchema = z.object({
type: z.literal(
`${STARDUST_PACKAGE_ID}::expiration_unlock_condition::ExpirationUnlockCondition`,
),
type: z.literal(STARDUST_EXPIRATION_UNLOCK_CONDITION_TYPE),
fields: z.object({
owner: z.string(),
return_address: z.string(),
Expand Down Expand Up @@ -60,6 +58,38 @@ export const BasicOutputObjectSchema = CommonOutputObjectWithUcSchema.extend({
sender: z.string().nullable().optional(),
});

const StardustIndexerOutputSchema = z.object({
id: z.string(),
balance: z.object({
value: z.number(),
}),
native_tokens: z.object({
id: z.string(),
size: z.number(),
}),
storage_deposit_return: z
.object({
return_address: z.string(),
return_amount: z.number(),
})
.nullable(),
timelock: z
.object({
unix_time: z.number(),
})
.nullable(),
expiration: z
.object({
owner: z.string(),
return_address: z.string(),
unix_time: z.number(),
})
.nullable(),
metadata: z.string().nullable(),
tag: z.string().nullable(),
sender: z.string().nullable(),
});

export const NftOutputObjectSchema = CommonOutputObjectWithUcSchema;

export type ExpirationUnlockCondition = z.infer<typeof ExpirationUnlockConditionSchema>;
Expand All @@ -71,3 +101,4 @@ export type CommonOutputObject = z.infer<typeof CommonOutputObjectSchema>;
export type CommonOutputObjectWithUc = z.infer<typeof CommonOutputObjectWithUcSchema>;
export type BasicOutputObject = z.infer<typeof BasicOutputObjectSchema>;
export type NftOutputObject = z.infer<typeof NftOutputObjectSchema>;
export type StardustIndexerOutput = z.infer<typeof StardustIndexerOutputSchema>;
33 changes: 27 additions & 6 deletions apps/wallet-dashboard/hooks/useGetStardustMigratableObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,41 @@ import { useQuery } from '@tanstack/react-query';
import { useGetCurrentEpochStartTimestamp } from '@/hooks';
import { groupStardustObjectsByMigrationStatus } from '@/lib/utils';
import {
mapStardustBasicOutputs,
mapStardustNftOutputs,
STARDUST_BASIC_OUTPUT_TYPE,
STARDUST_NFT_OUTPUT_TYPE,
TimeUnit,
useGetAllOwnedObjects,
useStardustIndexerClientContext,
} from '@iota/core';

export function useGetStardustMigratableObjects(address: string) {
const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp();
const { data: basicOutputObjects } = useGetAllOwnedObjects(address, {
StructType: STARDUST_BASIC_OUTPUT_TYPE,
});
const { data: nftOutputObjects } = useGetAllOwnedObjects(address, {
StructType: STARDUST_NFT_OUTPUT_TYPE,
});
const { stardustIndexerClient } = useStardustIndexerClientContext();
const { data: basicOutputObjects } = useGetAllOwnedObjects(
address,
{
StructType: STARDUST_BASIC_OUTPUT_TYPE,
},
async () => {
const outputs = await stardustIndexerClient?.getBasicResolvedOutputs(address);

return (outputs || []).map(mapStardustBasicOutputs);
},
);

const { data: nftOutputObjects } = useGetAllOwnedObjects(
address,
{
StructType: STARDUST_NFT_OUTPUT_TYPE,
},
async () => {
const outputs = await stardustIndexerClient?.getNftResolvedOutputs(address);

return (outputs || []).map(mapStardustNftOutputs);
},
);

return useQuery({
queryKey: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,6 @@ export function groupStardustObjectsByMigrationStatus(
continue;
}

if (outputObjectFields.expiration_uc) {
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
const unlockableAddress =
outputObjectFields.expiration_uc.fields.unix_time <= epochUnix
? outputObjectFields.expiration_uc.fields.return_address
: outputObjectFields.expiration_uc.fields.owner;

if (unlockableAddress !== address) {
continue;
}
}

migratable.push(outputObject);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/wallet-dashboard/lib/utils/timelock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function mapTimelockObjects(iotaObjects: IotaObjectData[]): TimelockedObj
const fields = iotaObject.content.fields as unknown as TimelockedIotaResponse;
return {
id: fields.id,
locked: { value: BigInt(fields.locked) },
locked: { value: BigInt(fields?.locked || 0n) },
panteleymonchuk marked this conversation as resolved.
Show resolved Hide resolved
expirationTimestampMs: Number(fields.expiration_timestamp_ms),
label: fields.label,
};
Expand Down
41 changes: 22 additions & 19 deletions apps/wallet-dashboard/providers/AppProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useState } from 'react';
import { KioskClientProvider, useLocalStorage } from '@iota/core';
import { growthbook } from '@/lib/utils';
import { ThemeProvider } from '@iota/core';
import { StardustIndexerClientProvider } from './StardustIndexerClientProvider';

growthbook.init();

Expand All @@ -34,25 +35,27 @@ export function AppProviders({ children }: React.PropsWithChildren) {
defaultNetwork={persistedNetwork}
onNetworkChange={handleNetworkChange}
>
<KioskClientProvider>
<WalletProvider
autoConnect={true}
theme={[
{
variables: lightTheme,
},
{
selector: '.dark',
variables: darkTheme,
},
]}
>
<ThemeProvider appId="iota-dashboard">
{children}
<Toaster />
</ThemeProvider>
</WalletProvider>
</KioskClientProvider>
<StardustIndexerClientProvider>
<KioskClientProvider>
<WalletProvider
autoConnect={true}
theme={[
{
variables: lightTheme,
},
{
selector: '.dark',
variables: darkTheme,
},
]}
>
<ThemeProvider appId="iota-dashboard">
{children}
<Toaster />
</ThemeProvider>
</WalletProvider>
</KioskClientProvider>
</StardustIndexerClientProvider>
</IotaClientProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
Expand Down
Loading
Loading