solana: fix token metadata program interaction (#2913)
* testing: fix pubkey caused by 0b0b9cea70
* solana: fix token-metadata forked dependency
* sdk/js: fix tokenMetadata and Solana IDLs
* testing: fix tests; add token-metadata dependency
---------
Co-authored-by: A5 Pickle <a5-pickle@users.noreply.github.com>
This commit is contained in:
parent
3a28b6169a
commit
6f8c8430ac
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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<Metadata> {
|
||||
return connection
|
||||
.getAccountInfo(deriveSplTokenMetadataKey(mint), commitment)
|
||||
.then((info) => Metadata.deserialize(getAccountData(info)));
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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 /
|
||||
|
|
Binary file not shown.
|
@ -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" }
|
||||
|
|
|
@ -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<Info<'b>, "authority_signer">;
|
||||
pub type CustodySigner<'b> = Derive<Info<'b>, "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<spl_token_metadata::state::Metadata> {
|
||||
// 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()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -59,9 +59,10 @@ pub enum TokenBridgeError {
|
|||
TokenNotNative,
|
||||
UninitializedMint,
|
||||
WrongAccountOwner,
|
||||
TokenNotNFT,
|
||||
NonexistentTokenMetadataAccount,
|
||||
InvalidAssociatedAccount,
|
||||
InvalidRecipient,
|
||||
NotMetadataV1Account,
|
||||
}
|
||||
|
||||
impl From<TokenBridgeError> for SolitaireError {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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<Info<'b>, "authority_signer">;
|
||||
pub type CustodySigner<'b> = Derive<Info<'b>, "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<spl_token_metadata::state::Metadata> {
|
||||
// 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()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -89,6 +89,8 @@ pub enum TokenBridgeError {
|
|||
InvalidFee,
|
||||
InvalidRecipient,
|
||||
InvalidVAA,
|
||||
NonexistentTokenMetadataAccount,
|
||||
NotMetadataV1Account,
|
||||
}
|
||||
|
||||
impl From<TokenBridgeError> for SolitaireError {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "spl-token-metadata"
|
||||
version = "0.0.1"
|
||||
description = "Metaplex Metadata"
|
||||
authors = ["Metaplex Maintainers <maintainers@metaplex.com>"]
|
||||
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"] }
|
|
@ -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.
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
Binary file not shown.
|
@ -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<Data>,
|
||||
pub update_authority: Option<Pubkey>,
|
||||
pub primary_sale_happened: Option<bool>,
|
||||
}
|
||||
|
||||
#[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<u64>,
|
||||
}
|
||||
|
||||
#[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<Vec<Creator>>,
|
||||
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<Pubkey>,
|
||||
data: Option<Data>,
|
||||
primary_sale_happened: Option<bool>,
|
||||
) -> 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(),
|
||||
}
|
||||
}
|
|
@ -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");
|
|
@ -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<Vec<Creator>>,
|
||||
}
|
||||
|
||||
#[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<Metadata> {
|
||||
try_from_slice_checked(a, Key::MetadataV1, MAX_METADATA_LEN)
|
||||
}
|
||||
|
||||
pub fn from_account_info(a: &AccountInfo) -> Option<Metadata> {
|
||||
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,
|
||||
}
|
|
@ -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<T: BorshDeserialize>(
|
||||
data: &[u8],
|
||||
data_type: Key,
|
||||
data_size: usize,
|
||||
) -> Option<T> {
|
||||
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()
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
.test
|
||||
artifacts
|
||||
artifacts*
|
||||
node_modules
|
||||
wormhole-main
|
||||
validator.log
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"pubkey": "A2mcN1tknMBAgMnCUF6ZT8tEJj1QE1AuCsquvXHgpWVJ",
|
||||
"account": {
|
||||
"lamports": 5616720,
|
||||
"data": [
|
||||
"BP0KziEuVYQFCsdn5PTCokaUsJwqy3LxSbhyCV219Z2d8nzo8j6OCMEC0yd3q42xDE00sIuZJtJx185IZhkbzwkgAAAARGVhZCBCZWVmIChXb3JtaG9sZSkAAAAAAAAAAAAAAAAKAAAAQkVFRgAAAAAAAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"base64"
|
||||
],
|
||||
"owner": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
|
||||
"executable": false,
|
||||
"rentEpoch": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"pubkey": "HKa8mGMJJm2qnja4xmMaoeGqF4LBpGCyXVZuaiGYFaYY",
|
||||
"account": {
|
||||
"lamports": 1461600,
|
||||
"data": [
|
||||
"AQAAAP0KziEuVYQFCsdn5PTCokaUsJwqy3LxSbhyCV219Z2dAAAAAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"base64"
|
||||
],
|
||||
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
||||
"executable": false,
|
||||
"rentEpoch": 0
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue