diff --git a/.github/workflows/publish_npm.yml b/.github/workflows/publish_npm.yml index 63b2c57..6d0460a 100644 --- a/.github/workflows/publish_npm.yml +++ b/.github/workflows/publish_npm.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: "16.x" + node-version: "20.x" registry-url: "https://registry.npmjs.org" - env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8105479..1d6a423 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,6 @@ jobs: with: anchor-version: '0.29.0' solana-cli-version: '1.17.6' - node-version: '16.15.1' + node-version: '20.9.0' features: 'anchor-test' - run: cargo fmt -- --check && cargo clippy diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5996a39..3c8cff2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1467,7 +1467,6 @@ packages: - encoding - fastestsmallesttextencoderdecoder - utf-8-validate - dev: false /@solana/spl-type-length-value@0.1.0: resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} @@ -4996,7 +4995,8 @@ packages: '@metaplex-foundation/mpl-token-auth-rules': 1.2.0(fastestsmallesttextencoderdecoder@1.0.22) '@metaplex-foundation/mpl-token-metadata': 3.1.2(@metaplex-foundation/umi@0.8.10) '@project-serum/anchor': 0.26.0 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.89.1)(fastestsmallesttextencoderdecoder@1.0.22) + '@solana/spl-token': 0.4.1(@solana/web3.js@1.89.1)(fastestsmallesttextencoderdecoder@1.0.22) + '@solana/spl-token-metadata': 0.1.2(@solana/web3.js@1.89.1)(fastestsmallesttextencoderdecoder@1.0.22) '@solana/web3.js': 1.89.1 old-mpl-token-metadata: /@metaplex-foundation/mpl-token-metadata@2.12.0(fastestsmallesttextencoderdecoder@1.0.22) transitivePeerDependencies: diff --git a/sdk/package.json b/sdk/package.json index d7c0f45..bda8dfa 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -20,7 +20,8 @@ "@metaplex-foundation/mpl-token-metadata": "^3.1.2", "old-mpl-token-metadata": "npm:@metaplex-foundation/mpl-token-metadata@2.12.0", "@project-serum/anchor": "^0.26.0", - "@solana/spl-token": "^0.3.5", + "@solana/spl-token": "^0.4.1", + "@solana/spl-token-metadata": "^0.1.2", "@solana/web3.js": "^1.65.0" }, "devDependencies": { diff --git a/sdk/src/metadataProvider.ts b/sdk/src/metadataProvider.ts index d41a095..e8e2ccd 100644 --- a/sdk/src/metadataProvider.ts +++ b/sdk/src/metadataProvider.ts @@ -5,7 +5,15 @@ import { import { Metaplex } from '@metaplex-foundation/js'; import { Creator, Metadata, TokenStandard } from 'old-mpl-token-metadata'; import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'; -import { Mint, unpackMint } from '@solana/spl-token'; +import { + ExtensionType, + getExtensionData, + getMetadataPointerState, + Mint, + TOKEN_2022_PROGRAM_ID, + unpackMint, +} from '@solana/spl-token'; +import { TokenMetadata, unpack } from '@solana/spl-token-metadata'; export class MetadataProviderError extends Error { name = 'MetadataProviderError'; @@ -30,16 +38,17 @@ export interface MetadataProvider { get mintAddress(): PublicKey; get tokenProgram(): PublicKey; get sellerFeeBasisPoints(): number; + get splTokenMetadata(): TokenMetadata | undefined; } export class RpcMetadataProvider implements MetadataProvider { constructor( private readonly mint: PublicKey, - public readonly metadataAccount: Metadata, + public readonly metadataAccount: Metadata | undefined, public readonly mintAccount: Mint & { tokenProgramId: PublicKey }, public readonly mintStateAccount: MintStateWithAddress | undefined, ) { - if (!mint.equals(metadataAccount.mint)) { + if (!!metadataAccount && !mint.equals(metadataAccount.mint)) { throw new MetadataProviderError('mint and metadata mismatch'); } } @@ -58,15 +67,19 @@ export class RpcMetadataProvider implements MetadataProvider { mint, ]); - if (!metadataAi) { - throw new MetadataProviderError('metadata not found'); - } if (!mintAi) { throw new MetadataProviderError('mint not found'); } + const mintParsed = unpackMint(mint, mintAi, mintAi.owner); + if ( + !metadataAi && + !getMetadataPointerState(mintParsed)?.metadataAddress?.equals(mint) + ) { + throw new MetadataProviderError('metadata not found'); + } return RpcMetadataProvider.loadFromAccountInfos(mint, mintStateAddress, { - metadata: metadataAi, mint: mintAi, + metadata: metadataAi, mintState: mintStateAi, }); } @@ -76,13 +89,15 @@ export class RpcMetadataProvider implements MetadataProvider { mintStateAddress: PublicKey, accounts: { mint: AccountInfo; - metadata: AccountInfo; + metadata: AccountInfo | null; mintState: AccountInfo | null; }, ): RpcMetadataProvider { return new RpcMetadataProvider( mint, - Metadata.fromAccountInfo(accounts.metadata)[0], + accounts.metadata + ? Metadata.fromAccountInfo(accounts.metadata)[0] + : undefined, { ...unpackMint(mint, accounts.mint, accounts.mint.owner), tokenProgramId: accounts.mint.owner, @@ -92,15 +107,15 @@ export class RpcMetadataProvider implements MetadataProvider { } get creators(): Creator[] { - return this.metadataAccount.data.creators ?? []; + return this.metadataAccount?.data.creators ?? []; } get tokenStandard(): TokenStandard | undefined { - return this.metadataAccount.tokenStandard ?? undefined; + return this.metadataAccount?.tokenStandard ?? undefined; } get ruleset(): PublicKey | undefined { - return this.metadataAccount.programmableConfig?.ruleSet ?? undefined; + return this.metadataAccount?.programmableConfig?.ruleSet ?? undefined; } get mintState(): MintStateWithAddress | undefined { @@ -116,7 +131,26 @@ export class RpcMetadataProvider implements MetadataProvider { } get sellerFeeBasisPoints(): number { - return this.metadataAccount.data.sellerFeeBasisPoints; + return this.metadataAccount?.data.sellerFeeBasisPoints ?? 0; + } + + get splTokenMetadata(): TokenMetadata | undefined { + if ( + !getMetadataPointerState(this.mintAccount)?.metadataAddress?.equals( + this.mint, + ) + ) { + return undefined; + } + + const data = getExtensionData( + ExtensionType.TokenMetadata, + this.mintAccount.tlvData, + ); + if (data === null) { + return undefined; + } + return unpack(data); } } @@ -134,3 +168,12 @@ function parseMintState( return undefined; } } + +export function doesTokenExtensionExist( + mintContext: MetadataProvider, +): boolean { + return ( + mintContext.tokenProgram.equals(TOKEN_2022_PROGRAM_ID) && + !!mintContext.splTokenMetadata + ); +} diff --git a/sdk/src/mmmClient.ts b/sdk/src/mmmClient.ts index d09db10..1e89d40 100644 --- a/sdk/src/mmmClient.ts +++ b/sdk/src/mmmClient.ts @@ -34,6 +34,7 @@ import { getTokenRecordPDA, } from './pda'; import { + doesTokenExtensionExist, MetadataProvider, MintStateWithAddress, RpcMetadataProvider, @@ -287,9 +288,29 @@ export class MMMClient { let builder: | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; - if (ocpMintState) { + if (doesTokenExtensionExist(mintContext)) { + builder = this.program.methods.solExtFulfillBuy(args).accountsStrict({ + payer, + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + referral: this.poolData.referral, + pool: this.poolData.pool, + buysideSolEscrowAccount, + assetMint, + payerAssetAccount: assetTokenAccount, + sellsideEscrowTokenAccount, + ownerTokenAccount, + allowlistAuxAccount: allowlistAuxAccount ?? SystemProgram.programId, + sellState, + systemProgram: SystemProgram.programId, + tokenProgram: mintContext.tokenProgram, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + rent: SYSVAR_RENT_PUBKEY, + }); + } else if (ocpMintState) { builder = this.program.methods.solOcpFulfillBuy(args).accountsStrict({ payer, owner: this.poolData.owner, @@ -438,9 +459,26 @@ export class MMMClient { let builder: | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; - if (ocpMintState) { + if (doesTokenExtensionExist(mintContext)) { + builder = this.program.methods.solExtFulfillSell(args).accountsStrict({ + payer, + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + referral: this.poolData.referral, + pool: this.poolData.pool, + buysideSolEscrowAccount, + assetMint, + sellsideEscrowTokenAccount, + payerAssetAccount, + sellState, + systemProgram: SystemProgram.programId, + tokenProgram: mintContext.tokenProgram, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }); + } else if (ocpMintState) { builder = this.program.methods .solOcpFulfillSell({ assetAmount: args.assetAmount, @@ -586,9 +624,23 @@ export class MMMClient { let builder: | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; - if (ocpMintState) { + if (doesTokenExtensionExist(mintContext)) { + builder = this.program.methods.extDepositSell(args).accountsStrict({ + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + pool: this.poolData.pool, + assetMint, + assetTokenAccount, + sellsideEscrowTokenAccount, + sellState, + systemProgram: SystemProgram.programId, + tokenProgram: mintContext.tokenProgram, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }); + } else if (ocpMintState) { builder = this.program.methods.ocpDepositSell(args).accountsStrict({ owner: this.poolData.owner, pool: this.poolData.pool, @@ -698,9 +750,25 @@ export class MMMClient { let builder: | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; - if (ocpMintState) { + if (doesTokenExtensionExist(mintContext)) { + builder = this.program.methods.extWithdrawSell(args).accountsStrict({ + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + pool: this.poolData.pool, + assetMint, + assetTokenAccount, + sellsideEscrowTokenAccount, + buysideSolEscrowAccount, + allowlistAuxAccount: allowlistAuxAccount ?? SystemProgram.programId, + sellState, + systemProgram: SystemProgram.programId, + tokenProgram: mintContext.tokenProgram, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }); + } else if (ocpMintState) { builder = this.program.methods.ocpWithdrawSell(args).accountsStrict({ owner: this.poolData.owner, pool: this.poolData.pool,