diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 444544054..d0344c858 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -177,7 +177,7 @@ jobs: export PATH="${HOME}/.local/share/solana/install/active_release/bin:${PATH}" mkdir -p "${BPF_OUT_DIR}" - cp modules/token_bridge/token-metadata/spl_token_metadata.so "${BPF_OUT_DIR}" + cp external/mpl_token_metadata.so "${BPF_OUT_DIR}" BPF_PACKAGES=( bridge/program/Cargo.toml diff --git a/devnet/solana-devnet.yaml b/devnet/solana-devnet.yaml index 1aae1cd29..40fc7a7ef 100644 --- a/devnet/solana-devnet.yaml +++ b/devnet/solana-devnet.yaml @@ -52,7 +52,7 @@ spec: - /opt/solana/deps/cpi_poster.so - --bpf-program - metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s - - /opt/solana/deps/spl_token_metadata.so + - /opt/solana/deps/mpl_token_metadata.so - --bpf-program - Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK - /opt/solana/deps/wormhole_migration.so diff --git a/sdk/js/src/solana/nftBridge/instructions/completeWrapped.ts b/sdk/js/src/solana/nftBridge/instructions/completeWrapped.ts index fbb61b220..b77e92758 100644 --- a/sdk/js/src/solana/nftBridge/instructions/completeWrapped.ts +++ b/sdk/js/src/solana/nftBridge/instructions/completeWrapped.ts @@ -1,3 +1,7 @@ +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, +} from "@solana/spl-token"; import { PublicKey, PublicKeyInitData, @@ -5,26 +9,22 @@ import { SYSVAR_RENT_PUBKEY, TransactionInstruction, } from "@solana/web3.js"; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, -} from "@solana/spl-token"; -import { createReadOnlyNftBridgeProgramInterface } from "../program"; -import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole"; -import { - deriveEndpointKey, - deriveNftBridgeConfigKey, - deriveWrappedMintKey, - deriveWrappedMetaKey, - deriveMintAuthorityKey, -} from "../accounts"; import { isBytes, ParsedNftTransferVaa, parseNftTransferVaa, SignedVaa, } from "../../../vaa"; -import { SplTokenMetadataProgram } from "../../utils"; +import { TOKEN_METADATA_PROGRAM_ID } from "../../utils"; +import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole"; +import { + deriveEndpointKey, + deriveMintAuthorityKey, + deriveNftBridgeConfigKey, + deriveWrappedMetaKey, + deriveWrappedMintKey, +} from "../accounts"; +import { createReadOnlyNftBridgeProgramInterface } from "../program"; export function createCompleteTransferWrappedInstruction( nftBridgeProgramId: PublicKeyInitData, @@ -110,7 +110,7 @@ export function getCompleteTransferWrappedAccounts( rent: SYSVAR_RENT_PUBKEY, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, + splMetadataProgram: TOKEN_METADATA_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, wormholeProgram: new PublicKey(wormholeProgramId), }; diff --git a/sdk/js/src/solana/nftBridge/instructions/completeWrappedMeta.ts b/sdk/js/src/solana/nftBridge/instructions/completeWrappedMeta.ts index 68278445c..b3e37b185 100644 --- a/sdk/js/src/solana/nftBridge/instructions/completeWrappedMeta.ts +++ b/sdk/js/src/solana/nftBridge/instructions/completeWrappedMeta.ts @@ -1,3 +1,4 @@ +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, PublicKeyInitData, @@ -5,26 +6,22 @@ import { SYSVAR_RENT_PUBKEY, TransactionInstruction, } from "@solana/web3.js"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { createReadOnlyNftBridgeProgramInterface } from "../program"; -import { derivePostedVaaKey } from "../../wormhole"; -import { - deriveEndpointKey, - deriveNftBridgeConfigKey, - deriveWrappedMintKey, - deriveWrappedMetaKey, - deriveMintAuthorityKey, -} from "../accounts"; import { isBytes, ParsedNftTransferVaa, parseNftTransferVaa, SignedVaa, } from "../../../vaa"; +import { deriveTokenMetadataKey, TOKEN_METADATA_PROGRAM_ID } from "../../utils"; +import { derivePostedVaaKey } from "../../wormhole"; import { - deriveSplTokenMetadataKey, - SplTokenMetadataProgram, -} from "../../utils"; + deriveEndpointKey, + deriveMintAuthorityKey, + deriveNftBridgeConfigKey, + deriveWrappedMetaKey, + deriveWrappedMintKey, +} from "../accounts"; +import { createReadOnlyNftBridgeProgramInterface } from "../program"; export function createCompleteWrappedMetaInstruction( nftBridgeProgramId: PublicKeyInitData, @@ -92,12 +89,12 @@ export function getCompleteWrappedMetaAccounts( ), mint, wrappedMeta: deriveWrappedMetaKey(nftBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), + splMetadata: deriveTokenMetadataKey(mint), mintAuthority: deriveMintAuthorityKey(nftBridgeProgramId), rent: SYSVAR_RENT_PUBKEY, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, + splMetadataProgram: TOKEN_METADATA_PROGRAM_ID, wormholeProgram: new PublicKey(wormholeProgramId), }; } diff --git a/sdk/js/src/solana/nftBridge/instructions/transferNative.ts b/sdk/js/src/solana/nftBridge/instructions/transferNative.ts index 2e1e44299..1c0473ae9 100644 --- a/sdk/js/src/solana/nftBridge/instructions/transferNative.ts +++ b/sdk/js/src/solana/nftBridge/instructions/transferNative.ts @@ -1,21 +1,18 @@ +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, PublicKeyInitData, TransactionInstruction, } from "@solana/web3.js"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { createReadOnlyNftBridgeProgramInterface } from "../program"; +import { TOKEN_METADATA_PROGRAM_ID, deriveTokenMetadataKey } from "../../utils"; import { getPostMessageAccounts } from "../../wormhole"; import { deriveAuthoritySignerKey, + deriveCustodyKey, deriveCustodySignerKey, deriveNftBridgeConfigKey, - deriveCustodyKey, } from "../accounts"; -import { - deriveSplTokenMetadataKey, - SplTokenMetadataProgram, -} from "../../utils"; +import { createReadOnlyNftBridgeProgramInterface } from "../program"; export function createTransferNativeInstruction( nftBridgeProgramId: PublicKeyInitData, @@ -103,7 +100,7 @@ export function getTransferNativeAccounts( config: deriveNftBridgeConfigKey(nftBridgeProgramId), from: new PublicKey(from), mint: new PublicKey(mint), - splMetadata: deriveSplTokenMetadataKey(mint), + splMetadata: deriveTokenMetadataKey(mint), custody: deriveCustodyKey(nftBridgeProgramId, mint), authoritySigner: deriveAuthoritySignerKey(nftBridgeProgramId), custodySigner: deriveCustodySignerKey(nftBridgeProgramId), @@ -116,7 +113,7 @@ export function getTransferNativeAccounts( rent, systemProgram, tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, + splMetadataProgram: TOKEN_METADATA_PROGRAM_ID, wormholeProgram: new PublicKey(wormholeProgramId), }; } diff --git a/sdk/js/src/solana/nftBridge/instructions/transferWrapped.ts b/sdk/js/src/solana/nftBridge/instructions/transferWrapped.ts index 9f57d47bd..a74123245 100644 --- a/sdk/js/src/solana/nftBridge/instructions/transferWrapped.ts +++ b/sdk/js/src/solana/nftBridge/instructions/transferWrapped.ts @@ -1,10 +1,10 @@ +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, PublicKeyInitData, TransactionInstruction, } from "@solana/web3.js"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { createReadOnlyNftBridgeProgramInterface } from "../program"; +import { TOKEN_METADATA_PROGRAM_ID, deriveTokenMetadataKey } from "../../utils"; import { getPostMessageAccounts } from "../../wormhole"; import { deriveAuthoritySignerKey, @@ -12,10 +12,7 @@ import { deriveWrappedMetaKey, deriveWrappedMintKey, } from "../accounts"; -import { - deriveSplTokenMetadataKey, - SplTokenMetadataProgram, -} from "../../utils"; +import { createReadOnlyNftBridgeProgramInterface } from "../program"; export function createTransferWrappedInstruction( nftBridgeProgramId: PublicKeyInitData, @@ -120,7 +117,7 @@ export function getTransferWrappedAccounts( fromOwner: new PublicKey(fromOwner), mint, wrappedMeta: deriveWrappedMetaKey(nftBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), + splMetadata: deriveTokenMetadataKey(mint), authoritySigner: deriveAuthoritySignerKey(nftBridgeProgramId), wormholeBridge, wormholeMessage, @@ -131,7 +128,7 @@ export function getTransferWrappedAccounts( rent, systemProgram, tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, + splMetadataProgram: TOKEN_METADATA_PROGRAM_ID, wormholeProgram: new PublicKey(wormholeProgramId), }; } diff --git a/sdk/js/src/solana/tokenBridge/accounts/wrapped.ts b/sdk/js/src/solana/tokenBridge/accounts/wrapped.ts index d302bdc96..8cfd46ab0 100644 --- a/sdk/js/src/solana/tokenBridge/accounts/wrapped.ts +++ b/sdk/js/src/solana/tokenBridge/accounts/wrapped.ts @@ -11,7 +11,7 @@ import { } from "../../../utils"; import { deriveAddress, getAccountData } from "../../utils"; -export { deriveSplTokenMetadataKey } from "../../utils/splMetadata"; +export { deriveTokenMetadataKey } from "../../utils/tokenMetadata"; export function deriveWrappedMintKey( tokenBridgeProgramId: PublicKeyInitData, diff --git a/sdk/js/src/solana/tokenBridge/instructions/attestToken.ts b/sdk/js/src/solana/tokenBridge/instructions/attestToken.ts index e75d71266..253a025ff 100644 --- a/sdk/js/src/solana/tokenBridge/instructions/attestToken.ts +++ b/sdk/js/src/solana/tokenBridge/instructions/attestToken.ts @@ -6,7 +6,7 @@ import { import { createReadOnlyTokenBridgeProgramInterface } from "../program"; import { getPostMessageAccounts } from "../../wormhole"; import { - deriveSplTokenMetadataKey, + deriveTokenMetadataKey, deriveTokenBridgeConfigKey, deriveWrappedMetaKey, } from "../accounts"; @@ -83,7 +83,7 @@ export function getAttestTokenAccounts( config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), mint: new PublicKey(mint), wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), + splMetadata: deriveTokenMetadataKey(mint), wormholeBridge, wormholeMessage: new PublicKey(message), wormholeEmitter, diff --git a/sdk/js/src/solana/tokenBridge/instructions/createWrapped.ts b/sdk/js/src/solana/tokenBridge/instructions/createWrapped.ts index d9228ddf5..a101cc7c3 100644 --- a/sdk/js/src/solana/tokenBridge/instructions/createWrapped.ts +++ b/sdk/js/src/solana/tokenBridge/instructions/createWrapped.ts @@ -1,3 +1,4 @@ +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, PublicKeyInitData, @@ -5,24 +6,23 @@ import { SYSVAR_RENT_PUBKEY, TransactionInstruction, } from "@solana/web3.js"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { createReadOnlyTokenBridgeProgramInterface } from "../program"; -import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole"; -import { - deriveEndpointKey, - deriveMintAuthorityKey, - deriveSplTokenMetadataKey, - deriveWrappedMetaKey, - deriveTokenBridgeConfigKey, - deriveWrappedMintKey, -} from "../accounts"; import { isBytes, parseAttestMetaVaa, ParsedAttestMetaVaa, SignedVaa, } from "../../../vaa"; -import { SplTokenMetadataProgram } from "../../utils"; +import { TOKEN_METADATA_PROGRAM_ID } from "../../utils"; +import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole"; +import { + deriveEndpointKey, + deriveMintAuthorityKey, + deriveTokenBridgeConfigKey, + deriveTokenMetadataKey, + deriveWrappedMetaKey, + deriveWrappedMintKey, +} from "../accounts"; +import { createReadOnlyTokenBridgeProgramInterface } from "../program"; export function createCreateWrappedInstruction( tokenBridgeProgramId: PublicKeyInitData, @@ -96,12 +96,12 @@ export function getCreateWrappedAccounts( ), mint, wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), + splMetadata: deriveTokenMetadataKey(mint), mintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId), rent: SYSVAR_RENT_PUBKEY, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, + splMetadataProgram: TOKEN_METADATA_PROGRAM_ID, wormholeProgram: new PublicKey(wormholeProgramId), }; } diff --git a/sdk/js/src/solana/utils/index.ts b/sdk/js/src/solana/utils/index.ts index a772221dc..127699684 100644 --- a/sdk/js/src/solana/utils/index.ts +++ b/sdk/js/src/solana/utils/index.ts @@ -2,5 +2,5 @@ export * from "./account"; export * from "./bpfLoaderUpgradeable"; export * from "./connection"; export * from "./secp256k1"; -export * from "./splMetadata"; +export * from "./tokenMetadata"; export * from "./transaction"; diff --git a/sdk/js/src/solana/utils/splMetadata.ts b/sdk/js/src/solana/utils/splMetadata.ts deleted file mode 100644 index b7e3daef8..000000000 --- a/sdk/js/src/solana/utils/splMetadata.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { - AccountMeta, - Commitment, - Connection, - PublicKey, - PublicKeyInitData, - SystemProgram, - SYSVAR_RENT_PUBKEY, - TransactionInstruction, -} from "@solana/web3.js"; -import { - deriveAddress, - getAccountData, - newAccountMeta, - newReadOnlyAccountMeta, -} from "./account"; - -export class Creator { - address: PublicKey; - verified: boolean; - share: number; - - constructor(address: PublicKeyInitData, verified: boolean, share: number) { - this.address = new PublicKey(address); - this.verified = verified; - this.share = share; - } - - static size: number = 34; - - serialize() { - const serialized = Buffer.alloc(Creator.size); - serialized.write(this.address.toBuffer().toString("hex"), 0, "hex"); - if (this.verified) { - serialized.writeUInt8(1, 32); - } - serialized.writeUInt8(this.share, 33); - return serialized; - } - - static deserialize(data: Buffer): Creator { - const address = data.subarray(0, 32); - const verified = data.readUInt8(32) > 0; - const share = data.readUInt8(33); - return new Creator(address, verified, share); - } -} - -export class Data { - name: string; - symbol: string; - uri: string; - sellerFeeBasisPoints: number; - creators: Creator[] | null; - - constructor( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null - ) { - this.name = name; - this.symbol = symbol; - this.uri = uri; - this.sellerFeeBasisPoints = sellerFeeBasisPoints; - this.creators = creators; - } - - serialize() { - const nameLen = this.name.length; - const symbolLen = this.symbol.length; - const uriLen = this.uri.length; - const creators = this.creators; - const [creatorsLen, creatorsSize] = (() => { - if (creators === null) { - return [0, 0]; - } - - const creatorsLen = creators.length; - return [creatorsLen, 4 + creatorsLen * Creator.size]; - })(); - const serialized = Buffer.alloc( - 15 + nameLen + symbolLen + uriLen + creatorsSize - ); - serialized.writeUInt32LE(nameLen, 0); - serialized.write(this.name, 4); - serialized.writeUInt32LE(symbolLen, 4 + nameLen); - serialized.write(this.symbol, 8 + nameLen); - serialized.writeUInt32LE(uriLen, 8 + nameLen + symbolLen); - serialized.write(this.uri, 12 + nameLen + symbolLen); - serialized.writeUInt16LE( - this.sellerFeeBasisPoints, - 12 + nameLen + symbolLen + uriLen - ); - if (creators === null) { - serialized.writeUInt8(0, 14 + nameLen + symbolLen + uriLen); - } else { - serialized.writeUInt8(1, 14 + nameLen + symbolLen + uriLen); - serialized.writeUInt32LE(creatorsLen, 15 + nameLen + symbolLen + uriLen); - for (let i = 0; i < creatorsLen; ++i) { - const creator = creators.at(i)!; - const idx = 19 + nameLen + symbolLen + uriLen + i * Creator.size; - serialized.write(creator.serialize().toString("hex"), idx, "hex"); - } - } - return serialized; - } - - static deserialize(data: Buffer): Data { - const nameLen = data.readUInt32LE(0); - const name = data.subarray(4, 4 + nameLen).toString(); - const symbolLen = data.readUInt32LE(4 + nameLen); - const symbol = data - .subarray(8 + nameLen, 8 + nameLen + symbolLen) - .toString(); - const uriLen = data.readUInt32LE(8 + nameLen + symbolLen); - const uri = data - .subarray(12 + nameLen + symbolLen, 12 + nameLen + symbolLen + uriLen) - .toString(); - const sellerFeeBasisPoints = data.readUInt16LE( - 12 + nameLen + symbolLen + uriLen - ); - const optionCreators = data.readUInt8(14 + nameLen + symbolLen + uriLen); - const creators = (() => { - if (optionCreators == 0) { - return null; - } - - const creators: Creator[] = []; - const creatorsLen = data.readUInt32LE(15 + nameLen + symbolLen + uriLen); - for (let i = 0; i < creatorsLen; ++i) { - const idx = 19 + nameLen + symbolLen + uriLen + i * Creator.size; - creators.push( - Creator.deserialize(data.subarray(idx, idx + Creator.size)) - ); - } - return creators; - })(); - return new Data(name, symbol, uri, sellerFeeBasisPoints, creators); - } -} - -export class CreateMetadataAccountArgs extends Data { - isMutable: boolean; - - constructor( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean - ) { - super(name, symbol, uri, sellerFeeBasisPoints, creators); - this.isMutable = isMutable; - } - - static serialize( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean - ) { - return new CreateMetadataAccountArgs( - name, - symbol, - uri, - sellerFeeBasisPoints, - creators, - isMutable - ).serialize(); - } - - static serializeInstructionData( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean - ) { - return Buffer.concat([ - Buffer.alloc(1, 0), - CreateMetadataAccountArgs.serialize( - name, - symbol, - uri, - sellerFeeBasisPoints, - creators, - isMutable - ), - ]); - } - - serialize() { - return Buffer.concat([ - super.serialize(), - Buffer.alloc(1, this.isMutable ? 1 : 0), - ]); - } -} - -export class SplTokenMetadataProgram { - /** - * @internal - */ - constructor() {} - - /** - * Public key that identifies the SPL Token Metadata program - */ - static programId: PublicKey = new PublicKey( - "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" - ); - - static createMetadataAccounts( - payer: PublicKey, - mint: PublicKey, - mintAuthority: PublicKey, - name: string, - symbol: string, - updateAuthority: PublicKey, - updateAuthorityIsSigner: boolean = false, - uri?: string, - creators?: Creator[] | null, - sellerFeeBasisPoints?: number, - isMutable: boolean = false, - metadataAccount: PublicKey = deriveSplTokenMetadataKey(mint) - ): TransactionInstruction { - const keys: AccountMeta[] = [ - newAccountMeta(metadataAccount, false), - newReadOnlyAccountMeta(mint, false), - newReadOnlyAccountMeta(mintAuthority, true), - newReadOnlyAccountMeta(payer, true), - newReadOnlyAccountMeta(updateAuthority, updateAuthorityIsSigner), - newReadOnlyAccountMeta(SystemProgram.programId, false), - newReadOnlyAccountMeta(SYSVAR_RENT_PUBKEY, false), - ]; - const data = CreateMetadataAccountArgs.serializeInstructionData( - name, - symbol, - uri === undefined ? "" : uri, - sellerFeeBasisPoints === undefined ? 0 : sellerFeeBasisPoints, - creators === undefined ? null : creators, - isMutable - ); - return { - programId: SplTokenMetadataProgram.programId, - keys, - data, - }; - } -} - -export function deriveSplTokenMetadataKey(mint: PublicKeyInitData): PublicKey { - return deriveAddress( - [ - Buffer.from("metadata"), - SplTokenMetadataProgram.programId.toBuffer(), - new PublicKey(mint).toBuffer(), - ], - SplTokenMetadataProgram.programId - ); -} - -export enum Key { - Uninitialized, - EditionV1, - MasterEditionV1, - ReservationListV1, - MetadataV1, - ReservationListV2, - MasterEditionV2, - EditionMarker, -} - -export class Metadata { - key: Key; - updateAuthority: PublicKey; - mint: PublicKey; - data: Data; - primarySaleHappened: boolean; - isMutable: boolean; - - constructor( - key: number, - updateAuthority: PublicKeyInitData, - mint: PublicKeyInitData, - data: Data, - primarySaleHappened: boolean, - isMutable: boolean - ) { - this.key = key as Key; - this.updateAuthority = new PublicKey(updateAuthority); - this.mint = new PublicKey(mint); - this.data = data; - this.primarySaleHappened = primarySaleHappened; - this.isMutable = isMutable; - } - - static deserialize(data: Buffer): Metadata { - const key = data.readUInt8(0); - const updateAuthority = data.subarray(1, 33); - const mint = data.subarray(33, 65); - const meta = Data.deserialize(data.subarray(65)); - const metaLen = meta.serialize().length; - const primarySaleHappened = data.readUInt8(65 + metaLen) > 0; - const isMutable = data.readUInt8(66 + metaLen) > 0; - return new Metadata( - key, - updateAuthority, - mint, - meta, - primarySaleHappened, - isMutable - ); - } -} - -export async function getMetadata( - connection: Connection, - mint: PublicKeyInitData, - commitment?: Commitment -): Promise { - return connection - .getAccountInfo(deriveSplTokenMetadataKey(mint), commitment) - .then((info) => Metadata.deserialize(getAccountData(info))); -} diff --git a/sdk/js/src/solana/utils/tokenMetadata.ts b/sdk/js/src/solana/utils/tokenMetadata.ts new file mode 100644 index 000000000..f00699ac0 --- /dev/null +++ b/sdk/js/src/solana/utils/tokenMetadata.ts @@ -0,0 +1,17 @@ +import { PublicKey, PublicKeyInitData } from "@solana/web3.js"; +import { deriveAddress } from "./account"; + +export const TOKEN_METADATA_PROGRAM_ID = new PublicKey( + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" +); + +export function deriveTokenMetadataKey(mint: PublicKeyInitData): PublicKey { + return deriveAddress( + [ + Buffer.from("metadata"), + TOKEN_METADATA_PROGRAM_ID.toBuffer(), + new PublicKey(mint).toBuffer(), + ], + TOKEN_METADATA_PROGRAM_ID + ); +} diff --git a/solana/Cargo.lock b/solana/Cargo.lock index 353911178..9ff31e883 100644 --- a/solana/Cargo.lock +++ b/solana/Cargo.lock @@ -1847,6 +1847,19 @@ dependencies = [ "syn 1.0.91", ] +[[package]] +name = "mpl-token-metadata" +version = "0.0.1" +source = "git+https://github.com/wormhole-foundation/metaplex-program-library?rev=a7ab32ab0defd89c98f205c80ebdaf77ed60152d#a7ab32ab0defd89c98f205c80ebdaf77ed60152d" +dependencies = [ + "borsh", + "num-derive", + "num-traits", + "solana-program", + "spl-token", + "thiserror", +] + [[package]] name = "nft-bridge" version = "0.1.0" @@ -1857,6 +1870,7 @@ dependencies = [ "hex", "hex-literal", "libsecp256k1", + "mpl-token-metadata", "primitive-types", "rand 0.7.3", "rocksalt", @@ -1868,7 +1882,6 @@ dependencies = [ "solitaire", "spl-associated-token-account", "spl-token", - "spl-token-metadata", "wasm-bindgen", "wormhole-bridge-solana", ] @@ -3985,15 +3998,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "spl-token-metadata" -version = "0.0.1" -dependencies = [ - "borsh", - "solana-program", - "spl-token", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4269,6 +4273,7 @@ dependencies = [ "hex", "hex-literal", "libsecp256k1", + "mpl-token-metadata", "primitive-types", "rand 0.7.3", "rocksalt", @@ -4279,7 +4284,6 @@ dependencies = [ "solana-sdk", "solitaire", "spl-token", - "spl-token-metadata", "wasm-bindgen", "wormhole-bridge-solana", ] @@ -4292,6 +4296,7 @@ dependencies = [ "borsh", "clap", "hex", + "mpl-token-metadata", "rand 0.7.3", "shellexpand", "solana-clap-utils", @@ -4300,7 +4305,6 @@ dependencies = [ "solana-program", "solana-sdk", "solitaire", - "spl-token-metadata", "token-bridge", ] diff --git a/solana/Dockerfile b/solana/Dockerfile index 12037e947..4739bc811 100644 --- a/solana/Dockerfile +++ b/solana/Dockerfile @@ -15,6 +15,7 @@ COPY migration migration COPY Cargo.toml Cargo.toml COPY Cargo.lock Cargo.lock COPY solitaire solitaire +COPY external external ENV RUST_LOG="solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug" ENV RUST_BACKTRACE=1 @@ -40,7 +41,7 @@ RUN --mount=type=cache,target=target,id=build \ cp target/deploy/wormhole_migration.so /opt/solana/deps/wormhole_migration.so && \ cp target/deploy/token_bridge.so /opt/solana/deps/token_bridge.so && \ cp target/deploy/nft_bridge.so /opt/solana/deps/nft_bridge.so && \ - cp modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so + cp external/mpl_token_metadata.so /opt/solana/deps/mpl_token_metadata.so FROM scratch AS export-stage COPY --from=builder /opt/solana/deps / diff --git a/solana/external/mpl_token_metadata.so b/solana/external/mpl_token_metadata.so new file mode 100755 index 000000000..2f6f053e6 Binary files /dev/null and b/solana/external/mpl_token_metadata.so differ diff --git a/solana/modules/nft_bridge/program/Cargo.toml b/solana/modules/nft_bridge/program/Cargo.toml index a3b29ed31..c194ed877 100644 --- a/solana/modules/nft_bridge/program/Cargo.toml +++ b/solana/modules/nft_bridge/program/Cargo.toml @@ -29,7 +29,7 @@ solana-program = "*" spl-token = { version = "=3.3.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "1.0.2", features = ["no-entrypoint"] } primitive-types = { version = "0.9.0", default-features = false } -spl-token-metadata = { path = "../../token_bridge/token-metadata" } +spl-token-metadata = { git = "https://github.com/wormhole-foundation/metaplex-program-library", rev = "a7ab32ab0defd89c98f205c80ebdaf77ed60152d", package = "mpl-token-metadata" } wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true } serde = { version = "1.0", features = ["derive"] } @@ -40,5 +40,3 @@ libsecp256k1 = { version = "0.6.0", features = [] } rand = "0.7.3" solana-program-test = "=1.10.31" solana-sdk = "=1.10.31" -spl-token = { version = "=3.3.0", features = ["no-entrypoint"] } -spl-token-metadata = { path = "../../token_bridge/token-metadata" } diff --git a/solana/modules/nft_bridge/program/src/accounts.rs b/solana/modules/nft_bridge/program/src/accounts.rs index 4b1b3de51..9bfbe0060 100644 --- a/solana/modules/nft_bridge/program/src/accounts.rs +++ b/solana/modules/nft_bridge/program/src/accounts.rs @@ -1,4 +1,7 @@ -use crate::types::*; +use crate::{ + types::*, + TokenBridgeError, +}; use bridge::{ accounts::BridgeData, api::ForeignAddress, @@ -9,6 +12,7 @@ use solitaire::{ processors::seeded::Seeded, *, }; +use spl_token_metadata::state::Key::MetadataV1; pub type AuthoritySigner<'b> = Derive, "authority_signer">; pub type CustodySigner<'b> = Derive, "custody_signer">; @@ -106,3 +110,44 @@ impl<'b> Seeded<&SplTokenMetaDerivationData> for SplTokenMeta<'b> { ] } } + +/// This method removes code duplication when checking token metadata. When metadata is read for +/// attestation and transfers, Token Bridge does not invoke Metaplex's Token Metadata program, so +/// it must validate the account the same way Token Metadata program does to ensure the correct +/// account is passed into Token Bridge's instruction context. +pub fn deserialize_and_verify_metadata( + info: &Info, + derivation_data: SplTokenMetaDerivationData, +) -> Result { + // Verify pda. + info.verify_derivation(&spl_token_metadata::id(), &derivation_data)?; + + // There must be account data for token's metadata. + if info.data_is_empty() { + return Err(TokenBridgeError::NonexistentTokenMetadataAccount.into()); + } + + // Account must belong to Metaplex Token Metadata program. + if *info.owner != spl_token_metadata::id() { + return Err(TokenBridgeError::WrongAccountOwner.into()); + } + + // Account must be the expected Metadata length. + if info.data_len() != spl_token_metadata::state::MAX_METADATA_LEN { + return Err(TokenBridgeError::InvalidMetadata.into()); + } + + let mut data: &[u8] = &info.data.borrow_mut(); + + // Unfortunately we cannot use `map_err` easily, so we will match certain deserialization conditions. + match spl_token_metadata::utils::meta_deser_unchecked(&mut data) { + Ok(deserialized) => { + if deserialized.key == MetadataV1 { + Ok(deserialized) + } else { + Err(TokenBridgeError::NotMetadataV1Account.into()) + } + } + _ => Err(TokenBridgeError::InvalidMetadata.into()), + } +} diff --git a/solana/modules/nft_bridge/program/src/api/complete_transfer.rs b/solana/modules/nft_bridge/program/src/api/complete_transfer.rs index 0b0232cbb..b98145122 100644 --- a/solana/modules/nft_bridge/program/src/api/complete_transfer.rs +++ b/solana/modules/nft_bridge/program/src/api/complete_transfer.rs @@ -382,7 +382,7 @@ pub fn complete_wrapped_meta( symbol.retain(|&c| c != '\u{FFFD}'); let symbol: String = symbol.iter().collect(); - let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts( + let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts_v3( spl_token_metadata::id(), *accs.spl_metadata.key, *accs.mint.info().key, @@ -396,6 +396,9 @@ pub fn complete_wrapped_meta( 0, false, true, + None, + None, + None, ); invoke_seeded(&spl_token_metadata_ix, ctx, &accs.mint_authority, None)?; diff --git a/solana/modules/nft_bridge/program/src/api/transfer.rs b/solana/modules/nft_bridge/program/src/api/transfer.rs index 51584c455..9d13cf2f5 100644 --- a/solana/modules/nft_bridge/program/src/api/transfer.rs +++ b/solana/modules/nft_bridge/program/src/api/transfer.rs @@ -1,5 +1,6 @@ use crate::{ accounts::{ + deserialize_and_verify_metadata, AuthoritySigner, ConfigAccount, CoreBridge, @@ -17,11 +18,7 @@ use crate::{ messages::PayloadTransfer, types::*, TokenBridgeError, - TokenBridgeError::{ - InvalidMetadata, - TokenNotNFT, - WrongAccountOwner, - }, + TokenBridgeError::WrongAccountOwner, }; use bridge::{ api::PostMessageData, @@ -50,7 +47,6 @@ use solitaire::{ CreationLamports::Exempt, *, }; -use spl_token_metadata::state::Metadata; #[derive(FromAccounts)] pub struct TransferNative<'b> { @@ -123,24 +119,11 @@ pub fn transfer_native( accs.custody .verify_derivation(ctx.program_id, &derivation_data)?; - let derivation_data: SplTokenMetaDerivationData = (&*accs).into(); - accs.spl_metadata - .verify_derivation(&spl_token_metadata::id(), &derivation_data)?; - // Verify mints if accs.from.mint != *accs.mint.info().key { return Err(TokenBridgeError::InvalidMint.into()); } - // Token must have metadata - if accs.spl_metadata.data_is_empty() { - return Err(TokenNotNFT.into()); - } - - if *accs.spl_metadata.owner != spl_token_metadata::id() { - return Err(WrongAccountOwner.into()); - } - // Verify that the token is not a wrapped token if let COption::Some(mint_authority) = accs.mint.mint_authority { if mint_authority == MintSigner::key(None, ctx.program_id) { @@ -180,8 +163,7 @@ pub fn transfer_native( ); invoke(&transfer_ix, ctx.accounts)?; - let metadata: Metadata = - Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?; + let metadata = deserialize_and_verify_metadata(&accs.spl_metadata, (&*accs).into())?; // Post message // Given there is no tokenID equivalent on Solana and each distinct token address is translated @@ -326,21 +308,7 @@ pub fn transfer_wrapped( accs.wrapped_meta .verify_derivation(ctx.program_id, &derivation_data)?; - // Token must have metadata - if accs.spl_metadata.data_is_empty() { - return Err(TokenNotNFT.into()); - } - - let derivation_data: SplTokenMetaDerivationData = (&*accs).into(); - accs.spl_metadata - .verify_derivation(&spl_token_metadata::id(), &derivation_data)?; - - if *accs.spl_metadata.owner != spl_token_metadata::id() { - return Err(WrongAccountOwner.into()); - } - - let metadata: Metadata = - Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?; + let metadata = deserialize_and_verify_metadata(&accs.spl_metadata, (&*accs).into())?; // Post message let payload = PayloadTransfer { diff --git a/solana/modules/nft_bridge/program/src/lib.rs b/solana/modules/nft_bridge/program/src/lib.rs index 2cd6e92f8..3846070e1 100644 --- a/solana/modules/nft_bridge/program/src/lib.rs +++ b/solana/modules/nft_bridge/program/src/lib.rs @@ -59,9 +59,10 @@ pub enum TokenBridgeError { TokenNotNative, UninitializedMint, WrongAccountOwner, - TokenNotNFT, + NonexistentTokenMetadataAccount, InvalidAssociatedAccount, InvalidRecipient, + NotMetadataV1Account, } impl From for SolitaireError { diff --git a/solana/modules/nft_bridge/program/tests/common.rs b/solana/modules/nft_bridge/program/tests/common.rs index 460aad355..482f4013f 100644 --- a/solana/modules/nft_bridge/program/tests/common.rs +++ b/solana/modules/nft_bridge/program/tests/common.rs @@ -128,7 +128,7 @@ mod helpers { ); let mut builder = ProgramTest::new("bridge", program, processor!(bridge::solitaire)); - builder.add_program("spl_token_metadata", spl_token_metadata::id(), None); + builder.add_program("mpl_token_metadata", spl_token_metadata::id(), None); builder.add_program( "nft_bridge", token_program, @@ -506,21 +506,26 @@ mod helpers { client, payer, &[payer, mint_authority], - &[spl_token_metadata::instruction::create_metadata_accounts( - spl_token_metadata::id(), - metadata_account, - mint, - mint_authority.pubkey(), - payer.pubkey(), - update_authority, - name, - symbol, - uri, - None, - 0, - false, - false, - )], + &[ + spl_token_metadata::instruction::create_metadata_accounts_v3( + spl_token_metadata::id(), + metadata_account, + mint, + mint_authority.pubkey(), + payer.pubkey(), + update_authority, + name, + symbol, + uri, + None, + 0, + false, + false, + None, + None, + None, + ), + ], CommitmentLevel::Processed, ) .await diff --git a/solana/modules/token_bridge/client/Cargo.toml b/solana/modules/token_bridge/client/Cargo.toml index 2ad19945d..ee10289e6 100644 --- a/solana/modules/token_bridge/client/Cargo.toml +++ b/solana/modules/token_bridge/client/Cargo.toml @@ -17,4 +17,4 @@ solana-cli-config = "=1.10.31" solitaire = { path = "../../../solitaire/program" } solana-clap-utils = "=1.10.31" hex = "0.4.3" -spl-token-metadata = { path = "../token-metadata" } +spl-token-metadata = { git = "https://github.com/wormhole-foundation/metaplex-program-library", rev = "a7ab32ab0defd89c98f205c80ebdaf77ed60152d", package = "mpl-token-metadata" } diff --git a/solana/modules/token_bridge/client/src/main.rs b/solana/modules/token_bridge/client/src/main.rs index d30200ffd..8ffd0ee6b 100644 --- a/solana/modules/token_bridge/client/src/main.rs +++ b/solana/modules/token_bridge/client/src/main.rs @@ -107,7 +107,7 @@ fn command_create_meta( ) .0; println!("Meta account: {}", meta_acc); - let ix = spl_token_metadata::instruction::create_metadata_accounts( + let ix = spl_token_metadata::instruction::create_metadata_accounts_v3( spl_token_metadata::id(), meta_acc, *mint, @@ -121,6 +121,9 @@ fn command_create_meta( 0, false, false, + None, + None, + None, ); let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey())); @@ -334,9 +337,15 @@ fn main() { &spl_token_metadata::id(), ) .0; - let meta_info = config.rpc_client.get_account(&meta_acc).unwrap(); - let meta_info = - spl_token_metadata::state::Metadata::from_bytes(&meta_info.data).unwrap(); + let meta_info = spl_token_metadata::utils::meta_deser_unchecked( + &mut config + .rpc_client + .get_account(&meta_acc) + .unwrap() + .data + .as_slice(), + ) + .unwrap(); println!("Key: {:?}", meta_info.key); println!("Mint: {}", meta_info.mint); println!("Metadata Key: {}", meta_acc); diff --git a/solana/modules/token_bridge/program/Cargo.toml b/solana/modules/token_bridge/program/Cargo.toml index 815b7c990..2fca2b8e9 100644 --- a/solana/modules/token_bridge/program/Cargo.toml +++ b/solana/modules/token_bridge/program/Cargo.toml @@ -28,7 +28,7 @@ sha3 = "0.9.1" solana-program = "*" spl-token = { version = "=3.3.0", features = ["no-entrypoint"] } primitive-types = { version = "0.9.0", default-features = false } -spl-token-metadata = { path = "../token-metadata" } +spl-token-metadata = { git = "https://github.com/wormhole-foundation/metaplex-program-library", rev = "a7ab32ab0defd89c98f205c80ebdaf77ed60152d", package = "mpl-token-metadata" } wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true } serde = { version = "1.0", features = ["derive"] } @@ -39,5 +39,3 @@ libsecp256k1 = { version = "0.6.0", features = [] } rand = "0.7.3" solana-program-test = "=1.10.31" solana-sdk = "=1.10.31" -spl-token = { version = "=3.3.0", features = ["no-entrypoint"] } -spl-token-metadata = { path = "../token-metadata" } diff --git a/solana/modules/token_bridge/program/src/accounts.rs b/solana/modules/token_bridge/program/src/accounts.rs index 53666738d..4dc55f43e 100644 --- a/solana/modules/token_bridge/program/src/accounts.rs +++ b/solana/modules/token_bridge/program/src/accounts.rs @@ -1,4 +1,7 @@ -use crate::types::*; +use crate::{ + types::*, + TokenBridgeError, +}; use bridge::{ accounts::BridgeData, api::ForeignAddress, @@ -8,6 +11,7 @@ use solitaire::{ processors::seeded::Seeded, *, }; +use spl_token_metadata::state::Key::MetadataV1; pub type AuthoritySigner<'b> = Derive, "authority_signer">; pub type CustodySigner<'b> = Derive, "custody_signer">; @@ -101,3 +105,44 @@ impl<'b> Seeded<&SplTokenMetaDerivationData> for SplTokenMeta<'b> { ] } } + +/// This method removes code duplication when checking token metadata. When metadata is read for +/// attestation and transfers, Token Bridge does not invoke Metaplex's Token Metadata program, so +/// it must validate the account the same way Token Metadata program does to ensure the correct +/// account is passed into Token Bridge's instruction context. +pub fn deserialize_and_verify_metadata( + info: &Info, + derivation_data: SplTokenMetaDerivationData, +) -> Result { + // Verify pda. + info.verify_derivation(&spl_token_metadata::id(), &derivation_data)?; + + // There must be account data for token's metadata. + if info.data_is_empty() { + return Err(TokenBridgeError::NonexistentTokenMetadataAccount.into()); + } + + // Account must belong to Metaplex Token Metadata program. + if *info.owner != spl_token_metadata::id() { + return Err(TokenBridgeError::WrongAccountOwner.into()); + } + + // Account must be the expected Metadata length. + if info.data_len() != spl_token_metadata::state::MAX_METADATA_LEN { + return Err(TokenBridgeError::InvalidMetadata.into()); + } + + let mut data: &[u8] = &info.data.borrow_mut(); + + // Unfortunately we cannot use `map_err` easily, so we will match certain deserialization conditions. + match spl_token_metadata::utils::meta_deser_unchecked(&mut data) { + Ok(deserialized) => { + if deserialized.key == MetadataV1 { + Ok(deserialized) + } else { + Err(TokenBridgeError::NotMetadataV1Account.into()) + } + } + _ => Err(TokenBridgeError::InvalidMetadata.into()), + } +} diff --git a/solana/modules/token_bridge/program/src/api/attest.rs b/solana/modules/token_bridge/program/src/api/attest.rs index d2af371de..319e0fca8 100644 --- a/solana/modules/token_bridge/program/src/api/attest.rs +++ b/solana/modules/token_bridge/program/src/api/attest.rs @@ -1,5 +1,6 @@ use crate::{ accounts::{ + deserialize_and_verify_metadata, ConfigAccount, CoreBridge, EmitterAccount, @@ -10,7 +11,6 @@ use crate::{ }, messages::PayloadAssetMeta, types::*, - TokenBridgeError::*, }; use bridge::{ api::PostMessageData, @@ -34,7 +34,6 @@ use solitaire::{ }, *, }; -use spl_token_metadata::state::Metadata; #[derive(FromAccounts)] pub struct AttestToken<'b> { @@ -118,16 +117,7 @@ pub fn attest_token( // Assign metadata if an SPL Metadata account exists for the SPL token in question. if !accs.spl_metadata.data_is_empty() { - let derivation_data: SplTokenMetaDerivationData = (&*accs).into(); - accs.spl_metadata - .verify_derivation(&spl_token_metadata::id(), &derivation_data)?; - - if *accs.spl_metadata.owner != spl_token_metadata::id() { - return Err(WrongAccountOwner.into()); - } - - let metadata: Metadata = - Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?; + let metadata = deserialize_and_verify_metadata(&accs.spl_metadata, (&*accs).into())?; payload.name = metadata.data.name.clone(); payload.symbol = metadata.data.symbol; } diff --git a/solana/modules/token_bridge/program/src/api/create_wrapped.rs b/solana/modules/token_bridge/program/src/api/create_wrapped.rs index 54883581d..31ea5852d 100644 --- a/solana/modules/token_bridge/program/src/api/create_wrapped.rs +++ b/solana/modules/token_bridge/program/src/api/create_wrapped.rs @@ -1,5 +1,6 @@ use crate::{ accounts::{ + deserialize_and_verify_metadata, ConfigAccount, Endpoint, EndpointDerivationData, @@ -14,7 +15,6 @@ use crate::{ messages::PayloadAssetMeta, TokenBridgeError::{ InvalidChain, - InvalidMetadata, InvalidVAA, }, INVALID_VAAS, @@ -40,10 +40,6 @@ use solitaire::{ *, }; -use spl_token_metadata::state::{ - Data as SplData, - Metadata, -}; use std::cmp::min; #[derive(FromAccounts)] @@ -164,7 +160,7 @@ pub fn create_accounts( let name = truncate_utf8(&accs.vaa.name, 32 - 11) + " (Wormhole)"; let symbol = truncate_utf8(&accs.vaa.symbol, 10); - let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts( + let spl_token_metadata_ix = spl_token_metadata::instruction::create_metadata_accounts_v3( spl_token_metadata::id(), *accs.spl_metadata.key, *accs.mint.info().key, @@ -178,6 +174,9 @@ pub fn create_accounts( 0, false, true, + None, + None, + None, ); invoke_seeded(&spl_token_metadata_ix, ctx, &accs.mint_authority, None)?; @@ -194,28 +193,34 @@ pub fn update_accounts( accs: &mut CreateWrapped, _data: CreateWrappedData, ) -> Result<()> { - accs.spl_metadata.verify_derivation( - &spl_token_metadata::id(), - &SplTokenMetaDerivationData { + // Checks in this method are redundant with what occurs in `update_metadata_accounts_v2`, but we want to make + // sure that the account we are deserializing is legitimate. + let metadata = deserialize_and_verify_metadata( + &accs.spl_metadata, + SplTokenMetaDerivationData { mint: *accs.mint.info().key, }, )?; - let mut metadata: SplData = Metadata::from_account_info(accs.spl_metadata.info()) - .ok_or(InvalidMetadata)? - .data; - - // Normalize token metadata. - metadata.name = truncate_utf8(&accs.vaa.name, 32 - 11) + " (Wormhole)"; - metadata.symbol = truncate_utf8(&accs.vaa.symbol, 10); + // Normalize token metadata's name and symbol. + let new_data_v2 = spl_token_metadata::state::DataV2 { + name: truncate_utf8(&accs.vaa.name, 32 - 11) + " (Wormhole)", + symbol: truncate_utf8(&accs.vaa.symbol, 10), + uri: metadata.data.uri, + seller_fee_basis_points: metadata.data.seller_fee_basis_points, + creators: metadata.data.creators, + collection: metadata.collection, + uses: metadata.uses, + }; // Update SPL Metadata - let spl_token_metadata_ix = spl_token_metadata::instruction::update_metadata_accounts( + let spl_token_metadata_ix = spl_token_metadata::instruction::update_metadata_accounts_v2( spl_token_metadata::id(), *accs.spl_metadata.key, *accs.mint_authority.info().key, None, - Some(metadata), + Some(new_data_v2), + None, None, ); invoke_seeded(&spl_token_metadata_ix, ctx, &accs.mint_authority, None)?; diff --git a/solana/modules/token_bridge/program/src/lib.rs b/solana/modules/token_bridge/program/src/lib.rs index c2555039c..6ccb82f5d 100644 --- a/solana/modules/token_bridge/program/src/lib.rs +++ b/solana/modules/token_bridge/program/src/lib.rs @@ -89,6 +89,8 @@ pub enum TokenBridgeError { InvalidFee, InvalidRecipient, InvalidVAA, + NonexistentTokenMetadataAccount, + NotMetadataV1Account, } impl From for SolitaireError { diff --git a/solana/modules/token_bridge/program/tests/common.rs b/solana/modules/token_bridge/program/tests/common.rs index bbbc374b0..cdea407eb 100644 --- a/solana/modules/token_bridge/program/tests/common.rs +++ b/solana/modules/token_bridge/program/tests/common.rs @@ -131,7 +131,7 @@ mod helpers { ); let mut builder = ProgramTest::new("bridge", program, processor!(bridge::solitaire)); - builder.add_program("spl_token_metadata", spl_token_metadata::id(), None); + builder.add_program("mpl_token_metadata", spl_token_metadata::id(), None); builder.add_program( "token_bridge", token_program, @@ -618,21 +618,26 @@ mod helpers { client, payer, &[payer, mint_authority], - &[spl_token_metadata::instruction::create_metadata_accounts( - spl_token_metadata::id(), - metadata_account, - mint.pubkey(), - mint_authority.pubkey(), - payer.pubkey(), - update_authority, - name, - symbol, - "https://token.org".to_string(), - None, - 0, - false, - false, - )], + &[ + spl_token_metadata::instruction::create_metadata_accounts_v3( + spl_token_metadata::id(), + metadata_account, + mint.pubkey(), + mint_authority.pubkey(), + payer.pubkey(), + update_authority, + name, + symbol, + "https://token.org".to_string(), + None, + 0, + false, + false, + None, + None, + None, + ), + ], CommitmentLevel::Processed, ) .await diff --git a/solana/modules/token_bridge/token-metadata/Cargo.toml b/solana/modules/token_bridge/token-metadata/Cargo.toml deleted file mode 100644 index 10ca4cfb9..000000000 --- a/solana/modules/token_bridge/token-metadata/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "spl-token-metadata" -version = "0.0.1" -description = "Metaplex Metadata" -authors = ["Metaplex Maintainers "] -repository = "https://github.com/metaplex-foundation/metaplex" -license = "Apache-2.0" -edition = "2018" - -[lib] -crate-type = ["cdylib", "lib"] - -[features] -no-entrypoint = [] -test-bpf = [] - -[dependencies] -borsh = "=0.9.3" -solana-program = "=1.10.31" -spl-token = { version = "=3.3.0", features = ["no-entrypoint"] } diff --git a/solana/modules/token_bridge/token-metadata/README.md b/solana/modules/token_bridge/token-metadata/README.md deleted file mode 100644 index ec5ef2155..000000000 --- a/solana/modules/token_bridge/token-metadata/README.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Token Metadata Program ---- - -Fork of the SPL Token Metadata program from the Metaplex repository, this is -temporary until there are versioned releases we can compile against. Currently -the upstream version depends on a version of solana with conflicting versions -of borsh. diff --git a/solana/modules/token_bridge/token-metadata/Xargo.toml b/solana/modules/token_bridge/token-metadata/Xargo.toml deleted file mode 100644 index 475fb71ed..000000000 --- a/solana/modules/token_bridge/token-metadata/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/solana/modules/token_bridge/token-metadata/spl_token_metadata.so b/solana/modules/token_bridge/token-metadata/spl_token_metadata.so deleted file mode 100644 index 79f13c0ec..000000000 Binary files a/solana/modules/token_bridge/token-metadata/spl_token_metadata.so and /dev/null differ diff --git a/solana/modules/token_bridge/token-metadata/src/instruction.rs b/solana/modules/token_bridge/token-metadata/src/instruction.rs deleted file mode 100644 index baa4aacc9..000000000 --- a/solana/modules/token_bridge/token-metadata/src/instruction.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::state::{ - Creator, - Data, - EDITION, - EDITION_MARKER_BIT_SIZE, - PREFIX, -}; -use borsh::{ - BorshDeserialize, - BorshSerialize, -}; -use solana_program::{ - instruction::{ - AccountMeta, - Instruction, - }, - pubkey::Pubkey, - sysvar, -}; - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] -/// Args for update call -pub struct UpdateMetadataAccountArgs { - pub data: Option, - pub update_authority: Option, - pub primary_sale_happened: Option, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] -/// Args for create call -pub struct CreateMetadataAccountArgs { - /// Note that unique metadatas are disabled for now. - pub data: Data, - /// Whether you want your metadata to be updateable in the future. - pub is_mutable: bool, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] -pub struct CreateMasterEditionArgs { - /// If set, means that no more than this number of editions can ever be minted. This is immutable. - pub max_supply: Option, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] -pub struct MintNewEditionFromMasterEditionViaTokenArgs { - pub edition: u64, -} - -/// Instructions supported by the Metadata program. -#[derive(BorshSerialize, BorshDeserialize, Clone)] -pub enum MetadataInstruction { - /// Create Metadata object. - /// 0. `[writable]` Metadata key (pda of ['metadata', program id, mint id]) - /// 1. `[]` Mint of token asset - /// 2. `[signer]` Mint authority - /// 3. `[signer]` payer - /// 4. `[]` update authority info - /// 5. `[]` System program - /// 6. `[]` Rent info - CreateMetadataAccount(CreateMetadataAccountArgs), - - /// Update a Metadata - /// 0. `[writable]` Metadata account - /// 1. `[signer]` Update authority key - UpdateMetadataAccount(UpdateMetadataAccountArgs), -} - -/// Creates an CreateMetadataAccounts instruction -#[allow(clippy::too_many_arguments)] -pub fn create_metadata_accounts( - program_id: Pubkey, - metadata_account: Pubkey, - mint: Pubkey, - mint_authority: Pubkey, - payer: Pubkey, - update_authority: Pubkey, - name: String, - symbol: String, - uri: String, - creators: Option>, - seller_fee_basis_points: u16, - update_authority_is_signer: bool, - is_mutable: bool, -) -> Instruction { - Instruction { - program_id, - accounts: vec![ - AccountMeta::new(metadata_account, false), - AccountMeta::new_readonly(mint, false), - AccountMeta::new_readonly(mint_authority, true), - AccountMeta::new(payer, true), - AccountMeta::new_readonly(update_authority, update_authority_is_signer), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ], - data: MetadataInstruction::CreateMetadataAccount(CreateMetadataAccountArgs { - data: Data { - name, - symbol, - uri, - seller_fee_basis_points, - creators, - }, - is_mutable, - }) - .try_to_vec() - .unwrap(), - } -} - -/// update metadata account instruction -pub fn update_metadata_accounts( - program_id: Pubkey, - metadata_account: Pubkey, - update_authority: Pubkey, - new_update_authority: Option, - data: Option, - primary_sale_happened: Option, -) -> Instruction { - Instruction { - program_id, - accounts: vec![ - AccountMeta::new(metadata_account, false), - AccountMeta::new_readonly(update_authority, true), - ], - data: MetadataInstruction::UpdateMetadataAccount(UpdateMetadataAccountArgs { - data, - update_authority: new_update_authority, - primary_sale_happened, - }) - .try_to_vec() - .unwrap(), - } -} diff --git a/solana/modules/token_bridge/token-metadata/src/lib.rs b/solana/modules/token_bridge/token-metadata/src/lib.rs deleted file mode 100644 index fc77d62c5..000000000 --- a/solana/modules/token_bridge/token-metadata/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -// The solana_program::declare_id! macro generates spurious import statements. -#[allow(unused_imports)] -pub mod instruction; -pub mod state; -pub mod utils; - -solana_program::declare_id!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); diff --git a/solana/modules/token_bridge/token-metadata/src/state.rs b/solana/modules/token_bridge/token-metadata/src/state.rs deleted file mode 100644 index bbe6e066c..000000000 --- a/solana/modules/token_bridge/token-metadata/src/state.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::utils::try_from_slice_checked; -use borsh::{ - BorshDeserialize, - BorshSerialize, -}; -use solana_program::{ - account_info::AccountInfo, - pubkey::Pubkey, -}; - -/// prefix used for PDAs to avoid certain collision attacks (https://en.wikipedia.org/wiki/Collision_attack#Chosen-prefix_collision_attack) -pub const PREFIX: &str = "metadata"; - -/// Used in seeds to make Edition model pda address -pub const EDITION: &str = "edition"; - -pub const RESERVATION: &str = "reservation"; - -pub const MAX_NAME_LENGTH: usize = 32; - -pub const MAX_SYMBOL_LENGTH: usize = 10; - -pub const MAX_URI_LENGTH: usize = 200; - -pub const MAX_METADATA_LEN: usize = 1 - + 32 - + 32 - + MAX_NAME_LENGTH - + MAX_SYMBOL_LENGTH - + MAX_URI_LENGTH - + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN - + 2 - + 1 - + 1 - + 198; - -pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200; - -// Large buffer because the older master editions have two pubkeys in them, -// need to keep two versions same size because the conversion process actually changes the same account -// by rewriting it. -pub const MAX_MASTER_EDITION_LEN: usize = 1 + 9 + 8 + 264; - -pub const MAX_CREATOR_LIMIT: usize = 5; - -pub const MAX_CREATOR_LEN: usize = 32 + 1 + 1; - -pub const MAX_RESERVATIONS: usize = 200; - -// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec -pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100; - -// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec -pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 8 + 84; - -pub const MAX_EDITION_MARKER_SIZE: usize = 32; - -pub const EDITION_MARKER_BIT_SIZE: u64 = 248; - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, Copy)] -pub enum Key { - Uninitialized, - EditionV1, - MasterEditionV1, - ReservationListV1, - MetadataV1, - ReservationListV2, - MasterEditionV2, - EditionMarker, -} - -impl Default for Key { - fn default() -> Self { - Key::Uninitialized - } -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, Default, PartialEq, Debug, Clone)] -pub struct Data { - /// The name of the asset - pub name: String, - /// The symbol for the asset - pub symbol: String, - /// URI pointing to JSON representing the asset - pub uri: String, - /// Royalty basis points that goes to creators in secondary sales (0-10000) - pub seller_fee_basis_points: u16, - /// Array of creators, optional - pub creators: Option>, -} - -#[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Default)] -pub struct Metadata { - pub key: Key, - pub update_authority: Pubkey, - pub mint: Pubkey, - pub data: Data, - // Immutable, once flipped, all sales of this metadata are considered secondary. - pub primary_sale_happened: bool, - // Whether or not the data struct is mutable, default is not - pub is_mutable: bool, -} - -impl Metadata { - pub fn from_bytes(a: &[u8]) -> Option { - try_from_slice_checked(a, Key::MetadataV1, MAX_METADATA_LEN) - } - - pub fn from_account_info(a: &AccountInfo) -> Option { - try_from_slice_checked(&a.data.borrow_mut(), Key::MetadataV1, MAX_METADATA_LEN) - } -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] -pub struct Creator { - pub address: Pubkey, - pub verified: bool, - // In percentages, NOT basis points ;) Watch out! - pub share: u8, -} diff --git a/solana/modules/token_bridge/token-metadata/src/utils.rs b/solana/modules/token_bridge/token-metadata/src/utils.rs deleted file mode 100644 index 6f96b66f9..000000000 --- a/solana/modules/token_bridge/token-metadata/src/utils.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::state::Key; -use borsh::BorshDeserialize; -use solana_program::borsh::try_from_slice_unchecked; - -pub fn try_from_slice_checked( - data: &[u8], - data_type: Key, - data_size: usize, -) -> Option { - if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8) - || data.len() != data_size - { - return None; - } - try_from_slice_unchecked(data).ok() -} diff --git a/testing/solana-test-validator/.gitignore b/testing/solana-test-validator/.gitignore index 14647191b..65279b95c 100644 --- a/testing/solana-test-validator/.gitignore +++ b/testing/solana-test-validator/.gitignore @@ -1,3 +1,5 @@ .test -artifacts +artifacts* node_modules +wormhole-main +validator.log diff --git a/testing/solana-test-validator/Makefile b/testing/solana-test-validator/Makefile index 7b56c460a..7acf31398 100644 --- a/testing/solana-test-validator/Makefile +++ b/testing/solana-test-validator/Makefile @@ -7,15 +7,33 @@ node_modules: yarn -artifacts: node_modules - cd ../../solana && DOCKER_BUILDKIT=1 docker build -f Dockerfile --build-arg BRIDGE_ADDRESS=agnnozV7x6ffAhi8xVhBd5dShfLnuUKKPEMX1tJ1nDC -o ../testing/solana-test-validator/artifacts . +artifacts: + cd ../../solana && \ + DOCKER_BUILDKIT=1 docker build \ + -f Dockerfile \ + --build-arg BRIDGE_ADDRESS=agnnozV7x6ffAhi8xVhBd5dShfLnuUKKPEMX1tJ1nDC \ + -o ../testing/solana-test-validator/artifacts . + +artifacts-main: + git clone \ + --depth 1 \ + --branch main \ + --filter=blob:none \ + https://github.com/wormhole-foundation/wormhole \ + wormhole-main + cd wormhole-main/solana && \ + DOCKER_BUILDKIT=1 docker build \ + -f Dockerfile \ + --build-arg BRIDGE_ADDRESS=agnnozV7x6ffAhi8xVhBd5dShfLnuUKKPEMX1tJ1nDC \ + -o ../../artifacts-main . + rm -rf wormhole-main .PHONY: test -test: artifacts +test: node_modules artifacts-main artifacts @echo "Running integration tests" yarn run sdk-tests .PHONY: clean clean: - rm -rf artifacts node_modules validator.log .test + rm -rf artifacts artifacts-main wormhole-main node_modules validator.log .test diff --git a/testing/solana-test-validator/package.json b/testing/solana-test-validator/package.json index 6c77e4e67..2ea044a7b 100644 --- a/testing/solana-test-validator/package.json +++ b/testing/solana-test-validator/package.json @@ -6,6 +6,7 @@ "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" }, "dependencies": { + "@metaplex-foundation/mpl-token-metadata": "^2.11.1", "@project-serum/anchor": "0.25.0", "@solana/spl-token": "^0.3.1", "@solana/web3.js": "^1.53.0", diff --git a/testing/solana-test-validator/run_sdk_tests.sh b/testing/solana-test-validator/run_sdk_tests.sh index 65f215859..3d6cc0967 100755 --- a/testing/solana-test-validator/run_sdk_tests.sh +++ b/testing/solana-test-validator/run_sdk_tests.sh @@ -15,9 +15,10 @@ ACCOUNTS=$ROOT/sdk-tests/accounts TEST=$ROOT/.test solana-test-validator --reset \ - --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s $ARTIFACTS/spl_token_metadata.so \ + --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s $ARTIFACTS/mpl_token_metadata.so \ + --account-dir $ACCOUNTS \ --ledger $TEST > validator.log 2>&1 & -sleep 2 +sleep 5 ### write program logs PROGRAM_LOGS=$TEST/program-logs diff --git a/testing/solana-test-validator/sdk-tests/0_deploy_and_upgrade.ts b/testing/solana-test-validator/sdk-tests/0_deploy_and_upgrade.ts index 7ef9f3155..6d9568a71 100644 --- a/testing/solana-test-validator/sdk-tests/0_deploy_and_upgrade.ts +++ b/testing/solana-test-validator/sdk-tests/0_deploy_and_upgrade.ts @@ -69,7 +69,7 @@ describe("Deploy and Upgrade Programs", () => { describe("Wormhole (Core Bridge)", () => { it("Deploy and Initialize", async () => { - const artifactPath = `${__dirname}/../artifacts/bridge.so`; + const artifactPath = `${__dirname}/../artifacts-main/bridge.so`; const programIdPath = `${__dirname}/keys/${CORE_BRIDGE_ADDRESS}.json`; const upgradeAuthority = deriveUpgradeAuthorityKey(CORE_BRIDGE_ADDRESS); @@ -176,7 +176,7 @@ describe("Deploy and Upgrade Programs", () => { describe("Token Bridge", () => { it("Deploy and Initialize", async () => { - const artifactPath = `${__dirname}/../artifacts/token_bridge.so`; + const artifactPath = `${__dirname}/../artifacts-main/token_bridge.so`; const programIdPath = `${__dirname}/keys/${TOKEN_BRIDGE_ADDRESS}.json`; const upgradeAuthority = deriveUpgradeAuthorityKey(TOKEN_BRIDGE_ADDRESS); @@ -270,7 +270,7 @@ describe("Deploy and Upgrade Programs", () => { describe("NFT Bridge", () => { it("Deploy and Initialize", async () => { - const artifactPath = `${__dirname}/../artifacts/nft_bridge.so`; + const artifactPath = `${__dirname}/../artifacts-main/nft_bridge.so`; const programIdPath = `${__dirname}/keys/${NFT_BRIDGE_ADDRESS}.json`; const upgradeAuthority = deriveUpgradeAuthorityKey(NFT_BRIDGE_ADDRESS); diff --git a/testing/solana-test-validator/sdk-tests/2_token_bridge.ts b/testing/solana-test-validator/sdk-tests/2_token_bridge.ts index acfb66bb3..7e49169ca 100644 --- a/testing/solana-test-validator/sdk-tests/2_token_bridge.ts +++ b/testing/solana-test-validator/sdk-tests/2_token_bridge.ts @@ -1,5 +1,9 @@ import { expect } from "chai"; import * as web3 from "@solana/web3.js"; +import { + Metadata, + PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID, +} from "@metaplex-foundation/mpl-token-metadata"; import { createMint, getAccount, @@ -30,6 +34,7 @@ import { deriveEndpointKey, deriveMintAuthorityKey, deriveRedeemerAccountKey, + deriveTokenMetadataKey, deriveWrappedMintKey, getAttestTokenAccounts, getCompleteTransferNativeAccounts, @@ -54,7 +59,6 @@ import { getTransferWrappedWithPayloadCpiAccounts, NodeWallet, signSendAndConfirmTransaction, - SplTokenMetadataProgram, } from "../../../sdk/js/src/solana"; import { deriveWormholeEmitterKey, @@ -80,6 +84,9 @@ import { GUARDIAN_SET_INDEX, LOCALHOST, WETH_ADDRESS, + DEADBEEF_ADDRESS, + DEADBEEF_METADATA_ADDRESS, + DEADBEEF_MINT_ADDRESS, } from "./helpers/consts"; import { ethAddressToBuffer, now } from "./helpers/utils"; import { @@ -517,7 +524,7 @@ describe("Token Bridge", () => { "4CgrjMnDneBjBBEyXtcikLTbAWpHAD1cwn8W1sSSCLru" ); expect(accounts.vaa.toString()).to.equal( - "AaZqjqvg8QirKetuR799Smfw5gyAWobUooGsvxzr1aoX" + "4NDyWDtRvfEdi48a9JgYG28m919hrcdW8gNgRg3jwU99" ); expect(accounts.claim.toString()).to.equal( "4dyk94hhqektDX9wUBCL1ZkyQC1Xn3QaTSAdJeZzbTcJ" @@ -538,9 +545,8 @@ describe("Token Bridge", () => { expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; - expect( - accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) - ).is.true; + expect(accounts.splMetadataProgram.equals(TOKEN_METADATA_PROGRAM_ID)).is + .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); @@ -1552,6 +1558,278 @@ describe("Token Bridge", () => { Buffer.compare(wrappedMeta.tokenAddress, expectedTokenAddress) ).to.equal(0); expect(wrappedMeta.originalDecimals).to.equal(decimals); + + // check metadata + const expectedName = `${name} (Wormhole)`.padEnd(32, "\0"); + const metadata = await Metadata.fromAccountAddress( + connection, + deriveTokenMetadataKey(mint) + ); + expect(metadata.data.symbol.toString()).equals(symbol.padEnd(10, "\0")); + expect(metadata.data.name.toString()).equals(expectedName); + localVariables.oldName = expectedName; + }); + + it("Update (Create) Wrapped with New Metadata", async () => { + const tokenAddress = WETH_ADDRESS; + const oldName: string = localVariables.oldName; + + const mint = deriveWrappedMintKey( + TOKEN_BRIDGE_ADDRESS, + ethereumTokenBridge.chain, + tokenAddress + ); + + // check existing metadata + { + const metadata = await Metadata.fromAccountAddress( + connection, + deriveTokenMetadataKey(mint) + ); + expect(metadata.data.name.toString()).equals(oldName); + } + + const decimals = 18; + const symbol = "WETH"; + const name = "Wrapped Ether"; + const nonce = 420; + const message = ethereumTokenBridge.publishAttestMeta( + tokenAddress, + decimals, + symbol, + name, + nonce + ); + const signedVaa = guardians.addSignatures( + message, + [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] + ); + + const txSignatures = await postVaa( + connection, + wallet.signTransaction, + CORE_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ).then((results) => results.map((result) => result.signature)); + const postTx = txSignatures.pop()!; + for (const verifyTx of txSignatures) { + // console.log(`verifySignatures: ${verifyTx}`); + } + // console.log(`postVaa: ${postTx}`); + + const createWrappedIx = createCreateWrappedInstruction( + TOKEN_BRIDGE_ADDRESS, + CORE_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ); + + const createWrappedTx = await web3.sendAndConfirmTransaction( + connection, + new web3.Transaction().add(createWrappedIx), + [wallet.signer()] + ); + // console.log(`createWrappedTx: ${createWrappedTx}`); + + // verify data + const parsed = parseAttestMetaVaa(signedVaa); + const messageData = await getPostedVaa( + connection, + CORE_BRIDGE_ADDRESS, + parsed.hash + ).then((posted) => posted.message); + + expect(messageData.consistencyLevel).to.equal( + ethereumTokenBridge.consistencyLevel + ); + const expectedEmitter = ethAddressToBuffer( + ETHEREUM_TOKEN_BRIDGE_ADDRESS + ); + expect( + Buffer.compare(messageData.emitterAddress, expectedEmitter) + ).to.equal(0); + expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); + expect(messageData.nonce).to.equal(nonce); + expect(messageData.sequence).to.equal(3n); + expect(messageData.vaaTime).to.equal(0); + expect(messageData.vaaVersion).to.equal(1); + expect(Buffer.compare(parsed.payload, messageData.payload)).to.equal(0); + + const assetMeta = parseAttestMetaPayload(messageData.payload); + expect(assetMeta.payloadType).to.equal(2); + const expectedTokenAddress = ethAddressToBuffer(tokenAddress); + expect( + Buffer.compare(assetMeta.tokenAddress, expectedTokenAddress) + ).to.equal(0); + expect(assetMeta.tokenChain).to.equal(ethereumTokenBridge.chain); + expect(assetMeta.decimals).to.equal(decimals); + expect(assetMeta.symbol).to.equal(symbol); + expect(assetMeta.name).to.equal(name); + + // check wrapped mint + const mintInfo = await getMint(connection, mint); + expect(mintInfo.decimals).to.equal(8); + expect(mintInfo.mintAuthority).is.not.null; + expect( + mintInfo.mintAuthority?.equals( + deriveMintAuthorityKey(TOKEN_BRIDGE_ADDRESS) + ) + ).is.true; + expect(mintInfo.supply).to.equal(0n); + + // check wrapped meta + const wrappedMeta = await getWrappedMeta( + connection, + TOKEN_BRIDGE_ADDRESS, + mint + ); + expect(wrappedMeta.chain).to.equal(ethereumTokenBridge.chain); + expect( + Buffer.compare(wrappedMeta.tokenAddress, expectedTokenAddress) + ).to.equal(0); + expect(wrappedMeta.originalDecimals).to.equal(decimals); + + // check metadata + const metadata = await Metadata.fromAccountAddress( + connection, + deriveTokenMetadataKey(mint) + ); + expect(metadata.data.name.toString()).not.equals(oldName); + + expect(metadata.data.symbol.toString()).equals(symbol.padEnd(10, "\0")); + expect(metadata.data.name.toString()).equals( + `${name} (Wormhole)`.padEnd(32, "\0") + ); + }); + + it("Update (Create) Wrapped with New Metadata for V1 Metadata Account", async () => { + const tokenAddress = DEADBEEF_ADDRESS; + const oldExpectedName = "Dead Beef (Wormhole)".padEnd(32, "\0"); + + // fetch previously created metadata account + // check wrapped mint + { + const mint = deriveWrappedMintKey( + TOKEN_BRIDGE_ADDRESS, + ethereumTokenBridge.chain, + tokenAddress + ); + expect(mint.toString()).equals(DEADBEEF_MINT_ADDRESS); + + const metadataKey = deriveTokenMetadataKey(mint); + expect(metadataKey.toString()).equals(DEADBEEF_METADATA_ADDRESS); + + const metadata = await Metadata.fromAccountAddress( + connection, + metadataKey + ); + expect(metadata.data.name.toString()).equals(oldExpectedName); + } + + const decimals = 18; + const symbol = "BEEF"; + const name = "Dead Beef Modified"; + const nonce = 420; + const message = ethereumTokenBridge.publishAttestMeta( + tokenAddress, + decimals, + symbol, + name, + nonce + ); + const signedVaa = guardians.addSignatures( + message, + [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] + ); + + const txSignatures = await postVaa( + connection, + wallet.signTransaction, + CORE_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ).then((results) => results.map((result) => result.signature)); + const postTx = txSignatures.pop()!; + for (const verifyTx of txSignatures) { + // console.log(`verifySignatures: ${verifyTx}`); + } + // console.log(`postVaa: ${postTx}`); + + const createWrappedIx = createCreateWrappedInstruction( + TOKEN_BRIDGE_ADDRESS, + CORE_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ); + + const createWrappedTx = await web3.sendAndConfirmTransaction( + connection, + new web3.Transaction().add(createWrappedIx), + [wallet.signer()] + ); + // console.log(`createWrappedTx: ${createWrappedTx}`); + + // verify data + const parsed = parseAttestMetaVaa(signedVaa); + const messageData = await getPostedVaa( + connection, + CORE_BRIDGE_ADDRESS, + parsed.hash + ).then((posted) => posted.message); + + expect(messageData.consistencyLevel).to.equal( + ethereumTokenBridge.consistencyLevel + ); + const expectedEmitter = ethAddressToBuffer( + ETHEREUM_TOKEN_BRIDGE_ADDRESS + ); + expect( + Buffer.compare(messageData.emitterAddress, expectedEmitter) + ).to.equal(0); + expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); + expect(messageData.nonce).to.equal(nonce); + expect(messageData.sequence).to.equal(4n); + expect(messageData.vaaTime).to.equal(0); + expect(messageData.vaaVersion).to.equal(1); + expect(Buffer.compare(parsed.payload, messageData.payload)).to.equal(0); + + const assetMeta = parseAttestMetaPayload(messageData.payload); + expect(assetMeta.payloadType).to.equal(2); + const expectedTokenAddress = ethAddressToBuffer(tokenAddress); + expect( + Buffer.compare(assetMeta.tokenAddress, expectedTokenAddress) + ).to.equal(0); + expect(assetMeta.tokenChain).to.equal(ethereumTokenBridge.chain); + expect(assetMeta.decimals).to.equal(decimals); + expect(assetMeta.symbol).to.equal(symbol); + expect(assetMeta.name).to.equal(name); + + // check wrapped mint + const mint = deriveWrappedMintKey( + TOKEN_BRIDGE_ADDRESS, + assetMeta.tokenChain, + assetMeta.tokenAddress + ); + const mintInfo = await getMint(connection, mint); + expect(mintInfo.decimals).to.equal(8); + expect(mintInfo.mintAuthority).is.not.null; + expect( + mintInfo.mintAuthority?.equals( + deriveMintAuthorityKey(TOKEN_BRIDGE_ADDRESS) + ) + ).is.true; + expect(mintInfo.supply).to.equal(0n); + + // check metadata + const metadata = await Metadata.fromAccountAddress( + connection, + deriveTokenMetadataKey(mint) + ); + expect(metadata.data.name.toString()).not.equals(oldExpectedName); + expect(metadata.data.name.toString()).equals( + `${name} (Wormhole)`.padEnd(32, "\0") + ); }); it("Receive Token", async () => { @@ -1653,7 +1931,7 @@ describe("Token Bridge", () => { ).to.equal(0); expect(messageData.emitterChain).to.equal(ethereumTokenBridge.chain); expect(messageData.nonce).to.equal(nonce); - expect(messageData.sequence).to.equal(3n); + expect(messageData.sequence).to.equal(5n); expect(messageData.vaaTime).to.equal(0); expect(messageData.vaaVersion).to.equal(1); expect( @@ -1900,7 +2178,7 @@ describe("Token Bridge", () => { // nft bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS, - 3 // startSequence + 10 // startSequence ); describe("getOriginalAssetSolana", () => { diff --git a/testing/solana-test-validator/sdk-tests/3_nft_bridge.ts b/testing/solana-test-validator/sdk-tests/3_nft_bridge.ts index b1de7a142..857569fc8 100644 --- a/testing/solana-test-validator/sdk-tests/3_nft_bridge.ts +++ b/testing/solana-test-validator/sdk-tests/3_nft_bridge.ts @@ -1,5 +1,10 @@ import { expect } from "chai"; import * as web3 from "@solana/web3.js"; +import { + Metadata, + PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID, + createCreateMetadataAccountV3Instruction, +} from "@metaplex-foundation/mpl-token-metadata"; import { ASSOCIATED_TOKEN_PROGRAM_ID, createMint, @@ -19,9 +24,8 @@ import { import { postVaa } from "../../../sdk/js/src/solana/sendAndConfirmPostVaa"; import { BpfLoaderUpgradeable, - getMetadata, NodeWallet, - SplTokenMetadataProgram, + deriveTokenMetadataKey, } from "../../../sdk/js/src/solana"; import { deriveWormholeEmitterKey, @@ -115,26 +119,37 @@ describe("NFT Bridge", () => { uri: "https://spl.solana.com/token#example-create-a-non-fungible-token", }; - const mint = localVariables.mint; - const name = localVariables.nftMeta.name; - const symbol = localVariables.nftMeta.symbol; - const updateAuthorityIsSigner = false; - const uri = localVariables.nftMeta.uri; - const creators = null; - const sellerFeeBasisPoints = 0; - const isMutable = false; - const createMetadataIx = SplTokenMetadataProgram.createMetadataAccounts( - wallet.key(), + const mint: web3.PublicKey = localVariables.mint; + const name: string = localVariables.nftMeta.name; + const symbol: string = localVariables.nftMeta.symbol; + const uri: string = localVariables.nftMeta.uri; + + const accounts = { + metadata: deriveTokenMetadataKey(mint), mint, - wallet.key(), - name, - symbol, - wallet.key(), - updateAuthorityIsSigner, - uri, - creators, - sellerFeeBasisPoints, - isMutable + mintAuthority: wallet.key(), + payer: wallet.key(), + updateAuthority: wallet.key(), + }; + const args = { + createMetadataAccountArgsV3: { + data: { + name, + symbol, + uri, + sellerFeeBasisPoints: 0, + creators: null, + collection: null, + uses: null, + }, + isMutable: false, + collectionDetails: null, + }, + }; + const createMetadataIx = createCreateMetadataAccountV3Instruction( + accounts, + args, + TOKEN_METADATA_PROGRAM_ID ); const createMetadataTx = await web3.sendAndConfirmTransaction( @@ -323,9 +338,8 @@ describe("NFT Bridge", () => { expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; - expect( - accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) - ).is.true; + expect(accounts.splMetadataProgram.equals(TOKEN_METADATA_PROGRAM_ID)).is + .true; expect( accounts.associatedTokenProgram.equals(ASSOCIATED_TOKEN_PROGRAM_ID) ).is.true; @@ -401,9 +415,8 @@ describe("NFT Bridge", () => { expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; - expect( - accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) - ).is.true; + expect(accounts.splMetadataProgram.equals(TOKEN_METADATA_PROGRAM_ID)).is + .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); @@ -467,9 +480,8 @@ describe("NFT Bridge", () => { expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; - expect( - accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) - ).is.true; + expect(accounts.splMetadataProgram.equals(TOKEN_METADATA_PROGRAM_ID)).is + .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); @@ -524,9 +536,8 @@ describe("NFT Bridge", () => { expect(accounts.systemProgram.equals(web3.SystemProgram.programId)).to.be .true; expect(accounts.tokenProgram.equals(TOKEN_PROGRAM_ID)).is.true; - expect( - accounts.splMetadataProgram.equals(SplTokenMetadataProgram.programId) - ).is.true; + expect(accounts.splMetadataProgram.equals(TOKEN_METADATA_PROGRAM_ID)).is + .true; expect(accounts.wormholeProgram.equals(CORE_BRIDGE_ADDRESS)).is.true; }); @@ -792,8 +803,9 @@ describe("NFT Bridge", () => { custodyAccount ).then((account) => account.amount); - const metadata = await getMetadata(connection, mint).then( - (info) => info.data + const metadata = await Metadata.fromAccountAddress( + connection, + deriveTokenMetadataKey(mint) ); const tokenChain = 1; @@ -803,10 +815,10 @@ describe("NFT Bridge", () => { const message = ethereumNftBridge.publishTransferNft( NFT_TRANSFER_NATIVE_TOKEN_ADDRESS.toString("hex"), tokenChain, - metadata.name, - metadata.symbol, + metadata.data.name, + metadata.data.symbol, tokenId, - metadata.uri, + metadata.data.uri, recipientChain, mintAta.toBuffer().toString("hex"), nonce diff --git a/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_metadata.json b/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_metadata.json new file mode 100644 index 000000000..eaa6b6466 --- /dev/null +++ b/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_metadata.json @@ -0,0 +1,13 @@ +{ + "pubkey": "A2mcN1tknMBAgMnCUF6ZT8tEJj1QE1AuCsquvXHgpWVJ", + "account": { + "lamports": 5616720, + "data": [ + "BP0KziEuVYQFCsdn5PTCokaUsJwqy3LxSbhyCV219Z2d8nzo8j6OCMEC0yd3q42xDE00sIuZJtJx185IZhkbzwkgAAAARGVhZCBCZWVmIChXb3JtaG9sZSkAAAAAAAAAAAAAAAAKAAAAQkVFRgAAAAAAAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "base64" + ], + "owner": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "executable": false, + "rentEpoch": 0 + } +} \ No newline at end of file diff --git a/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_mint.json b/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_mint.json new file mode 100644 index 000000000..20cc4ea97 --- /dev/null +++ b/testing/solana-test-validator/sdk-tests/accounts/wrapped_deadbeef_mint.json @@ -0,0 +1,13 @@ +{ + "pubkey": "HKa8mGMJJm2qnja4xmMaoeGqF4LBpGCyXVZuaiGYFaYY", + "account": { + "lamports": 1461600, + "data": [ + "AQAAAP0KziEuVYQFCsdn5PTCokaUsJwqy3LxSbhyCV219Z2dAAAAAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "base64" + ], + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "rentEpoch": 0 + } +} \ No newline at end of file diff --git a/testing/solana-test-validator/sdk-tests/helpers/consts.ts b/testing/solana-test-validator/sdk-tests/helpers/consts.ts index 7c02d92cc..580bee963 100644 --- a/testing/solana-test-validator/sdk-tests/helpers/consts.ts +++ b/testing/solana-test-validator/sdk-tests/helpers/consts.ts @@ -50,3 +50,10 @@ export const ETHEREUM_TOKEN_BRIDGE_ADDRESS = export const WETH_ADDRESS = "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"; export const ETHEREUM_NFT_BRIDGE_ADDRESS = "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"; + +// preloaded +export const DEADBEEF_ADDRESS = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; +export const DEADBEEF_MINT_ADDRESS = + "HKa8mGMJJm2qnja4xmMaoeGqF4LBpGCyXVZuaiGYFaYY"; +export const DEADBEEF_METADATA_ADDRESS = + "A2mcN1tknMBAgMnCUF6ZT8tEJj1QE1AuCsquvXHgpWVJ"; diff --git a/testing/solana-test-validator/yarn.lock b/testing/solana-test-validator/yarn.lock index 00c527f82..b2edd83df 100644 --- a/testing/solana-test-validator/yarn.lock +++ b/testing/solana-test-validator/yarn.lock @@ -55,11 +55,60 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@metaplex-foundation/beet-solana@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet-solana/-/beet-solana-0.4.0.tgz#52891e78674aaa54e0031f1bca5bfbc40de12e8d" + integrity sha512-B1L94N3ZGMo53b0uOSoznbuM5GBNJ8LwSeznxBxJ+OThvfHQ4B5oMUqb+0zdLRfkKGS7Q6tpHK9P+QK0j3w2cQ== + dependencies: + "@metaplex-foundation/beet" ">=0.1.0" + "@solana/web3.js" "^1.56.2" + bs58 "^5.0.0" + debug "^4.3.4" + +"@metaplex-foundation/beet@>=0.1.0", "@metaplex-foundation/beet@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.7.1.tgz#0975314211643f87b5f6f3e584fa31abcf4c612c" + integrity sha512-hNCEnS2WyCiYyko82rwuISsBY3KYpe828ubsd2ckeqZr7tl0WVLivGkoyA/qdiaaHEBGdGl71OpfWa2rqL3DiA== + dependencies: + ansicolors "^0.3.2" + bn.js "^5.2.0" + debug "^4.3.3" + +"@metaplex-foundation/cusper@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/cusper/-/cusper-0.0.2.tgz#dc2032a452d6c269e25f016aa4dd63600e2af975" + integrity sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA== + +"@metaplex-foundation/mpl-token-metadata@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-2.11.1.tgz#b7755c5cc7bae5e98e285dd675fa65b62e9bd881" + integrity sha512-FNJhDAFmpXD5K9lstJYXROjUjHQmCHFpzVs4asUpVvtkF645+PGyDtqoUENfVEwoUPY8ZT6Exs7d0exRgYqxUA== + dependencies: + "@metaplex-foundation/beet" "^0.7.1" + "@metaplex-foundation/beet-solana" "^0.4.0" + "@metaplex-foundation/cusper" "^0.0.2" + "@solana/spl-token" "^0.3.6" + "@solana/web3.js" "^1.66.2" + bn.js "^5.2.0" + debug "^4.3.4" + +"@noble/curves@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" + integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== + dependencies: + "@noble/hashes" "1.3.0" + "@noble/ed25519@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.0.tgz#583ac38340a479314b9e348d4572101ed9492f9d" integrity sha512-LeAxFK0+181zQOhOUuKE8Jnd3duzYhDNd3iCLxpmzA5K+e4I1FdbrK3Ot0ZHBwZMeRD/6EojyUfTbpHZ+hkQHg== +"@noble/hashes@1.3.0", "@noble/hashes@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + "@noble/hashes@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" @@ -125,6 +174,15 @@ "@solana/buffer-layout-utils" "^0.2.0" "@solana/web3.js" "^1.41.0" +"@solana/spl-token@^0.3.6": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.7.tgz#6f027f9ad8e841f792c32e50920d9d2e714fc8da" + integrity sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + buffer "^6.0.3" + "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.41.0", "@solana/web3.js@^1.53.0": version "1.53.0" resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.53.0.tgz" @@ -170,6 +228,27 @@ rpc-websockets "^7.5.0" superstruct "^0.14.2" +"@solana/web3.js@^1.56.2", "@solana/web3.js@^1.66.2": + version "1.76.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.76.0.tgz#0f888e25d727d0dadf3dd8a01967347555200b2b" + integrity sha512-aJtF/nTs+9St+KtTK/wgVJ+SinfjYzn+3w1ygYIPw8ST6LH+qHBn8XkodgDTwlv/xzNkaVz1kkUDOZ8BPXyZWA== + dependencies: + "@babel/runtime" "^7.12.5" + "@noble/curves" "^1.0.0" + "@noble/hashes" "^1.3.0" + "@solana/buffer-layout" "^4.0.0" + agentkeepalive "^4.2.1" + bigint-buffer "^1.1.5" + bn.js "^5.0.0" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^3.4.4" + node-fetch "^2.6.7" + rpc-websockets "^7.5.1" + superstruct "^0.14.2" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" @@ -283,6 +362,15 @@ acorn@^8.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + dependencies: + debug "^4.1.0" + depd "^2.0.0" + humanize-ms "^1.2.1" + ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" @@ -300,6 +388,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansicolors@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" @@ -340,6 +433,11 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -420,6 +518,13 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + buffer-from@^1.0.0, buffer-from@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -438,6 +543,14 @@ buffer@6.0.1: base64-js "^1.3.1" ieee754 "^1.2.1" +buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.4.3: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -446,14 +559,6 @@ buffer@^5.4.3: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3, buffer@~6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.6" resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz" @@ -577,6 +682,13 @@ debug@4.3.3: dependencies: ms "2.1.2" +debug@^4.1.0, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" @@ -594,6 +706,11 @@ delay@^5.0.0: resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== +depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + diff@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" @@ -768,6 +885,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -1018,7 +1142,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.0.0: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -1048,6 +1172,13 @@ node-fetch@2, node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.7: + version "2.6.11" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" + integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: version "4.5.0" resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz" @@ -1167,6 +1298,19 @@ rpc-websockets@^7.5.0: bufferutil "^4.0.1" utf-8-validate "^5.0.2" +rpc-websockets@^7.5.1: + version "7.5.1" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" + integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w== + dependencies: + "@babel/runtime" "^7.17.2" + eventemitter3 "^4.0.7" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"