sdk/js: finish aptos tests
This commit is contained in:
parent
54cf6781d1
commit
aa56dc5498
|
@ -1,3 +1,5 @@
|
|||
import { TokenTypes } from "aptos";
|
||||
|
||||
export type TokenBridgeState = {
|
||||
consumed_vaas: {
|
||||
elems: {
|
||||
|
@ -74,11 +76,7 @@ export type CreateTokenDataEvent = {
|
|||
type: "0x3::token::CreateTokenDataEvent";
|
||||
data: {
|
||||
description: string;
|
||||
id: {
|
||||
collection: string;
|
||||
creator: string;
|
||||
name: string;
|
||||
};
|
||||
id: TokenTypes.TokenDataId;
|
||||
maximum: string;
|
||||
mutability_config: {
|
||||
description: boolean;
|
||||
|
@ -108,22 +106,6 @@ export type DepositEvent = {
|
|||
type: "0x3::token::DepositEvent";
|
||||
data: {
|
||||
amount: string;
|
||||
id: RawTokenId;
|
||||
id: TokenTypes.TokenId;
|
||||
};
|
||||
};
|
||||
|
||||
export type RawTokenId = {
|
||||
property_version: string;
|
||||
token_data_id: {
|
||||
collection: string;
|
||||
creator: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TokenId = {
|
||||
creatorAddress: string;
|
||||
collectionName: string;
|
||||
tokenName: string;
|
||||
propertyVersion: number;
|
||||
};
|
||||
|
|
|
@ -6,24 +6,26 @@ import {
|
|||
jest,
|
||||
test,
|
||||
} from "@jest/globals";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { getAssociatedTokenAddress } from "@solana/spl-token";
|
||||
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { AptosAccount, AptosClient, FaucetClient, Types } from "aptos";
|
||||
import { ethers } from "ethers";
|
||||
import Web3 from "web3";
|
||||
import { DepositEvent, TokenId } from "../../aptos/types";
|
||||
import { DepositEvent } from "../../aptos/types";
|
||||
import {
|
||||
CHAIN_ID_APTOS,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CONTRACTS,
|
||||
deriveCollectionHashFromTokenId,
|
||||
deriveTokenHashFromTokenId,
|
||||
generateSignAndSubmitEntryFunction,
|
||||
tryNativeToHexString,
|
||||
tryNativeToUint8Array,
|
||||
} from "../../utils";
|
||||
import { parseNftTransferVaa } from "../../vaa";
|
||||
import { getForeignAssetAptos } from "../getForeignAsset";
|
||||
import { getForeignAssetAptos, getForeignAssetEth } from "../getForeignAsset";
|
||||
import { getIsTransferCompletedAptos } from "../getIsTransferCompleted";
|
||||
import { getIsWrappedAssetAptos } from "../getIsWrappedAsset";
|
||||
import { getOriginalAssetAptos } from "../getOriginalAsset";
|
||||
|
@ -130,35 +132,31 @@ describe("Aptos NFT SDK tests", () => {
|
|||
)) as Types.UserTransaction;
|
||||
expect(aptosRedeemTxResult.success).toBe(true);
|
||||
expect(
|
||||
await getIsTransferCompletedAptos(
|
||||
getIsTransferCompletedAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
ethTransferVaa
|
||||
)
|
||||
).toBe(true);
|
||||
).resolves.toBe(true);
|
||||
|
||||
// get token data
|
||||
const tokenData = await getForeignAssetAptos(
|
||||
const tokenId = await getForeignAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(ethNft.address, CHAIN_ID_ETH)
|
||||
);
|
||||
assertIsNotNull(tokenData);
|
||||
assertIsNotNull(tokenId);
|
||||
expect(
|
||||
await getIsWrappedAssetAptos(
|
||||
getIsWrappedAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
tokenData.creatorAddress
|
||||
tokenId.token_data_id.creator
|
||||
)
|
||||
).toBe(true);
|
||||
).resolves.toBe(true);
|
||||
expect(
|
||||
await getOriginalAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
tokenData.creatorAddress
|
||||
)
|
||||
).toMatchObject({
|
||||
getOriginalAssetAptos(aptosClient, APTOS_NFT_BRIDGE_ADDRESS, tokenId)
|
||||
).resolves.toStrictEqual({
|
||||
isWrapped: true,
|
||||
chainId: CHAIN_ID_ETH,
|
||||
assetAddress: Uint8Array.from(ethTransferVaaParsed.tokenAddress),
|
||||
|
@ -167,9 +165,9 @@ describe("Aptos NFT SDK tests", () => {
|
|||
// transfer NFT from Aptos back to Ethereum
|
||||
const aptosTransferPayload = await transferFromAptos(
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
tokenData.creatorAddress,
|
||||
tokenData.collectionName,
|
||||
tokenData.tokenName.padStart(64, "0"),
|
||||
tokenId.token_data_id.creator,
|
||||
tokenId.token_data_id.collection,
|
||||
tokenId.token_data_id.name.padStart(64, "0"),
|
||||
0,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(ethSigner.address, CHAIN_ID_ETH)
|
||||
|
@ -215,6 +213,13 @@ describe("Aptos NFT SDK tests", () => {
|
|||
APTOS_COLLECTION_NAME,
|
||||
"APE🦧"
|
||||
);
|
||||
expect(
|
||||
await getIsWrappedAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
aptosAccount.address().toString()
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
// get token data from user wallet
|
||||
const event = (
|
||||
|
@ -222,24 +227,18 @@ describe("Aptos NFT SDK tests", () => {
|
|||
aptosAccount.address(),
|
||||
"0x3::token::TokenStore",
|
||||
"deposit_events",
|
||||
{ limit: 1 } // most users will more than one deposit event
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0] as DepositEvent;
|
||||
const tokenId: TokenId = {
|
||||
creatorAddress: event.data.id.token_data_id.creator,
|
||||
collectionName: event.data.id.token_data_id.collection,
|
||||
tokenName: event.data.id.token_data_id.name,
|
||||
propertyVersion: Number(event.data.id.property_version),
|
||||
};
|
||||
console.log(tokenId);
|
||||
const depositTokenId = event.data.id;
|
||||
|
||||
// transfer NFT from Aptos to Solana
|
||||
// transfer NFT from Aptos to Ethereum
|
||||
const aptosTransferPayload = await transferFromAptos(
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
tokenId.creatorAddress,
|
||||
tokenId.collectionName,
|
||||
tokenId.tokenName,
|
||||
tokenId.propertyVersion,
|
||||
depositTokenId.token_data_id.creator,
|
||||
depositTokenId.token_data_id.collection,
|
||||
depositTokenId.token_data_id.name,
|
||||
Number(depositTokenId.property_version),
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(ethSigner.address, CHAIN_ID_ETH)
|
||||
);
|
||||
|
@ -268,44 +267,85 @@ describe("Aptos NFT SDK tests", () => {
|
|||
ethSigner,
|
||||
aptosTransferVaa
|
||||
);
|
||||
console.log(JSON.stringify(ethRedeemTx, null, 2));
|
||||
expect(ethRedeemTx.status).toBe(1);
|
||||
|
||||
// sanity check token hash and id
|
||||
const tokenHash = await deriveTokenHashFromTokenId(depositTokenId);
|
||||
expect(Buffer.from(tokenHash).toString("hex")).toBe(
|
||||
new BN(aptosTransferVaaParsed.tokenId.toString())
|
||||
.toString("hex")
|
||||
.padStart(64, "0") // conversion to BN strips leading zeros
|
||||
);
|
||||
|
||||
const foreignAssetTokenId = await getForeignAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
CHAIN_ID_APTOS,
|
||||
tokenHash
|
||||
);
|
||||
assertIsNotNull(foreignAssetTokenId);
|
||||
expect(foreignAssetTokenId).toStrictEqual(depositTokenId);
|
||||
|
||||
// get token address on Ethereum
|
||||
const tokenHash = await deriveTokenHashFromTokenId(tokenId);
|
||||
const tokenAddressAptos = await deriveCollectionHashFromTokenId(
|
||||
foreignAssetTokenId
|
||||
);
|
||||
const tokenAddressEth = await getForeignAssetEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
ethSigner,
|
||||
CHAIN_ID_APTOS,
|
||||
tokenAddressAptos
|
||||
);
|
||||
assertIsNotNull(tokenAddressEth);
|
||||
|
||||
// transfer NFT from Ethereum back to Aptos
|
||||
const ethTransferTx = await transferFromEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
ethSigner,
|
||||
tokenAddressEth,
|
||||
tokenHash,
|
||||
CHAIN_ID_APTOS,
|
||||
tryNativeToUint8Array(aptosAccount.address().toString(), CHAIN_ID_APTOS)
|
||||
);
|
||||
expect(ethTransferTx.status).toBe(1);
|
||||
|
||||
// observe tx and get vaa
|
||||
const ethTransferVaa = await getSignedVaaEthereum(ethTransferTx);
|
||||
const ethTransferVaaParsed = parseNftTransferVaa(ethTransferVaa);
|
||||
expect(ethTransferVaaParsed.name).toBe(APTOS_COLLECTION_NAME);
|
||||
|
||||
// redeem NFT on Aptos
|
||||
const aptosRedeemTxPayload = await redeemOnAptos(
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
ethTransferVaa
|
||||
);
|
||||
const aptosRedeemTx = await generateSignAndSubmitEntryFunction(
|
||||
aptosClient,
|
||||
aptosAccount,
|
||||
aptosRedeemTxPayload
|
||||
);
|
||||
const aptosRedeemTxResult = (await aptosClient.waitForTransactionWithResult(
|
||||
aptosRedeemTx.hash
|
||||
)) as Types.UserTransaction;
|
||||
expect(aptosRedeemTxResult.success).toBe(true);
|
||||
expect(
|
||||
await getForeignAssetAptos(
|
||||
getIsTransferCompletedAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
CHAIN_ID_APTOS,
|
||||
tokenHash
|
||||
ethTransferVaa
|
||||
)
|
||||
).toMatchObject(tokenId);
|
||||
// const tokenAddress = await getForeignAssetEth(
|
||||
// ETH_NFT_BRIDGE_ADDRESS,
|
||||
// ethSigner,
|
||||
// CHAIN_ID_APTOS,
|
||||
// tokenHash
|
||||
// );
|
||||
// console.log(tokenAddress);
|
||||
// assertIsNotNull(tokenAddress);
|
||||
|
||||
// // transfer NFT from Ethereum back to Aptos
|
||||
// const ethTransferTx = await transferFromEth(
|
||||
// ETH_NFT_BRIDGE_ADDRESS,
|
||||
// ethSigner,
|
||||
// tokenAddress,
|
||||
// 0,
|
||||
// CHAIN_ID_APTOS,
|
||||
// tryNativeToUint8Array(aptosAccount.address().toString(), CHAIN_ID_APTOS)
|
||||
// );
|
||||
// expect(ethTransferTx.status).toBe(1);
|
||||
|
||||
// // observe tx and get vaa
|
||||
|
||||
// // redeem NFT on Aptos
|
||||
|
||||
// check NFT is the same
|
||||
).resolves.toBe(true);
|
||||
expect(
|
||||
getOriginalAssetAptos(
|
||||
aptosClient,
|
||||
APTOS_NFT_BRIDGE_ADDRESS,
|
||||
foreignAssetTokenId
|
||||
)
|
||||
).resolves.toStrictEqual({
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_APTOS,
|
||||
assetAddress: tokenAddressAptos,
|
||||
});
|
||||
});
|
||||
|
||||
test("Transfer Solana SPL to Aptos", async () => {
|
||||
|
@ -359,7 +399,9 @@ describe("Aptos NFT SDK tests", () => {
|
|||
new Uint8Array(solanaTransferVaaParsed.tokenAddress)
|
||||
);
|
||||
assertIsNotNull(tokenData);
|
||||
expect(tokenData.collectionName).toBe("Wormhole Bridged Solana-NFT"); // this will change if SPL cache is deprecated in favor of separate collections
|
||||
expect(tokenData.token_data_id.collection).toBe(
|
||||
"Wormhole Bridged Solana-NFT" // this will change if SPL cache is deprecated in favor of separate collections
|
||||
);
|
||||
|
||||
// check if token is in user's account
|
||||
const events = (await aptosClient.getEventsByEventHandle(
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import { BN } from "@project-serum/anchor";
|
||||
import { PublicKeyInitData } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { AptosClient, HexString } from "aptos";
|
||||
import { AptosClient, HexString, TokenTypes } from "aptos";
|
||||
import { ethers } from "ethers";
|
||||
import { isBytes } from "ethers/lib/utils";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { CHAIN_ID_SOLANA } from "..";
|
||||
import {
|
||||
CreateTokenDataEvent,
|
||||
NftBridgeState,
|
||||
RawTokenId,
|
||||
TokenId,
|
||||
} from "../aptos/types";
|
||||
import { CreateTokenDataEvent, NftBridgeState } from "../aptos/types";
|
||||
import { NFTBridge__factory } from "../ethers-contracts";
|
||||
import { deriveWrappedMintKey } from "../solana/nftBridge";
|
||||
import {
|
||||
|
@ -116,14 +111,15 @@ export const getForeignAssetSol = getForeignAssetSolana;
|
|||
/**
|
||||
* Get the token id of a foreign asset on Aptos. Tokens on Aptos are identified
|
||||
* by the tuple (creatorAddress, collectionName, tokenName, propertyVersion),
|
||||
* which this method returns.
|
||||
* which this method returns as an object.
|
||||
*
|
||||
* This method also supports native assets, in which case it expects the token
|
||||
* hash (which can be obtained from `deriveTokenHashFromTokenId`).
|
||||
* @param client
|
||||
* @param nftBridgeAddress
|
||||
* @param originChain
|
||||
* @param originAddress External address of token on origin chain, or token hash if origin chain is Aptos
|
||||
* @param originAddress External address of token on origin chain, or token hash
|
||||
* if origin chain is Aptos
|
||||
* @returns Unique token identifier on Aptos
|
||||
*/
|
||||
export async function getForeignAssetAptos(
|
||||
|
@ -131,7 +127,7 @@ export async function getForeignAssetAptos(
|
|||
nftBridgeAddress: string,
|
||||
originChain: ChainId | ChainName,
|
||||
originAddress: Uint8Array
|
||||
): Promise<TokenId | null> {
|
||||
): Promise<TokenTypes.TokenId | null> {
|
||||
const originChainId = coalesceChainId(originChain);
|
||||
if (originChainId === CHAIN_ID_APTOS) {
|
||||
const state = (
|
||||
|
@ -141,19 +137,17 @@ export async function getForeignAssetAptos(
|
|||
)
|
||||
).data as NftBridgeState;
|
||||
const handle = state.native_infos.handle;
|
||||
const value = (await client.getTableItem(handle, {
|
||||
key_type: `${nftBridgeAddress}::token_hash::TokenHash`,
|
||||
value_type: `0x3::token::TokenId`,
|
||||
key: {
|
||||
hash: HexString.fromUint8Array(originAddress).hex(),
|
||||
},
|
||||
})) as RawTokenId;
|
||||
return {
|
||||
creatorAddress: value.token_data_id.creator,
|
||||
collectionName: value.token_data_id.collection,
|
||||
tokenName: value.token_data_id.name,
|
||||
propertyVersion: Number(value.property_version),
|
||||
};
|
||||
const { token_data_id, property_version } = (await client.getTableItem(
|
||||
handle,
|
||||
{
|
||||
key_type: `${nftBridgeAddress}::token_hash::TokenHash`,
|
||||
value_type: `0x3::token::TokenId`,
|
||||
key: {
|
||||
hash: HexString.fromUint8Array(originAddress).hex(),
|
||||
},
|
||||
}
|
||||
)) as TokenTypes.TokenId & { __headers: unknown };
|
||||
return { token_data_id, property_version };
|
||||
}
|
||||
|
||||
const creatorAddress = await deriveResourceAccountAddress(
|
||||
|
@ -165,19 +159,18 @@ export async function getForeignAssetAptos(
|
|||
throw new Error("Could not derive creator account address");
|
||||
}
|
||||
|
||||
// Each creator account should contain a single collection and a single token
|
||||
// creation event. The latter contains the token id that we're looking for.
|
||||
const event = (
|
||||
await client.getEventsByEventHandle(
|
||||
creatorAddress,
|
||||
"0x3::token::Collections",
|
||||
"create_token_data_events",
|
||||
{ limit: 1 } // there should only ever be one event per resource account
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0] as CreateTokenDataEvent;
|
||||
const tokenData = event.data.id;
|
||||
return {
|
||||
creatorAddress: tokenData.creator,
|
||||
collectionName: tokenData.collection,
|
||||
tokenName: tokenData.name,
|
||||
propertyVersion: 0,
|
||||
token_data_id: event.data.id,
|
||||
property_version: "0",
|
||||
};
|
||||
}
|
||||
|
|
|
@ -56,8 +56,11 @@ export async function getIsWrappedAssetAptos(
|
|||
`${nftBridgeAddress}::state::OriginInfo`
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof Types.ApiError && e.status === 404) {
|
||||
} catch (e: any) {
|
||||
if (
|
||||
(e instanceof Types.ApiError || e.errorCode === "resource_not_found") &&
|
||||
e.status === 404
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
PublicKeyInitData,
|
||||
} from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { AptosClient, Types } from "aptos";
|
||||
import { AptosClient, TokenTypes, Types } from "aptos";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { arrayify, zeroPad } from "ethers/lib/utils";
|
||||
import { WormholeWrappedInfo } from "..";
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
CHAIN_ID_APTOS,
|
||||
CHAIN_ID_SOLANA,
|
||||
coalesceChainId,
|
||||
deriveCollectionHashFromTokenId,
|
||||
hex,
|
||||
} from "../utils";
|
||||
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
|
||||
|
@ -182,16 +183,26 @@ export async function getOriginalAssetTerra(
|
|||
};
|
||||
}
|
||||
|
||||
// TODO(aki): should this also return tokenId?
|
||||
/**
|
||||
* Given a token ID, returns the original asset chain and address. If this is a
|
||||
* native asset, the asset address will be the collection hash.
|
||||
* @param client
|
||||
* @param nftBridgeAddress
|
||||
* @param tokenId An object containing creator address, collection name, token
|
||||
* name, and property version, which together uniquely identify a token on
|
||||
* Aptos. For wrapped assets, property version will be 0.
|
||||
* @returns Object containing origin chain and Wormhole compatible 32-byte asset
|
||||
* address.
|
||||
*/
|
||||
export async function getOriginalAssetAptos(
|
||||
client: AptosClient,
|
||||
nftBridgeAddress: string,
|
||||
creatorAddress: string
|
||||
tokenId: TokenTypes.TokenId
|
||||
): Promise<WormholeWrappedInfo> {
|
||||
try {
|
||||
const originInfo = (
|
||||
await client.getAccountResource(
|
||||
creatorAddress,
|
||||
tokenId.token_data_id.creator,
|
||||
`${nftBridgeAddress}::state::OriginInfo`
|
||||
)
|
||||
).data as OriginInfo;
|
||||
|
@ -204,15 +215,20 @@ export async function getOriginalAssetAptos(
|
|||
hex(originInfo.token_address.external_address)
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Types.ApiError && e.status === 404) {
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_APTOS,
|
||||
assetAddress: new Uint8Array(hex(creatorAddress)),
|
||||
};
|
||||
} catch (e: any) {
|
||||
if (
|
||||
!(
|
||||
(e instanceof Types.ApiError || e.errorCode === "resource_not_found") &&
|
||||
e.status === 404
|
||||
)
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_APTOS,
|
||||
assetAddress: await deriveCollectionHashFromTokenId(tokenId),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import { AptosAccount, AptosClient, BCS, TxnBuilderTypes, Types } from "aptos";
|
||||
import {
|
||||
AptosAccount,
|
||||
AptosClient,
|
||||
BCS,
|
||||
HexString,
|
||||
TokenTypes,
|
||||
TxnBuilderTypes,
|
||||
Types,
|
||||
} from "aptos";
|
||||
import { hexZeroPad } from "ethers/lib/utils";
|
||||
import { sha3_256 } from "js-sha3";
|
||||
import { TokenBridgeState, TokenId } from "../aptos/types";
|
||||
import { NftBridgeState, TokenBridgeState } from "../aptos/types";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
|
@ -248,7 +256,7 @@ export const coalesceModuleAddress = (str: string): string => {
|
|||
|
||||
/**
|
||||
* The NFT bridge creates resource accounts, which in turn create a collection
|
||||
* and mint a singl token for each transferred NFT. This method derives the
|
||||
* and mint a single token for each transferred NFT. This method derives the
|
||||
* address of that resource account from the given origin chain and address.
|
||||
* @param nftBridgeAddress
|
||||
* @param originChain
|
||||
|
@ -276,28 +284,82 @@ export const deriveResourceAccountAddress = async (
|
|||
};
|
||||
|
||||
/**
|
||||
* Native tokens in Aptos are represented by a single token hash that is
|
||||
* derived from creator address, collection name, token name, and property
|
||||
* version, ensuring that it is unique. This method derives that token hash.
|
||||
* Get a hash that uniquely identifies a collection on Aptos.
|
||||
* @param tokenId
|
||||
* @returns Collection hash
|
||||
*/
|
||||
export const deriveCollectionHashFromTokenId = async (
|
||||
tokenId: TokenTypes.TokenId
|
||||
): Promise<Uint8Array> => {
|
||||
const inputs = Buffer.concat([
|
||||
BCS.bcsToBytes(
|
||||
TxnBuilderTypes.AccountAddress.fromHex(tokenId.token_data_id.creator)
|
||||
),
|
||||
Buffer.from(sha3_256(tokenId.token_data_id.collection), "hex"),
|
||||
]);
|
||||
return new Uint8Array(Buffer.from(sha3_256(inputs), "hex"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a hash that uniquely identifies a token on Aptos.
|
||||
*
|
||||
* Native tokens in Aptos are uniquely identified by a hash of creator address,
|
||||
* collection name, token name, and property version. This hash is converted to
|
||||
* a bigint in the `tokenId` field in NFT transfer VAAs.
|
||||
* @param tokenId
|
||||
* @returns Token hash identifying the token
|
||||
*/
|
||||
export const deriveTokenHashFromTokenId = async (
|
||||
tokenId: TokenId
|
||||
tokenId: TokenTypes.TokenId
|
||||
): Promise<Uint8Array> => {
|
||||
const propertyVersion = Buffer.alloc(8);
|
||||
propertyVersion.writeBigUInt64BE(BigInt(tokenId.propertyVersion));
|
||||
propertyVersion.writeBigUInt64BE(BigInt(tokenId.property_version));
|
||||
const inputs = Buffer.concat([
|
||||
BCS.bcsToBytes(
|
||||
TxnBuilderTypes.AccountAddress.fromHex(tokenId.creatorAddress)
|
||||
),
|
||||
Buffer.from(sha3_256(tokenId.collectionName), "hex"),
|
||||
Buffer.from(sha3_256(tokenId.tokenName), "hex"),
|
||||
Buffer.from(tokenId.token_data_id.creator, "hex"),
|
||||
Buffer.from(sha3_256(tokenId.token_data_id.collection), "hex"),
|
||||
Buffer.from(sha3_256(tokenId.token_data_id.name), "hex"),
|
||||
propertyVersion,
|
||||
]);
|
||||
return new Uint8Array(Buffer.from(sha3_256(inputs), "hex"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get creator address, collection name, token name, and property version from
|
||||
* a token hash. Note that this method is meant to be used for native tokens
|
||||
* that have already been registered in the NFT bridge.
|
||||
*
|
||||
* The token hash is stored in the `tokenId` field of NFT transfer VAAs and
|
||||
* is calculated by the operations in `deriveTokenHashFromTokenId`.
|
||||
* @param client
|
||||
* @param nftBridgeAddress
|
||||
* @param tokenHash Token hash
|
||||
* @returns Token ID
|
||||
*/
|
||||
export const getTokenIdFromTokenHash = async (
|
||||
client: AptosClient,
|
||||
nftBridgeAddress: string,
|
||||
tokenHash: Uint8Array
|
||||
): Promise<TokenTypes.TokenId> => {
|
||||
const state = (
|
||||
await client.getAccountResource(
|
||||
nftBridgeAddress,
|
||||
`${nftBridgeAddress}::state::State`
|
||||
)
|
||||
).data as NftBridgeState;
|
||||
const handle = state.native_infos.handle;
|
||||
const { token_data_id, property_version } = (await client.getTableItem(
|
||||
handle,
|
||||
{
|
||||
key_type: `${nftBridgeAddress}::token_hash::TokenHash`,
|
||||
value_type: `0x3::token::TokenId`,
|
||||
key: {
|
||||
hash: HexString.fromUint8Array(tokenHash).hex(),
|
||||
},
|
||||
}
|
||||
)) as TokenTypes.TokenId & { __headers: unknown };
|
||||
return { token_data_id, property_version };
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulates given raw transaction and either returns the resulting transaction that was submitted
|
||||
* to the mempool, or throws if it fails.
|
||||
|
|
Loading…
Reference in New Issue