Skip to content

Commit

Permalink
feat: setStoreV2 with StoreConfig (#90)
Browse files Browse the repository at this point in the history
* feat: setStoreV2 with StoreConfig

* chore: add test

* fix: fix settingsUri type

* fix: (store) store init config pda

* fix: fix StoreConfig type

Co-authored-by: austbot <me@austbot.com>
  • Loading branch information
exromany and austbot authored Dec 6, 2021
1 parent 34dab01 commit 0cea78d
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './transactions';
export * from './initStore';
export * from './initStoreV2';
export * from './mintNFT';
export * from './mintEditionFromMaster';
export * from './closeVault';
Expand Down
42 changes: 42 additions & 0 deletions src/actions/initStoreV2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { PublicKey } from '@solana/web3.js';
import { Wallet } from '../wallet';
import { Connection } from '../Connection';
import { sendTransaction } from './transactions';
import { SetStoreV2, Store, StoreConfig } from '../programs/metaplex';

interface IInitStoreV2Params {
connection: Connection;
wallet: Wallet;
isPublic?: boolean;
settingsUri?: string | null;
}

interface IInitStoreV2Response {
storeId: PublicKey;
configId: PublicKey;
txId: string;
}

export const initStoreV2 = async ({
connection,
wallet,
settingsUri = null,
isPublic = true,
}: IInitStoreV2Params): Promise<IInitStoreV2Response> => {
const storeId = await Store.getPDA(wallet.publicKey);
const configId = await StoreConfig.getPDA(storeId);
const tx = new SetStoreV2(
{ feePayer: wallet.publicKey },
{
admin: new PublicKey(wallet.publicKey),
store: storeId,
config: configId,
isPublic,
settingsUri,
},
);

const txId = await sendTransaction({ connection, wallet, txs: [tx] });

return { storeId, configId, txId };
};
4 changes: 4 additions & 0 deletions src/programs/metaplex/MetaplexProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ export enum MetaplexKey {
AuctionManagerV2 = 10,
BidRedemptionTicketV2 = 11,
AuctionWinnerTokenTypeTrackerV1 = 12,
StoreIndexerV1 = 13,
AuctionCacheV1 = 14,
StoreConfigV1 = 15,
}

export class MetaplexProgram extends Program {
static readonly PREFIX = 'metaplex';
static readonly CONFIG = 'config';
static readonly TOTALS = 'totals';
static readonly PUBKEY = new PublicKey(config.programs.metaplex);
}
54 changes: 54 additions & 0 deletions src/programs/metaplex/accounts/StoreConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ERROR_INVALID_ACCOUNT_DATA, ERROR_INVALID_OWNER } from '@metaplex/errors';
import { AnyPublicKey } from '@metaplex/types';
import { Borsh } from '@metaplex/utils';
import { AccountInfo, PublicKey } from '@solana/web3.js';
import { Buffer } from 'buffer';
import { Account } from '../../../Account';
import { MetaplexKey, MetaplexProgram } from '../MetaplexProgram';

type Args = {
settingsUri: string;
};
export class StoreConfigData extends Borsh.Data<Args> {
static readonly SCHEMA = this.struct([
['key', 'u8'],
['settingsUri', { kind: 'option', type: 'string' }],
]);

key: MetaplexKey = MetaplexKey.StoreConfigV1;
settingsUri: string;

constructor(args: Args) {
super(args);
this.key = MetaplexKey.StoreConfigV1;
}
}

export class StoreConfig extends Account<StoreConfigData> {
constructor(pubkey: AnyPublicKey, info: AccountInfo<Buffer>) {
super(pubkey, info);

if (!this.assertOwner(MetaplexProgram.PUBKEY)) {
throw ERROR_INVALID_OWNER();
}

if (!StoreConfig.isCompatible(this.info.data)) {
throw ERROR_INVALID_ACCOUNT_DATA();
}

this.data = StoreConfigData.deserialize(this.info.data);
}

static isCompatible(data: Buffer) {
return data[0] === MetaplexKey.StoreConfigV1;
}

static async getPDA(store: AnyPublicKey) {
return MetaplexProgram.findProgramAddress([
Buffer.from(MetaplexProgram.PREFIX),
MetaplexProgram.PUBKEY.toBuffer(),
Buffer.from(MetaplexProgram.CONFIG),
new PublicKey(store).toBuffer(),
]);
}
}
1 change: 1 addition & 0 deletions src/programs/metaplex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './accounts/PayoutTicket';
export * from './accounts/PrizeTrackingTicket';
export * from './accounts/SafetyDepositConfig';
export * from './accounts/Store';
export * from './accounts/StoreConfig';
export * from './accounts/WhitelistedCreator';
export * from './accounts/AuctionWinnerTokenTypeTracker';
export * from './MetaplexProgram';
Expand Down
99 changes: 99 additions & 0 deletions src/programs/metaplex/transactions/SetStoreV2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import {
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionCtorFields,
TransactionInstruction,
} from '@solana/web3.js';
import { Borsh } from '@metaplex/utils';
import { Transaction } from '../../../Transaction';
import { VaultProgram } from '../../vault';
import { MetadataProgram } from '../../metadata';
import { AuctionProgram } from '../../auction';
import { MetaplexProgram } from '../MetaplexProgram';
import { ParamsWithStore } from '@metaplex/types';

export class SetStoreV2Args extends Borsh.Data<{ public: boolean; settingsUri: string | null }> {
static readonly SCHEMA = this.struct([
['instruction', 'u8'],
['public', 'u8'],
['settingsUri', { kind: 'option', type: 'string' }],
]);

instruction = 23;
public: boolean;
settingsUri: string | null;
}

type SetStoreV2Params = {
admin: PublicKey;
config: PublicKey;
isPublic: boolean;
settingsUri: string | null;
};

export class SetStoreV2 extends Transaction {
constructor(options: TransactionCtorFields, params: ParamsWithStore<SetStoreV2Params>) {
super(options);
const { feePayer } = options;
const { admin, store, config, isPublic, settingsUri } = params;

const data = SetStoreV2Args.serialize({ public: isPublic, settingsUri });

this.add(
new TransactionInstruction({
keys: [
{
pubkey: store,
isSigner: false,
isWritable: true,
},
{
pubkey: config,
isSigner: false,
isWritable: true,
},
{
pubkey: admin,
isSigner: true,
isWritable: false,
},
{
pubkey: feePayer,
isSigner: true,
isWritable: false,
},
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{
pubkey: VaultProgram.PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: MetadataProgram.PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: AuctionProgram.PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
],
programId: MetaplexProgram.PUBKEY,
data,
}),
);
}
}
1 change: 1 addition & 0 deletions src/programs/metaplex/transactions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './SetStore';
export * from './SetStoreV2';
export * from './SetWhitelistedCreator';
export * from './StartAuction';
export * from './InitAuctionManagerV2';
Expand Down
60 changes: 60 additions & 0 deletions test/actions/initStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Keypair } from '@solana/web3.js';
import { Connection, NodeWallet } from '../../src';
import { initStore, initStoreV2 } from '../../src/actions';
import { FEE_PAYER, NETWORK } from '../utils';
import { Store } from '../../src/programs/metaplex';
import { uri } from './shared';

describe('init Store', () => {
const connection = new Connection(NETWORK);
const wallet = new NodeWallet(FEE_PAYER);
let mint: Keypair;

beforeEach(() => {
mint = Keypair.generate();
jest.spyOn(Keypair, 'generate').mockReturnValue(mint);
});

test('creates store with initStore', async () => {
const storeResponse = await initStore({
connection,
wallet,
isPublic: false,
});

const storeId = await Store.getPDA(wallet.publicKey);

expect(storeResponse).toMatchObject({
storeId,
});
});

test('creates store with initStoreV2', async () => {
const storeResponse = await initStoreV2({
connection,
wallet,
isPublic: false,
settingsUri: uri,
});

const storeId = await Store.getPDA(wallet.publicKey);

expect(storeResponse).toMatchObject({
storeId,
});
});

test('creates store with initStoreV2 without storeV2', async () => {
const storeResponse = await initStoreV2({
connection,
wallet,
isPublic: false,
});

const storeId = await Store.getPDA(wallet.publicKey);

expect(storeResponse).toMatchObject({
storeId,
});
});
});

0 comments on commit 0cea78d

Please sign in to comment.