-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wallet-dashboard): Add migration PTB (#3908)
* feat: fetch stardust basic and nft objects * feat: organize stardust objects on migratable and unmigratable * fix: migration constants and variable nameing * fix: variable naming * feat:add migration popup * fix: move constants to core * fix: evert adding routes to barrel * feat: add migration ptb * fix: remove leftover testing code * fix: remove undefined object check * fix: add schemas for migration transaction * fix: change migration popup params and add loader to button * remove migration inteface * fix: remove empty spaces and add iota coin type constant migration constants * fix: exporting type and eslint * fix: migration object schema * fix: add missing exports * fix: remove IOTA_COIN_TYPE and use sdk constant IOTA_TYPE_ARG * fix: move migration types to dedicated file and add literal strings for UC types --------- Co-authored-by: Marc Espin <[email protected]>
- Loading branch information
Showing
10 changed files
with
256 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
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<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_TYPE_ARG}>`; | ||
export const STARDUST_NFT_OUTPUT_TYPE = `${STARDUST_PACKAGE_ID}::nft_output::NftOutput<${IOTA_TYPE_ARG}>`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
apps/core/src/utils/migration/createMigrationTransaction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// 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_TYPE_ARG } from '@iota/iota-sdk/utils'; | ||
import { | ||
BasicOutputObject, | ||
BasicOutputObjectSchema, | ||
NftOutputObject, | ||
NftOutputObjectSchema, | ||
} from './types'; | ||
|
||
type NestedResultType = { | ||
$kind: 'NestedResult'; | ||
NestedResult: [number, number]; | ||
}; | ||
|
||
export async function getNativeTokenTypesFromBag( | ||
bagId: string, | ||
client: IotaClient, | ||
): Promise<string[]> { | ||
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 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; | ||
} | ||
|
||
export function validateNftOutputObject(outputObject: IotaObjectData): NftOutputObject { | ||
if (outputObject.content?.dataType !== 'moveObject') { | ||
throw new Error('Invalid nft output object'); | ||
} | ||
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, | ||
basicOutputs: IotaObjectData[] = [], | ||
nftOutputs: IotaObjectData[] = [], | ||
): Promise<Transaction> { | ||
const ptb = new Transaction(); | ||
|
||
const coinsFromBasicOutputs: NestedResultType[] = []; | ||
|
||
// Basic Outputs | ||
for (const basicOutputObject of basicOutputs) { | ||
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) : []; | ||
|
||
const migratableResult = ptb.moveCall({ | ||
target: `${STARDUST_PACKAGE_ID}::basic_output::extract_assets`, | ||
typeArguments: [IOTA_TYPE_ARG], | ||
arguments: [ptb.object(basicOutputObjectId)], | ||
}); | ||
|
||
const balance = migratableResult[0]; | ||
let nativeTokensBag = migratableResult[1]; | ||
|
||
// Convert Balance in Coin | ||
const [coin] = ptb.moveCall({ | ||
target: '0x02::coin::from_balance', | ||
typeArguments: [IOTA_TYPE_ARG], | ||
arguments: [ptb.object(balance)], | ||
}); | ||
|
||
coinsFromBasicOutputs.push(coin); | ||
|
||
for (const nativeTokenType of nativeTokenTypes) { | ||
[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', | ||
arguments: [ptb.object(nativeTokensBag)], | ||
}); | ||
} | ||
|
||
// NFT Outputs | ||
const coinsFromNftOutputs: NestedResultType[] = []; | ||
const nftsFromNftOutputs: NestedResultType[] = []; | ||
|
||
for (const nftOutputObject of nftOutputs) { | ||
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) : []; | ||
|
||
const migratableResult = ptb.moveCall({ | ||
target: `${STARDUST_PACKAGE_ID}::nft_output::extract_assets`, | ||
typeArguments: [IOTA_TYPE_ARG], | ||
arguments: [ptb.object(nftOutputObjectId)], | ||
}); | ||
|
||
const balance = migratableResult[0]; | ||
let nativeTokensBag = migratableResult[1]; | ||
const nft = migratableResult[2]; | ||
|
||
nftsFromNftOutputs.push(nft); | ||
|
||
// Convert Balance in Coin | ||
const [coin] = ptb.moveCall({ | ||
target: '0x02::coin::from_balance', | ||
typeArguments: [IOTA_TYPE_ARG], | ||
arguments: [ptb.object(balance)], | ||
}); | ||
coinsFromNftOutputs.push(coin); | ||
|
||
for (const nativeTokenType of nativeTokenTypes) { | ||
[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', | ||
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)); | ||
} | ||
|
||
return ptb; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
export * from './createMigrationTransaction'; | ||
export * from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof ExpirationUnlockConditionSchema>; | ||
export type StorageDepositReturnUnlockCondition = z.infer< | ||
typeof StorageDepositReturnUnlockConditionSchema | ||
>; | ||
export type TimelockUnlockCondition = z.infer<typeof TimelockUnlockConditionSchema>; | ||
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>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 0 additions & 47 deletions
47
apps/wallet-dashboard/lib/interfaces/migration.interface.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters