diff --git a/packages/common/package.json b/packages/common/package.json index 7eebd1b1..91c77d4c 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -31,7 +31,7 @@ "@solana/spl-token-swap": "0.1.0", "@solana/wallet-base": "0.0.1", "@solana/wallet-ledger": "0.0.1", - "@solana/web3.js": "^1.22.0", + "@solana/web3.js": "^1.29.2" "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", @@ -42,7 +42,7 @@ "antd": "^4.6.6", "bignumber.js": "^9.0.1", "bn.js": "^5.1.3", - "borsh": "^0.3.1", + "borsh": "^0.4.0", "bs58": "^4.0.1", "buffer-layout": "^1.2.0", "eventemitter3": "^4.0.7", diff --git a/packages/common/src/actions/account.ts b/packages/common/src/actions/account.ts index aac8577f..c94e65e4 100644 --- a/packages/common/src/actions/account.ts +++ b/packages/common/src/actions/account.ts @@ -1,6 +1,6 @@ import { AccountLayout, MintLayout, Token } from '@solana/spl-token'; import { - Account, + Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, @@ -11,6 +11,7 @@ import { TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT, } from '../utils/ids'; +import { programIds } from '../utils/programIds'; import { TokenAccount } from '../models/account'; import { cache, TokenAccountParser } from '../contexts/accounts'; @@ -20,7 +21,7 @@ export function ensureSplAccount( toCheck: TokenAccount, payer: PublicKey, amount: number, - signers: Account[], + signers: Keypair[], ) { if (!toCheck.info.isNative) { return toCheck.pubkey; @@ -60,11 +61,11 @@ export const DEFAULT_TEMP_MEM_SPACE = 65548; export function createTempMemoryAccount( instructions: TransactionInstruction[], payer: PublicKey, - signers: Account[], + signers: Keypair[], owner: PublicKey, space = DEFAULT_TEMP_MEM_SPACE, ) { - const account = new Account(); + const account = Keypair.generate(); instructions.push( SystemProgram.createAccount({ fromPubkey: payer, @@ -85,9 +86,9 @@ export function createUninitializedMint( instructions: TransactionInstruction[], payer: PublicKey, amount: number, - signers: Account[], + signers: Keypair[], ) { - const account = new Account(); + const account = Keypair.generate(); instructions.push( SystemProgram.createAccount({ fromPubkey: payer, @@ -107,9 +108,9 @@ export function createUninitializedAccount( instructions: TransactionInstruction[], payer: PublicKey, amount: number, - signers: Account[], + signers: Keypair[], ) { - const account = new Account(); + const account = Keypair.generate(); instructions.push( SystemProgram.createAccount({ fromPubkey: payer, @@ -185,7 +186,7 @@ export function createMint( decimals: number, owner: PublicKey, freezeAuthority: PublicKey, - signers: Account[], + signers: Keypair[], ) { const account = createUninitializedMint( instructions, @@ -213,7 +214,7 @@ export function createTokenAccount( accountRentExempt: number, mint: PublicKey, owner: PublicKey, - signers: Account[], + signers: Keypair[], ) { const account = createUninitializedAccount( instructions, @@ -229,6 +230,54 @@ export function createTokenAccount( return account; } +export function ensureWrappedAccount( + instructions: TransactionInstruction[], + cleanupInstructions: TransactionInstruction[], + toCheck: TokenAccount | undefined, + payer: PublicKey, + amount: number, + signers: Keypair[], +) { + if (toCheck && !toCheck.info.isNative) { + return toCheck.pubkey; + } + + const TOKEN_PROGRAM_ID = programIds().token; + const account = Keypair.generate(); + instructions.push( + SystemProgram.createAccount({ + fromPubkey: payer, + newAccountPubkey: account.publicKey, + lamports: amount, + space: AccountLayout.span, + programId: TOKEN_PROGRAM_ID, + }), + ); + + instructions.push( + Token.createInitAccountInstruction( + TOKEN_PROGRAM_ID, + WRAPPED_SOL_MINT, + account.publicKey, + payer, + ), + ); + + cleanupInstructions.push( + Token.createCloseAccountInstruction( + TOKEN_PROGRAM_ID, + account.publicKey, + payer, + payer, + [], + ), + ); + + signers.push(account); + + return account.publicKey.toBase58(); +} + // TODO: check if one of to accounts needs to be native sol ... if yes unwrap it ... export function findOrCreateAccountByMint( payer: PublicKey, @@ -237,10 +286,11 @@ export function findOrCreateAccountByMint( cleanupInstructions: TransactionInstruction[], accountRentExempt: number, mint: PublicKey, // use to identify same type - signers: Account[], + signers: Keypair[], excluded?: Set, ): PublicKey { const accountToFind = mint.toBase58(); + const ownerKey = owner.toBase58(); const account = cache .byParser(TokenAccountParser) .map(id => cache.get(id)) @@ -248,8 +298,8 @@ export function findOrCreateAccountByMint( acc => acc !== undefined && acc.info.mint.toBase58() === accountToFind && - acc.info.owner.toBase58() === owner.toBase58() && - (excluded === undefined || !excluded.has(acc.pubkey.toBase58())), + acc.info.owner.toBase58() === ownerKey && + (excluded === undefined || !excluded.has(acc.pubkey.toString())), ); const isWrappedSol = accountToFind === WRAPPED_SOL_MINT.toBase58(); diff --git a/packages/common/src/actions/auction.ts b/packages/common/src/actions/auction.ts index c972b076..a33eff07 100644 --- a/packages/common/src/actions/auction.ts +++ b/packages/common/src/actions/auction.ts @@ -6,14 +6,16 @@ import { SYSVAR_RENT_PUBKEY, TransactionInstruction, } from '@solana/web3.js'; -import { programIds } from '../utils/ids'; -import { deserializeBorsh } from './../utils/borsh'; -import { serialize } from 'borsh'; +import { programIds } from '../utils/programIds'; +import { deserializeUnchecked, serialize } from 'borsh'; import BN from 'bn.js'; -import { AccountParser, cache } from '../contexts'; - +import { AccountParser } from '../contexts'; +import moment from 'moment'; +import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils'; export const AUCTION_PREFIX = 'auction'; export const METADATA = 'metadata'; +export const EXTENDED = 'extended'; +export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200; export enum AuctionState { Created = 0, @@ -27,9 +29,9 @@ export enum BidStateType { } export class Bid { - key: PublicKey; + key: StringPublicKey; amount: BN; - constructor(args: { key: PublicKey; amount: BN }) { + constructor(args: { key: StringPublicKey; amount: BN }) { this.key = args.key; this.amount = args.amount; } @@ -40,18 +42,35 @@ export class BidState { bids: Bid[]; max: BN; - public getWinnerIndex(bidder: PublicKey): number | null { + public getWinnerAt(winnerIndex: number): StringPublicKey | null { + const convertedIndex = this.bids.length - winnerIndex - 1; + + if (convertedIndex >= 0 && convertedIndex < this.bids.length) { + return this.bids[convertedIndex].key; + } else { + return null; + } + } + + public getAmountAt(winnerIndex: number): BN | null { + const convertedIndex = this.bids.length - winnerIndex - 1; + + if (convertedIndex >= 0 && convertedIndex < this.bids.length) { + return this.bids[convertedIndex].amount; + } else { + return null; + } + } + + public getWinnerIndex(bidder: StringPublicKey): number | null { if (!this.bids) return null; - console.log( - 'bids', - this.bids.map(b => b.key.toBase58()), - bidder.toBase58(), - ); - const index = this.bids.findIndex( - b => b.key.toBase58() == bidder.toBase58(), - ); - if (index != -1) return index; - else return null; + + const index = this.bids.findIndex(b => b.key === bidder); + // auction stores data in reverse order + if (index !== -1) { + const zeroBased = this.bids.length - index - 1; + return zeroBased < this.max.toNumber() ? zeroBased : null; + } else return null; } constructor(args: { type: BidStateType; bids: Bid[]; max: BN }) { @@ -71,7 +90,11 @@ export const AuctionParser: AccountParser = ( }); export const decodeAuction = (buffer: Buffer) => { - return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData; + return deserializeUnchecked( + AUCTION_SCHEMA, + AuctionData, + buffer, + ) as AuctionData; }; export const BidderPotParser: AccountParser = ( @@ -84,7 +107,24 @@ export const BidderPotParser: AccountParser = ( }); export const decodeBidderPot = (buffer: Buffer) => { - return deserializeBorsh(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot; + return deserializeUnchecked(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot; +}; + +export const AuctionDataExtendedParser: AccountParser = ( + pubkey: PublicKey, + account: AccountInfo, +) => ({ + pubkey, + account, + info: decodeAuctionDataExtended(account.data), +}); + +export const decodeAuctionDataExtended = (buffer: Buffer) => { + return deserializeUnchecked( + AUCTION_SCHEMA, + AuctionDataExtended, + buffer, + ) as AuctionDataExtended; }; export const BidderMetadataParser: AccountParser = ( @@ -97,22 +137,78 @@ export const BidderMetadataParser: AccountParser = ( }); export const decodeBidderMetadata = (buffer: Buffer) => { - return deserializeBorsh( + return deserializeUnchecked( AUCTION_SCHEMA, BidderMetadata, buffer, ) as BidderMetadata; }; -export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9; +export const BASE_AUCTION_DATA_SIZE = + 32 + 32 + 32 + 9 + 9 + 9 + 9 + 1 + 32 + 1 + 8 + 8; + +export enum PriceFloorType { + None = 0, + Minimum = 1, + BlindedPrice = 2, +} +export class PriceFloor { + type: PriceFloorType; + // It's an array of 32 u8s, when minimum, only first 8 are used (a u64), when blinded price, the entire + // thing is a hash and not actually a public key, and none is all zeroes + hash: Uint8Array; + + minPrice?: BN; + + constructor(args: { + type: PriceFloorType; + hash?: Uint8Array; + minPrice?: BN; + }) { + this.type = args.type; + this.hash = args.hash || new Uint8Array(32); + if (this.type === PriceFloorType.Minimum) { + if (args.minPrice) { + this.hash.set(args.minPrice.toArrayLike(Buffer, 'le', 8), 0); + } else { + this.minPrice = new BN( + (args.hash || new Uint8Array(0)).slice(0, 8), + 'le', + ); + } + } + } +} + +export class AuctionDataExtended { + /// Total uncancelled bids + totalUncancelledBids: BN; + tickSize: BN | null; + gapTickSizePercentage: number | null; + + constructor(args: { + totalUncancelledBids: BN; + tickSize: BN | null; + gapTickSizePercentage: number | null; + }) { + this.totalUncancelledBids = args.totalUncancelledBids; + this.tickSize = args.tickSize; + this.gapTickSizePercentage = args.gapTickSizePercentage; + } +} + +export interface CountdownState { + days: number; + hours: number; + minutes: number; + seconds: number; +} export class AuctionData { /// Pubkey of the authority with permission to modify this auction. - authority: PublicKey; - /// Pubkey of the resource being bid on. - resource: PublicKey; + authority: StringPublicKey; /// Token mint for the SPL token being used to bid - tokenMint: PublicKey; + tokenMint: StringPublicKey; /// The time the last bid was placed, used to keep track of auction timing. lastBid: BN | null; /// Slot time the auction was officially ended by. @@ -121,34 +217,78 @@ export class AuctionData { endAuctionAt: BN | null; /// Gap time is the amount of time in slots after the previous bid at which the auction ends. auctionGap: BN | null; + /// Minimum price for any bid to meet. + priceFloor: PriceFloor; /// The state the auction is in, whether it has started or ended. state: AuctionState; /// Auction Bids, each user may have one bid open at a time. bidState: BidState; - - /// Used for precalculation on the front end, not a backend key - auctionManagerKey?: PublicKey; /// Used for precalculation on the front end, not a backend key - bidRedemptionKey?: PublicKey; + bidRedemptionKey?: StringPublicKey; + + public timeToEnd(): CountdownState { + const now = moment().unix(); + const ended = { days: 0, hours: 0, minutes: 0, seconds: 0 }; + let endAt = this.endedAt?.toNumber() || 0; + + if (this.auctionGap && this.lastBid) { + endAt = Math.max( + endAt, + this.auctionGap.toNumber() + this.lastBid.toNumber(), + ); + } + + let delta = endAt - now; + + if (!endAt || delta <= 0) return ended; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const seconds = Math.floor(delta % 60); + + return { days, hours, minutes, seconds }; + } + + public ended() { + const now = moment().unix(); + if (!this.endedAt) return false; + + if (this.endedAt.toNumber() > now) return false; + + if (this.endedAt.toNumber() < now) { + if (this.auctionGap && this.lastBid) { + const newEnding = this.auctionGap.toNumber() + this.lastBid.toNumber(); + return newEnding < now; + } else return true; + } + } constructor(args: { - authority: PublicKey; - resource: PublicKey; - tokenMint: PublicKey; + authority: StringPublicKey; + tokenMint: StringPublicKey; lastBid: BN | null; endedAt: BN | null; endAuctionAt: BN | null; auctionGap: BN | null; + priceFloor: PriceFloor; state: AuctionState; bidState: BidState; + totalUncancelledBids: BN; }) { this.authority = args.authority; - this.resource = args.resource; this.tokenMint = args.tokenMint; this.lastBid = args.lastBid; this.endedAt = args.endedAt; this.endAuctionAt = args.endAuctionAt; this.auctionGap = args.auctionGap; + this.priceFloor = args.priceFloor; this.state = args.state; this.bidState = args.bidState; } @@ -157,9 +297,9 @@ export class AuctionData { export const BIDDER_METADATA_LEN = 32 + 32 + 8 + 8 + 1; export class BidderMetadata { // Relationship with the bidder who's metadata this covers. - bidderPubkey: PublicKey; + bidderPubkey: StringPublicKey; // Relationship with the auction this bid was placed on. - auctionPubkey: PublicKey; + auctionPubkey: StringPublicKey; // Amount that the user bid. lastBid: BN; // Tracks the last time this user bid. @@ -168,8 +308,8 @@ export class BidderMetadata { // user is a winner, as if cancelled it implies previous bids were also cancelled. cancelled: boolean; constructor(args: { - bidderPubkey: PublicKey; - auctionPubkey: PublicKey; + bidderPubkey: StringPublicKey; + auctionPubkey: StringPublicKey; lastBid: BN; lastBidTimestamp: BN; cancelled: boolean; @@ -182,20 +322,23 @@ export class BidderMetadata { } } -export const BIDDER_POT_LEN = 32 + 32 + 32; +export const BIDDER_POT_LEN = 32 + 32 + 32 + 1; export class BidderPot { /// Points at actual pot that is a token account - bidderPot: PublicKey; - bidderAct: PublicKey; - auctionAct: PublicKey; + bidderPot: StringPublicKey; + bidderAct: StringPublicKey; + auctionAct: StringPublicKey; + emptied: boolean; constructor(args: { - bidderPot: PublicKey; - bidderAct: PublicKey; - auctionAct: PublicKey; + bidderPot: StringPublicKey; + bidderAct: StringPublicKey; + auctionAct: StringPublicKey; + emptied: boolean; }) { this.bidderPot = args.bidderPot; this.bidderAct = args.bidderAct; this.auctionAct = args.auctionAct; + this.emptied = args.emptied; } } @@ -213,8 +356,7 @@ export class WinnerLimit { } } -class CreateAuctionArgs { - instruction: number = 0; +export interface IPartialCreateAuctionArgs { /// How many winners are allowed for this auction. See AuctionData. winners: WinnerLimit; /// End time is the cut-off point that the auction is forced to end by. See AuctionData. @@ -222,19 +364,46 @@ class CreateAuctionArgs { /// Gap time is how much time after the previous bid where the auction ends. See AuctionData. auctionGap: BN | null; /// Token mint for the SPL token used for bidding. - tokenMint: PublicKey; + tokenMint: StringPublicKey; + + priceFloor: PriceFloor; + + tickSize: BN | null; + + gapTickSizePercentage: number | null; +} + +export class CreateAuctionArgs implements IPartialCreateAuctionArgs { + instruction: number = 1; + /// How many winners are allowed for this auction. See AuctionData. + winners: WinnerLimit; + /// End time is the cut-off point that the auction is forced to end by. See AuctionData. + endAuctionAt: BN | null; + /// Gap time is how much time after the previous bid where the auction ends. See AuctionData. + auctionGap: BN | null; + /// Token mint for the SPL token used for bidding. + tokenMint: StringPublicKey; /// Authority - authority: PublicKey; + authority: StringPublicKey; /// The resource being auctioned. See AuctionData. - resource: PublicKey; + resource: StringPublicKey; + + priceFloor: PriceFloor; + + tickSize: BN | null; + + gapTickSizePercentage: number | null; constructor(args: { winners: WinnerLimit; endAuctionAt: BN | null; auctionGap: BN | null; - tokenMint: PublicKey; - authority: PublicKey; - resource: PublicKey; + tokenMint: StringPublicKey; + authority: StringPublicKey; + resource: StringPublicKey; + priceFloor: PriceFloor; + tickSize: BN | null; + gapTickSizePercentage: number | null; }) { this.winners = args.winners; this.endAuctionAt = args.endAuctionAt; @@ -242,29 +411,45 @@ class CreateAuctionArgs { this.tokenMint = args.tokenMint; this.authority = args.authority; this.resource = args.resource; + this.priceFloor = args.priceFloor; + this.tickSize = args.tickSize; + this.gapTickSizePercentage = args.gapTickSizePercentage; } } class StartAuctionArgs { - instruction: number = 1; - resource: PublicKey; + instruction: number = 4; + resource: StringPublicKey; - constructor(args: { resource: PublicKey }) { + constructor(args: { resource: StringPublicKey }) { this.resource = args.resource; } } class PlaceBidArgs { - instruction: number = 2; - resource: PublicKey; + instruction: number = 6; + resource: StringPublicKey; amount: BN; - constructor(args: { resource: PublicKey; amount: BN }) { + constructor(args: { resource: StringPublicKey; amount: BN }) { this.resource = args.resource; this.amount = args.amount; } } +class CancelBidArgs { + instruction: number = 0; + resource: StringPublicKey; + + constructor(args: { resource: StringPublicKey }) { + this.resource = args.resource; + } +} + +class SetAuthorityArgs { + instruction: number = 5; +} + export const AUCTION_SCHEMA = new Map([ [ CreateAuctionArgs, @@ -275,9 +460,12 @@ export const AUCTION_SCHEMA = new Map([ ['winners', WinnerLimit], ['endAuctionAt', { kind: 'option', type: 'u64' }], ['auctionGap', { kind: 'option', type: 'u64' }], - ['tokenMint', 'pubkey'], - ['authority', 'pubkey'], - ['resource', 'pubkey'], + ['tokenMint', 'pubkeyAsString'], + ['authority', 'pubkeyAsString'], + ['resource', 'pubkeyAsString'], + ['priceFloor', PriceFloor], + ['tickSize', { kind: 'option', type: 'u64' }], + ['gapTickSizePercentage', { kind: 'option', type: 'u8' }], ], }, ], @@ -297,7 +485,7 @@ export const AUCTION_SCHEMA = new Map([ kind: 'struct', fields: [ ['instruction', 'u8'], - ['resource', 'pubkey'], + ['resource', 'pubkeyAsString'], ], }, ], @@ -308,27 +496,66 @@ export const AUCTION_SCHEMA = new Map([ fields: [ ['instruction', 'u8'], ['amount', 'u64'], - ['resource', 'pubkey'], + ['resource', 'pubkeyAsString'], + ], + }, + ], + [ + CancelBidArgs, + { + kind: 'struct', + fields: [ + ['instruction', 'u8'], + ['resource', 'pubkeyAsString'], ], }, ], + + [ + SetAuthorityArgs, + { + kind: 'struct', + fields: [['instruction', 'u8']], + }, + ], [ AuctionData, { kind: 'struct', fields: [ - ['authority', 'pubkey'], - ['resource', 'pubkey'], - ['tokenMint', 'pubkey'], + ['authority', 'pubkeyAsString'], + ['tokenMint', 'pubkeyAsString'], ['lastBid', { kind: 'option', type: 'u64' }], ['endedAt', { kind: 'option', type: 'u64' }], ['endAuctionAt', { kind: 'option', type: 'u64' }], ['auctionGap', { kind: 'option', type: 'u64' }], + ['priceFloor', PriceFloor], ['state', 'u8'], ['bidState', BidState], ], }, ], + [ + AuctionDataExtended, + { + kind: 'struct', + fields: [ + ['totalUncancelledBids', 'u64'], + ['tickSize', { kind: 'option', type: 'u64' }], + ['gapTickSizePercentage', { kind: 'option', type: 'u8' }], + ], + }, + ], + [ + PriceFloor, + { + kind: 'struct', + fields: [ + ['type', 'u8'], + ['hash', [32]], + ], + }, + ], [ BidState, { @@ -345,7 +572,7 @@ export const AUCTION_SCHEMA = new Map([ { kind: 'struct', fields: [ - ['key', 'pubkey'], + ['key', 'pubkeyAsString'], ['amount', 'u64'], ], }, @@ -355,8 +582,8 @@ export const AUCTION_SCHEMA = new Map([ { kind: 'struct', fields: [ - ['bidderPubkey', 'pubkey'], - ['auctionPubkey', 'pubkey'], + ['bidderPubkey', 'pubkeyAsString'], + ['auctionPubkey', 'pubkeyAsString'], ['lastBid', 'u64'], ['lastBidTimestamp', 'u64'], ['cancelled', 'u8'], @@ -368,63 +595,61 @@ export const AUCTION_SCHEMA = new Map([ { kind: 'struct', fields: [ - ['bidderPot', 'pubkey'], - ['bidderAct', 'pubkey'], - ['auctionAct', 'pubkey'], + ['bidderPot', 'pubkeyAsString'], + ['bidderAct', 'pubkeyAsString'], + ['auctionAct', 'pubkeyAsString'], + ['emptied', 'u8'], ], }, ], ]); export const decodeAuctionData = (buffer: Buffer) => { - return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData; + return deserializeUnchecked( + AUCTION_SCHEMA, + AuctionData, + buffer, + ) as AuctionData; }; export async function createAuction( - winners: WinnerLimit, - resource: PublicKey, - endAuctionAt: BN | null, - auctionGap: BN | null, - tokenMint: PublicKey, - authority: PublicKey, - creator: PublicKey, + settings: CreateAuctionArgs, + creator: StringPublicKey, instructions: TransactionInstruction[], ) { const auctionProgramId = programIds().auction; - const data = Buffer.from( - serialize( - AUCTION_SCHEMA, - new CreateAuctionArgs({ - winners, - resource, - endAuctionAt, - auctionGap, - tokenMint, - authority, - }), - ), - ); + const data = Buffer.from(serialize(AUCTION_SCHEMA, settings)); - const auctionKey: PublicKey = ( - await PublicKey.findProgramAddress( + const auctionKey: StringPublicKey = ( + await findProgramAddress( [ Buffer.from(AUCTION_PREFIX), - auctionProgramId.toBuffer(), - resource.toBuffer(), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(settings.resource).toBuffer(), ], - auctionProgramId, + toPublicKey(auctionProgramId), ) )[0]; const keys = [ { - pubkey: creator, + pubkey: toPublicKey(creator), isSigner: true, isWritable: true, }, { - pubkey: auctionKey, + pubkey: toPublicKey(auctionKey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey( + await getAuctionExtended({ + auctionProgramId: auctionProgramId.toString(), + resource: settings.resource, + }), + ), isSigner: false, isWritable: true, }, @@ -442,15 +667,15 @@ export async function createAuction( instructions.push( new TransactionInstruction({ keys, - programId: auctionProgramId, + programId: toPublicKey(auctionProgramId), data: data, }), ); } export async function startAuction( - resource: PublicKey, - creator: PublicKey, + resource: StringPublicKey, + creator: StringPublicKey, instructions: TransactionInstruction[], ) { const auctionProgramId = programIds().auction; @@ -464,25 +689,25 @@ export async function startAuction( ), ); - const auctionKey: PublicKey = ( - await PublicKey.findProgramAddress( + const auctionKey: StringPublicKey = ( + await findProgramAddress( [ Buffer.from(AUCTION_PREFIX), - auctionProgramId.toBuffer(), - resource.toBuffer(), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(resource).toBuffer(), ], - auctionProgramId, + toPublicKey(auctionProgramId), ) )[0]; const keys = [ { - pubkey: creator, + pubkey: toPublicKey(creator), isSigner: false, isWritable: true, }, { - pubkey: auctionKey, + pubkey: toPublicKey(auctionKey), isSigner: false, isWritable: true, }, @@ -495,19 +720,56 @@ export async function startAuction( instructions.push( new TransactionInstruction({ keys, - programId: auctionProgramId, + programId: toPublicKey(auctionProgramId), + data: data, + }), + ); +} + +export async function setAuctionAuthority( + auction: StringPublicKey, + currentAuthority: StringPublicKey, + newAuthority: StringPublicKey, + instructions: TransactionInstruction[], +) { + const auctionProgramId = programIds().auction; + + const data = Buffer.from(serialize(AUCTION_SCHEMA, new SetAuthorityArgs())); + + const keys = [ + { + pubkey: toPublicKey(auction), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(currentAuthority), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(newAuthority), + isSigner: false, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(auctionProgramId), data: data, }), ); } export async function placeBid( - bidderPubkey: PublicKey, - bidderPotTokenPubkey: PublicKey, - tokenMintPubkey: PublicKey, - transferAuthority: PublicKey, - payer: PublicKey, - resource: PublicKey, + bidderPubkey: StringPublicKey, + bidderTokenPubkey: StringPublicKey, + bidderPotTokenPubkey: StringPublicKey, + tokenMintPubkey: StringPublicKey, + transferAuthority: StringPublicKey, + payer: StringPublicKey, + resource: StringPublicKey, amount: BN, instructions: TransactionInstruction[], ) { @@ -523,82 +785,263 @@ export async function placeBid( ), ); - const auctionKey: PublicKey = ( - await PublicKey.findProgramAddress( + const auctionKey: StringPublicKey = ( + await findProgramAddress( [ Buffer.from(AUCTION_PREFIX), - auctionProgramId.toBuffer(), - resource.toBuffer(), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(resource).toBuffer(), ], - auctionProgramId, + toPublicKey(auctionProgramId), ) )[0]; - const bidderPotKey: PublicKey = ( - await PublicKey.findProgramAddress( - [ - Buffer.from(AUCTION_PREFIX), - auctionProgramId.toBuffer(), - auctionKey.toBuffer(), - bidderPubkey.toBuffer(), - ], - auctionProgramId, - ) - )[0]; - const bidderMetaKey: PublicKey = ( - await PublicKey.findProgramAddress( + const bidderPotKey = await getBidderPotKey({ + auctionProgramId: auctionProgramId.toString(), + auctionKey, + bidderPubkey, + }); + + const bidderMetaKey: StringPublicKey = ( + await findProgramAddress( [ Buffer.from(AUCTION_PREFIX), - auctionProgramId.toBuffer(), - auctionKey.toBuffer(), - bidderPubkey.toBuffer(), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(auctionKey).toBuffer(), + toPublicKey(bidderPubkey).toBuffer(), Buffer.from('metadata'), ], - auctionProgramId, + toPublicKey(auctionProgramId), ) )[0]; const keys = [ { - pubkey: bidderPubkey, + pubkey: toPublicKey(bidderPubkey), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(bidderTokenPubkey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(bidderPotKey), isSigner: false, isWritable: true, }, { - pubkey: bidderPotKey, + pubkey: toPublicKey(bidderPotTokenPubkey), isSigner: false, isWritable: true, }, { - pubkey: bidderPotTokenPubkey, + pubkey: toPublicKey(bidderMetaKey), isSigner: false, isWritable: true, }, { - pubkey: bidderMetaKey, + pubkey: toPublicKey(auctionKey), isSigner: false, isWritable: true, }, { - pubkey: auctionKey, + pubkey: toPublicKey( + await getAuctionExtended({ + auctionProgramId: auctionProgramId.toString(), + resource, + }), + ), isSigner: false, isWritable: true, }, { - pubkey: tokenMintPubkey, + pubkey: toPublicKey(tokenMintPubkey), isSigner: false, isWritable: true, }, { - pubkey: transferAuthority, + pubkey: toPublicKey(transferAuthority), isSigner: true, isWritable: false, }, { - pubkey: payer, + pubkey: toPublicKey(payer), + isSigner: true, + isWritable: false, + }, + { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: programIds().token, + isSigner: false, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(auctionProgramId), + data: data, + }), + ); + + return { + amount, + }; +} + +export async function getBidderPotKey({ + auctionProgramId, + auctionKey, + bidderPubkey, +}: { + auctionProgramId: StringPublicKey; + auctionKey: StringPublicKey; + bidderPubkey: StringPublicKey; +}): Promise { + return ( + await findProgramAddress( + [ + Buffer.from(AUCTION_PREFIX), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(auctionKey).toBuffer(), + toPublicKey(bidderPubkey).toBuffer(), + ], + toPublicKey(auctionProgramId), + ) + )[0]; +} + +export async function getAuctionExtended({ + auctionProgramId, + resource, +}: { + auctionProgramId: StringPublicKey; + resource: StringPublicKey; +}): Promise { + return ( + await findProgramAddress( + [ + Buffer.from(AUCTION_PREFIX), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(resource).toBuffer(), + Buffer.from(EXTENDED), + ], + toPublicKey(auctionProgramId), + ) + )[0]; +} + +export async function cancelBid( + bidderPubkey: StringPublicKey, + bidderTokenPubkey: StringPublicKey, + bidderPotTokenPubkey: StringPublicKey, + tokenMintPubkey: StringPublicKey, + resource: StringPublicKey, + instructions: TransactionInstruction[], +) { + const auctionProgramId = programIds().auction; + + const data = Buffer.from( + serialize( + AUCTION_SCHEMA, + new CancelBidArgs({ + resource, + }), + ), + ); + + const auctionKey = ( + await findProgramAddress( + [ + Buffer.from(AUCTION_PREFIX), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(resource).toBuffer(), + ], + toPublicKey(auctionProgramId), + ) + )[0]; + + const bidderPotKey = await getBidderPotKey({ + auctionProgramId: auctionProgramId.toString(), + auctionKey, + bidderPubkey, + }); + + const bidderMetaKey = ( + await findProgramAddress( + [ + Buffer.from(AUCTION_PREFIX), + toPublicKey(auctionProgramId).toBuffer(), + toPublicKey(auctionKey).toBuffer(), + toPublicKey(bidderPubkey).toBuffer(), + Buffer.from('metadata'), + ], + toPublicKey(auctionProgramId), + ) + )[0]; + + const keys = [ + { + pubkey: toPublicKey(bidderPubkey), isSigner: true, isWritable: false, }, + { + pubkey: toPublicKey(bidderTokenPubkey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(bidderPotKey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(bidderPotTokenPubkey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(bidderMetaKey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(auctionKey), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey( + await getAuctionExtended({ + auctionProgramId: auctionProgramId.toString(), + resource, + }), + ), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(tokenMintPubkey), + isSigner: false, + isWritable: true, + }, { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, @@ -623,7 +1066,7 @@ export async function placeBid( instructions.push( new TransactionInstruction({ keys, - programId: auctionProgramId, + programId: toPublicKey(auctionProgramId), data: data, }), ); diff --git a/packages/common/src/actions/metadata.ts b/packages/common/src/actions/metadata.ts index ca86c3ec..a91b97d8 100644 --- a/packages/common/src/actions/metadata.ts +++ b/packages/common/src/actions/metadata.ts @@ -1,18 +1,15 @@ import { - PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, TransactionInstruction, } from '@solana/web3.js'; -import { programIds } from '../utils/ids'; -import { deserializeBorsh } from './../utils/borsh'; -import { serialize } from 'borsh'; +import { programIds } from '../utils/programIds'; +import { deserializeUnchecked, serialize } from 'borsh'; import BN from 'bn.js'; -import { PublicKeyInput } from 'node:crypto'; -import { ParsedAccount } from '..'; - +import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils'; export const METADATA_PREFIX = 'metadata'; export const EDITION = 'edition'; +export const RESERVATION = 'reservation'; export const MAX_NAME_LENGTH = 32; @@ -20,164 +17,283 @@ export const MAX_SYMBOL_LENGTH = 10; export const MAX_URI_LENGTH = 200; +export const MAX_CREATOR_LIMIT = 5; + +export const MAX_CREATOR_LEN = 32 + 1 + 1; export const MAX_METADATA_LEN = - 1 + 32 + MAX_NAME_LENGTH + MAX_SYMBOL_LENGTH + MAX_URI_LENGTH + 200; + 1 + + 32 + + 32 + + MAX_NAME_LENGTH + + MAX_SYMBOL_LENGTH + + MAX_URI_LENGTH + + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN + + 2 + + 1 + + 1 + + 198; -export const MAX_NAME_SYMBOL_LEN = 1 + 32 + 8; -export const MAX_MASTER_EDITION_KEN = 1 + 9 + 8 + 32; +export const MAX_EDITION_LEN = 1 + 32 + 8 + 200; + +export const EDITION_MARKER_BIT_SIZE = 248; export enum MetadataKey { - MetadataV1 = 0, - NameSymbolTupleV1 = 1, - EditionV1 = 2, - MasterEditionV1 = 3, + Uninitialized = 0, + MetadataV1 = 4, + EditionV1 = 1, + MasterEditionV1 = 2, + MasterEditionV2 = 6, + EditionMarker = 7, } export enum MetadataCategory { Audio = 'audio', Video = 'video', Image = 'image', + VR = 'vr', } +export type MetadataFile = { + uri: string; + type: string; +}; + +export type FileOrString = MetadataFile | string; + export interface IMetadataExtension { name: string; symbol: string; + + creators: Creator[] | null; description: string; - // preview image + // preview image absolute URI image: string; + animation_url?: string; + // stores link to item on meta - externalUrl: string; - royalty: number; - files?: File[]; - category: MetadataCategory; + external_url: string; + + seller_fee_basis_points: number; + + properties: { + files?: FileOrString[]; + category: MetadataCategory; + maxSupply?: number; + creators?: { + address: string; + shares: number; + }[]; + }; } -export class MasterEdition { +export class MasterEditionV1 { key: MetadataKey; supply: BN; maxSupply?: BN; /// Can be used to mint tokens that give one-time permission to mint a single limited edition. - masterMint: PublicKey; + printingMint: StringPublicKey; + /// If you don't know how many printing tokens you are going to need, but you do know + /// you are going to need some amount in the future, you can use a token from this mint. + /// Coming back to token metadata with one of these tokens allows you to mint (one time) + /// any number of printing tokens you want. This is used for instance by Auction Manager + /// with participation NFTs, where we dont know how many people will bid and need participation + /// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over, + /// because when the auction begins we just dont know how many printing tokens we will need, + /// but at the end we will. At the end it then burns this token with token-metadata to + /// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token + /// to get their limited editions. + oneTimePrintingAuthorizationMint: StringPublicKey; constructor(args: { key: MetadataKey; supply: BN; maxSupply?: BN; - /// Can be used to mint tokens that give one-time permission to mint a single limited edition. - masterMint: PublicKey; + printingMint: StringPublicKey; + oneTimePrintingAuthorizationMint: StringPublicKey; }) { this.key = MetadataKey.MasterEditionV1; this.supply = args.supply; this.maxSupply = args.maxSupply; - this.masterMint = args.masterMint; + this.printingMint = args.printingMint; + this.oneTimePrintingAuthorizationMint = + args.oneTimePrintingAuthorizationMint; + } +} + +export class MasterEditionV2 { + key: MetadataKey; + supply: BN; + maxSupply?: BN; + + constructor(args: { key: MetadataKey; supply: BN; maxSupply?: BN }) { + this.key = MetadataKey.MasterEditionV2; + this.supply = args.supply; + this.maxSupply = args.maxSupply; + } +} + +export class EditionMarker { + key: MetadataKey; + ledger: number[]; + + constructor(args: { key: MetadataKey; ledger: number[] }) { + this.key = MetadataKey.EditionMarker; + this.ledger = args.ledger; + } + + editionTaken(edition: number) { + const editionOffset = edition % EDITION_MARKER_BIT_SIZE; + const indexOffset = Math.floor(editionOffset / 8); + + if (indexOffset > 30) { + throw Error('bad index for edition'); + } + + const positionInBitsetFromRight = 7 - (editionOffset % 8); + + const mask = Math.pow(2, positionInBitsetFromRight); + + const appliedMask = this.ledger[indexOffset] & mask; + + return appliedMask != 0; } } export class Edition { key: MetadataKey; /// Points at MasterEdition struct - parent: PublicKey; + parent: StringPublicKey; /// Starting at 0 for master record, this is incremented for each edition minted. edition: BN; - constructor(args: { key: MetadataKey; parent: PublicKey; edition: BN }) { + constructor(args: { + key: MetadataKey; + parent: StringPublicKey; + edition: BN; + }) { this.key = MetadataKey.EditionV1; this.parent = args.parent; this.edition = args.edition; } } -export class Metadata { - key: MetadataKey; - nonUniqueSpecificUpdateAuthority?: PublicKey; +export class Creator { + address: StringPublicKey; + verified: boolean; + share: number; - mint: PublicKey; + constructor(args: { + address: StringPublicKey; + verified: boolean; + share: number; + }) { + this.address = args.address; + this.verified = args.verified; + this.share = args.share; + } +} + +export class Data { name: string; symbol: string; uri: string; - - extended?: IMetadataExtension; - masterEdition?: PublicKey; - edition?: PublicKey; - nameSymbolTuple?: PublicKey; - + sellerFeeBasisPoints: number; + creators: Creator[] | null; constructor(args: { - nonUniqueSpecificUpdateAuthority?: PublicKey; - mint: PublicKey; name: string; symbol: string; uri: string; + sellerFeeBasisPoints: number; + creators: Creator[] | null; }) { - this.key = MetadataKey.MetadataV1; - this.nonUniqueSpecificUpdateAuthority = - args.nonUniqueSpecificUpdateAuthority; - this.mint = args.mint; this.name = args.name; this.symbol = args.symbol; this.uri = args.uri; + this.sellerFeeBasisPoints = args.sellerFeeBasisPoints; + this.creators = args.creators; } } -export class NameSymbolTuple { +export class Metadata { key: MetadataKey; - updateAuthority: PublicKey; - metadata: PublicKey; + updateAuthority: StringPublicKey; + mint: StringPublicKey; + data: Data; + primarySaleHappened: boolean; + isMutable: boolean; + editionNonce: number | null; + + // set lazy + masterEdition?: StringPublicKey; + edition?: StringPublicKey; + + constructor(args: { + updateAuthority: StringPublicKey; + mint: StringPublicKey; + data: Data; + primarySaleHappened: boolean; + isMutable: boolean; + editionNonce: number | null; + }) { + this.key = MetadataKey.MetadataV1; + this.updateAuthority = args.updateAuthority; + this.mint = args.mint; + this.data = args.data; + this.primarySaleHappened = args.primarySaleHappened; + this.isMutable = args.isMutable; + this.editionNonce = args.editionNonce; + } - constructor(args: { updateAuthority: Buffer; metadata: Buffer }) { - this.key = MetadataKey.NameSymbolTupleV1; - this.updateAuthority = new PublicKey(args.updateAuthority); - this.metadata = new PublicKey(args.metadata); + public async init() { + const edition = await getEdition(this.mint); + this.edition = edition; + this.masterEdition = edition; } } class CreateMetadataArgs { instruction: number = 0; - allowDuplicates: boolean = false; - name: string; - symbol: string; - uri: string; + data: Data; + isMutable: boolean; - constructor(args: { - name: string; - symbol: string; - uri: string; - allowDuplicates?: boolean; - }) { - this.name = args.name; - this.symbol = args.symbol; - this.uri = args.uri; - this.allowDuplicates = !!args.allowDuplicates; + constructor(args: { data: Data; isMutable: boolean }) { + this.data = args.data; + this.isMutable = args.isMutable; } } class UpdateMetadataArgs { instruction: number = 1; - uri: string; + data: Data | null; // Not used by this app, just required for instruction - nonUniqueSpecificUpdateAuthority: PublicKey | null; - + updateAuthority: StringPublicKey | null; + primarySaleHappened: boolean | null; constructor(args: { - uri: string; - nonUniqueSpecificUpdateAuthority?: string; + data?: Data; + updateAuthority?: string; + primarySaleHappened: boolean | null; }) { - this.uri = args.uri; - this.nonUniqueSpecificUpdateAuthority = args.nonUniqueSpecificUpdateAuthority - ? new PublicKey(args.nonUniqueSpecificUpdateAuthority) - : null; + this.data = args.data ? args.data : null; + this.updateAuthority = args.updateAuthority ? args.updateAuthority : null; + this.primarySaleHappened = args.primarySaleHappened; } } -class TransferUpdateAuthorityArgs { - instruction: number = 2; - constructor() {} -} - class CreateMasterEditionArgs { - instruction: number = 3; + instruction: number = 10; maxSupply: BN | null; constructor(args: { maxSupply: BN | null }) { this.maxSupply = args.maxSupply; } } +class MintPrintingTokensArgs { + instruction: number = 9; + supply: BN; + + constructor(args: { supply: BN }) { + this.supply = args.supply; + } +} + export const METADATA_SCHEMA = new Map([ [ CreateMetadataArgs, @@ -185,10 +301,8 @@ export const METADATA_SCHEMA = new Map([ kind: 'struct', fields: [ ['instruction', 'u8'], - ['allowDuplicates', 'u8'], - ['name', 'string'], - ['symbol', 'string'], - ['uri', 'string'], + ['data', Data], + ['isMutable', 'u8'], // bool ], }, ], @@ -198,40 +312,54 @@ export const METADATA_SCHEMA = new Map([ kind: 'struct', fields: [ ['instruction', 'u8'], - ['uri', 'string'], - [ - 'nonUniqueSpecificUpdateAuthority', - { kind: 'option', type: 'pubkey' }, - ], + ['data', { kind: 'option', type: Data }], + ['updateAuthority', { kind: 'option', type: 'pubkeyAsString' }], + ['primarySaleHappened', { kind: 'option', type: 'u8' }], ], }, ], + [ - TransferUpdateAuthorityArgs, + CreateMasterEditionArgs, { kind: 'struct', - fields: [['instruction', 'u8']], + fields: [ + ['instruction', 'u8'], + ['maxSupply', { kind: 'option', type: 'u64' }], + ], }, ], [ - CreateMasterEditionArgs, + MintPrintingTokensArgs, { kind: 'struct', fields: [ ['instruction', 'u8'], + ['supply', 'u64'], + ], + }, + ], + [ + MasterEditionV1, + { + kind: 'struct', + fields: [ + ['key', 'u8'], + ['supply', 'u64'], ['maxSupply', { kind: 'option', type: 'u64' }], + ['printingMint', 'pubkeyAsString'], + ['oneTimePrintingAuthorizationMint', 'pubkeyAsString'], ], }, ], [ - MasterEdition, + MasterEditionV2, { kind: 'struct', fields: [ ['key', 'u8'], ['supply', 'u64'], ['maxSupply', { kind: 'option', type: 'u64' }], - ['masterMint', 'pubkey'], ], }, ], @@ -241,171 +369,214 @@ export const METADATA_SCHEMA = new Map([ kind: 'struct', fields: [ ['key', 'u8'], - ['parent', 'pubkey'], + ['parent', 'pubkeyAsString'], ['edition', 'u64'], ], }, ], [ - Metadata, + Data, { kind: 'struct', fields: [ - ['key', 'u8'], - [ - 'nonUniqueSpecificUpdateAuthority', - { kind: 'option', type: 'pubkey' }, - ], - ['mint', 'pubkey'], ['name', 'string'], ['symbol', 'string'], ['uri', 'string'], + ['sellerFeeBasisPoints', 'u16'], + ['creators', { kind: 'option', type: [Creator] }], + ], + }, + ], + [ + Creator, + { + kind: 'struct', + fields: [ + ['address', 'pubkeyAsString'], + ['verified', 'u8'], + ['share', 'u8'], ], }, ], [ - NameSymbolTuple, + Metadata, { kind: 'struct', fields: [ ['key', 'u8'], - ['updateAuthority', 'pubkey'], - ['metadata', 'pubkey'], + ['updateAuthority', 'pubkeyAsString'], + ['mint', 'pubkeyAsString'], + ['data', Data], + ['primarySaleHappened', 'u8'], // bool + ['isMutable', 'u8'], // bool + ], + }, + ], + [ + EditionMarker, + { + kind: 'struct', + fields: [ + ['key', 'u8'], + ['ledger', [31]], ], }, ], ]); -export const decodeMetadata = async (buffer: Buffer): Promise => { - const metadata = deserializeBorsh( +export const decodeMetadata = (buffer: Buffer): Metadata => { + const metadata = deserializeUnchecked( METADATA_SCHEMA, Metadata, buffer, ) as Metadata; - metadata.nameSymbolTuple = await getNameSymbol(metadata); - metadata.edition = await getEdition(metadata.mint); - metadata.masterEdition = await getEdition(metadata.mint); return metadata; }; -export const decodeEdition = (buffer: Buffer) => { - return deserializeBorsh(METADATA_SCHEMA, Edition, buffer) as Edition; -}; - -export const decodeMasterEdition = (buffer: Buffer) => { - return deserializeBorsh( +export const decodeEditionMarker = (buffer: Buffer): EditionMarker => { + const editionMarker = deserializeUnchecked( METADATA_SCHEMA, - MasterEdition, + EditionMarker, buffer, - ) as MasterEdition; + ) as EditionMarker; + return editionMarker; }; -export const decodeNameSymbolTuple = (buffer: Buffer) => { - return deserializeBorsh( - METADATA_SCHEMA, - NameSymbolTuple, - buffer, - ) as NameSymbolTuple; +export const decodeEdition = (buffer: Buffer) => { + return deserializeUnchecked(METADATA_SCHEMA, Edition, buffer) as Edition; +}; + +export const decodeMasterEdition = ( + buffer: Buffer, +): MasterEditionV1 | MasterEditionV2 => { + if (buffer[0] == MetadataKey.MasterEditionV1) { + return deserializeUnchecked( + METADATA_SCHEMA, + MasterEditionV1, + buffer, + ) as MasterEditionV1; + } else { + return deserializeUnchecked( + METADATA_SCHEMA, + MasterEditionV2, + buffer, + ) as MasterEditionV2; + } }; -export async function transferUpdateAuthority( - account: PublicKey, - currentUpdateAuthority: PublicKey, - newUpdateAuthority: PublicKey, +export async function updateMetadata( + data: Data | undefined, + newUpdateAuthority: string | undefined, + primarySaleHappened: boolean | null | undefined, + mintKey: StringPublicKey, + updateAuthority: StringPublicKey, instructions: TransactionInstruction[], + metadataAccount?: StringPublicKey, ) { const metadataProgramId = programIds().metadata; - const data = Buffer.from( - serialize(METADATA_SCHEMA, new TransferUpdateAuthorityArgs()), - ); + metadataAccount = + metadataAccount || + ( + await findProgramAddress( + [ + Buffer.from('metadata'), + toPublicKey(metadataProgramId).toBuffer(), + toPublicKey(mintKey).toBuffer(), + ], + toPublicKey(metadataProgramId), + ) + )[0]; + const value = new UpdateMetadataArgs({ + data, + updateAuthority: !newUpdateAuthority ? undefined : newUpdateAuthority, + primarySaleHappened: + primarySaleHappened === null || primarySaleHappened === undefined + ? null + : primarySaleHappened, + }); + const txnData = Buffer.from(serialize(METADATA_SCHEMA, value)); const keys = [ { - pubkey: account, + pubkey: toPublicKey(metadataAccount), isSigner: false, isWritable: true, }, { - pubkey: currentUpdateAuthority, + pubkey: toPublicKey(updateAuthority), isSigner: true, isWritable: false, }, - { - pubkey: newUpdateAuthority, - isSigner: false, - isWritable: false, - }, ]; instructions.push( new TransactionInstruction({ keys, - programId: metadataProgramId, - data: data, + programId: toPublicKey(metadataProgramId), + data: txnData, }), ); + + return metadataAccount; } -export async function updateMetadata( - symbol: string, - name: string, - uri: string, - newNonUniqueSpecificUpdateAuthority: string | undefined, - mintKey: PublicKey, - updateAuthority: PublicKey, +export async function createMetadata( + data: Data, + updateAuthority: StringPublicKey, + mintKey: StringPublicKey, + mintAuthorityKey: StringPublicKey, instructions: TransactionInstruction[], - metadataAccount?: PublicKey, - nameSymbolAccount?: PublicKey, + payer: StringPublicKey, ) { const metadataProgramId = programIds().metadata; - metadataAccount = - metadataAccount || - ( - await PublicKey.findProgramAddress( - [ - Buffer.from('metadata'), - metadataProgramId.toBuffer(), - mintKey.toBuffer(), - ], - metadataProgramId, - ) - )[0]; - - nameSymbolAccount = - nameSymbolAccount || - ( - await PublicKey.findProgramAddress( - [ - Buffer.from('metadata'), - metadataProgramId.toBuffer(), - Buffer.from(name), - Buffer.from(symbol), - ], - metadataProgramId, - ) - )[0]; + const metadataAccount = ( + await findProgramAddress( + [ + Buffer.from('metadata'), + toPublicKey(metadataProgramId).toBuffer(), + toPublicKey(mintKey).toBuffer(), + ], + toPublicKey(metadataProgramId), + ) + )[0]; + console.log('Data', data); + const value = new CreateMetadataArgs({ data, isMutable: true }); + const txnData = Buffer.from(serialize(METADATA_SCHEMA, value)); - const value = new UpdateMetadataArgs({ - uri, - nonUniqueSpecificUpdateAuthority: !newNonUniqueSpecificUpdateAuthority - ? undefined - : newNonUniqueSpecificUpdateAuthority, - }); - const data = Buffer.from(serialize(METADATA_SCHEMA, value)); const keys = [ { - pubkey: metadataAccount, + pubkey: toPublicKey(metadataAccount), isSigner: false, isWritable: true, }, { - pubkey: updateAuthority, + pubkey: toPublicKey(mintKey), + isSigner: false, + isWritable: false, + }, + { + pubkey: toPublicKey(mintAuthorityKey), isSigner: true, isWritable: false, }, { - pubkey: nameSymbolAccount, + pubkey: toPublicKey(payer), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(updateAuthority), + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false, }, @@ -413,81 +584,84 @@ export async function updateMetadata( instructions.push( new TransactionInstruction({ keys, - programId: metadataProgramId, - data, + programId: toPublicKey(metadataProgramId), + data: txnData, }), ); - return [metadataAccount, nameSymbolAccount]; + return metadataAccount; } -export async function createMetadata( - symbol: string, - name: string, - uri: string, - allowDuplicates: boolean, - updateAuthority: PublicKey, - mintKey: PublicKey, - mintAuthorityKey: PublicKey, +export async function createMasterEdition( + maxSupply: BN | undefined, + mintKey: StringPublicKey, + updateAuthorityKey: StringPublicKey, + mintAuthorityKey: StringPublicKey, + payer: StringPublicKey, instructions: TransactionInstruction[], - payer: PublicKey, ) { const metadataProgramId = programIds().metadata; const metadataAccount = ( - await PublicKey.findProgramAddress( + await findProgramAddress( [ - Buffer.from('metadata'), - metadataProgramId.toBuffer(), - mintKey.toBuffer(), + Buffer.from(METADATA_PREFIX), + toPublicKey(metadataProgramId).toBuffer(), + toPublicKey(mintKey).toBuffer(), ], - metadataProgramId, + toPublicKey(metadataProgramId), ) )[0]; - const nameSymbolAccount = ( - await PublicKey.findProgramAddress( + const editionAccount = ( + await findProgramAddress( [ - Buffer.from('metadata'), - metadataProgramId.toBuffer(), - Buffer.from(name), - Buffer.from(symbol), + Buffer.from(METADATA_PREFIX), + toPublicKey(metadataProgramId).toBuffer(), + toPublicKey(mintKey).toBuffer(), + Buffer.from(EDITION), ], - metadataProgramId, + toPublicKey(metadataProgramId), ) )[0]; - const value = new CreateMetadataArgs({ name, symbol, uri, allowDuplicates }); + const value = new CreateMasterEditionArgs({ maxSupply: maxSupply || null }); const data = Buffer.from(serialize(METADATA_SCHEMA, value)); const keys = [ { - pubkey: nameSymbolAccount, + pubkey: toPublicKey(editionAccount), isSigner: false, isWritable: true, }, { - pubkey: metadataAccount, + pubkey: toPublicKey(mintKey), isSigner: false, isWritable: true, }, { - pubkey: mintKey, - isSigner: false, + pubkey: toPublicKey(updateAuthorityKey), + isSigner: true, isWritable: false, }, { - pubkey: mintAuthorityKey, + pubkey: toPublicKey(mintAuthorityKey), isSigner: true, isWritable: false, }, { - pubkey: payer, + pubkey: toPublicKey(payer), isSigner: true, isWritable: false, }, { - pubkey: updateAuthority, + pubkey: toPublicKey(metadataAccount), + isSigner: false, + isWritable: false, + }, + + { + pubkey: programIds().token, isSigner: false, isWritable: false, }, @@ -502,107 +676,91 @@ export async function createMetadata( isWritable: false, }, ]; + instructions.push( new TransactionInstruction({ keys, - programId: metadataProgramId, + programId: toPublicKey(metadataProgramId), data, }), ); - - return [metadataAccount, nameSymbolAccount]; } -export async function createMasterEdition( - name: string, - symbol: string, - maxSupply: BN | undefined, - mintKey: PublicKey, - masterMintKey: PublicKey, - updateAuthorityKey: PublicKey, - mintAuthorityKey: PublicKey, +export async function deprecatedMintNewEditionFromMasterEditionViaPrintingToken( + newMint: StringPublicKey, + tokenMint: StringPublicKey, + newMintAuthority: StringPublicKey, + printingMint: StringPublicKey, + authorizationTokenHoldingAccount: StringPublicKey, + burnAuthority: StringPublicKey, + updateAuthorityOfMaster: StringPublicKey, + reservationList: StringPublicKey | undefined, instructions: TransactionInstruction[], - payer: PublicKey, + payer: StringPublicKey, ) { const metadataProgramId = programIds().metadata; - const metadataAccount = ( - await PublicKey.findProgramAddress( - [ - Buffer.from(METADATA_PREFIX), - metadataProgramId.toBuffer(), - mintKey.toBuffer(), - ], - metadataProgramId, - ) - )[0]; - - const nameSymbolAccount = ( - await PublicKey.findProgramAddress( - [ - Buffer.from(METADATA_PREFIX), - metadataProgramId.toBuffer(), - Buffer.from(name), - Buffer.from(symbol), - ], - metadataProgramId, - ) - )[0]; - - const editionAccount = ( - await PublicKey.findProgramAddress( - [ - Buffer.from(METADATA_PREFIX), - metadataProgramId.toBuffer(), - mintKey.toBuffer(), - Buffer.from(EDITION), - ], - metadataProgramId, - ) - )[0]; + const newMetadataKey = await getMetadata(newMint); + const masterMetadataKey = await getMetadata(tokenMint); + const newEdition = await getEdition(newMint); + const masterEdition = await getEdition(tokenMint); - const value = new CreateMasterEditionArgs({ maxSupply: maxSupply || null }); - const data = Buffer.from(serialize(METADATA_SCHEMA, value)); + const data = Buffer.from([3]); const keys = [ { - pubkey: editionAccount, + pubkey: toPublicKey(newMetadataKey), isSigner: false, isWritable: true, }, { - pubkey: mintKey, + pubkey: toPublicKey(newEdition), isSigner: false, isWritable: true, }, { - pubkey: masterMintKey, + pubkey: toPublicKey(masterEdition), isSigner: false, - isWritable: false, + isWritable: true, }, { - pubkey: updateAuthorityKey, + pubkey: toPublicKey(newMint), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(newMintAuthority), isSigner: true, isWritable: false, }, { - pubkey: mintAuthorityKey, + pubkey: toPublicKey(printingMint), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(authorizationTokenHoldingAccount), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(burnAuthority), isSigner: true, isWritable: false, }, { - pubkey: metadataAccount, - isSigner: false, + pubkey: toPublicKey(payer), + isSigner: true, isWritable: false, }, { - pubkey: nameSymbolAccount, + pubkey: toPublicKey(updateAuthorityOfMaster), isSigner: false, isWritable: false, }, { - pubkey: payer, - isSigner: true, + pubkey: toPublicKey(masterMetadataKey), + isSigner: false, isWritable: false, }, { @@ -621,25 +779,33 @@ export async function createMasterEdition( isWritable: false, }, ]; + + if (reservationList) { + keys.push({ + pubkey: toPublicKey(reservationList), + isSigner: false, + isWritable: true, + }); + } instructions.push( new TransactionInstruction({ keys, - programId: metadataProgramId, + programId: toPublicKey(metadataProgramId), data, }), ); } export async function mintNewEditionFromMasterEditionViaToken( - newMint: PublicKey, - tokenMint: PublicKey, - newMintAuthority: PublicKey, - masterMint: PublicKey, - authorizationTokenHoldingAccount: PublicKey, - burnAuthority: PublicKey, - updateAuthorityOfMaster: PublicKey, + newMint: StringPublicKey, + tokenMint: StringPublicKey, + newMintAuthority: StringPublicKey, + newUpdateAuthority: StringPublicKey, + tokenOwner: StringPublicKey, + tokenAccount: StringPublicKey, instructions: TransactionInstruction[], - payer: PublicKey, + payer: StringPublicKey, + edition: BN, ) { const metadataProgramId = programIds().metadata; @@ -647,67 +813,173 @@ export async function mintNewEditionFromMasterEditionViaToken( const masterMetadataKey = await getMetadata(tokenMint); const newEdition = await getEdition(newMint); const masterEdition = await getEdition(tokenMint); + const editionMarkPda = await getEditionMarkPda(tokenMint, edition); - const data = Buffer.from([5]); + const data = Buffer.from([11, ...edition.toArray('le', 8)]); const keys = [ { - pubkey: newMetadataKey, + pubkey: toPublicKey(newMetadataKey), isSigner: false, isWritable: true, }, { - pubkey: newEdition, + pubkey: toPublicKey(newEdition), isSigner: false, isWritable: true, }, { - pubkey: masterEdition, + pubkey: toPublicKey(masterEdition), isSigner: false, isWritable: true, }, { - pubkey: newMint, + pubkey: toPublicKey(newMint), isSigner: false, isWritable: true, }, { - pubkey: newMintAuthority, + pubkey: toPublicKey(editionMarkPda), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(newMintAuthority), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(payer), isSigner: true, isWritable: false, }, { - pubkey: masterMint, + pubkey: toPublicKey(tokenOwner), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(tokenAccount), + isSigner: false, + isWritable: false, + }, + { + pubkey: toPublicKey(newUpdateAuthority), + isSigner: false, + isWritable: false, + }, + { + pubkey: toPublicKey(masterMetadataKey), + isSigner: false, + isWritable: false, + }, + { + pubkey: programIds().token, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + ]; + + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(metadataProgramId), + data, + }), + ); +} + +export async function updatePrimarySaleHappenedViaToken( + metadata: StringPublicKey, + owner: StringPublicKey, + tokenAccount: StringPublicKey, + instructions: TransactionInstruction[], +) { + const metadataProgramId = programIds().metadata; + + const data = Buffer.from([4]); + + const keys = [ + { + pubkey: toPublicKey(metadata), isSigner: false, isWritable: true, }, { - pubkey: authorizationTokenHoldingAccount, + pubkey: toPublicKey(owner), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(tokenAccount), + isSigner: false, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(metadataProgramId), + data, + }), + ); +} + +export async function deprecatedCreateReservationList( + metadata: StringPublicKey, + masterEdition: StringPublicKey, + resource: StringPublicKey, + updateAuthority: StringPublicKey, + payer: StringPublicKey, + instructions: TransactionInstruction[], +) { + const metadataProgramId = programIds().metadata; + + const reservationList = await deprecatedGetReservationList( + masterEdition, + resource, + ); + const data = Buffer.from([6]); + + const keys = [ + { + pubkey: toPublicKey(reservationList), isSigner: false, isWritable: true, }, { - pubkey: burnAuthority, + pubkey: toPublicKey(payer), isSigner: true, isWritable: false, }, { - pubkey: payer, + pubkey: toPublicKey(updateAuthority), isSigner: true, isWritable: false, }, + { - pubkey: updateAuthorityOfMaster, + pubkey: toPublicKey(masterEdition), isSigner: false, isWritable: false, }, { - pubkey: masterMetadataKey, + pubkey: toPublicKey(resource), isSigner: false, isWritable: false, }, { - pubkey: programIds().token, + pubkey: toPublicKey(metadata), isSigner: false, isWritable: false, }, @@ -725,56 +997,211 @@ export async function mintNewEditionFromMasterEditionViaToken( instructions.push( new TransactionInstruction({ keys, - programId: metadataProgramId, + programId: toPublicKey(metadataProgramId), + data, + }), + ); +} + +export async function signMetadata( + metadata: StringPublicKey, + creator: StringPublicKey, + instructions: TransactionInstruction[], +) { + const metadataProgramId = programIds().metadata; + + const data = Buffer.from([7]); + + const keys = [ + { + pubkey: toPublicKey(metadata), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(creator), + isSigner: true, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(metadataProgramId), + data, + }), + ); +} + +export async function deprecatedMintPrintingTokens( + destination: StringPublicKey, + printingMint: StringPublicKey, + updateAuthority: StringPublicKey, + metadata: StringPublicKey, + masterEdition: StringPublicKey, + supply: BN, + instructions: TransactionInstruction[], +) { + const PROGRAM_IDS = programIds(); + const metadataProgramId = PROGRAM_IDS.metadata; + + const value = new MintPrintingTokensArgs({ supply }); + const data = Buffer.from(serialize(METADATA_SCHEMA, value)); + + const keys = [ + { + pubkey: toPublicKey(destination), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(printingMint), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(updateAuthority), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(metadata), + isSigner: false, + isWritable: false, + }, + { + pubkey: toPublicKey(masterEdition), + isSigner: false, + isWritable: false, + }, + { + pubkey: PROGRAM_IDS.token, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(metadataProgramId), + data, + }), + ); +} + +export async function convertMasterEditionV1ToV2( + masterEdition: StringPublicKey, + oneTimeAuthMint: StringPublicKey, + printingMint: StringPublicKey, + instructions: TransactionInstruction[], +) { + const metadataProgramId = programIds().metadata; + + const data = Buffer.from([12]); + + const keys = [ + { + pubkey: toPublicKey(masterEdition), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(oneTimeAuthMint), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(printingMint), + isSigner: false, + isWritable: true, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(metadataProgramId), data, }), ); } -export async function getNameSymbol(metadata: Metadata): Promise { +export async function getEdition( + tokenMint: StringPublicKey, +): Promise { const PROGRAM_IDS = programIds(); return ( - await PublicKey.findProgramAddress( + await findProgramAddress( [ Buffer.from(METADATA_PREFIX), - PROGRAM_IDS.metadata.toBuffer(), - metadata.mint.toBuffer(), - Buffer.from(metadata.name), - Buffer.from(metadata.symbol), + toPublicKey(PROGRAM_IDS.metadata).toBuffer(), + toPublicKey(tokenMint).toBuffer(), + Buffer.from(EDITION), ], - PROGRAM_IDS.metadata, + toPublicKey(PROGRAM_IDS.metadata), ) )[0]; } -export async function getEdition(tokenMint: PublicKey): Promise { +export async function getMetadata( + tokenMint: StringPublicKey, +): Promise { const PROGRAM_IDS = programIds(); return ( - await PublicKey.findProgramAddress( + await findProgramAddress( [ Buffer.from(METADATA_PREFIX), - PROGRAM_IDS.metadata.toBuffer(), - tokenMint.toBuffer(), - Buffer.from(EDITION), + toPublicKey(PROGRAM_IDS.metadata).toBuffer(), + toPublicKey(tokenMint).toBuffer(), + ], + toPublicKey(PROGRAM_IDS.metadata), + ) + )[0]; +} + +export async function deprecatedGetReservationList( + masterEdition: StringPublicKey, + resource: StringPublicKey, +): Promise { + const PROGRAM_IDS = programIds(); + + return ( + await findProgramAddress( + [ + Buffer.from(METADATA_PREFIX), + toPublicKey(PROGRAM_IDS.metadata).toBuffer(), + toPublicKey(masterEdition).toBuffer(), + Buffer.from(RESERVATION), + toPublicKey(resource).toBuffer(), ], - PROGRAM_IDS.metadata, + toPublicKey(PROGRAM_IDS.metadata), ) )[0]; } -export async function getMetadata(tokenMint: PublicKey): Promise { +export async function getEditionMarkPda( + mint: StringPublicKey, + edition: BN, +): Promise { const PROGRAM_IDS = programIds(); + const editionNumber = Math.floor(edition.toNumber() / 248); return ( - await PublicKey.findProgramAddress( + await findProgramAddress( [ Buffer.from(METADATA_PREFIX), - PROGRAM_IDS.metadata.toBuffer(), - tokenMint.toBuffer(), + toPublicKey(PROGRAM_IDS.metadata).toBuffer(), + toPublicKey(mint).toBuffer(), + Buffer.from(EDITION), + Buffer.from(editionNumber.toString()), ], - PROGRAM_IDS.metadata, + toPublicKey(PROGRAM_IDS.metadata), ) )[0]; } diff --git a/packages/common/src/actions/vault.ts b/packages/common/src/actions/vault.ts index ea993c8d..8e1a58ab 100644 --- a/packages/common/src/actions/vault.ts +++ b/packages/common/src/actions/vault.ts @@ -1,17 +1,17 @@ import { - PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, TransactionInstruction, } from '@solana/web3.js'; -import { programIds } from '../utils/ids'; -import { deserializeBorsh } from './../utils/borsh'; -import { serialize } from 'borsh'; +import { programIds } from '../utils/programIds'; +import { deserializeUnchecked, serialize } from 'borsh'; import BN from 'bn.js'; +import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils'; export const VAULT_PREFIX = 'vault'; export enum VaultKey { - VaultV1 = 0, + Uninitialized = 0, + VaultV1 = 3, SafetyDepositBoxV1 = 1, ExternalPriceAccountV1 = 2, } @@ -30,20 +30,20 @@ export const MAX_EXTERNAL_ACCOUNT_SIZE = 1 + 8 + 32 + 1; export class Vault { key: VaultKey; /// Store token program used - tokenProgram: PublicKey; + tokenProgram: StringPublicKey; /// Mint that produces the fractional shares - fractionMint: PublicKey; + fractionMint: StringPublicKey; /// Authority who can make changes to the vault - authority: PublicKey; + authority: StringPublicKey; /// treasury where fractional shares are held for redemption by authority - fractionTreasury: PublicKey; + fractionTreasury: StringPublicKey; /// treasury where monies are held for fractional share holders to redeem(burn) shares once buyout is made - redeemTreasury: PublicKey; + redeemTreasury: StringPublicKey; /// Can authority mint more shares from fraction_mint after activation allowFurtherShareCreation: boolean; /// Must point at an ExternalPriceAccount, which gives permission and price for buyout. - pricingLookupAddress: PublicKey; + pricingLookupAddress: StringPublicKey; /// In inactive state, we use this to set the order key on Safety Deposit Boxes being added and /// then we increment it and save so the next safety deposit box gets the next number. /// In the Combined state during token redemption by authority, we use it as a decrementing counter each time @@ -58,13 +58,13 @@ export class Vault { lockedPricePerShare: BN; constructor(args: { - tokenProgram: PublicKey; - fractionMint: PublicKey; - authority: PublicKey; - fractionTreasury: PublicKey; - redeemTreasury: PublicKey; + tokenProgram: StringPublicKey; + fractionMint: StringPublicKey; + authority: StringPublicKey; + fractionTreasury: StringPublicKey; + redeemTreasury: StringPublicKey; allowFurtherShareCreation: boolean; - pricingLookupAddress: PublicKey; + pricingLookupAddress: StringPublicKey; tokenTypeCount: number; state: VaultState; lockedPricePerShare: BN; @@ -86,18 +86,18 @@ export class SafetyDepositBox { /// Each token type in a vault has it's own box that contains it's mint and a look-back key: VaultKey; /// VaultKey pointing to the parent vault - vault: PublicKey; + vault: StringPublicKey; /// This particular token's mint - tokenMint: PublicKey; + tokenMint: StringPublicKey; /// Account that stores the tokens under management - store: PublicKey; + store: StringPublicKey; /// the order in the array of registries order: number; constructor(args: { - vault: PublicKey; - tokenMint: PublicKey; - store: PublicKey; + vault: StringPublicKey; + tokenMint: StringPublicKey; + store: StringPublicKey; order: number; }) { this.key = VaultKey.SafetyDepositBoxV1; @@ -113,13 +113,13 @@ export class ExternalPriceAccount { pricePerShare: BN; /// Mint of the currency we are pricing the shares against, should be same as redeem_treasury. /// Most likely will be USDC mint most of the time. - priceMint: PublicKey; + priceMint: StringPublicKey; /// Whether or not combination has been allowed for this vault. allowedToCombine: boolean; constructor(args: { pricePerShare: BN; - priceMint: PublicKey; + priceMint: StringPublicKey; allowedToCombine: boolean; }) { this.key = VaultKey.ExternalPriceAccountV1; @@ -214,13 +214,13 @@ export const VAULT_SCHEMA = new Map([ kind: 'struct', fields: [ ['key', 'u8'], - ['tokenProgram', 'pubkey'], - ['fractionMint', 'pubkey'], - ['authority', 'pubkey'], - ['fractionTreasury', 'pubkey'], - ['redeemTreasury', 'pubkey'], + ['tokenProgram', 'pubkeyAsString'], + ['fractionMint', 'pubkeyAsString'], + ['authority', 'pubkeyAsString'], + ['fractionTreasury', 'pubkeyAsString'], + ['redeemTreasury', 'pubkeyAsString'], ['allowFurtherShareCreation', 'u8'], - ['pricingLookupAddress', 'u8'], + ['pricingLookupAddress', 'pubkeyAsString'], ['tokenTypeCount', 'u8'], ['state', 'u8'], ['lockedPricePerShare', 'u64'], @@ -233,9 +233,9 @@ export const VAULT_SCHEMA = new Map([ kind: 'struct', fields: [ ['key', 'u8'], - ['vault', 'pubkey'], - ['tokenMint', 'pubkey'], - ['store', 'pubkey'], + ['vault', 'pubkeyAsString'], + ['tokenMint', 'pubkeyAsString'], + ['store', 'pubkeyAsString'], ['order', 'u8'], ], }, @@ -247,7 +247,7 @@ export const VAULT_SCHEMA = new Map([ fields: [ ['key', 'u8'], ['pricePerShare', 'u64'], - ['priceMint', 'pubkey'], + ['priceMint', 'pubkeyAsString'], ['allowedToCombine', 'u8'], ], }, @@ -255,25 +255,69 @@ export const VAULT_SCHEMA = new Map([ ]); export const decodeVault = (buffer: Buffer) => { - return deserializeBorsh(VAULT_SCHEMA, Vault, buffer) as Vault; + return deserializeUnchecked(VAULT_SCHEMA, Vault, buffer) as Vault; +}; + +export const decodeExternalPriceAccount = (buffer: Buffer) => { + return deserializeUnchecked( + VAULT_SCHEMA, + ExternalPriceAccount, + buffer, + ) as ExternalPriceAccount; }; export const decodeSafetyDeposit = (buffer: Buffer) => { - return deserializeBorsh( + return deserializeUnchecked( VAULT_SCHEMA, SafetyDepositBox, buffer, ) as SafetyDepositBox; }; +export async function setVaultAuthority( + vault: StringPublicKey, + currentAuthority: StringPublicKey, + newAuthority: StringPublicKey, + instructions: TransactionInstruction[], +) { + const vaultProgramId = programIds().vault; + + const data = Buffer.from([10]); + + const keys = [ + { + pubkey: toPublicKey(vault), + isSigner: false, + isWritable: true, + }, + { + pubkey: toPublicKey(currentAuthority), + isSigner: true, + isWritable: false, + }, + { + pubkey: toPublicKey(newAuthority), + isSigner: false, + isWritable: false, + }, + ]; + instructions.push( + new TransactionInstruction({ + keys, + programId: toPublicKey(vaultProgramId), + data: data, + }), + ); +} + export async function initVault( allowFurtherShareCreation: boolean, - fractionalMint: PublicKey, - redeemTreasury: PublicKey, - fractionalTreasury: PublicKey, - vault: PublicKey, - vaultAuthority: PublicKey, - pricingLookupAddress: PublicKey, + fractionalMint: StringPublicKey, + redeemTreasury: StringPublicKey, + fractionalTreasury: StringPublicKey, + vault: StringPublicKey, + vaultAuthority: StringPublicKey, + pricingLookupAddress: StringPublicKey, instructions: TransactionInstruction[], ) { const vaultProgramId = programIds().vault; @@ -284,32 +328,32 @@ export async function initVault( const keys = [ { - pubkey: fractionalMint, + pubkey: toPublicKey(fractionalMint), isSigner: false, isWritable: true, }, { - pubkey: redeemTreasury, + pubkey: toPublicKey(redeemTreasury), isSigner: false, isWritable: true, }, { - pubkey: fractionalTreasury, + pubkey: toPublicKey(fractionalTreasury), isSigner: false, isWritable: true, }, { - pubkey: vault, + pubkey: toPublicKey(vault), isSigner: false, isWritable: true, }, { - pubkey: vaultAuthority, + pubkey: toPublicKey(vaultAuthority), isSigner: false, isWritable: false, }, { - pubkey: pricingLookupAddress, + pubkey: toPublicKey(pricingLookupAddress), isSigner: false, isWritable: false, }, @@ -328,43 +372,44 @@ export async function initVault( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data: data, }), ); } export async function getSafetyDepositBox( - vault: PublicKey, - tokenMint: PublicKey, -): Promise { + vault: StringPublicKey, + tokenMint: StringPublicKey, +): Promise { const vaultProgramId = programIds().vault; return ( - await PublicKey.findProgramAddress( - [Buffer.from(VAULT_PREFIX), vault.toBuffer(), tokenMint.toBuffer()], - vaultProgramId, + await findProgramAddress( + [ + Buffer.from(VAULT_PREFIX), + toPublicKey(vault).toBuffer(), + toPublicKey(tokenMint).toBuffer(), + ], + toPublicKey(vaultProgramId), ) )[0]; } export async function addTokenToInactiveVault( amount: BN, - tokenMint: PublicKey, - tokenAccount: PublicKey, - tokenStoreAccount: PublicKey, - vault: PublicKey, - vaultAuthority: PublicKey, - payer: PublicKey, - transferAuthority: PublicKey, + tokenMint: StringPublicKey, + tokenAccount: StringPublicKey, + tokenStoreAccount: StringPublicKey, + vault: StringPublicKey, + vaultAuthority: StringPublicKey, + payer: StringPublicKey, + transferAuthority: StringPublicKey, instructions: TransactionInstruction[], ) { const vaultProgramId = programIds().vault; - const safetyDepositBox: PublicKey = await getSafetyDepositBox( - vault, - tokenMint, - ); + const safetyDepositBox = await getSafetyDepositBox(vault, tokenMint); const value = new AmountArgs({ instruction: 1, @@ -374,37 +419,37 @@ export async function addTokenToInactiveVault( const data = Buffer.from(serialize(VAULT_SCHEMA, value)); const keys = [ { - pubkey: safetyDepositBox, + pubkey: toPublicKey(safetyDepositBox), isSigner: false, isWritable: true, }, { - pubkey: tokenAccount, + pubkey: toPublicKey(tokenAccount), isSigner: false, isWritable: true, }, { - pubkey: tokenStoreAccount, + pubkey: toPublicKey(tokenStoreAccount), isSigner: false, isWritable: true, }, { - pubkey: vault, + pubkey: toPublicKey(vault), isSigner: false, isWritable: true, }, { - pubkey: vaultAuthority, + pubkey: toPublicKey(vaultAuthority), isSigner: true, isWritable: false, }, { - pubkey: payer, + pubkey: toPublicKey(payer), isSigner: true, isWritable: false, }, { - pubkey: transferAuthority, + pubkey: toPublicKey(transferAuthority), isSigner: true, isWritable: false, }, @@ -427,7 +472,7 @@ export async function addTokenToInactiveVault( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data, }), ); @@ -435,18 +480,22 @@ export async function addTokenToInactiveVault( export async function activateVault( numberOfShares: BN, - vault: PublicKey, - fractionMint: PublicKey, - fractionTreasury: PublicKey, - vaultAuthority: PublicKey, + vault: StringPublicKey, + fractionMint: StringPublicKey, + fractionTreasury: StringPublicKey, + vaultAuthority: StringPublicKey, instructions: TransactionInstruction[], ) { const vaultProgramId = programIds().vault; const fractionMintAuthority = ( - await PublicKey.findProgramAddress( - [Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()], - vaultProgramId, + await findProgramAddress( + [ + Buffer.from(VAULT_PREFIX), + toPublicKey(vaultProgramId).toBuffer(), + toPublicKey(vault).toBuffer(), + ], + toPublicKey(vaultProgramId), ) )[0]; @@ -455,27 +504,27 @@ export async function activateVault( const keys = [ { - pubkey: vault, + pubkey: toPublicKey(vault), isSigner: false, isWritable: true, }, { - pubkey: fractionMint, + pubkey: toPublicKey(fractionMint), isSigner: false, isWritable: true, }, { - pubkey: fractionTreasury, + pubkey: toPublicKey(fractionTreasury), isSigner: false, isWritable: true, }, { - pubkey: fractionMintAuthority, + pubkey: toPublicKey(fractionMintAuthority), isSigner: false, isWritable: false, }, { - pubkey: vaultAuthority, + pubkey: toPublicKey(vaultAuthority), isSigner: true, isWritable: false, }, @@ -488,31 +537,35 @@ export async function activateVault( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data, }), ); } export async function combineVault( - vault: PublicKey, - outstandingShareTokenAccount: PublicKey, - payingTokenAccount: PublicKey, - fractionMint: PublicKey, - fractionTreasury: PublicKey, - redeemTreasury: PublicKey, - newVaultAuthority: PublicKey | undefined, - vaultAuthority: PublicKey, - transferAuthority: PublicKey, - externalPriceAccount: PublicKey, + vault: StringPublicKey, + outstandingShareTokenAccount: StringPublicKey, + payingTokenAccount: StringPublicKey, + fractionMint: StringPublicKey, + fractionTreasury: StringPublicKey, + redeemTreasury: StringPublicKey, + newVaultAuthority: StringPublicKey | undefined, + vaultAuthority: StringPublicKey, + transferAuthority: StringPublicKey, + externalPriceAccount: StringPublicKey, instructions: TransactionInstruction[], ) { const vaultProgramId = programIds().vault; const burnAuthority = ( - await PublicKey.findProgramAddress( - [Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()], - vaultProgramId, + await findProgramAddress( + [ + Buffer.from(VAULT_PREFIX), + toPublicKey(vaultProgramId).toBuffer(), + toPublicKey(vault).toBuffer(), + ], + toPublicKey(vaultProgramId), ) )[0]; @@ -520,57 +573,57 @@ export async function combineVault( const keys = [ { - pubkey: vault, + pubkey: toPublicKey(vault), isSigner: false, isWritable: true, }, { - pubkey: outstandingShareTokenAccount, + pubkey: toPublicKey(outstandingShareTokenAccount), isSigner: false, isWritable: true, }, { - pubkey: payingTokenAccount, + pubkey: toPublicKey(payingTokenAccount), isSigner: false, isWritable: true, }, { - pubkey: fractionMint, + pubkey: toPublicKey(fractionMint), isSigner: false, isWritable: true, }, { - pubkey: fractionTreasury, + pubkey: toPublicKey(fractionTreasury), isSigner: false, isWritable: true, }, { - pubkey: redeemTreasury, + pubkey: toPublicKey(redeemTreasury), isSigner: false, isWritable: true, }, { - pubkey: newVaultAuthority || vaultAuthority, + pubkey: toPublicKey(newVaultAuthority || vaultAuthority), isSigner: false, isWritable: false, }, { - pubkey: vaultAuthority, + pubkey: toPublicKey(vaultAuthority), isSigner: true, isWritable: false, }, { - pubkey: transferAuthority, + pubkey: toPublicKey(transferAuthority), isSigner: true, isWritable: false, }, { - pubkey: burnAuthority, + pubkey: toPublicKey(burnAuthority), isSigner: false, isWritable: false, }, { - pubkey: externalPriceAccount, + pubkey: toPublicKey(externalPriceAccount), isSigner: false, isWritable: false, }, @@ -583,7 +636,7 @@ export async function combineVault( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data, }), ); @@ -591,20 +644,24 @@ export async function combineVault( export async function withdrawTokenFromSafetyDepositBox( amount: BN, - destination: PublicKey, - safetyDepositBox: PublicKey, - storeKey: PublicKey, - vault: PublicKey, - fractionMint: PublicKey, - vaultAuthority: PublicKey, + destination: StringPublicKey, + safetyDepositBox: StringPublicKey, + storeKey: StringPublicKey, + vault: StringPublicKey, + fractionMint: StringPublicKey, + vaultAuthority: StringPublicKey, instructions: TransactionInstruction[], ) { const vaultProgramId = programIds().vault; const transferAuthority = ( - await PublicKey.findProgramAddress( - [Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()], - vaultProgramId, + await findProgramAddress( + [ + Buffer.from(VAULT_PREFIX), + toPublicKey(vaultProgramId).toBuffer(), + toPublicKey(vault).toBuffer(), + ], + toPublicKey(vaultProgramId), ) )[0]; @@ -613,37 +670,37 @@ export async function withdrawTokenFromSafetyDepositBox( const keys = [ { - pubkey: destination, + pubkey: toPublicKey(destination), isSigner: false, isWritable: true, }, { - pubkey: safetyDepositBox, + pubkey: toPublicKey(safetyDepositBox), isSigner: false, isWritable: true, }, { - pubkey: storeKey, + pubkey: toPublicKey(storeKey), isSigner: false, isWritable: true, }, { - pubkey: vault, + pubkey: toPublicKey(vault), isSigner: false, isWritable: true, }, { - pubkey: fractionMint, + pubkey: toPublicKey(fractionMint), isSigner: false, isWritable: true, }, { - pubkey: vaultAuthority, + pubkey: toPublicKey(vaultAuthority), isSigner: true, isWritable: false, }, { - pubkey: transferAuthority, + pubkey: toPublicKey(transferAuthority), isSigner: false, isWritable: false, }, @@ -661,14 +718,14 @@ export async function withdrawTokenFromSafetyDepositBox( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data, }), ); } export async function updateExternalPriceAccount( - externalPriceAccountKey: PublicKey, + externalPriceAccountKey: StringPublicKey, externalPriceAccount: ExternalPriceAccount, instructions: TransactionInstruction[], ) { @@ -680,7 +737,7 @@ export async function updateExternalPriceAccount( const keys = [ { - pubkey: externalPriceAccountKey, + pubkey: toPublicKey(externalPriceAccountKey), isSigner: false, isWritable: true, }, @@ -688,8 +745,25 @@ export async function updateExternalPriceAccount( instructions.push( new TransactionInstruction({ keys, - programId: vaultProgramId, + programId: toPublicKey(vaultProgramId), data, }), ); } + +export async function getSafetyDepositBoxAddress( + vault: StringPublicKey, + tokenMint: StringPublicKey, +): Promise { + const PROGRAM_IDS = programIds(); + return ( + await findProgramAddress( + [ + Buffer.from(VAULT_PREFIX), + toPublicKey(vault).toBuffer(), + toPublicKey(tokenMint).toBuffer(), + ], + toPublicKey(PROGRAM_IDS.vault), + ) + )[0]; +} diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index 91106273..ee48d643 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -1,6 +1,8 @@ import { PublicKey } from '@solana/web3.js'; import { TokenSwapLayout, TokenSwapLayoutV1 } from '../models/tokenSwap'; +export type StringPublicKey = string; + export const WRAPPED_SOL_MINT = new PublicKey( 'So11111111111111111111111111111111111111112', ); @@ -38,6 +40,22 @@ export const METAPLEX_ID = new PublicKey( 'EPtpKdKW8qciGVd1UFyGjgbBHTbSAyvbY61h9uQGVgeu', ); +const PubKeysInternedMap = new Map(); + +export const toPublicKey = (key: string | PublicKey) => { + if (typeof key !== 'string') { + return key; + } + + let result = PubKeysInternedMap.get(key); + if (!result) { + result = new PublicKey(key); + PubKeysInternedMap.set(key, result); + } + + return result; +}; + export let SYSTEM = new PublicKey('11111111111111111111111111111111'); let WORMHOLE_BRIDGE: { diff --git a/packages/common/src/utils/programIds.ts b/packages/common/src/utils/programIds.ts new file mode 100644 index 00000000..81343e2e --- /dev/null +++ b/packages/common/src/utils/programIds.ts @@ -0,0 +1,104 @@ +import { PublicKey } from '@solana/web3.js'; +import { findProgramAddress } from '../utils'; + +import { + METADATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + METAPLEX_ID, + BPF_UPGRADE_LOADER_ID, + SYSTEM, + MEMO_ID, + VAULT_ID, + AUCTION_ID, + toPublicKey, +} from './ids'; + +export const ENABLE_FEES_INPUT = false; + +// legacy pools are used to show users contributions in those pools to allow for withdrawals of funds +export const PROGRAM_IDS = [ + { + name: 'mainnet-beta', + }, + { + name: 'testnet', + }, + + { + name: 'devnet', + }, + { + name: 'localnet', + }, +]; + +let STORE_OWNER_ADDRESS: PublicKey | undefined; + +export const setStoreID = (storeId: any) => { + STORE_OWNER_ADDRESS = storeId + ? new PublicKey(`${storeId}`) + : // DEFAULT STORE FRONT OWNER FOR METAPLEX + undefined; +}; + +const getStoreID = async () => { + if (!STORE_OWNER_ADDRESS) { + return undefined; + } + + let urlStoreId: PublicKey | null = null; + try { + const urlParams = new URLSearchParams(window.location.search); + const text = urlParams.get('store'); + if (text) { + urlStoreId = new PublicKey(text); + } + } catch { + // ignore + } + + const storeOwnerAddress = urlStoreId || STORE_OWNER_ADDRESS; + console.log(`STORE_OWNER_ADDRESS: ${storeOwnerAddress?.toBase58()}`); + const programs = await findProgramAddress( + [ + Buffer.from('metaplex'), + toPublicKey(METAPLEX_ID).toBuffer(), + storeOwnerAddress.toBuffer(), + ], + toPublicKey(METAPLEX_ID), + ); + const CUSTOM = programs[0]; + console.log(`CUSTOM STORE: ${CUSTOM}`); + + return CUSTOM; +}; + +export const setProgramIds = async (envName: string) => { + let instance = PROGRAM_IDS.find(env => envName.indexOf(env.name) >= 0); + if (!instance) { + return; + } + + if (!STORE) { + const potential_store = await getStoreID(); + STORE = potential_store ? toPublicKey(potential_store) : undefined; + } +}; + +let STORE: PublicKey | undefined; + +export const programIds = () => { + return { + token: TOKEN_PROGRAM_ID, + associatedToken: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + bpf_upgrade_loader: BPF_UPGRADE_LOADER_ID, + system: SYSTEM, + metadata: METADATA_PROGRAM_ID, + memo: MEMO_ID, + vault: VAULT_ID, + auction: AUCTION_ID, + metaplex: METAPLEX_ID, + store: STORE, + }; +}; diff --git a/packages/common/src/utils/utils.ts b/packages/common/src/utils/utils.ts index 85c3d25b..2a4e308e 100644 --- a/packages/common/src/utils/utils.ts +++ b/packages/common/src/utils/utils.ts @@ -35,7 +35,11 @@ export function useLocalStorageState(key: string, defaultState?: string) { if (newState === null) { localStorage.removeItem(key); } else { - localStorage.setItem(key, JSON.stringify(newState)); + try { + localStorage.setItem(key, JSON.stringify(newState)); + } catch { + // ignore + } } }, [state, key], @@ -44,6 +48,38 @@ export function useLocalStorageState(key: string, defaultState?: string) { return [state, setLocalStorageState]; } +export const findProgramAddress = async ( + seeds: (Buffer | Uint8Array)[], + programId: PublicKey, +) => { + const key = + 'pda-' + + seeds.reduce((agg, item) => agg + item.toString('hex'), '') + + programId.toString(); + let cached = localStorage.getItem(key); + if (cached) { + const value = JSON.parse(cached); + + return [value.key, parseInt(value.nonce)] as [string, number]; + } + + const result = await PublicKey.findProgramAddress(seeds, programId); + + try { + localStorage.setItem( + key, + JSON.stringify({ + key: result[0].toBase58(), + nonce: result[1], + }), + ); + } catch { + // ignore + } + + return [result[0].toBase58(), result[1]] as [string, number]; +}; + // shorten the checksummed version of the input address to have 4 characters at start and end export function shortenAddress(address: string, chars = 4): string { return `${address.slice(0, chars)}...${address.slice(-chars)}`; @@ -159,7 +195,7 @@ export function fromLamports( : account.info.amount.toNumber(), ); - const precision = Math.pow(10, mint?.decimals || 0); + const precision = Math.pow(10, mint?.decimals || 9); return (amount / precision) * rate; } @@ -187,17 +223,17 @@ const abbreviateNumber = (number: number, precision: number) => { export const formatAmount = ( val: number, - precision: number = 6, + precision: number = 2, abbr: boolean = true, ) => (abbr ? abbreviateNumber(val, precision) : val.toFixed(precision)); export function formatTokenAmount( - account?: TokenAccount, + account?: TokenAccount | number | BN, mint?: MintInfo, rate: number = 1.0, prefix = '', suffix = '', - precision = 6, + precision = 2, abbr = false, ): string { if (!account) {