Merge branch 'master' into instant-sale
# Conflicts: # js/packages/cli/src/helpers/accounts.ts # js/packages/cli/src/types.ts # js/packages/common/src/models/metaplex/claimBid.ts # js/packages/common/src/models/metaplex/index.ts # js/packages/common/src/models/metaplex/redeemBid.ts # js/packages/common/src/models/metaplex/redeemFullRightsTransferBid.ts # js/packages/common/src/models/metaplex/redeemPrintingV2Bid.ts # js/packages/web/src/components/AuctionCard/index.tsx # js/packages/web/src/contexts/meta/meta.tsx # js/packages/web/src/views/auctionCreate/index.tsx
This commit is contained in:
commit
bf39727cc9
|
@ -1,15 +1,121 @@
|
|||
# CANDY MACHINE
|
||||
|
||||
Format
|
||||
https://user-images.githubusercontent.com/81876372/133098938-dc2c91a6-1280-4ee1-bf0e-db0ccc972ff7.mp4
|
||||
|
||||
* Folder with files named from 0-1.png
|
||||
* JSON file with attributes, format
|
||||
- Array with indices matching images
|
||||
- Contains: title, description and array of traits ({"display_type":"number","trait_type":"generation","value":2})
|
||||
|
||||
|
||||
Install dependencies
|
||||
```
|
||||
yarn
|
||||
## assets folder
|
||||
* Folder with file pairs named with inrementing integer numbers starting from 0.png and 0.json
|
||||
* the image HAS TO be a `PNG`
|
||||
* JSON format can be checked out here: https://docs.metaplex.com/nft-standard. example below:
|
||||
```json
|
||||
{
|
||||
"name": "Solflare X NFT",
|
||||
"symbol": "",
|
||||
"description": "Celebratory Solflare NFT for the Solflare X launch",
|
||||
"seller_fee_basis_points": 0,
|
||||
"image": "https://www.arweave.net/abcd5678?ext=png",
|
||||
"animation_url": "https://www.arweave.net/efgh1234?ext=mp4",
|
||||
"external_url": "https://solflare.com",
|
||||
"attributes": [
|
||||
{
|
||||
"trait_type": "web",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"trait_type": "mobile",
|
||||
"value": "yes"
|
||||
},
|
||||
{
|
||||
"trait_type": "extension",
|
||||
"value": "yes"
|
||||
}
|
||||
],
|
||||
"collection": {
|
||||
"name": "Solflare X NFT",
|
||||
"family": "Solflare"
|
||||
},
|
||||
"properties": {
|
||||
"files": [
|
||||
{
|
||||
"uri": "https://www.arweave.net/abcd5678?ext=png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"uri": "https://watch.videodelivery.net/9876jkl",
|
||||
"type": "unknown",
|
||||
"cdn": true
|
||||
},
|
||||
{
|
||||
"uri": "https://www.arweave.net/efgh1234?ext=mp4",
|
||||
"type": "video/mp4"
|
||||
}
|
||||
],
|
||||
"category": "video",
|
||||
"creators": [
|
||||
{
|
||||
"address": "SOLFLR15asd9d21325bsadythp547912501b",
|
||||
"share": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Install and build
|
||||
```
|
||||
yarn install
|
||||
yarn build
|
||||
yarn run package:linuxb
|
||||
OR
|
||||
yarn run package:linux
|
||||
OR
|
||||
yarn run package:macos
|
||||
```
|
||||
|
||||
You can now either use `metaplex` OR the `ts-node cli` to execute the following commands.
|
||||
|
||||
1. Upload your images and metadata. Refer to the NFT [standard](https://docs.metaplex.com/nft-standard) for the correct format.
|
||||
```
|
||||
metaplex upload ~/nft-test/mini_drop --keypair ~/.config/solana/id.json
|
||||
ts-node cli upload ~/nft-test/mini_drop --keypair ~/.config/solana/id.json
|
||||
```
|
||||
|
||||
2. Verify everything is uploaded. Rerun the first command until it is.
|
||||
```
|
||||
metaplex verify --keypair ~/.config/solana/id.json
|
||||
ts-node cli verify --keypair ~/.config/solana/id.json
|
||||
```
|
||||
|
||||
3. Create your candy machine. It can cost up to ~15 solana per 10,000 images.
|
||||
```
|
||||
metaplex create_candy_machine -k ~/.config/solana/id.json -p 1
|
||||
ts-node cli create_candy_machine -k ~/.config/solana/id.json -p 3
|
||||
```
|
||||
|
||||
4. Set the start date for your candy machine.
|
||||
```
|
||||
metaplex set_start_date -k ~/.config/solana/id.json -d "20 Apr 2021 04:20:00 GMT"
|
||||
ts-node cli set_start_date -k ~/.config/solana/id.json -d "20 Apr 2021 04:20:00 GMT"
|
||||
```
|
||||
|
||||
5. Test mint a token (provided it's after the start date)
|
||||
```
|
||||
metaplex mint_one_token -k ~/.config/solana/id.json
|
||||
ts-node cli mint_one_token -k ~/.config/solana/id.json
|
||||
```
|
||||
|
||||
6. Check if you received any tokens.
|
||||
```
|
||||
spl-token accounts
|
||||
```
|
||||
|
||||
7. If you are listed as a creator, run this command to sign your NFTs post sale. This will sign only the latest candy machine that you've created (stored in .cache/candyMachineList.json).
|
||||
```
|
||||
metaplex sign_candy_machine_metadata -k ~/.config/solana/id.json
|
||||
ts-node cli sign_candy_machine_metadata -k ~/.config/solana/id.json
|
||||
```
|
||||
|
||||
8. If you wish to sign metadata from another candy machine run with the --cndy flag.
|
||||
```
|
||||
metaplex sign_candy_machine_metadata -k ~/.config/solana/id.json --cndy CANDY_MACHINE_ADDRESS_HERE
|
||||
ts-node cli sign_candy_machine_metadata -k ~/.config/solana/id.json --cndy CANDY_MACHINE_ADDRESS_HERE
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"build": "tsc -p ./src",
|
||||
"watch": "tsc -w -p ./src",
|
||||
"package:linux": "pkg . --no-bytecode --targets node14-linux-x64 --output bin/linux/metaplex",
|
||||
"package:linuxb": "pkg . --targets node14-linux-x64 --output bin/linux/metaplex",
|
||||
"package:macos": "pkg . --no-bytecode --targets node14-macos-x64 --output bin/macos/metaplex",
|
||||
"format": "prettier --loglevel warn --write \"**/*.{ts,js,json,yaml}\"",
|
||||
"format:check": "prettier --loglevel warn --check \"**/*.{ts,js,json,yaml}\"",
|
||||
|
@ -21,10 +22,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@project-serum/anchor": "^0.13.2",
|
||||
"@solana/spl-token": "^0.1.8",
|
||||
"arweave": "^1.10.16",
|
||||
"bn.js": "^5.2.0",
|
||||
"borsh": "^0.4.0",
|
||||
"commander": "^8.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"loglevel": "^1.7.1",
|
||||
"node-fetch": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,43 +1,21 @@
|
|||
#!/usr/bin/env node
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import FormData from 'form-data';
|
||||
import { program } from 'commander';
|
||||
import * as anchor from '@project-serum/anchor';
|
||||
import BN from 'bn.js';
|
||||
import { MintLayout, Token } from '@solana/spl-token';
|
||||
|
||||
import {
|
||||
chunks,
|
||||
fromUTF8Array,
|
||||
loadCache,
|
||||
parsePrice,
|
||||
saveCache,
|
||||
upload,
|
||||
} from './helpers/various';
|
||||
import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js';
|
||||
import { createAssociatedTokenAccountInstruction } from './helpers/instructions';
|
||||
import {
|
||||
CACHE_PATH,
|
||||
CONFIG_ARRAY_START,
|
||||
CONFIG_LINE_SIZE,
|
||||
EXTENSION_JSON,
|
||||
EXTENSION_PNG,
|
||||
PAYMENT_WALLET,
|
||||
TOKEN_METADATA_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from './helpers/constants';
|
||||
import { sendTransactionWithRetryWithKeypair } from './helpers/transactions';
|
||||
import {
|
||||
createConfig,
|
||||
getCandyMachineAddress,
|
||||
getMasterEdition,
|
||||
getMetadata,
|
||||
getTokenWallet,
|
||||
loadAnchorProgram,
|
||||
loadWalletKey,
|
||||
} from './helpers/accounts';
|
||||
import { fromUTF8Array, parsePrice } from './helpers/various';
|
||||
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { CACHE_PATH, CONFIG_ARRAY_START, CONFIG_LINE_SIZE, EXTENSION_JSON, EXTENSION_PNG, } from './helpers/constants';
|
||||
import { getCandyMachineAddress, loadAnchorProgram, loadWalletKey, } from './helpers/accounts';
|
||||
import { Config } from './types';
|
||||
import { upload } from './commands/upload';
|
||||
import { loadCache, saveCache } from './helpers/cache';
|
||||
import { mint } from "./commands/mint";
|
||||
import { signMetadata } from "./commands/sign";
|
||||
import { signAllMetadataFromCandyMachine } from "./commands/signAll";
|
||||
import log from 'loglevel';
|
||||
|
||||
program.version('0.0.1');
|
||||
|
||||
|
@ -45,8 +23,9 @@ if (!fs.existsSync(CACHE_PATH)) {
|
|||
fs.mkdirSync(CACHE_PATH);
|
||||
}
|
||||
|
||||
program
|
||||
.command('upload')
|
||||
log.setLevel(log.levels.INFO);
|
||||
|
||||
programCommand('upload')
|
||||
.argument(
|
||||
'<directory>',
|
||||
'Directory containing images named from 0-n',
|
||||
|
@ -54,22 +33,9 @@ program
|
|||
return fs.readdirSync(`${val}`).map(file => path.join(val, file));
|
||||
},
|
||||
)
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
// .argument('[second]', 'integer argument', (val) => parseInt(val), 1000)
|
||||
.option('-n, --number <number>', 'Number of images to upload')
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp')
|
||||
.action(async (files: string[], options, cmd) => {
|
||||
const { number, keypair, env, cacheName } = cmd.opts();
|
||||
const parsedNumber = parseInt(number);
|
||||
const {number, keypair, env, cacheName} = cmd.opts();
|
||||
|
||||
const pngFileCount = files.filter(it => {
|
||||
return it.endsWith(EXTENSION_PNG);
|
||||
|
@ -78,244 +44,39 @@ program
|
|||
return it.endsWith(EXTENSION_JSON);
|
||||
}).length;
|
||||
|
||||
const parsedNumber = parseInt(number);
|
||||
const elemCount = parsedNumber ? parsedNumber : pngFileCount;
|
||||
|
||||
if (pngFileCount !== jsonFileCount) {
|
||||
throw new Error(
|
||||
`number of png files (${pngFileCount}) is different than the number of json files (${jsonFileCount})`,
|
||||
);
|
||||
throw new Error(`number of png files (${pngFileCount}) is different than the number of json files (${jsonFileCount})`);
|
||||
}
|
||||
|
||||
if (parsedNumber < pngFileCount) {
|
||||
throw new Error(
|
||||
`max number (${parsedNumber})cannot be smaller than the number of elements in the source folder (${pngFileCount})`,
|
||||
);
|
||||
if (elemCount < pngFileCount) {
|
||||
throw new Error(`max number (${elemCount})cannot be smaller than the number of elements in the source folder (${pngFileCount})`);
|
||||
}
|
||||
|
||||
const savedContent = loadCache(cacheName, env);
|
||||
const cacheContent = savedContent || {};
|
||||
log.info(`Beginning the upload for ${elemCount} (png+json) pairs`)
|
||||
|
||||
if (!cacheContent.program) {
|
||||
cacheContent.program = {};
|
||||
}
|
||||
|
||||
let existingInCache = [];
|
||||
if (!cacheContent.items) {
|
||||
cacheContent.items = {};
|
||||
} else {
|
||||
existingInCache = Object.keys(cacheContent.items);
|
||||
}
|
||||
|
||||
const seen = {};
|
||||
const newFiles = [];
|
||||
|
||||
files.forEach(f => {
|
||||
if (!seen[f.replace(EXTENSION_PNG, '').split('/').pop()]) {
|
||||
seen[f.replace(EXTENSION_PNG, '').split('/').pop()] = true;
|
||||
newFiles.push(f);
|
||||
}
|
||||
});
|
||||
existingInCache.forEach(f => {
|
||||
if (!seen[f]) {
|
||||
seen[f] = true;
|
||||
newFiles.push(f + '.png');
|
||||
}
|
||||
});
|
||||
|
||||
const images = newFiles.filter(val => path.extname(val) === EXTENSION_PNG);
|
||||
const SIZE = images.length;
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
|
||||
let config = cacheContent.program.config
|
||||
? new PublicKey(cacheContent.program.config)
|
||||
: undefined;
|
||||
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
const image = images[i];
|
||||
const imageName = path.basename(image);
|
||||
const index = imageName.replace(EXTENSION_PNG, '');
|
||||
|
||||
console.log(`Processing file: ${index}`);
|
||||
|
||||
const storageCost = 10;
|
||||
|
||||
let link = cacheContent?.items?.[index]?.link;
|
||||
if (!link || !cacheContent.program.uuid) {
|
||||
const manifestPath = image.replace(EXTENSION_PNG, '.json');
|
||||
const manifestContent = fs
|
||||
.readFileSync(manifestPath)
|
||||
.toString()
|
||||
.replace(imageName, 'image.png')
|
||||
.replace(imageName, 'image.png');
|
||||
const manifest = JSON.parse(manifestContent);
|
||||
|
||||
const manifestBuffer = Buffer.from(JSON.stringify(manifest));
|
||||
|
||||
if (i === 0 && !cacheContent.program.uuid) {
|
||||
// initialize config
|
||||
console.log(`initializing config`);
|
||||
try {
|
||||
const res = await createConfig(anchorProgram, walletKeyPair, {
|
||||
maxNumberOfLines: new BN(parsedNumber || SIZE),
|
||||
symbol: manifest.symbol,
|
||||
sellerFeeBasisPoints: manifest.seller_fee_basis_points,
|
||||
isMutable: true,
|
||||
maxSupply: new BN(0),
|
||||
retainAuthority: true,
|
||||
creators: manifest.properties.creators.map(creator => {
|
||||
return {
|
||||
address: new PublicKey(creator.address),
|
||||
verified: true,
|
||||
share: creator.share,
|
||||
};
|
||||
}),
|
||||
});
|
||||
cacheContent.program.uuid = res.uuid;
|
||||
cacheContent.program.config = res.config.toBase58();
|
||||
config = res.config;
|
||||
|
||||
console.log(
|
||||
`initialized config for a candy machine with uuid: ${res.uuid}`,
|
||||
);
|
||||
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (exx) {
|
||||
console.error('Error deploying config to Solana network.', exx);
|
||||
// console.error(exx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!link) {
|
||||
const instructions = [
|
||||
anchor.web3.SystemProgram.transfer({
|
||||
fromPubkey: walletKeyPair.publicKey,
|
||||
toPubkey: PAYMENT_WALLET,
|
||||
lamports: storageCost,
|
||||
}),
|
||||
];
|
||||
|
||||
const tx = await sendTransactionWithRetryWithKeypair(
|
||||
anchorProgram.provider.connection,
|
||||
walletKeyPair,
|
||||
instructions,
|
||||
[],
|
||||
'single',
|
||||
);
|
||||
console.info('transaction for arweave payment:', tx);
|
||||
|
||||
// data.append('tags', JSON.stringify(tags));
|
||||
// payment transaction
|
||||
const data = new FormData();
|
||||
data.append('transaction', tx['txid']);
|
||||
data.append('env', env);
|
||||
data.append('file[]', fs.createReadStream(image), {
|
||||
filename: `image.png`,
|
||||
contentType: 'image/png',
|
||||
});
|
||||
data.append('file[]', manifestBuffer, 'metadata.json');
|
||||
try {
|
||||
const result = await upload(data, manifest, index);
|
||||
|
||||
const metadataFile = result.messages?.find(
|
||||
m => m.filename === 'manifest.json',
|
||||
);
|
||||
if (metadataFile?.transactionId) {
|
||||
link = `https://arweave.net/${metadataFile.transactionId}`;
|
||||
console.log(`File uploaded: ${link}`);
|
||||
}
|
||||
console.log('setting cache for ', index);
|
||||
cacheContent.items[index] = {
|
||||
link,
|
||||
name: manifest.name,
|
||||
onChain: false,
|
||||
};
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (er) {
|
||||
console.error(`Error uploading file ${index}`, er);
|
||||
}
|
||||
}
|
||||
const startMs = Date.now();
|
||||
log.info("started at: " + startMs.toString())
|
||||
let warn = false;
|
||||
for (; ;) {
|
||||
const successful = await upload(files, cacheName, env, keypair, elemCount);
|
||||
if (successful) {
|
||||
warn = false;
|
||||
break;
|
||||
} else {
|
||||
warn = true;
|
||||
log.warn("upload was not successful, rerunning");
|
||||
}
|
||||
}
|
||||
|
||||
let updateSuccessful = true;
|
||||
const keys = Object.keys(cacheContent.items);
|
||||
try {
|
||||
await Promise.all(
|
||||
chunks(Array.from(Array(keys.length).keys()), 1000).map(
|
||||
async allIndexesInSlice => {
|
||||
for (
|
||||
let offset = 0;
|
||||
offset < allIndexesInSlice.length;
|
||||
offset += 10
|
||||
) {
|
||||
const indexes = allIndexesInSlice.slice(offset, offset + 10);
|
||||
const onChain = indexes.filter(i => {
|
||||
const index = keys[i];
|
||||
return cacheContent.items[index]?.onChain || false;
|
||||
});
|
||||
const ind = keys[indexes[0]];
|
||||
|
||||
if (onChain.length != indexes.length) {
|
||||
console.log(
|
||||
`Writing indices ${ind}-${keys[indexes[indexes.length - 1]]}`,
|
||||
);
|
||||
try {
|
||||
await anchorProgram.rpc.addConfigLines(
|
||||
ind,
|
||||
indexes.map(i => ({
|
||||
uri: cacheContent.items[keys[i]].link,
|
||||
name: cacheContent.items[keys[i]].name,
|
||||
})),
|
||||
{
|
||||
accounts: {
|
||||
config,
|
||||
authority: walletKeyPair.publicKey,
|
||||
},
|
||||
signers: [walletKeyPair],
|
||||
},
|
||||
);
|
||||
indexes.forEach(i => {
|
||||
cacheContent.items[keys[i]] = {
|
||||
...cacheContent.items[keys[i]],
|
||||
onChain: true,
|
||||
};
|
||||
});
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`saving config line ${ind}-${
|
||||
keys[indexes[indexes.length - 1]]
|
||||
} failed`,
|
||||
e,
|
||||
);
|
||||
updateSuccessful = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
}
|
||||
console.log(`Done. Successful = ${updateSuccessful}. If 'false' - rerun`);
|
||||
const endMs = Date.now();
|
||||
const timeTaken = new Date(endMs - startMs).toISOString().substr(11, 8);
|
||||
log.info(`ended at: ${new Date(endMs).toString()}. time taken: ${timeTaken}`)
|
||||
if (warn) { log.info("not all images have been uplaoded, rerun this step.") }
|
||||
});
|
||||
|
||||
program
|
||||
.command('verify')
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp')
|
||||
programCommand('verify')
|
||||
.action(async (directory, cmd) => {
|
||||
const { env, keypair, cacheName } = cmd.opts();
|
||||
|
||||
|
@ -331,7 +92,7 @@ program
|
|||
|
||||
const keys = Object.keys(cacheContent.items);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
console.log('Looking at key ', i);
|
||||
log.debug('Looking at key ', i);
|
||||
const key = keys[i];
|
||||
const thisSlice = config.data.slice(
|
||||
CONFIG_ARRAY_START + 4 + CONFIG_LINE_SIZE * i,
|
||||
|
@ -341,22 +102,16 @@ program
|
|||
const uri = fromUTF8Array([...thisSlice.slice(40, 240)]);
|
||||
const cacheItem = cacheContent.items[key];
|
||||
if (!name.match(cacheItem.name) || !uri.match(cacheItem.link)) {
|
||||
console.log(
|
||||
'Name',
|
||||
name,
|
||||
'or uri',
|
||||
uri,
|
||||
'didnt match cache values of',
|
||||
cacheItem.name,
|
||||
'and',
|
||||
cacheItem.link,
|
||||
' marking to rerun for image',
|
||||
key,
|
||||
);
|
||||
//leaving here for debugging reasons, but it's pretty useless. if the first upload fails - all others are wrong
|
||||
// log.info(
|
||||
// `Name (${name}) or uri (${uri}) didnt match cache values of (${cacheItem.name})` +
|
||||
// `and (${cacheItem.link}). marking to rerun for image`,
|
||||
// key,
|
||||
// );
|
||||
cacheItem.onChain = false;
|
||||
allGood = false;
|
||||
} else {
|
||||
console.debug('Name', name, 'with', uri, 'checked out');
|
||||
log.debug('Name', name, 'with', uri, 'checked out');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,47 +129,116 @@ program
|
|||
|
||||
const lineCount = new BN(config.data.slice(247, 247 + 4), undefined, 'le');
|
||||
|
||||
console.log(
|
||||
`uploaded (${lineCount.toNumber()}) out of (${
|
||||
configData.data.maxNumberOfLines
|
||||
log.info(
|
||||
`uploaded (${lineCount.toNumber()}) out of (${configData.data.maxNumberOfLines
|
||||
})`,
|
||||
);
|
||||
if (configData.data.maxNumberOfLines > lineCount.toNumber()) {
|
||||
throw new Error(
|
||||
`predefined number of NFTs (${
|
||||
configData.data.maxNumberOfLines
|
||||
`predefined number of NFTs (${configData.data.maxNumberOfLines
|
||||
}) is smaller than the uploaded one (${lineCount.toNumber()})`,
|
||||
);
|
||||
} else {
|
||||
console.log('ready to deploy!');
|
||||
log.info('ready to deploy!');
|
||||
}
|
||||
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
});
|
||||
|
||||
program
|
||||
.command('create_candy_machine')
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp')
|
||||
.option('-p, --price <string>', 'SOL price', '1')
|
||||
programCommand('verify_price')
|
||||
.option('-p, --price <string>')
|
||||
.option('--cache-path <string>')
|
||||
.action(async (directory, cmd) => {
|
||||
const { keypair, env, price, cacheName } = cmd.opts();
|
||||
|
||||
const { keypair, env, price, cacheName, cachePath } = cmd.opts();
|
||||
const lamports = parsePrice(price);
|
||||
|
||||
if (isNaN(lamports)) {
|
||||
return log.error(`verify_price requires a --price to be set`);
|
||||
}
|
||||
|
||||
log.info(`Expected price is: ${lamports}`);
|
||||
|
||||
const cacheContent = loadCache(cacheName, env, cachePath);
|
||||
|
||||
if (!cacheContent) {
|
||||
return log.error(
|
||||
`No cache found, can't continue. Make sure you are in the correct directory where the assets are located or use the --cache-path option.`,
|
||||
);
|
||||
}
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
|
||||
const [candyMachine] = await getCandyMachineAddress(
|
||||
new PublicKey(cacheContent.program.config),
|
||||
cacheContent.program.uuid,
|
||||
);
|
||||
|
||||
const machine = await anchorProgram.account.candyMachine.fetch(
|
||||
candyMachine,
|
||||
);
|
||||
|
||||
//@ts-ignore
|
||||
const candyMachineLamports = machine.data.price.toNumber();
|
||||
|
||||
log.info(`Candymachine price is: ${candyMachineLamports}`);
|
||||
|
||||
if (lamports != candyMachineLamports) {
|
||||
throw new Error(`Expected price and CandyMachine's price do not match!`);
|
||||
}
|
||||
|
||||
log.info(`Good to go!`);
|
||||
});
|
||||
|
||||
programCommand('create_candy_machine')
|
||||
.option('-p, --price <string>', 'Price denominated in SOL or spl-token override', '1')
|
||||
.option('-t, --spl-token <string>', 'SPL token used to price NFT mint. To use SOL leave this empty.')
|
||||
.option('-t, --spl-token-account <string>', 'SPL token account that receives mint payments. Only required if spl-token is specified.')
|
||||
.action(async (directory, cmd) => {
|
||||
const { keypair, env, price, cacheName, splToken, splTokenAccount } = cmd.opts();
|
||||
|
||||
let parsedPrice = parsePrice(price);
|
||||
const cacheContent = loadCache(cacheName, env);
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
|
||||
let wallet = walletKeyPair.publicKey;
|
||||
const remainingAccounts = [];
|
||||
if (splToken || splTokenAccount) {
|
||||
if (!splToken) {
|
||||
throw new Error("If spl-token-account is set, spl-token must also be set")
|
||||
}
|
||||
const splTokenKey = new PublicKey(splToken);
|
||||
const splTokenAccountKey = new PublicKey(splTokenAccount);
|
||||
if (!splTokenAccount) {
|
||||
throw new Error("If spl-token is set, spl-token-account must also be set")
|
||||
}
|
||||
|
||||
const token = new Token(
|
||||
anchorProgram.provider.connection,
|
||||
splTokenKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
walletKeyPair
|
||||
);
|
||||
|
||||
const mintInfo = await token.getMintInfo();
|
||||
if (!mintInfo.isInitialized) {
|
||||
throw new Error(`The specified spl-token is not initialized`);
|
||||
}
|
||||
const tokenAccount = await token.getAccountInfo(splTokenAccountKey);
|
||||
if (!tokenAccount.isInitialized) {
|
||||
throw new Error(`The specified spl-token-account is not initialized`);
|
||||
}
|
||||
if (!tokenAccount.mint.equals(splTokenKey)) {
|
||||
throw new Error(`The spl-token-account's mint (${tokenAccount.mint.toString()}) does not match specified spl-token ${splTokenKey.toString()}`);
|
||||
}
|
||||
|
||||
wallet = splTokenAccountKey;
|
||||
parsedPrice = parsePrice(price, 10 ** mintInfo.decimals);
|
||||
remainingAccounts.push({ pubkey: splTokenKey, isWritable: false, isSigner: false });
|
||||
}
|
||||
|
||||
const config = new PublicKey(cacheContent.program.config);
|
||||
const [candyMachine, bump] = await getCandyMachineAddress(
|
||||
config,
|
||||
|
@ -424,14 +248,14 @@ program
|
|||
bump,
|
||||
{
|
||||
uuid: cacheContent.program.uuid,
|
||||
price: new anchor.BN(lamports),
|
||||
price: new anchor.BN(parsedPrice),
|
||||
itemsAvailable: new anchor.BN(Object.keys(cacheContent.items).length),
|
||||
goLiveDate: null,
|
||||
},
|
||||
{
|
||||
accounts: {
|
||||
candyMachine,
|
||||
wallet: walletKeyPair.publicKey,
|
||||
wallet,
|
||||
config: config,
|
||||
authority: walletKeyPair.publicKey,
|
||||
payer: walletKeyPair.publicKey,
|
||||
|
@ -439,43 +263,33 @@ program
|
|||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [],
|
||||
remainingAccounts,
|
||||
},
|
||||
);
|
||||
|
||||
console.log(`create_candy_machine Done: ${candyMachine.toBase58()}`);
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
log.info(`create_candy_machine finished. candy machine pubkey: ${candyMachine.toBase58()}`);
|
||||
});
|
||||
|
||||
program
|
||||
.command('set_start_date')
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp')
|
||||
programCommand('update_candy_machine')
|
||||
.option('-d, --date <string>', 'timestamp - eg "04 Dec 1995 00:12:00 GMT"')
|
||||
.option('-p, --price <string>', 'SOL price')
|
||||
.action(async (directory, cmd) => {
|
||||
const { keypair, env, date, cacheName } = cmd.opts();
|
||||
const { keypair, env, date, price, cacheName } = cmd.opts();
|
||||
const cacheContent = loadCache(cacheName, env);
|
||||
|
||||
const secondsSinceEpoch = (date ? Date.parse(date) : Date.now()) / 1000;
|
||||
const secondsSinceEpoch = date ? Date.parse(date) / 1000 : null;
|
||||
const lamports = price ? parsePrice(price) : null;
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [candyMachine, _] = await getCandyMachineAddress(
|
||||
const [candyMachine] = await getCandyMachineAddress(
|
||||
new PublicKey(cacheContent.program.config),
|
||||
cacheContent.program.uuid,
|
||||
);
|
||||
const tx = await anchorProgram.rpc.updateCandyMachine(
|
||||
null,
|
||||
new anchor.BN(secondsSinceEpoch),
|
||||
lamports ? new anchor.BN(lamports) : null,
|
||||
secondsSinceEpoch ? new anchor.BN(secondsSinceEpoch) : null,
|
||||
{
|
||||
accounts: {
|
||||
candyMachine,
|
||||
|
@ -484,103 +298,94 @@ program
|
|||
},
|
||||
);
|
||||
|
||||
console.log('set_start_date Done', secondsSinceEpoch, tx);
|
||||
if (date) log.info(` - updated startDate timestamp: ${secondsSinceEpoch} (${date})`)
|
||||
if (lamports) log.info(` - updated price: ${lamports} lamports (${price} SOL)`)
|
||||
log.info('updated_candy_machine Done', tx);
|
||||
});
|
||||
|
||||
program
|
||||
.command('mint_one_token')
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp')
|
||||
programCommand('mint_one_token')
|
||||
.option('-t, --spl-token-account <string>', 'SPL token account to payfrom')
|
||||
.action(async (directory, cmd) => {
|
||||
const { keypair, env, cacheName } = cmd.opts();
|
||||
const {keypair, env, cacheName, splTokenAccount} = cmd.opts();
|
||||
|
||||
const cacheContent = loadCache(cacheName, env);
|
||||
const mint = Keypair.generate();
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
const userTokenAccountAddress = await getTokenWallet(
|
||||
walletKeyPair.publicKey,
|
||||
mint.publicKey,
|
||||
);
|
||||
|
||||
const configAddress = new PublicKey(cacheContent.program.config);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [candyMachineAddress, bump] = await getCandyMachineAddress(
|
||||
configAddress,
|
||||
cacheContent.program.uuid,
|
||||
);
|
||||
const candyMachine = await anchorProgram.account.candyMachine.fetch(
|
||||
candyMachineAddress,
|
||||
);
|
||||
const metadataAddress = await getMetadata(mint.publicKey);
|
||||
const masterEdition = await getMasterEdition(mint.publicKey);
|
||||
const tx = await anchorProgram.rpc.mintNft({
|
||||
accounts: {
|
||||
config: configAddress,
|
||||
candyMachine: candyMachineAddress,
|
||||
payer: walletKeyPair.publicKey,
|
||||
//@ts-ignore
|
||||
wallet: candyMachine.wallet,
|
||||
mint: mint.publicKey,
|
||||
metadata: metadataAddress,
|
||||
masterEdition,
|
||||
mintAuthority: walletKeyPair.publicKey,
|
||||
updateAuthority: walletKeyPair.publicKey,
|
||||
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
||||
},
|
||||
signers: [mint, walletKeyPair],
|
||||
instructions: [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: walletKeyPair.publicKey,
|
||||
newAccountPubkey: mint.publicKey,
|
||||
space: MintLayout.span,
|
||||
lamports:
|
||||
await anchorProgram.provider.connection.getMinimumBalanceForRentExemption(
|
||||
MintLayout.span,
|
||||
),
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
Token.createInitMintInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
0,
|
||||
walletKeyPair.publicKey,
|
||||
walletKeyPair.publicKey,
|
||||
),
|
||||
createAssociatedTokenAccountInstruction(
|
||||
userTokenAccountAddress,
|
||||
walletKeyPair.publicKey,
|
||||
walletKeyPair.publicKey,
|
||||
mint.publicKey,
|
||||
),
|
||||
Token.createMintToInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
userTokenAccountAddress,
|
||||
walletKeyPair.publicKey,
|
||||
[],
|
||||
1,
|
||||
),
|
||||
],
|
||||
});
|
||||
const splTokenAccountKey = splTokenAccount ? new PublicKey(splTokenAccount) : undefined;
|
||||
const tx = await mint(keypair, env, configAddress, splTokenAccountKey);
|
||||
|
||||
console.log('Done', tx);
|
||||
log.info('Done', tx);
|
||||
});
|
||||
|
||||
program.command('find-wallets').action(() => {});
|
||||
programCommand('sign')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.option('-m, --metadata <string>', 'base58 metadata account id')
|
||||
.action(async (directory, cmd) => {
|
||||
const { keypair, env, metadata } = cmd.opts();
|
||||
|
||||
await signMetadata(
|
||||
metadata,
|
||||
keypair,
|
||||
env
|
||||
);
|
||||
});
|
||||
|
||||
function programCommand(name: string) {
|
||||
return program
|
||||
.command(name)
|
||||
.option(
|
||||
'-e, --env <string>',
|
||||
'Solana cluster env name',
|
||||
'devnet', //mainnet-beta, testnet, devnet
|
||||
)
|
||||
.option(
|
||||
'-k, --keypair <path>',
|
||||
`Solana wallet location`,
|
||||
'--keypair not provided',
|
||||
)
|
||||
.option('-l, --log-level <string>', 'log level', setLogLevel)
|
||||
.option('-c, --cache-name <string>', 'Cache file name', 'temp');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function setLogLevel(value, prev) {
|
||||
if (value === undefined || value === null) {
|
||||
return
|
||||
}
|
||||
log.info("setting the log value to: " + value);
|
||||
log.setLevel(value);
|
||||
}
|
||||
|
||||
programCommand("sign_candy_machine_metadata")
|
||||
.option('-cndy, --candy-address <string>', 'Candy machine address', '')
|
||||
.option('-b, --batch-size <string>', 'Batch size', '10')
|
||||
.action(async (directory, cmd) => {
|
||||
let { keypair, env, cacheName, candyAddress, batchSize } = cmd.opts();
|
||||
if (!keypair || keypair == '') {
|
||||
log.info("Keypair required!");
|
||||
return;
|
||||
}
|
||||
if (!candyAddress || candyAddress == '') {
|
||||
log.info("Candy machine address required! Using from saved list.")
|
||||
const cacheContent = loadCache(cacheName, env);
|
||||
const config = new PublicKey(cacheContent.program.config);
|
||||
const [candyMachine, bump] = await getCandyMachineAddress(
|
||||
config,
|
||||
cacheContent.program.uuid,
|
||||
);
|
||||
candyAddress = candyMachine.toBase58();
|
||||
}
|
||||
let batchSizeParsed = parseInt(batchSize)
|
||||
if (!parseInt(batchSize)) {
|
||||
log.info("Batch size needs to be an integer!")
|
||||
return;
|
||||
}
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
log.info("Creator pubkey: ", walletKeyPair.publicKey.toBase58())
|
||||
log.info("Environment: ", env)
|
||||
log.info("Candy machine address: ", candyAddress)
|
||||
log.info("Batch Size: ", batchSizeParsed)
|
||||
await signAllMetadataFromCandyMachine(anchorProgram.provider.connection, walletKeyPair, candyAddress, batchSizeParsed)
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import { Keypair, PublicKey, SystemProgram } from "@solana/web3.js";
|
||||
import {
|
||||
getCandyMachineAddress,
|
||||
getMasterEdition,
|
||||
getMetadata,
|
||||
getTokenWallet,
|
||||
loadAnchorProgram,
|
||||
loadWalletKey,
|
||||
uuidFromConfigPubkey
|
||||
} from "../helpers/accounts";
|
||||
import { TOKEN_METADATA_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../helpers/constants";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { MintLayout, Token } from "@solana/spl-token";
|
||||
import { createAssociatedTokenAccountInstruction } from "../helpers/instructions";
|
||||
|
||||
export async function mint(keypair: string, env: string, configAddress: PublicKey, splTokenAccountKey?: PublicKey): Promise<string> {
|
||||
const mint = Keypair.generate();
|
||||
|
||||
const userKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(userKeyPair, env);
|
||||
const userTokenAccountAddress = await getTokenWallet(
|
||||
userKeyPair.publicKey,
|
||||
mint.publicKey,
|
||||
);
|
||||
|
||||
const uuid = uuidFromConfigPubkey(configAddress);
|
||||
const [candyMachineAddress] = await getCandyMachineAddress(
|
||||
configAddress,
|
||||
uuid,
|
||||
);
|
||||
const candyMachine : any = await anchorProgram.account.candyMachine.fetch(
|
||||
candyMachineAddress,
|
||||
);
|
||||
|
||||
const remainingAccounts = [];
|
||||
if (splTokenAccountKey) {
|
||||
const candyMachineTokenMintKey = candyMachine.tokenMint;
|
||||
if (!candyMachineTokenMintKey) {
|
||||
throw new Error('Candy machine data does not have token mint configured. Can\'t use spl-token-account');
|
||||
}
|
||||
const token = new Token(
|
||||
anchorProgram.provider.connection,
|
||||
candyMachine.tokenMint,
|
||||
TOKEN_PROGRAM_ID,
|
||||
userKeyPair
|
||||
);
|
||||
|
||||
const tokenAccount = await token.getAccountInfo(splTokenAccountKey);
|
||||
if (!candyMachine.tokenMint.equals(tokenAccount.mint)) {
|
||||
throw new Error(`Specified spl-token-account's mint (${tokenAccount.mint.toString()}) does not match candy machine's token mint (${candyMachine.tokenMint.toString()})`);
|
||||
}
|
||||
|
||||
if (!tokenAccount.owner.equals(userKeyPair.publicKey)) {
|
||||
throw new Error(`Specified spl-token-account's owner (${tokenAccount.owner.toString()}) does not match user public key (${userKeyPair.publicKey})`);
|
||||
}
|
||||
|
||||
remainingAccounts.push({ pubkey: splTokenAccountKey, isWritable: true, isSigner: false });
|
||||
remainingAccounts.push({ pubkey: userKeyPair.publicKey, isWritable: false, isSigner: true });
|
||||
}
|
||||
|
||||
const metadataAddress = await getMetadata(mint.publicKey);
|
||||
const masterEdition = await getMasterEdition(mint.publicKey);
|
||||
return await anchorProgram.rpc.mintNft({
|
||||
accounts: {
|
||||
config: configAddress,
|
||||
candyMachine: candyMachineAddress,
|
||||
payer: userKeyPair.publicKey,
|
||||
//@ts-ignore
|
||||
wallet: candyMachine.wallet,
|
||||
mint: mint.publicKey,
|
||||
metadata: metadataAddress,
|
||||
masterEdition,
|
||||
mintAuthority: userKeyPair.publicKey,
|
||||
updateAuthority: userKeyPair.publicKey,
|
||||
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
||||
},
|
||||
signers: [mint, userKeyPair],
|
||||
remainingAccounts,
|
||||
instructions: [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: userKeyPair.publicKey,
|
||||
newAccountPubkey: mint.publicKey,
|
||||
space: MintLayout.span,
|
||||
lamports:
|
||||
await anchorProgram.provider.connection.getMinimumBalanceForRentExemption(
|
||||
MintLayout.span,
|
||||
),
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
Token.createInitMintInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
0,
|
||||
userKeyPair.publicKey,
|
||||
userKeyPair.publicKey,
|
||||
),
|
||||
createAssociatedTokenAccountInstruction(
|
||||
userTokenAccountAddress,
|
||||
userKeyPair.publicKey,
|
||||
userKeyPair.publicKey,
|
||||
mint.publicKey,
|
||||
),
|
||||
Token.createMintToInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
userTokenAccountAddress,
|
||||
userKeyPair.publicKey,
|
||||
[],
|
||||
1,
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import { Keypair, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||
import { TOKEN_METADATA_PROGRAM_ID } from "../helpers/constants";
|
||||
import { sendTransactionWithRetryWithKeypair } from "../helpers/transactions";
|
||||
import { loadAnchorProgram, loadWalletKey } from "../helpers/accounts";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
|
||||
|
||||
export async function signMetadata(
|
||||
metadata: string,
|
||||
keypair: string,
|
||||
env: string
|
||||
) {
|
||||
const creatorKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(creatorKeyPair, env);
|
||||
await signWithRetry(anchorProgram, creatorKeyPair, metadata);
|
||||
}
|
||||
|
||||
export async function signAllUnapprovedMetadata(
|
||||
keypair: string,
|
||||
env: string
|
||||
) {
|
||||
const creatorKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(creatorKeyPair, env);
|
||||
const metadataIds = await findAllUnapprovedMetadataIds(anchorProgram, creatorKeyPair);
|
||||
|
||||
for(const id in metadataIds) {
|
||||
await signWithRetry(anchorProgram, creatorKeyPair, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function findAllUnapprovedMetadataIds(anchorProgram: Program, creatorKeyPair: Keypair): Promise<string[]> {
|
||||
//TODO well I need some help with that... so... help? :D
|
||||
throw new Error("Unsupported yet")
|
||||
}
|
||||
|
||||
async function signWithRetry(anchorProgram: Program, creatorKeyPair: Keypair, metadataAddress: string) {
|
||||
await sendTransactionWithRetryWithKeypair(
|
||||
anchorProgram.provider.connection,
|
||||
creatorKeyPair,
|
||||
[signMetadataInstruction(new PublicKey(metadataAddress), creatorKeyPair.publicKey)],
|
||||
[],
|
||||
'single',
|
||||
);
|
||||
}
|
||||
|
||||
export function signMetadataInstruction(
|
||||
metadata: PublicKey,
|
||||
creator: PublicKey,
|
||||
): TransactionInstruction {
|
||||
const data = Buffer.from([7]); //now thats bloody magic. WTF metaplex? XD
|
||||
|
||||
const keys = [
|
||||
{
|
||||
pubkey: metadata,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: creator,
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
];
|
||||
return new TransactionInstruction({
|
||||
keys,
|
||||
programId: TOKEN_METADATA_PROGRAM_ID,
|
||||
data,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
import { Keypair, PublicKey, TransactionInstruction, Connection, AccountInfo } from '@solana/web3.js';
|
||||
import { sendTransactionWithRetryWithKeypair } from '../helpers/transactions';
|
||||
import * as borsh from "borsh"
|
||||
import {
|
||||
MAX_NAME_LENGTH,
|
||||
MAX_URI_LENGTH,
|
||||
MAX_SYMBOL_LENGTH,
|
||||
MAX_CREATOR_LEN,
|
||||
TOKEN_METADATA_PROGRAM_ID,
|
||||
} from '../helpers/constants';
|
||||
import {
|
||||
AccountAndPubkey,
|
||||
Metadata,
|
||||
METADATA_SCHEMA
|
||||
} from '../types'
|
||||
/*
|
||||
Get accounts by candy machine creator address
|
||||
Get only verified ones
|
||||
Get only unverified ones with creator address
|
||||
Grab n at a time and batch sign and send transaction
|
||||
|
||||
PS: Don't sign candy machine addresses that you do not know about. Signing verifies your participation.
|
||||
*/
|
||||
async function decodeMetadata(buffer) {
|
||||
const metadata = borsh.deserializeUnchecked(METADATA_SCHEMA, Metadata, buffer);
|
||||
return metadata;
|
||||
};
|
||||
|
||||
async function getProgramAccounts(
|
||||
connection: Connection,
|
||||
programId: String,
|
||||
configOrCommitment?: any,
|
||||
): Promise<Array<AccountAndPubkey>> {
|
||||
const extra: any = {};
|
||||
let commitment;
|
||||
//let encoding;
|
||||
|
||||
if (configOrCommitment) {
|
||||
if (typeof configOrCommitment === 'string') {
|
||||
commitment = configOrCommitment;
|
||||
} else {
|
||||
commitment = configOrCommitment.commitment;
|
||||
//encoding = configOrCommitment.encoding;
|
||||
|
||||
if (configOrCommitment.dataSlice) {
|
||||
extra.dataSlice = configOrCommitment.dataSlice;
|
||||
}
|
||||
|
||||
if (configOrCommitment.filters) {
|
||||
extra.filters = configOrCommitment.filters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const args = connection._buildArgs([programId], commitment, 'base64', extra);
|
||||
const unsafeRes = await (connection as any)._rpcRequest(
|
||||
'getProgramAccounts',
|
||||
args,
|
||||
);
|
||||
//console.log(unsafeRes)
|
||||
const data = (
|
||||
unsafeRes.result as Array<{
|
||||
account: AccountInfo<[string, string]>;
|
||||
pubkey: string;
|
||||
}>
|
||||
).map(item => {
|
||||
return {
|
||||
account: {
|
||||
// TODO: possible delay parsing could be added here
|
||||
data: Buffer.from(item.account.data[0], 'base64'),
|
||||
executable: item.account.executable,
|
||||
lamports: item.account.lamports,
|
||||
// TODO: maybe we can do it in lazy way? or just use string
|
||||
owner: item.account.owner,
|
||||
} as AccountInfo<Buffer>,
|
||||
pubkey: item.pubkey,
|
||||
};
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function signAllMetadataFromCandyMachine(
|
||||
connection,
|
||||
wallet,
|
||||
candyMachineAddress,
|
||||
batchSize
|
||||
){
|
||||
let metadataByCandyMachine = await getAccountsByCreatorAddress(candyMachineAddress, connection)
|
||||
console.log(`Found ${metadataByCandyMachine.length} nft's minted by candy machine ${candyMachineAddress}`)
|
||||
let candyVerifiedListToSign = await getCandyMachineVerifiedMetadata(metadataByCandyMachine, candyMachineAddress, wallet.publicKey.toBase58())
|
||||
console.log(`Found ${candyVerifiedListToSign.length} nft's to sign by ${wallet.publicKey.toBase58()}`)
|
||||
await sendSignMetadata(connection, wallet, candyVerifiedListToSign, batchSize)
|
||||
}
|
||||
|
||||
async function getAccountsByCreatorAddress(creatorAddress, connection) {
|
||||
let metadataAccounts = await getProgramAccounts(connection, TOKEN_METADATA_PROGRAM_ID.toBase58(), {
|
||||
filters: [
|
||||
{
|
||||
memcmp: {
|
||||
offset:
|
||||
1 + // key
|
||||
32 + // update auth
|
||||
32 + // mint
|
||||
4 + // name string length
|
||||
MAX_NAME_LENGTH + // name
|
||||
4 + // uri string length
|
||||
MAX_URI_LENGTH + // uri*
|
||||
4 + // symbol string length
|
||||
MAX_SYMBOL_LENGTH + // symbol
|
||||
2 + // seller fee basis points
|
||||
1 + // whether or not there is a creators vec
|
||||
4 + // creators vec length
|
||||
0 * MAX_CREATOR_LEN,
|
||||
bytes: creatorAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
let decodedAccounts = []
|
||||
for (let i = 0; i < metadataAccounts.length; i++) {
|
||||
let e = metadataAccounts[i];
|
||||
let decoded = await decodeMetadata(e.account.data)
|
||||
let accountPubkey = e.pubkey
|
||||
let store = [decoded, accountPubkey]
|
||||
decodedAccounts.push(store)
|
||||
}
|
||||
return decodedAccounts
|
||||
}
|
||||
|
||||
async function getCandyMachineVerifiedMetadata(metadataList, candyAddress, creatorAddress){
|
||||
let verifiedList = [];
|
||||
metadataList.forEach(meta => {
|
||||
let verifiedCandy = false;
|
||||
let verifiedCreator = true;
|
||||
meta[0].data.creators.forEach(creator => {
|
||||
if (new PublicKey(creator.address).toBase58() == candyAddress && creator.verified === 1) {
|
||||
verifiedCandy = true;
|
||||
}
|
||||
if (new PublicKey(creator.address).toBase58() == creatorAddress && creator.verified === 0) {
|
||||
verifiedCreator = false;
|
||||
}
|
||||
});
|
||||
if(verifiedCandy && !verifiedCreator){
|
||||
verifiedList.push(meta)
|
||||
}
|
||||
});
|
||||
return verifiedList
|
||||
}
|
||||
|
||||
async function sendSignMetadata(
|
||||
connection,
|
||||
wallet,
|
||||
metadataList,
|
||||
batchsize
|
||||
) {
|
||||
let total = 0;
|
||||
while(metadataList.length > 0){
|
||||
console.log("Signing metadata")
|
||||
let sliceAmount = batchsize;
|
||||
if (metadataList.length < batchsize) {
|
||||
sliceAmount = metadataList.length;
|
||||
}
|
||||
var removed = metadataList.splice(0,sliceAmount);
|
||||
total += sliceAmount;
|
||||
await delay(500)
|
||||
await signMetadataBatch(removed, connection, wallet)
|
||||
console.log(`Processed ${total} nfts`)
|
||||
}
|
||||
console.log("Finished signing metadata..")
|
||||
}
|
||||
|
||||
async function signMetadataBatch(metadataList, connection, keypair){
|
||||
|
||||
const signers: Keypair[] = [];
|
||||
const instructions: TransactionInstruction[] = [];
|
||||
for (let i = 0; i < metadataList.length; i++) {
|
||||
const meta = metadataList[i];
|
||||
await signMetadataSingle(meta[1], keypair.publicKey.toBase58(), instructions)
|
||||
}
|
||||
await sendTransactionWithRetryWithKeypair(connection, keypair, instructions, [], 'single')
|
||||
}
|
||||
|
||||
async function signMetadataSingle(
|
||||
metadata,
|
||||
creator,
|
||||
instructions,
|
||||
) {
|
||||
const data = Buffer.from([7]);
|
||||
const keys = [
|
||||
{
|
||||
pubkey: new PublicKey(metadata),
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: new PublicKey(creator),
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
];
|
||||
instructions.push(
|
||||
({
|
||||
keys,
|
||||
programId: TOKEN_METADATA_PROGRAM_ID.toBase58(),
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
import { EXTENSION_PNG, ARWEAVE_PAYMENT_WALLET } from "../helpers/constants";
|
||||
import path from "path";
|
||||
import { createConfig, loadAnchorProgram, loadWalletKey } from "../helpers/accounts";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import fs from "fs";
|
||||
import BN from "bn.js";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { sendTransactionWithRetryWithKeypair } from "../helpers/transactions";
|
||||
import FormData from "form-data";
|
||||
import { loadCache, saveCache } from "../helpers/cache";
|
||||
import fetch from 'node-fetch';
|
||||
import log from "loglevel";
|
||||
|
||||
export async function upload(files: string[], cacheName: string, env: string, keypair: string, totalNFTs: number): Promise<boolean> {
|
||||
let uploadSuccessful = true;
|
||||
|
||||
const savedContent = loadCache(cacheName, env);
|
||||
const cacheContent = savedContent || {};
|
||||
|
||||
if (!cacheContent.program) {
|
||||
cacheContent.program = {};
|
||||
}
|
||||
|
||||
let existingInCache = [];
|
||||
if (!cacheContent.items) {
|
||||
cacheContent.items = {};
|
||||
} else {
|
||||
existingInCache = Object.keys(cacheContent.items);
|
||||
}
|
||||
|
||||
const seen = {};
|
||||
const newFiles = [];
|
||||
|
||||
files.forEach(f => {
|
||||
if (!seen[f.replace(EXTENSION_PNG, '').split('/').pop()]) {
|
||||
seen[f.replace(EXTENSION_PNG, '').split('/').pop()] = true;
|
||||
newFiles.push(f);
|
||||
}
|
||||
});
|
||||
existingInCache.forEach(f => {
|
||||
if (!seen[f]) {
|
||||
seen[f] = true;
|
||||
newFiles.push(f + '.png');
|
||||
}
|
||||
});
|
||||
|
||||
const images = newFiles.filter(val => path.extname(val) === EXTENSION_PNG);
|
||||
const SIZE = images.length;
|
||||
|
||||
const walletKeyPair = loadWalletKey(keypair);
|
||||
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
|
||||
|
||||
let config = cacheContent.program.config
|
||||
? new PublicKey(cacheContent.program.config)
|
||||
: undefined;
|
||||
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
const image = images[i];
|
||||
const imageName = path.basename(image);
|
||||
const index = imageName.replace(EXTENSION_PNG, '');
|
||||
|
||||
log.debug(`Processing file: ${i}`);
|
||||
if (i % 50 === 0) {
|
||||
log.info(`Processing file: ${i}`);
|
||||
}
|
||||
|
||||
const storageCost = 10;
|
||||
|
||||
let link = cacheContent?.items?.[index]?.link;
|
||||
if (!link || !cacheContent.program.uuid) {
|
||||
const manifestPath = image.replace(EXTENSION_PNG, '.json');
|
||||
const manifestContent = fs
|
||||
.readFileSync(manifestPath)
|
||||
.toString()
|
||||
.replace(imageName, 'image.png')
|
||||
.replace(imageName, 'image.png');
|
||||
const manifest = JSON.parse(manifestContent);
|
||||
|
||||
const manifestBuffer = Buffer.from(JSON.stringify(manifest));
|
||||
|
||||
if (i === 0 && !cacheContent.program.uuid) {
|
||||
// initialize config
|
||||
log.info(`initializing config`)
|
||||
try {
|
||||
const res = await createConfig(anchorProgram, walletKeyPair, {
|
||||
maxNumberOfLines: new BN(totalNFTs),
|
||||
symbol: manifest.symbol,
|
||||
sellerFeeBasisPoints: manifest.seller_fee_basis_points,
|
||||
isMutable: true,
|
||||
maxSupply: new BN(0),
|
||||
retainAuthority: true,
|
||||
creators: manifest.properties.creators.map(creator => {
|
||||
return {
|
||||
address: new PublicKey(creator.address),
|
||||
verified: true,
|
||||
share: creator.share,
|
||||
};
|
||||
}),
|
||||
});
|
||||
cacheContent.program.uuid = res.uuid;
|
||||
cacheContent.program.config = res.config.toBase58();
|
||||
config = res.config;
|
||||
|
||||
log.info(`initialized config for a candy machine with publickey: ${res.config.toBase58()}`)
|
||||
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (exx) {
|
||||
log.error('Error deploying config to Solana network.', exx);
|
||||
throw exx;
|
||||
}
|
||||
}
|
||||
|
||||
if (!link) {
|
||||
const instructions = [
|
||||
anchor.web3.SystemProgram.transfer({
|
||||
fromPubkey: walletKeyPair.publicKey,
|
||||
toPubkey: ARWEAVE_PAYMENT_WALLET,
|
||||
lamports: storageCost,
|
||||
}),
|
||||
];
|
||||
|
||||
const tx = await sendTransactionWithRetryWithKeypair(
|
||||
anchorProgram.provider.connection,
|
||||
walletKeyPair,
|
||||
instructions,
|
||||
[],
|
||||
'single',
|
||||
);
|
||||
log.debug('transaction for arweave payment:', tx);
|
||||
|
||||
// data.append('tags', JSON.stringify(tags));
|
||||
// payment transaction
|
||||
const data = new FormData();
|
||||
data.append('transaction', tx['txid']);
|
||||
data.append('env', env);
|
||||
data.append('file[]', fs.createReadStream(image), {filename: `image.png`, contentType: 'image/png'});
|
||||
data.append('file[]', manifestBuffer, 'metadata.json');
|
||||
try {
|
||||
const result = await uploadToArweave(data, manifest, index);
|
||||
|
||||
const metadataFile = result.messages?.find(
|
||||
m => m.filename === 'manifest.json',
|
||||
);
|
||||
if (metadataFile?.transactionId) {
|
||||
link = `https://arweave.net/${metadataFile.transactionId}`;
|
||||
log.debug(`File uploaded: ${link}`);
|
||||
}
|
||||
|
||||
cacheContent.items[index] = {
|
||||
link,
|
||||
name: manifest.name,
|
||||
onChain: false,
|
||||
};
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (er) {
|
||||
uploadSuccessful = false;
|
||||
log.error(`Error uploading file ${index}`, er);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const keys = Object.keys(cacheContent.items);
|
||||
try {
|
||||
await Promise.all(
|
||||
chunks(Array.from(Array(keys.length).keys()), 1000).map(
|
||||
async allIndexesInSlice => {
|
||||
for (
|
||||
let offset = 0;
|
||||
offset < allIndexesInSlice.length;
|
||||
offset += 10
|
||||
) {
|
||||
const indexes = allIndexesInSlice.slice(offset, offset + 10);
|
||||
const onChain = indexes.filter(i => {
|
||||
const index = keys[i];
|
||||
return cacheContent.items[index]?.onChain || false;
|
||||
});
|
||||
const ind = keys[indexes[0]];
|
||||
|
||||
if (onChain.length != indexes.length) {
|
||||
log.info(`Writing indices ${ind}-${keys[indexes[indexes.length - 1]]}`);
|
||||
try {
|
||||
await anchorProgram.rpc.addConfigLines(
|
||||
ind,
|
||||
indexes.map(i => ({
|
||||
uri: cacheContent.items[keys[i]].link,
|
||||
name: cacheContent.items[keys[i]].name,
|
||||
})),
|
||||
{
|
||||
accounts: {
|
||||
config,
|
||||
authority: walletKeyPair.publicKey,
|
||||
},
|
||||
signers: [walletKeyPair],
|
||||
},
|
||||
);
|
||||
indexes.forEach(i => {
|
||||
cacheContent.items[keys[i]] = {
|
||||
...cacheContent.items[keys[i]],
|
||||
onChain: true,
|
||||
};
|
||||
});
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
} catch (e) {
|
||||
log.error(`saving config line ${ind}-${keys[indexes[indexes.length - 1]]} failed`, e);
|
||||
uploadSuccessful = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
} finally {
|
||||
saveCache(cacheName, env, cacheContent);
|
||||
}
|
||||
console.log(`Done. Successful = ${uploadSuccessful}.`);
|
||||
return uploadSuccessful;
|
||||
}
|
||||
|
||||
async function uploadToArweave(data: FormData, manifest, index) {
|
||||
log.debug(`trying to upload ${index}.png: ${manifest.name}`)
|
||||
return await (
|
||||
await fetch(
|
||||
'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFile4',
|
||||
{
|
||||
method: 'POST',
|
||||
// @ts-ignore
|
||||
body: data,
|
||||
},
|
||||
)
|
||||
).json();
|
||||
}
|
||||
|
||||
function chunks(array, size) {
|
||||
return Array.apply(0, new Array(Math.ceil(array.length / size))).map(
|
||||
(_, index) => array.slice(index * size, (index + 1) * size),
|
||||
);
|
||||
}
|
|
@ -8,8 +8,10 @@ import {
|
|||
} from './constants';
|
||||
import * as anchor from '@project-serum/anchor';
|
||||
import fs from 'fs';
|
||||
import BN from 'bn.js';
|
||||
import { createConfigAccount } from './instructions';
|
||||
import BN from "bn.js";
|
||||
import { createConfigAccount } from "./instructions";
|
||||
import { web3 } from "@project-serum/anchor";
|
||||
import log from "loglevel";
|
||||
|
||||
export const createConfig = async function (
|
||||
anchorProgram: anchor.Program,
|
||||
|
@ -29,7 +31,7 @@ export const createConfig = async function (
|
|||
},
|
||||
) {
|
||||
const configAccount = Keypair.generate();
|
||||
const uuid = configAccount.publicKey.toBase58().slice(0, 6);
|
||||
const uuid = uuidFromConfigPubkey(configAccount.publicKey);
|
||||
|
||||
return {
|
||||
config: configAccount.publicKey,
|
||||
|
@ -61,6 +63,10 @@ export const createConfig = async function (
|
|||
};
|
||||
};
|
||||
|
||||
export function uuidFromConfigPubkey(configAccount: PublicKey) {
|
||||
return configAccount.toBase58().slice(0, 6);
|
||||
}
|
||||
|
||||
export const getTokenWallet = async function (
|
||||
wallet: PublicKey,
|
||||
mint: PublicKey,
|
||||
|
@ -76,7 +82,7 @@ export const getTokenWallet = async function (
|
|||
export const getCandyMachineAddress = async (
|
||||
config: anchor.web3.PublicKey,
|
||||
uuid: string,
|
||||
) => {
|
||||
): Promise<[PublicKey, number]> => {
|
||||
return await anchor.web3.PublicKey.findProgramAddress(
|
||||
[Buffer.from(CANDY_MACHINE), config.toBuffer(), Buffer.from(uuid)],
|
||||
CANDY_MACHINE_PROGRAM_ID,
|
||||
|
@ -86,7 +92,7 @@ export const getCandyMachineAddress = async (
|
|||
export const getConfig = async (
|
||||
authority: anchor.web3.PublicKey,
|
||||
uuid: string,
|
||||
) => {
|
||||
): Promise<[PublicKey, number]> => {
|
||||
return await anchor.web3.PublicKey.findProgramAddress(
|
||||
[Buffer.from(CANDY_MACHINE), authority.toBuffer(), Buffer.from(uuid)],
|
||||
CANDY_MACHINE_PROGRAM_ID,
|
||||
|
@ -125,20 +131,23 @@ export const getMasterEdition = async (
|
|||
};
|
||||
|
||||
export function loadWalletKey(keypair): Keypair {
|
||||
return Keypair.fromSecretKey(
|
||||
const loaded = Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(keypair).toString())),
|
||||
);
|
||||
log.info(`wallet public key: ${loaded.publicKey}`)
|
||||
return loaded;
|
||||
}
|
||||
|
||||
export async function loadAnchorProgram(walletKeyPair: Keypair, env: string) {
|
||||
const solConnection = new anchor.web3.Connection(
|
||||
`https://api.${env}.solana.com/`,
|
||||
);
|
||||
// @ts-ignore
|
||||
const solConnection = new web3.Connection(web3.clusterApiUrl(env));
|
||||
const walletWrapper = new anchor.Wallet(walletKeyPair);
|
||||
const provider = new anchor.Provider(solConnection, walletWrapper, {
|
||||
preflightCommitment: 'recent',
|
||||
});
|
||||
const idl = await anchor.Program.fetchIdl(CANDY_MACHINE_PROGRAM_ID, provider);
|
||||
|
||||
return new anchor.Program(idl, CANDY_MACHINE_PROGRAM_ID, provider);
|
||||
const program = new anchor.Program(idl, CANDY_MACHINE_PROGRAM_ID, provider);
|
||||
log.debug("program id from anchor", program.programId.toBase58());
|
||||
return program;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import path from "path";
|
||||
import { CACHE_PATH } from "./constants";
|
||||
import fs from "fs";
|
||||
|
||||
export function cachePath(env: string, cacheName: string, cPath: string = CACHE_PATH) {
|
||||
return path.join(cPath, `${env}-${cacheName}`);
|
||||
}
|
||||
|
||||
export function loadCache(cacheName: string, env: string, cPath: string = CACHE_PATH) {
|
||||
const path = cachePath(env, cacheName, cPath);
|
||||
return fs.existsSync(path)
|
||||
? JSON.parse(fs.readFileSync(path).toString())
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function saveCache(cacheName: string, env: string, cacheContent, cPath: string = CACHE_PATH) {
|
||||
fs.writeFileSync(cachePath(env, cacheName, cPath), JSON.stringify(cacheContent));
|
||||
}
|
||||
|
|
@ -1,23 +1,14 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const CANDY_MACHINE = 'candy_machine';
|
||||
|
||||
export const PAYMENT_WALLET = new PublicKey(
|
||||
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
|
||||
);
|
||||
export const CANDY_MACHINE_PROGRAM_ID = new PublicKey(
|
||||
'cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ',
|
||||
);
|
||||
export const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
|
||||
'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
|
||||
);
|
||||
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
|
||||
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
|
||||
);
|
||||
export const TOKEN_PROGRAM_ID = new PublicKey(
|
||||
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
|
||||
);
|
||||
|
||||
export const MAX_NAME_LENGTH = 32;
|
||||
export const MAX_URI_LENGTH = 200;
|
||||
export const MAX_SYMBOL_LENGTH = 10;
|
||||
export const MAX_CREATOR_LEN = 32 + 1 + 1;
|
||||
export const ARWEAVE_PAYMENT_WALLET = new PublicKey('HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm');
|
||||
export const CANDY_MACHINE_PROGRAM_ID = new PublicKey('cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ');
|
||||
export const TOKEN_METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
|
||||
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
|
||||
export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
||||
export const CONFIG_ARRAY_START =
|
||||
32 + // authority
|
||||
4 +
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '@solana/web3.js';
|
||||
import { getUnixTs, sleep } from './various';
|
||||
import { DEFAULT_TIMEOUT } from './constants';
|
||||
import log from "loglevel";
|
||||
|
||||
interface BlockhashAndFeeCalculator {
|
||||
blockhash: Blockhash;
|
||||
|
@ -85,7 +86,7 @@ export async function sendSignedTransaction({
|
|||
},
|
||||
);
|
||||
|
||||
console.log('Started awaiting confirmation for', txid);
|
||||
log.debug('Started awaiting confirmation for', txid);
|
||||
|
||||
let done = false;
|
||||
(async () => {
|
||||
|
@ -109,13 +110,13 @@ export async function sendSignedTransaction({
|
|||
throw new Error('Timed out awaiting confirmation on transaction');
|
||||
|
||||
if (confirmation.err) {
|
||||
console.error(confirmation.err);
|
||||
log.error(confirmation.err);
|
||||
throw new Error('Transaction failed: Custom instruction error');
|
||||
}
|
||||
|
||||
slot = confirmation?.slot || 0;
|
||||
} catch (err) {
|
||||
console.error('Timeout Error caught', err);
|
||||
log.error('Timeout Error caught', err);
|
||||
if (err.timeout) {
|
||||
throw new Error('Timed out awaiting confirmation on transaction');
|
||||
}
|
||||
|
@ -125,7 +126,7 @@ export async function sendSignedTransaction({
|
|||
await simulateTransaction(connection, signedTransaction, 'single')
|
||||
).value;
|
||||
} catch (e) {
|
||||
console.error('Simulate Transaction error', e);
|
||||
log.error('Simulate Transaction error', e);
|
||||
}
|
||||
if (simulateResult && simulateResult.err) {
|
||||
if (simulateResult.logs) {
|
||||
|
@ -145,7 +146,7 @@ export async function sendSignedTransaction({
|
|||
done = true;
|
||||
}
|
||||
|
||||
console.log('Latency', txid, getUnixTs() - startTime);
|
||||
log.debug('Latency', txid, getUnixTs() - startTime);
|
||||
return { txid, slot };
|
||||
}
|
||||
|
||||
|
@ -196,7 +197,7 @@ async function awaitTransactionSignatureConfirmation(
|
|||
return;
|
||||
}
|
||||
done = true;
|
||||
console.log('Rejecting for timeout...');
|
||||
log.warn('Rejecting for timeout...');
|
||||
reject({ timeout: true });
|
||||
}, timeout);
|
||||
try {
|
||||
|
@ -210,10 +211,10 @@ async function awaitTransactionSignatureConfirmation(
|
|||
confirmations: 0,
|
||||
};
|
||||
if (result.err) {
|
||||
console.log('Rejected via websocket', result.err);
|
||||
log.warn('Rejected via websocket', result.err);
|
||||
reject(status);
|
||||
} else {
|
||||
console.log('Resolved via websocket', result);
|
||||
log.debug('Resolved via websocket', result);
|
||||
resolve(status);
|
||||
}
|
||||
},
|
||||
|
@ -221,7 +222,7 @@ async function awaitTransactionSignatureConfirmation(
|
|||
);
|
||||
} catch (e) {
|
||||
done = true;
|
||||
console.error('WS error in setup', txid, e);
|
||||
log.error('WS error in setup', txid, e);
|
||||
}
|
||||
while (!done && queryStatus) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
|
@ -233,22 +234,22 @@ async function awaitTransactionSignatureConfirmation(
|
|||
status = signatureStatuses && signatureStatuses.value[0];
|
||||
if (!done) {
|
||||
if (!status) {
|
||||
console.log('REST null result for', txid, status);
|
||||
log.debug('REST null result for', txid, status);
|
||||
} else if (status.err) {
|
||||
console.log('REST error for', txid, status);
|
||||
log.error('REST error for', txid, status);
|
||||
done = true;
|
||||
reject(status.err);
|
||||
} else if (!status.confirmations) {
|
||||
console.log('REST no confirmations for', txid, status);
|
||||
log.error('REST no confirmations for', txid, status);
|
||||
} else {
|
||||
console.log('REST confirmation for', txid, status);
|
||||
log.info('REST confirmation for', txid, status);
|
||||
done = true;
|
||||
resolve(status);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (!done) {
|
||||
console.log('REST connection error: txid', txid, e);
|
||||
log.error('REST connection error: txid', txid, e);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -260,6 +261,6 @@ async function awaitTransactionSignatureConfirmation(
|
|||
if (connection._signatureSubscriptions[subId])
|
||||
connection.removeSignatureListener(subId);
|
||||
done = true;
|
||||
console.log('Returning status', status);
|
||||
log.debug('Returning status', status);
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||
import { CACHE_PATH } from './constants';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import FormData from 'form-data';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import path from "path";
|
||||
import { CACHE_PATH } from "./constants";
|
||||
import fs from "fs";
|
||||
export const getUnixTs = () => {
|
||||
return new Date().getTime() / 1000;
|
||||
};
|
||||
|
@ -53,29 +50,8 @@ export function fromUTF8Array(data: number[]) {
|
|||
return str;
|
||||
}
|
||||
|
||||
export function chunks(array, size) {
|
||||
return Array.apply(0, new Array(Math.ceil(array.length / size))).map(
|
||||
(_, index) => array.slice(index * size, (index + 1) * size),
|
||||
);
|
||||
}
|
||||
|
||||
export function cachePath(env: string, cacheName: string) {
|
||||
return path.join(CACHE_PATH, `${env}-${cacheName}`);
|
||||
}
|
||||
|
||||
export function loadCache(cacheName: string, env: string) {
|
||||
const path = cachePath(env, cacheName);
|
||||
return fs.existsSync(path)
|
||||
? JSON.parse(fs.readFileSync(path).toString())
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function saveCache(cacheName: string, env: string, cacheContent) {
|
||||
fs.writeFileSync(cachePath(env, cacheName), JSON.stringify(cacheContent));
|
||||
}
|
||||
|
||||
export function parsePrice(price): number {
|
||||
return Math.ceil(parseFloat(price) * LAMPORTS_PER_SOL);
|
||||
export function parsePrice(price: string, mantissa: number = LAMPORTS_PER_SOL) {
|
||||
return Math.ceil(parseFloat(price) * mantissa);
|
||||
}
|
||||
|
||||
export async function upload(data: FormData, manifest, index) {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import { BN } from '@project-serum/anchor';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import { PublicKey, AccountInfo } from "@solana/web3.js";
|
||||
|
||||
export class Creator {
|
||||
address: string;
|
||||
address: PublicKey;
|
||||
verified: boolean;
|
||||
share: number;
|
||||
|
||||
constructor(args: { address: string; verified: boolean; share: number }) {
|
||||
constructor(args: {
|
||||
address: PublicKey;
|
||||
verified: boolean;
|
||||
share: number;
|
||||
}) {
|
||||
this.address = args.address;
|
||||
this.verified = args.verified;
|
||||
this.share = args.share;
|
||||
|
@ -28,6 +32,7 @@ export class ConfigData {
|
|||
isMutable: boolean;
|
||||
maxSupply: BN;
|
||||
retainAuthority: boolean;
|
||||
|
||||
constructor(args: {
|
||||
name: string;
|
||||
symbol: string;
|
||||
|
@ -50,3 +55,214 @@ export class ConfigData {
|
|||
this.retainAuthority = args.retainAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
export type AccountAndPubkey = {
|
||||
pubkey: string;
|
||||
account: AccountInfo<Buffer>;
|
||||
};
|
||||
|
||||
export enum MetadataKey {
|
||||
Uninitialized = 0,
|
||||
MetadataV1 = 4,
|
||||
EditionV1 = 1,
|
||||
MasterEditionV1 = 2,
|
||||
MasterEditionV2 = 6,
|
||||
EditionMarker = 7
|
||||
}
|
||||
|
||||
export class MasterEditionV1 {
|
||||
key: MetadataKey;
|
||||
supply: BN;
|
||||
maxSupply?: BN;
|
||||
printingMint: PublicKey;
|
||||
oneTimePrintingAuthorizationMint: PublicKey;
|
||||
constructor(args: {
|
||||
key: MetadataKey;
|
||||
supply: BN;
|
||||
maxSupply?: BN;
|
||||
printingMint: PublicKey;
|
||||
oneTimePrintingAuthorizationMint: PublicKey;
|
||||
}) {
|
||||
this.key = MetadataKey.MasterEditionV1;
|
||||
this.supply = args.supply;
|
||||
this.maxSupply = args.maxSupply;
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
export class Edition {
|
||||
key: MetadataKey;
|
||||
parent: PublicKey;
|
||||
edition: BN;
|
||||
constructor(args: {
|
||||
key: MetadataKey;
|
||||
parent: PublicKey;
|
||||
edition: BN;
|
||||
}) {
|
||||
this.key = MetadataKey.EditionV1;
|
||||
this.parent = args.parent;
|
||||
this.edition = args.edition;
|
||||
};
|
||||
}
|
||||
|
||||
export class Data {
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
sellerFeeBasisPoints: number;
|
||||
creators: Creator[] | null;
|
||||
constructor(args: {
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
sellerFeeBasisPoints: number;
|
||||
creators: Creator[] | null;
|
||||
}) {
|
||||
this.name = args.name;
|
||||
this.symbol = args.symbol;
|
||||
this.uri = args.uri;
|
||||
this.sellerFeeBasisPoints = args.sellerFeeBasisPoints;
|
||||
this.creators = args.creators;
|
||||
};
|
||||
}
|
||||
|
||||
export class Metadata {
|
||||
key: MetadataKey;
|
||||
updateAuthority: PublicKey;
|
||||
mint: PublicKey;
|
||||
data: Data;
|
||||
primarySaleHappened: boolean;
|
||||
isMutable: boolean;
|
||||
masterEdition?: PublicKey;
|
||||
edition?: PublicKey;
|
||||
constructor(args: {
|
||||
updateAuthority: PublicKey;
|
||||
mint: PublicKey;
|
||||
data: Data;
|
||||
primarySaleHappened: boolean;
|
||||
isMutable: boolean;
|
||||
masterEdition?: PublicKey;
|
||||
}) {
|
||||
this.key = MetadataKey.MetadataV1;
|
||||
this.updateAuthority = args.updateAuthority;
|
||||
this.mint = args.mint;
|
||||
this.data = args.data;
|
||||
this.primarySaleHappened = args.primarySaleHappened;
|
||||
this.isMutable = args.isMutable;
|
||||
};
|
||||
}
|
||||
|
||||
export const METADATA_SCHEMA = new Map<any, any>([
|
||||
[
|
||||
MasterEditionV1,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['supply', 'u64'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
['printingMint', 'pubkey'],
|
||||
['oneTimePrintingAuthorizationMint', [32]],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
MasterEditionV2,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['supply', 'u64'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Edition,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['parent', [32]],
|
||||
['edition', 'u64'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Data,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['name', 'string'],
|
||||
['symbol', 'string'],
|
||||
['uri', 'string'],
|
||||
['sellerFeeBasisPoints', 'u16'],
|
||||
['creators', { kind: 'option', type: [Creator] }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Creator,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['address', [32]],
|
||||
['verified', 'u8'],
|
||||
['share', 'u8'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Metadata,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['updateAuthority', [32]],
|
||||
['mint', [32]],
|
||||
['data', Data],
|
||||
['primarySaleHappened', 'u8'],
|
||||
['isMutable', 'u8'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
EditionMarker,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['ledger', [31]],
|
||||
],
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
} from '../utils/ids';
|
||||
import { programIds } from '../utils/programIds';
|
||||
import { TokenAccount } from '../models/account';
|
||||
import { cache, TokenAccountParser } from '../contexts/accounts';
|
||||
import { cache } from '../contexts/accounts/cache';
|
||||
import { TokenAccountParser } from '../contexts/accounts/parsesrs';
|
||||
|
||||
export function ensureSplAccount(
|
||||
instructions: TransactionInstruction[],
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { programIds } from '../utils/programIds';
|
||||
import { deserializeUnchecked, serialize } from 'borsh';
|
||||
import BN from 'bn.js';
|
||||
import { AccountParser } from '../contexts';
|
||||
import { AccountParser } from '../contexts/accounts/types';
|
||||
import moment from 'moment';
|
||||
import { findProgramAddress, StringPublicKey, toPublicKey } from '../utils';
|
||||
export const AUCTION_PREFIX = 'auction';
|
||||
|
@ -696,7 +696,7 @@ export async function createAuction(
|
|||
);
|
||||
}
|
||||
|
||||
export async function startAuction(
|
||||
export async function startAuctionWithResource(
|
||||
resource: StringPublicKey,
|
||||
creator: StringPublicKey,
|
||||
instructions: TransactionInstruction[],
|
||||
|
|
|
@ -1,664 +0,0 @@
|
|||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useConnection } from '../contexts/connection';
|
||||
import { useWallet } from '@solana/wallet-adapter-react';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { AccountLayout, MintInfo, MintLayout, u64 } from '@solana/spl-token';
|
||||
import { TokenAccount } from '../models';
|
||||
import { chunks } from '../utils/utils';
|
||||
import { EventEmitter } from '../utils/eventEmitter';
|
||||
import { StringPublicKey, WRAPPED_SOL_MINT } from '../utils/ids';
|
||||
import { programIds } from '../utils/programIds';
|
||||
|
||||
const AccountsContext = React.createContext<any>(null);
|
||||
|
||||
const pendingCalls = new Map<string, Promise<ParsedAccountBase>>();
|
||||
const genericCache = new Map<string, ParsedAccountBase>();
|
||||
const pendingMintCalls = new Map<string, Promise<MintInfo>>();
|
||||
const mintCache = new Map<string, MintInfo>();
|
||||
|
||||
export interface ParsedAccountBase {
|
||||
pubkey: StringPublicKey;
|
||||
account: AccountInfo<Buffer>;
|
||||
info: any; // TODO: change to unknown
|
||||
}
|
||||
|
||||
export type AccountParser = (
|
||||
pubkey: StringPublicKey,
|
||||
data: AccountInfo<Buffer>,
|
||||
) => ParsedAccountBase | undefined;
|
||||
|
||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||
info: T;
|
||||
}
|
||||
|
||||
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
||||
const info = await connection.getAccountInfo(pubKey);
|
||||
if (info === null) {
|
||||
throw new Error('Failed to find mint account');
|
||||
}
|
||||
|
||||
const data = Buffer.from(info.data);
|
||||
|
||||
return deserializeMint(data);
|
||||
};
|
||||
|
||||
export const MintParser = (pubKey: string, info: AccountInfo<Buffer>) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const data = deserializeMint(buffer);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: data,
|
||||
} as ParsedAccountBase;
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
export const TokenAccountParser = (
|
||||
pubKey: string,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
// Sometimes a wrapped sol account gets closed, goes to 0 length,
|
||||
// triggers an update over wss which triggers this guy to get called
|
||||
// since your UI already logged that pubkey as a token account. Check for length.
|
||||
if (info.data.length > 0) {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = deserializeAccount(buffer);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: data,
|
||||
} as TokenAccount;
|
||||
|
||||
return details;
|
||||
}
|
||||
};
|
||||
|
||||
export const GenericAccountParser = (
|
||||
pubKey: string,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: buffer,
|
||||
} as ParsedAccountBase;
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
export const keyToAccountParser = new Map<string, AccountParser>();
|
||||
|
||||
export const cache = {
|
||||
emitter: new EventEmitter(),
|
||||
query: async (
|
||||
connection: Connection,
|
||||
pubKey: string | PublicKey,
|
||||
parser?: AccountParser,
|
||||
) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
}
|
||||
|
||||
const address = id.toBase58();
|
||||
|
||||
let account = genericCache.get(address);
|
||||
if (account) {
|
||||
return account;
|
||||
}
|
||||
|
||||
let query = pendingCalls.get(address);
|
||||
if (query) {
|
||||
return query;
|
||||
}
|
||||
|
||||
// TODO: refactor to use multiple accounts query with flush like behavior
|
||||
query = connection.getAccountInfo(id).then(data => {
|
||||
if (!data) {
|
||||
throw new Error('Account not found');
|
||||
}
|
||||
|
||||
return cache.add(id, data, parser);
|
||||
}) as Promise<TokenAccount>;
|
||||
pendingCalls.set(address, query as any);
|
||||
|
||||
return query;
|
||||
},
|
||||
add: (
|
||||
id: PublicKey | string,
|
||||
obj: AccountInfo<Buffer>,
|
||||
parser?: AccountParser,
|
||||
isActive?: boolean | undefined | ((parsed: any) => boolean),
|
||||
) => {
|
||||
const address = typeof id === 'string' ? id : id?.toBase58();
|
||||
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
||||
if (!deserialize) {
|
||||
throw new Error(
|
||||
'Deserializer needs to be registered or passed as a parameter',
|
||||
);
|
||||
}
|
||||
|
||||
cache.registerParser(id, deserialize);
|
||||
pendingCalls.delete(address);
|
||||
const account = deserialize(address, obj);
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive === undefined) isActive = true;
|
||||
else if (isActive instanceof Function) isActive = isActive(account);
|
||||
|
||||
const isNew = !genericCache.has(address);
|
||||
|
||||
genericCache.set(address, account);
|
||||
cache.emitter.raiseCacheUpdated(address, isNew, deserialize, isActive);
|
||||
return account;
|
||||
},
|
||||
get: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
return genericCache.get(key);
|
||||
},
|
||||
delete: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
if (genericCache.get(key)) {
|
||||
genericCache.delete(key);
|
||||
cache.emitter.raiseCacheDeleted(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
byParser: (parser: AccountParser) => {
|
||||
const result: string[] = [];
|
||||
for (const id of keyToAccountParser.keys()) {
|
||||
if (keyToAccountParser.get(id) === parser) {
|
||||
result.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||
if (pubkey) {
|
||||
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||
keyToAccountParser.set(address, parser);
|
||||
}
|
||||
|
||||
return pubkey;
|
||||
},
|
||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
}
|
||||
|
||||
const address = id.toBase58();
|
||||
let mint = mintCache.get(address);
|
||||
if (mint) {
|
||||
return mint;
|
||||
}
|
||||
|
||||
let query = pendingMintCalls.get(address);
|
||||
if (query) {
|
||||
return query;
|
||||
}
|
||||
|
||||
query = getMintInfo(connection, id).then(data => {
|
||||
pendingMintCalls.delete(address);
|
||||
|
||||
mintCache.set(address, data);
|
||||
return data;
|
||||
}) as Promise<MintInfo>;
|
||||
pendingMintCalls.set(address, query as any);
|
||||
|
||||
return query;
|
||||
},
|
||||
getMint: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
return mintCache.get(key);
|
||||
},
|
||||
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
|
||||
const mint = deserializeMint(obj.data);
|
||||
const id = pubKey.toBase58();
|
||||
mintCache.set(id, mint);
|
||||
return mint;
|
||||
},
|
||||
};
|
||||
|
||||
export const useAccountsContext = () => {
|
||||
const context = useContext(AccountsContext);
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
function wrapNativeAccount(
|
||||
pubkey: string,
|
||||
account?: AccountInfo<Buffer>,
|
||||
): TokenAccount | undefined {
|
||||
if (!account) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const key = new PublicKey(pubkey);
|
||||
|
||||
return {
|
||||
pubkey: pubkey,
|
||||
account,
|
||||
info: {
|
||||
address: key,
|
||||
mint: WRAPPED_SOL_MINT,
|
||||
owner: key,
|
||||
amount: new u64(account.lamports),
|
||||
delegate: null,
|
||||
delegatedAmount: new u64(0),
|
||||
isInitialized: true,
|
||||
isFrozen: false,
|
||||
isNative: true,
|
||||
rentExemptReserve: null,
|
||||
closeAuthority: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const getCachedAccount = (
|
||||
predicate: (account: TokenAccount) => boolean,
|
||||
) => {
|
||||
for (const account of genericCache.values()) {
|
||||
if (predicate(account)) {
|
||||
return account as TokenAccount;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const UseNativeAccount = () => {
|
||||
const connection = useConnection();
|
||||
const { publicKey } = useWallet();
|
||||
|
||||
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
|
||||
|
||||
const updateCache = useCallback(
|
||||
account => {
|
||||
if (publicKey) {
|
||||
const wrapped = wrapNativeAccount(publicKey.toBase58(), account);
|
||||
if (wrapped !== undefined) {
|
||||
const id = publicKey.toBase58();
|
||||
cache.registerParser(id, TokenAccountParser);
|
||||
genericCache.set(id, wrapped as TokenAccount);
|
||||
cache.emitter.raiseCacheUpdated(id, false, TokenAccountParser, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[publicKey],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let subId = 0;
|
||||
const updateAccount = (account: AccountInfo<Buffer> | null) => {
|
||||
if (account) {
|
||||
updateCache(account);
|
||||
setNativeAccount(account);
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
if (!connection || !publicKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = await connection.getAccountInfo(publicKey);
|
||||
updateAccount(account);
|
||||
|
||||
subId = connection.onAccountChange(publicKey, updateAccount);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (subId) {
|
||||
connection.removeAccountChangeListener(subId);
|
||||
}
|
||||
};
|
||||
}, [setNativeAccount, publicKey, connection, updateCache]);
|
||||
|
||||
return { nativeAccount };
|
||||
};
|
||||
|
||||
const PRECACHED_OWNERS = new Set<string>();
|
||||
const precacheUserTokenAccounts = async (
|
||||
connection: Connection,
|
||||
owner?: PublicKey,
|
||||
) => {
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// used for filtering account updates over websocket
|
||||
PRECACHED_OWNERS.add(owner.toBase58());
|
||||
|
||||
// user accounts are updated via ws subscription
|
||||
const accounts = await connection.getTokenAccountsByOwner(owner, {
|
||||
programId: programIds().token,
|
||||
});
|
||||
|
||||
accounts.value.forEach(info => {
|
||||
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
|
||||
});
|
||||
};
|
||||
|
||||
export function AccountsProvider({ children = null as any }) {
|
||||
const connection = useConnection();
|
||||
const { publicKey } = useWallet();
|
||||
const [tokenAccounts, setTokenAccounts] = useState<TokenAccount[]>([]);
|
||||
const [userAccounts, setUserAccounts] = useState<TokenAccount[]>([]);
|
||||
const { nativeAccount } = UseNativeAccount();
|
||||
const walletKey = publicKey?.toBase58();
|
||||
|
||||
const selectUserAccounts = useCallback(() => {
|
||||
return cache
|
||||
.byParser(TokenAccountParser)
|
||||
.map(id => cache.get(id))
|
||||
.filter(a => a && a.info.owner.toBase58() === walletKey)
|
||||
.map(a => a as TokenAccount);
|
||||
}, [walletKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = selectUserAccounts().filter(
|
||||
a => a !== undefined,
|
||||
) as TokenAccount[];
|
||||
setUserAccounts(accounts);
|
||||
}, [nativeAccount, tokenAccounts, selectUserAccounts]);
|
||||
|
||||
useEffect(() => {
|
||||
const subs: number[] = [];
|
||||
cache.emitter.onCache(args => {
|
||||
if (args.isNew && args.isActive) {
|
||||
let id = args.id;
|
||||
let deserialize = args.parser;
|
||||
connection.onAccountChange(new PublicKey(id), info => {
|
||||
cache.add(id, info, deserialize);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subs.forEach(id => connection.removeAccountChangeListener(id));
|
||||
};
|
||||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection || !publicKey) {
|
||||
setTokenAccounts([]);
|
||||
} else {
|
||||
precacheUserTokenAccounts(connection, publicKey).then(() => {
|
||||
setTokenAccounts(selectUserAccounts());
|
||||
});
|
||||
|
||||
// This can return different types of accounts: token-account, mint, multisig
|
||||
// TODO: web3.js expose ability to filter.
|
||||
// this should use only filter syntax to only get accounts that are owned by user
|
||||
const tokenSubID = connection.onProgramAccountChange(
|
||||
programIds().token,
|
||||
info => {
|
||||
// TODO: fix type in web3.js
|
||||
const id = info.accountId as unknown as string;
|
||||
// TODO: do we need a better way to identify layout (maybe a enum identifing type?)
|
||||
if (info.accountInfo.data.length === AccountLayout.span) {
|
||||
const data = deserializeAccount(info.accountInfo.data);
|
||||
|
||||
if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
|
||||
cache.add(id, info.accountInfo, TokenAccountParser);
|
||||
setTokenAccounts(selectUserAccounts());
|
||||
}
|
||||
}
|
||||
},
|
||||
'singleGossip',
|
||||
);
|
||||
|
||||
return () => {
|
||||
connection.removeProgramAccountChangeListener(tokenSubID);
|
||||
};
|
||||
}
|
||||
}, [connection, publicKey, selectUserAccounts]);
|
||||
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
value={{
|
||||
userAccounts,
|
||||
nativeAccount,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNativeAccount() {
|
||||
const context = useContext(AccountsContext);
|
||||
return {
|
||||
account: context.nativeAccount as AccountInfo<Buffer>,
|
||||
};
|
||||
}
|
||||
|
||||
export const getMultipleAccounts = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string,
|
||||
) => {
|
||||
const result = await Promise.all(
|
||||
chunks(keys, 99).map(chunk =>
|
||||
getMultipleAccountsCore(connection, chunk, commitment),
|
||||
),
|
||||
);
|
||||
|
||||
const array = result
|
||||
.map(
|
||||
a =>
|
||||
a.array.map(acc => {
|
||||
if (!acc) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { data, ...rest } = acc;
|
||||
const obj = {
|
||||
...rest,
|
||||
data: Buffer.from(data[0], 'base64'),
|
||||
} as AccountInfo<Buffer>;
|
||||
return obj;
|
||||
}) as AccountInfo<Buffer>[],
|
||||
)
|
||||
.flat();
|
||||
return { keys, array };
|
||||
};
|
||||
|
||||
const getMultipleAccountsCore = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string,
|
||||
) => {
|
||||
const args = connection._buildArgs([keys], commitment, 'base64');
|
||||
|
||||
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
|
||||
if (unsafeRes.error) {
|
||||
throw new Error(
|
||||
'failed to get info about account ' + unsafeRes.error.message,
|
||||
);
|
||||
}
|
||||
|
||||
if (unsafeRes.result.value) {
|
||||
const array = unsafeRes.result.value as AccountInfo<string[]>[];
|
||||
return { keys, array };
|
||||
}
|
||||
|
||||
// TODO: fix
|
||||
throw new Error();
|
||||
};
|
||||
|
||||
export function useMint(key?: string | PublicKey) {
|
||||
const connection = useConnection();
|
||||
const [mint, setMint] = useState<MintInfo>();
|
||||
|
||||
const id = typeof key === 'string' ? key : key?.toBase58();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then(acc => setMint(acc.info as any))
|
||||
.catch(err => console.log(err));
|
||||
|
||||
const dispose = cache.emitter.onCache(e => {
|
||||
const event = e;
|
||||
if (event.id === id) {
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then(mint => setMint(mint.info as any));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, id]);
|
||||
|
||||
return mint;
|
||||
}
|
||||
|
||||
export function useAccount(pubKey?: PublicKey) {
|
||||
const connection = useConnection();
|
||||
const [account, setAccount] = useState<TokenAccount>();
|
||||
|
||||
const key = pubKey?.toBase58();
|
||||
useEffect(() => {
|
||||
const query = async () => {
|
||||
try {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const acc = await cache
|
||||
.query(connection, key, TokenAccountParser)
|
||||
.catch(err => console.log(err));
|
||||
if (acc) {
|
||||
setAccount(acc);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
query();
|
||||
|
||||
const dispose = cache.emitter.onCache(e => {
|
||||
const event = e;
|
||||
if (event.id === key) {
|
||||
query();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, key]);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
// TODO: expose in spl package
|
||||
export const deserializeAccount = (data: Buffer) => {
|
||||
const accountInfo = AccountLayout.decode(data);
|
||||
accountInfo.mint = new PublicKey(accountInfo.mint);
|
||||
accountInfo.owner = new PublicKey(accountInfo.owner);
|
||||
accountInfo.amount = u64.fromBuffer(accountInfo.amount);
|
||||
|
||||
if (accountInfo.delegateOption === 0) {
|
||||
accountInfo.delegate = null;
|
||||
accountInfo.delegatedAmount = new u64(0);
|
||||
} else {
|
||||
accountInfo.delegate = new PublicKey(accountInfo.delegate);
|
||||
accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
|
||||
}
|
||||
|
||||
accountInfo.isInitialized = accountInfo.state !== 0;
|
||||
accountInfo.isFrozen = accountInfo.state === 2;
|
||||
|
||||
if (accountInfo.isNativeOption === 1) {
|
||||
accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
|
||||
accountInfo.isNative = true;
|
||||
} else {
|
||||
accountInfo.rentExemptReserve = null;
|
||||
accountInfo.isNative = false;
|
||||
}
|
||||
|
||||
if (accountInfo.closeAuthorityOption === 0) {
|
||||
accountInfo.closeAuthority = null;
|
||||
} else {
|
||||
accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
|
||||
}
|
||||
|
||||
return accountInfo;
|
||||
};
|
||||
|
||||
// TODO: expose in spl package
|
||||
export const deserializeMint = (data: Buffer) => {
|
||||
if (data.length !== MintLayout.span) {
|
||||
throw new Error('Not a valid Mint');
|
||||
}
|
||||
|
||||
const mintInfo = MintLayout.decode(data);
|
||||
|
||||
if (mintInfo.mintAuthorityOption === 0) {
|
||||
mintInfo.mintAuthority = null;
|
||||
} else {
|
||||
mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
|
||||
}
|
||||
|
||||
mintInfo.supply = u64.fromBuffer(mintInfo.supply);
|
||||
mintInfo.isInitialized = mintInfo.isInitialized !== 0;
|
||||
|
||||
if (mintInfo.freezeAuthorityOption === 0) {
|
||||
mintInfo.freezeAuthority = null;
|
||||
} else {
|
||||
mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
|
||||
}
|
||||
|
||||
return mintInfo as MintInfo;
|
||||
};
|
|
@ -0,0 +1,286 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useWallet } from '@solana/wallet-adapter-react';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { AccountLayout, MintInfo, u64 } from '@solana/spl-token';
|
||||
import { useConnection } from '../../contexts/connection';
|
||||
import { TokenAccount } from '../../models';
|
||||
import { StringPublicKey, WRAPPED_SOL_MINT } from '../../utils/ids';
|
||||
import { programIds } from '../../utils/programIds';
|
||||
import { genericCache, cache } from './cache';
|
||||
import { deserializeAccount } from './deserialize';
|
||||
import { TokenAccountParser, MintParser } from './parsesrs';
|
||||
|
||||
const AccountsContext = React.createContext<any>(null);
|
||||
|
||||
export const useAccountsContext = () => {
|
||||
const context = useContext(AccountsContext);
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
function wrapNativeAccount(
|
||||
pubkey: StringPublicKey,
|
||||
account?: AccountInfo<Buffer>,
|
||||
): TokenAccount | undefined {
|
||||
if (!account) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const key = new PublicKey(pubkey);
|
||||
|
||||
return {
|
||||
pubkey: pubkey,
|
||||
account,
|
||||
info: {
|
||||
address: key,
|
||||
mint: WRAPPED_SOL_MINT,
|
||||
owner: key,
|
||||
amount: new u64(account.lamports),
|
||||
delegate: null,
|
||||
delegatedAmount: new u64(0),
|
||||
isInitialized: true,
|
||||
isFrozen: false,
|
||||
isNative: true,
|
||||
rentExemptReserve: null,
|
||||
closeAuthority: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const UseNativeAccount = () => {
|
||||
const connection = useConnection();
|
||||
const { publicKey } = useWallet();
|
||||
|
||||
const [nativeAccount, setNativeAccount] = useState<AccountInfo<Buffer>>();
|
||||
|
||||
const updateCache = useCallback(
|
||||
account => {
|
||||
if (publicKey) {
|
||||
const wrapped = wrapNativeAccount(publicKey.toBase58(), account);
|
||||
if (wrapped !== undefined) {
|
||||
const id = publicKey.toBase58();
|
||||
cache.registerParser(id, TokenAccountParser);
|
||||
genericCache.set(id, wrapped as TokenAccount);
|
||||
cache.emitter.raiseCacheUpdated(id, false, TokenAccountParser, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[publicKey],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let subId = 0;
|
||||
const updateAccount = (account: AccountInfo<Buffer> | null) => {
|
||||
if (account) {
|
||||
updateCache(account);
|
||||
setNativeAccount(account);
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
if (!connection || !publicKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = await connection.getAccountInfo(publicKey);
|
||||
updateAccount(account);
|
||||
|
||||
subId = connection.onAccountChange(publicKey, updateAccount);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (subId) {
|
||||
connection.removeAccountChangeListener(subId);
|
||||
}
|
||||
};
|
||||
}, [setNativeAccount, publicKey, connection, updateCache]);
|
||||
|
||||
return { nativeAccount };
|
||||
};
|
||||
|
||||
const PRECACHED_OWNERS = new Set<string>();
|
||||
const precacheUserTokenAccounts = async (
|
||||
connection: Connection,
|
||||
owner?: PublicKey,
|
||||
) => {
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// used for filtering account updates over websocket
|
||||
PRECACHED_OWNERS.add(owner.toBase58());
|
||||
|
||||
// user accounts are updated via ws subscription
|
||||
const accounts = await connection.getTokenAccountsByOwner(owner, {
|
||||
programId: programIds().token,
|
||||
});
|
||||
|
||||
accounts.value.forEach(info => {
|
||||
cache.add(info.pubkey.toBase58(), info.account, TokenAccountParser);
|
||||
});
|
||||
};
|
||||
|
||||
export function AccountsProvider({ children = null as any }) {
|
||||
const connection = useConnection();
|
||||
const { publicKey } = useWallet();
|
||||
const [tokenAccounts, setTokenAccounts] = useState<TokenAccount[]>([]);
|
||||
const [userAccounts, setUserAccounts] = useState<TokenAccount[]>([]);
|
||||
const { nativeAccount } = UseNativeAccount();
|
||||
const walletKey = publicKey?.toBase58();
|
||||
|
||||
const selectUserAccounts = useCallback(() => {
|
||||
return cache
|
||||
.byParser(TokenAccountParser)
|
||||
.map(id => cache.get(id))
|
||||
.filter(a => a && a.info.owner.toBase58() === walletKey)
|
||||
.map(a => a as TokenAccount);
|
||||
}, [walletKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = selectUserAccounts().filter(
|
||||
a => a !== undefined,
|
||||
) as TokenAccount[];
|
||||
setUserAccounts(accounts);
|
||||
}, [nativeAccount, tokenAccounts, selectUserAccounts]);
|
||||
|
||||
useEffect(() => {
|
||||
const subs: number[] = [];
|
||||
cache.emitter.onCache(args => {
|
||||
if (args.isNew && args.isActive) {
|
||||
let id = args.id;
|
||||
let deserialize = args.parser;
|
||||
connection.onAccountChange(new PublicKey(id), info => {
|
||||
cache.add(id, info, deserialize);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subs.forEach(id => connection.removeAccountChangeListener(id));
|
||||
};
|
||||
}, [connection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection || !publicKey) {
|
||||
setTokenAccounts([]);
|
||||
} else {
|
||||
precacheUserTokenAccounts(connection, publicKey).then(() => {
|
||||
setTokenAccounts(selectUserAccounts());
|
||||
});
|
||||
|
||||
// This can return different types of accounts: token-account, mint, multisig
|
||||
// TODO: web3.js expose ability to filter.
|
||||
// this should use only filter syntax to only get accounts that are owned by user
|
||||
const tokenSubID = connection.onProgramAccountChange(
|
||||
programIds().token,
|
||||
info => {
|
||||
// TODO: fix type in web3.js
|
||||
const id = info.accountId as unknown as string;
|
||||
// TODO: do we need a better way to identify layout (maybe a enum identifing type?)
|
||||
if (info.accountInfo.data.length === AccountLayout.span) {
|
||||
const data = deserializeAccount(info.accountInfo.data);
|
||||
|
||||
if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
|
||||
cache.add(id, info.accountInfo, TokenAccountParser);
|
||||
setTokenAccounts(selectUserAccounts());
|
||||
}
|
||||
}
|
||||
},
|
||||
'singleGossip',
|
||||
);
|
||||
|
||||
return () => {
|
||||
connection.removeProgramAccountChangeListener(tokenSubID);
|
||||
};
|
||||
}
|
||||
}, [connection, publicKey, selectUserAccounts]);
|
||||
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
value={{
|
||||
userAccounts,
|
||||
nativeAccount,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNativeAccount() {
|
||||
const context = useContext(AccountsContext);
|
||||
return {
|
||||
account: context.nativeAccount as AccountInfo<Buffer>,
|
||||
};
|
||||
}
|
||||
|
||||
export function useMint(key?: string | PublicKey) {
|
||||
const connection = useConnection();
|
||||
const [mint, setMint] = useState<MintInfo>();
|
||||
|
||||
const id = typeof key === 'string' ? key : key?.toBase58();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then(acc => setMint(acc.info as any))
|
||||
.catch(err => console.log(err));
|
||||
|
||||
const dispose = cache.emitter.onCache(e => {
|
||||
const event = e;
|
||||
if (event.id === id) {
|
||||
cache
|
||||
.query(connection, id, MintParser)
|
||||
.then(mint => setMint(mint.info as any));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, id]);
|
||||
|
||||
return mint;
|
||||
}
|
||||
|
||||
export function useAccount(pubKey?: PublicKey) {
|
||||
const connection = useConnection();
|
||||
const [account, setAccount] = useState<TokenAccount>();
|
||||
|
||||
const key = pubKey?.toBase58();
|
||||
useEffect(() => {
|
||||
const query = async () => {
|
||||
try {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const acc = await cache
|
||||
.query(connection, key, TokenAccountParser)
|
||||
.catch(err => console.log(err));
|
||||
if (acc) {
|
||||
setAccount(acc);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
query();
|
||||
|
||||
const dispose = cache.emitter.onCache(e => {
|
||||
const event = e;
|
||||
if (event.id === key) {
|
||||
query();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [connection, key]);
|
||||
|
||||
return account;
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { TokenAccount } from '../../models';
|
||||
import { EventEmitter } from '../../utils/eventEmitter';
|
||||
import { ParsedAccountBase, AccountParser } from './types';
|
||||
import { deserializeMint } from './deserialize';
|
||||
|
||||
export const genericCache = new Map<string, ParsedAccountBase>();
|
||||
const mintCache = new Map<string, MintInfo>();
|
||||
const pendingCalls = new Map<string, Promise<ParsedAccountBase>>();
|
||||
const pendingMintCalls = new Map<string, Promise<MintInfo>>();
|
||||
|
||||
const keyToAccountParser = new Map<string, AccountParser>();
|
||||
|
||||
const getMintInfo = async (connection: Connection, pubKey: PublicKey) => {
|
||||
const info = await connection.getAccountInfo(pubKey);
|
||||
if (info === null) {
|
||||
throw new Error('Failed to find mint account');
|
||||
}
|
||||
|
||||
const data = Buffer.from(info.data);
|
||||
|
||||
return deserializeMint(data);
|
||||
};
|
||||
|
||||
export const cache = {
|
||||
emitter: new EventEmitter(),
|
||||
query: async (
|
||||
connection: Connection,
|
||||
pubKey: string | PublicKey,
|
||||
parser?: AccountParser,
|
||||
) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
}
|
||||
|
||||
const address = id.toBase58();
|
||||
|
||||
const account = genericCache.get(address);
|
||||
if (account) {
|
||||
return account;
|
||||
}
|
||||
|
||||
let query = pendingCalls.get(address);
|
||||
if (query) {
|
||||
return query;
|
||||
}
|
||||
|
||||
// TODO: refactor to use multiple accounts query with flush like behavior
|
||||
query = connection.getAccountInfo(id).then(data => {
|
||||
if (!data) {
|
||||
throw new Error('Account not found');
|
||||
}
|
||||
|
||||
return cache.add(id, data, parser);
|
||||
}) as Promise<TokenAccount>;
|
||||
pendingCalls.set(address, query as any);
|
||||
|
||||
return query;
|
||||
},
|
||||
add: (
|
||||
id: PublicKey | string,
|
||||
obj: AccountInfo<Buffer>,
|
||||
parser?: AccountParser,
|
||||
isActive?: boolean | undefined | ((parsed: any) => boolean),
|
||||
) => {
|
||||
const address = typeof id === 'string' ? id : id?.toBase58();
|
||||
const deserialize = parser ? parser : keyToAccountParser.get(address);
|
||||
if (!deserialize) {
|
||||
throw new Error(
|
||||
'Deserializer needs to be registered or passed as a parameter',
|
||||
);
|
||||
}
|
||||
|
||||
cache.registerParser(id, deserialize);
|
||||
pendingCalls.delete(address);
|
||||
const account = deserialize(address, obj);
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive === undefined) isActive = true;
|
||||
else if (isActive instanceof Function) isActive = isActive(account);
|
||||
|
||||
const isNew = !genericCache.has(address);
|
||||
|
||||
genericCache.set(address, account);
|
||||
cache.emitter.raiseCacheUpdated(address, isNew, deserialize, isActive);
|
||||
return account;
|
||||
},
|
||||
get: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
return genericCache.get(key);
|
||||
},
|
||||
delete: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
if (genericCache.get(key)) {
|
||||
genericCache.delete(key);
|
||||
cache.emitter.raiseCacheDeleted(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
byParser: (parser: AccountParser) => {
|
||||
const result: string[] = [];
|
||||
for (const id of keyToAccountParser.keys()) {
|
||||
if (keyToAccountParser.get(id) === parser) {
|
||||
result.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
registerParser: (pubkey: PublicKey | string, parser: AccountParser) => {
|
||||
if (pubkey) {
|
||||
const address = typeof pubkey === 'string' ? pubkey : pubkey?.toBase58();
|
||||
keyToAccountParser.set(address, parser);
|
||||
}
|
||||
|
||||
return pubkey;
|
||||
},
|
||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
||||
let id: PublicKey;
|
||||
if (typeof pubKey === 'string') {
|
||||
id = new PublicKey(pubKey);
|
||||
} else {
|
||||
id = pubKey;
|
||||
}
|
||||
|
||||
const address = id.toBase58();
|
||||
const mint = mintCache.get(address);
|
||||
if (mint) {
|
||||
return mint;
|
||||
}
|
||||
|
||||
let query = pendingMintCalls.get(address);
|
||||
if (query) {
|
||||
return query;
|
||||
}
|
||||
|
||||
query = getMintInfo(connection, id).then(data => {
|
||||
pendingMintCalls.delete(address);
|
||||
|
||||
mintCache.set(address, data);
|
||||
return data;
|
||||
}) as Promise<MintInfo>;
|
||||
pendingMintCalls.set(address, query as any);
|
||||
|
||||
return query;
|
||||
},
|
||||
getMint: (pubKey: string | PublicKey) => {
|
||||
let key: string;
|
||||
if (typeof pubKey !== 'string') {
|
||||
key = pubKey.toBase58();
|
||||
} else {
|
||||
key = pubKey;
|
||||
}
|
||||
|
||||
return mintCache.get(key);
|
||||
},
|
||||
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
|
||||
const mint = deserializeMint(obj.data);
|
||||
const id = pubKey.toBase58();
|
||||
mintCache.set(id, mint);
|
||||
return mint;
|
||||
},
|
||||
};
|
||||
|
||||
export const getCachedAccount = (
|
||||
predicate: (account: TokenAccount) => boolean,
|
||||
) => {
|
||||
for (const account of genericCache.values()) {
|
||||
if (predicate(account)) {
|
||||
return account as TokenAccount;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { AccountLayout, MintInfo, MintLayout, u64 } from '@solana/spl-token';
|
||||
|
||||
// TODO: expose in spl package
|
||||
export const deserializeAccount = (data: Buffer) => {
|
||||
const accountInfo = AccountLayout.decode(data);
|
||||
accountInfo.mint = new PublicKey(accountInfo.mint);
|
||||
accountInfo.owner = new PublicKey(accountInfo.owner);
|
||||
accountInfo.amount = u64.fromBuffer(accountInfo.amount);
|
||||
|
||||
if (accountInfo.delegateOption === 0) {
|
||||
accountInfo.delegate = null;
|
||||
accountInfo.delegatedAmount = new u64(0);
|
||||
} else {
|
||||
accountInfo.delegate = new PublicKey(accountInfo.delegate);
|
||||
accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
|
||||
}
|
||||
|
||||
accountInfo.isInitialized = accountInfo.state !== 0;
|
||||
accountInfo.isFrozen = accountInfo.state === 2;
|
||||
|
||||
if (accountInfo.isNativeOption === 1) {
|
||||
accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
|
||||
accountInfo.isNative = true;
|
||||
} else {
|
||||
accountInfo.rentExemptReserve = null;
|
||||
accountInfo.isNative = false;
|
||||
}
|
||||
|
||||
if (accountInfo.closeAuthorityOption === 0) {
|
||||
accountInfo.closeAuthority = null;
|
||||
} else {
|
||||
accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
|
||||
}
|
||||
|
||||
return accountInfo;
|
||||
};
|
||||
|
||||
// TODO: expose in spl package
|
||||
export const deserializeMint = (data: Buffer) => {
|
||||
if (data.length !== MintLayout.span) {
|
||||
throw new Error('Not a valid Mint');
|
||||
}
|
||||
|
||||
const mintInfo = MintLayout.decode(data);
|
||||
|
||||
if (mintInfo.mintAuthorityOption === 0) {
|
||||
mintInfo.mintAuthority = null;
|
||||
} else {
|
||||
mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
|
||||
}
|
||||
|
||||
mintInfo.supply = u64.fromBuffer(mintInfo.supply);
|
||||
mintInfo.isInitialized = mintInfo.isInitialized !== 0;
|
||||
|
||||
if (mintInfo.freezeAuthorityOption === 0) {
|
||||
mintInfo.freezeAuthority = null;
|
||||
} else {
|
||||
mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
|
||||
}
|
||||
|
||||
return mintInfo as MintInfo;
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
import { AccountInfo } from '@solana/web3.js';
|
||||
import { chunks } from '../../utils/utils';
|
||||
|
||||
export const getMultipleAccounts = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string,
|
||||
) => {
|
||||
const result = await Promise.all(
|
||||
chunks(keys, 99).map(chunk =>
|
||||
getMultipleAccountsCore(connection, chunk, commitment),
|
||||
),
|
||||
);
|
||||
|
||||
const array = result
|
||||
.map(
|
||||
a =>
|
||||
a.array.map(acc => {
|
||||
if (!acc) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { data, ...rest } = acc;
|
||||
const obj = {
|
||||
...rest,
|
||||
data: Buffer.from(data[0], 'base64'),
|
||||
} as AccountInfo<Buffer>;
|
||||
return obj;
|
||||
}) as AccountInfo<Buffer>[],
|
||||
)
|
||||
.flat();
|
||||
return { keys, array };
|
||||
};
|
||||
|
||||
const getMultipleAccountsCore = async (
|
||||
connection: any,
|
||||
keys: string[],
|
||||
commitment: string,
|
||||
) => {
|
||||
const args = connection._buildArgs([keys], commitment, 'base64');
|
||||
|
||||
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
|
||||
if (unsafeRes.error) {
|
||||
throw new Error(
|
||||
'failed to get info about account ' + unsafeRes.error.message,
|
||||
);
|
||||
}
|
||||
|
||||
if (unsafeRes.result.value) {
|
||||
const array = unsafeRes.result.value as AccountInfo<string[]>[];
|
||||
return { keys, array };
|
||||
}
|
||||
|
||||
// TODO: fix
|
||||
throw new Error();
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export * from './accounts';
|
||||
export * from './cache';
|
||||
export * from './getMultipleAccounts';
|
||||
export * from './parsesrs';
|
||||
export * from './deserialize';
|
||||
export * from './types';
|
|
@ -0,0 +1,64 @@
|
|||
import { AccountInfo } from '@solana/web3.js';
|
||||
import { TokenAccount } from '../../models';
|
||||
import { ParsedAccountBase } from './types';
|
||||
import { deserializeMint, deserializeAccount } from './deserialize';
|
||||
import { StringPublicKey } from '../../utils';
|
||||
|
||||
export const MintParser = (
|
||||
pubKey: StringPublicKey,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const data = deserializeMint(buffer);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: data,
|
||||
} as ParsedAccountBase;
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
export const TokenAccountParser = (
|
||||
pubKey: StringPublicKey,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
// Sometimes a wrapped sol account gets closed, goes to 0 length,
|
||||
// triggers an update over wss which triggers this guy to get called
|
||||
// since your UI already logged that pubkey as a token account. Check for length.
|
||||
if (info.data.length > 0) {
|
||||
const buffer = Buffer.from(info.data);
|
||||
const data = deserializeAccount(buffer);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: data,
|
||||
} as TokenAccount;
|
||||
|
||||
return details;
|
||||
}
|
||||
};
|
||||
|
||||
export const GenericAccountParser = (
|
||||
pubKey: StringPublicKey,
|
||||
info: AccountInfo<Buffer>,
|
||||
) => {
|
||||
const buffer = Buffer.from(info.data);
|
||||
|
||||
const details = {
|
||||
pubkey: pubKey,
|
||||
account: {
|
||||
...info,
|
||||
},
|
||||
info: buffer,
|
||||
} as ParsedAccountBase;
|
||||
|
||||
return details;
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
import { AccountInfo } from '@solana/web3.js';
|
||||
import { StringPublicKey } from '../../utils';
|
||||
|
||||
export interface ParsedAccountBase {
|
||||
pubkey: StringPublicKey;
|
||||
account: AccountInfo<Buffer>;
|
||||
info: any; // TODO: change to unknown
|
||||
}
|
||||
|
||||
export type AccountParser = (
|
||||
pubkey: StringPublicKey,
|
||||
data: AccountInfo<Buffer>,
|
||||
) => ParsedAccountBase | undefined;
|
||||
|
||||
export interface ParsedAccount<T> extends ParsedAccountBase {
|
||||
info: T;
|
||||
}
|
|
@ -4,5 +4,5 @@ export * as Connection from './connection';
|
|||
export * from './connection';
|
||||
export * as Wallet from './wallet';
|
||||
export * from './wallet';
|
||||
export * as Store from './store';
|
||||
export * from './store';
|
||||
export * from './meta';
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { MetaState } from './types';
|
||||
|
||||
export const getEmptyMetaState = (): MetaState => ({
|
||||
metadata: [],
|
||||
metadataByMint: {},
|
||||
masterEditions: {},
|
||||
masterEditionsByPrintingMint: {},
|
||||
masterEditionsByOneTimeAuthMint: {},
|
||||
metadataByMasterEdition: {},
|
||||
editions: {},
|
||||
auctionManagersByAuction: {},
|
||||
bidRedemptions: {},
|
||||
auctions: {},
|
||||
auctionDataExtended: {},
|
||||
vaults: {},
|
||||
payoutTickets: {},
|
||||
store: null,
|
||||
whitelistedCreatorsByCreator: {},
|
||||
bidderMetadataByAuctionAndBidder: {},
|
||||
bidderPotsByAuctionAndBidder: {},
|
||||
safetyDepositBoxesByVaultAndIndex: {},
|
||||
prizeTrackingTickets: {},
|
||||
safetyDepositConfigsByAuctionManagerAndIndex: {},
|
||||
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
|
||||
stores: {},
|
||||
creators: {},
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
export * from './meta';
|
||||
export * from './isMetadataPartOfStore';
|
||||
export * from './loadAccounts';
|
||||
export * from './onChangeAccount';
|
||||
export * from './subscribeAccountsChange';
|
||||
export * from './processAuctions';
|
||||
export * from './processMetaData';
|
||||
export * from './processMetaplexAccounts';
|
||||
export * from './processVaultData';
|
||||
export * from './queryExtendedMetadata';
|
||||
export * from './types';
|
|
@ -1,5 +1,6 @@
|
|||
import { Metadata, ParsedAccount } from '@oyster/common';
|
||||
import { Metadata } from '../../actions';
|
||||
import { Store, WhitelistedCreator } from '../../models/metaplex';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
|
||||
export const isMetadataPartOfStore = (
|
||||
m: ParsedAccount<Metadata>,
|
||||
|
@ -8,11 +9,7 @@ export const isMetadataPartOfStore = (
|
|||
string,
|
||||
ParsedAccount<WhitelistedCreator>
|
||||
>,
|
||||
useAll: boolean,
|
||||
) => {
|
||||
if (useAll) {
|
||||
return true;
|
||||
}
|
||||
if (!m?.info?.data?.creators || !store?.info) {
|
||||
return false;
|
||||
}
|
|
@ -5,7 +5,18 @@ import {
|
|||
StringPublicKey,
|
||||
toPublicKey,
|
||||
VAULT_ID,
|
||||
} from '@oyster/common/dist/lib/utils/ids';
|
||||
} from '../../utils/ids';
|
||||
import { MAX_WHITELISTED_CREATOR_SIZE } from '../../models';
|
||||
import {
|
||||
getEdition,
|
||||
Metadata,
|
||||
MAX_CREATOR_LEN,
|
||||
MAX_CREATOR_LIMIT,
|
||||
MAX_NAME_LENGTH,
|
||||
MAX_SYMBOL_LENGTH,
|
||||
MAX_URI_LENGTH,
|
||||
METADATA_PREFIX,
|
||||
} from '../../actions';
|
||||
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
|
||||
import { AccountAndPubkey, MetaState, ProcessAccountsFunc } from './types';
|
||||
import { isMetadataPartOfStore } from './isMetadataPartOfStore';
|
||||
|
@ -13,19 +24,9 @@ import { processAuctions } from './processAuctions';
|
|||
import { processMetaplexAccounts } from './processMetaplexAccounts';
|
||||
import { processMetaData } from './processMetaData';
|
||||
import { processVaultData } from './processVaultData';
|
||||
import {
|
||||
getEdition,
|
||||
getMultipleAccounts,
|
||||
MAX_CREATOR_LEN,
|
||||
MAX_CREATOR_LIMIT,
|
||||
MAX_NAME_LENGTH,
|
||||
MAX_SYMBOL_LENGTH,
|
||||
MAX_URI_LENGTH,
|
||||
Metadata,
|
||||
METADATA_PREFIX,
|
||||
ParsedAccount,
|
||||
} from '@oyster/common';
|
||||
import { MAX_WHITELISTED_CREATOR_SIZE } from '../../models/metaplex';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { getEmptyMetaState } from './getEmptyMetaState';
|
||||
import { getMultipleAccounts } from '../accounts/getMultipleAccounts';
|
||||
|
||||
async function getProgramAccounts(
|
||||
connection: Connection,
|
||||
|
@ -82,30 +83,7 @@ async function getProgramAccounts(
|
|||
}
|
||||
|
||||
export const loadAccounts = async (connection: Connection, all: boolean) => {
|
||||
const tempCache: MetaState = {
|
||||
metadata: [],
|
||||
metadataByMint: {},
|
||||
masterEditions: {},
|
||||
masterEditionsByPrintingMint: {},
|
||||
masterEditionsByOneTimeAuthMint: {},
|
||||
metadataByMasterEdition: {},
|
||||
editions: {},
|
||||
auctionManagersByAuction: {},
|
||||
bidRedemptions: {},
|
||||
auctions: {},
|
||||
auctionDataExtended: {},
|
||||
vaults: {},
|
||||
payoutTickets: {},
|
||||
store: null,
|
||||
whitelistedCreatorsByCreator: {},
|
||||
bidderMetadataByAuctionAndBidder: {},
|
||||
bidderPotsByAuctionAndBidder: {},
|
||||
safetyDepositBoxesByVaultAndIndex: {},
|
||||
prizeTrackingTickets: {},
|
||||
safetyDepositConfigsByAuctionManagerAndIndex: {},
|
||||
bidRedemptionV2sByAuctionManagerAndWinningIndex: {},
|
||||
stores: {},
|
||||
};
|
||||
const tempCache: MetaState = getEmptyMetaState();
|
||||
const updateTemp = makeSetter(tempCache);
|
||||
|
||||
const forEach =
|
||||
|
@ -115,89 +93,58 @@ export const loadAccounts = async (connection: Connection, all: boolean) => {
|
|||
}
|
||||
};
|
||||
|
||||
const additionalPromises: Promise<void>[] = [];
|
||||
let isSelectivePullMetadata = false;
|
||||
const pullMetadata = async (creators: AccountAndPubkey[]) => {
|
||||
await forEach(processMetaplexAccounts)(creators);
|
||||
|
||||
const IS_BIG_STORE =
|
||||
process.env.NEXT_PUBLIC_BIG_STORE?.toLowerCase() === 'true';
|
||||
console.log(`Is big store: ${IS_BIG_STORE}`);
|
||||
const whitelistedCreators = Object.values(
|
||||
tempCache.whitelistedCreatorsByCreator,
|
||||
);
|
||||
|
||||
const promises = [
|
||||
getProgramAccounts(connection, VAULT_ID).then(forEach(processVaultData)),
|
||||
getProgramAccounts(connection, AUCTION_ID).then(forEach(processAuctions)),
|
||||
getProgramAccounts(connection, METAPLEX_ID).then(
|
||||
forEach(processMetaplexAccounts),
|
||||
),
|
||||
IS_BIG_STORE
|
||||
? getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
|
||||
if (whitelistedCreators.length > 3) {
|
||||
console.log(' too many creators, pulling all nfts in one go');
|
||||
additionalPromises.push(
|
||||
getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
|
||||
forEach(processMetaData),
|
||||
)
|
||||
: undefined,
|
||||
getProgramAccounts(connection, METAPLEX_ID, {
|
||||
filters: [
|
||||
{
|
||||
dataSize: MAX_WHITELISTED_CREATOR_SIZE,
|
||||
},
|
||||
],
|
||||
}).then(async creators => {
|
||||
const result = await forEach(processMetaplexAccounts)(creators);
|
||||
|
||||
if (IS_BIG_STORE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const whitelistedCreators = Object.values(
|
||||
tempCache.whitelistedCreatorsByCreator,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log('pulling optimized nfts');
|
||||
isSelectivePullMetadata = true;
|
||||
|
||||
if (whitelistedCreators.length > 3) {
|
||||
console.log(' too many creators, pulling all nfts in one go');
|
||||
additionalPromises.push(
|
||||
getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
|
||||
forEach(processMetaData),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log('pulling optimized nfts');
|
||||
|
||||
for (let i = 0; i < MAX_CREATOR_LIMIT; i++) {
|
||||
for (let j = 0; j < whitelistedCreators.length; j++) {
|
||||
additionalPromises.push(
|
||||
getProgramAccounts(connection, METADATA_PROGRAM_ID, {
|
||||
filters: [
|
||||
{
|
||||
memcmp: {
|
||||
offset:
|
||||
1 + // key
|
||||
32 + // update auth
|
||||
32 + // mint
|
||||
4 + // name string length
|
||||
MAX_NAME_LENGTH + // name
|
||||
4 + // uri string length
|
||||
MAX_URI_LENGTH + // uri
|
||||
4 + // symbol string length
|
||||
MAX_SYMBOL_LENGTH + // symbol
|
||||
2 + // seller fee basis points
|
||||
1 + // whether or not there is a creators vec
|
||||
4 + // creators vec length
|
||||
i * MAX_CREATOR_LEN,
|
||||
bytes: whitelistedCreators[j].info.address,
|
||||
},
|
||||
for (let i = 0; i < MAX_CREATOR_LIMIT; i++) {
|
||||
for (let j = 0; j < whitelistedCreators.length; j++) {
|
||||
additionalPromises.push(
|
||||
getProgramAccounts(connection, METADATA_PROGRAM_ID, {
|
||||
filters: [
|
||||
{
|
||||
memcmp: {
|
||||
offset:
|
||||
1 + // key
|
||||
32 + // update auth
|
||||
32 + // mint
|
||||
4 + // name string length
|
||||
MAX_NAME_LENGTH + // name
|
||||
4 + // uri string length
|
||||
MAX_URI_LENGTH + // uri
|
||||
4 + // symbol string length
|
||||
MAX_SYMBOL_LENGTH + // symbol
|
||||
2 + // seller fee basis points
|
||||
1 + // whether or not there is a creators vec
|
||||
4 + // creators vec length
|
||||
i * MAX_CREATOR_LEN,
|
||||
bytes: whitelistedCreators[j].info.address,
|
||||
},
|
||||
],
|
||||
}).then(forEach(processMetaData)),
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
}).then(forEach(processMetaData)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
];
|
||||
await Promise.all(promises);
|
||||
await Promise.all(additionalPromises);
|
||||
}
|
||||
};
|
||||
|
||||
await postProcessMetadata(tempCache, all);
|
||||
console.log('Metadata size', tempCache.metadata.length);
|
||||
|
||||
if (additionalPromises.length > 0) {
|
||||
const pullEditions = async () => {
|
||||
console.log('Pulling editions for optimized metadata');
|
||||
let setOf100MetadataEditionKeys: string[] = [];
|
||||
const editionPromises: Promise<{
|
||||
|
@ -263,6 +210,39 @@ export const loadAccounts = async (connection: Connection, all: boolean) => {
|
|||
Object.keys(tempCache.editions).length,
|
||||
Object.keys(tempCache.masterEditions).length,
|
||||
);
|
||||
};
|
||||
|
||||
const IS_BIG_STORE =
|
||||
all || process.env.NEXT_PUBLIC_BIG_STORE?.toLowerCase() === 'true';
|
||||
console.log(`Is big store: ${IS_BIG_STORE}`);
|
||||
|
||||
const additionalPromises: Promise<void>[] = [];
|
||||
const basePromises = [
|
||||
getProgramAccounts(connection, VAULT_ID).then(forEach(processVaultData)),
|
||||
getProgramAccounts(connection, AUCTION_ID).then(forEach(processAuctions)),
|
||||
getProgramAccounts(connection, METAPLEX_ID).then(
|
||||
forEach(processMetaplexAccounts),
|
||||
),
|
||||
IS_BIG_STORE
|
||||
? getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
|
||||
forEach(processMetaData),
|
||||
)
|
||||
: getProgramAccounts(connection, METAPLEX_ID, {
|
||||
filters: [
|
||||
{
|
||||
dataSize: MAX_WHITELISTED_CREATOR_SIZE,
|
||||
},
|
||||
],
|
||||
}).then(pullMetadata),
|
||||
];
|
||||
await Promise.all(basePromises);
|
||||
await Promise.all(additionalPromises);
|
||||
|
||||
await postProcessMetadata(tempCache, all);
|
||||
console.log('Metadata size', tempCache.metadata.length);
|
||||
|
||||
if (isSelectivePullMetadata) {
|
||||
await pullEditions();
|
||||
}
|
||||
|
||||
return tempCache;
|
||||
|
@ -294,11 +274,11 @@ export const metadataByMintUpdater = async (
|
|||
) => {
|
||||
const key = metadata.info.mint;
|
||||
if (
|
||||
all ||
|
||||
isMetadataPartOfStore(
|
||||
metadata,
|
||||
state.store,
|
||||
state.whitelistedCreatorsByCreator,
|
||||
all,
|
||||
)
|
||||
) {
|
||||
await metadata.info.init();
|
|
@ -0,0 +1,124 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { queryExtendedMetadata } from './queryExtendedMetadata';
|
||||
import { subscribeAccountsChange } from './subscribeAccountsChange';
|
||||
import { getEmptyMetaState } from './getEmptyMetaState';
|
||||
import { loadAccounts } from './loadAccounts';
|
||||
import { MetaContextState, MetaState } from './types';
|
||||
import { useConnection } from '../connection';
|
||||
import { useStore } from '../store';
|
||||
import { useQuerySearch } from '../../hooks';
|
||||
|
||||
const MetaContext = React.createContext<MetaContextState>({
|
||||
...getEmptyMetaState(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
export function MetaProvider({ children = null as any }) {
|
||||
const connection = useConnection();
|
||||
const { isReady, storeAddress } = useStore();
|
||||
const searchParams = useQuerySearch();
|
||||
const all = searchParams.get('all') == 'true';
|
||||
|
||||
const [state, setState] = useState<MetaState>(getEmptyMetaState());
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const updateMints = useCallback(
|
||||
async metadataByMint => {
|
||||
try {
|
||||
if (!all) {
|
||||
const { metadata, mintToMetadata } = await queryExtendedMetadata(
|
||||
connection,
|
||||
metadataByMint,
|
||||
);
|
||||
setState(current => ({
|
||||
...current,
|
||||
metadata,
|
||||
metadataByMint: mintToMetadata,
|
||||
}));
|
||||
}
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
}
|
||||
},
|
||||
[setState],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!storeAddress) {
|
||||
if (isReady) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
return;
|
||||
} else if (!state.store) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
console.log('-----> Query started');
|
||||
|
||||
const nextState = await loadAccounts(connection, all);
|
||||
|
||||
console.log('------->Query finished');
|
||||
|
||||
setState(nextState);
|
||||
|
||||
setIsLoading(false);
|
||||
console.log('------->set finished');
|
||||
|
||||
updateMints(nextState.metadataByMint);
|
||||
})();
|
||||
}, [connection, setState, updateMints, storeAddress, isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
return subscribeAccountsChange(connection, all, () => state, setState);
|
||||
}, [connection, setState, isLoading]);
|
||||
|
||||
// TODO: fetch names dynamically
|
||||
// TODO: get names for creators
|
||||
// useEffect(() => {
|
||||
// (async () => {
|
||||
// const twitterHandles = await connection.getProgramAccounts(NAME_PROGRAM_ID, {
|
||||
// filters: [
|
||||
// {
|
||||
// dataSize: TWITTER_ACCOUNT_LENGTH,
|
||||
// },
|
||||
// {
|
||||
// memcmp: {
|
||||
// offset: VERIFICATION_AUTHORITY_OFFSET,
|
||||
// bytes: TWITTER_VERIFICATION_AUTHORITY.toBase58()
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// });
|
||||
|
||||
// const handles = twitterHandles.map(t => {
|
||||
// const owner = new PublicKey(t.account.data.slice(32, 64));
|
||||
// const name = t.account.data.slice(96, 114).toString();
|
||||
// });
|
||||
|
||||
// console.log(handles);
|
||||
|
||||
// })();
|
||||
// }, [whitelistedCreatorsByCreator]);
|
||||
|
||||
return (
|
||||
<MetaContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
isLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MetaContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useMeta = () => {
|
||||
const context = useContext(MetaContext);
|
||||
return context;
|
||||
};
|
|
@ -1,7 +1,5 @@
|
|||
import {
|
||||
KeyedAccountInfo,
|
||||
ProgramAccountChangeCallback,
|
||||
} from '@solana/web3.js';
|
||||
import { ProgramAccountChangeCallback } from '@solana/web3.js';
|
||||
import { pubkeyToString } from '../../utils';
|
||||
import { ProcessAccountsFunc, UpdateStateValueFunc } from './types';
|
||||
|
||||
export const onChangeAccount =
|
||||
|
@ -11,7 +9,7 @@ export const onChangeAccount =
|
|||
all: boolean,
|
||||
): ProgramAccountChangeCallback =>
|
||||
async info => {
|
||||
const pubkey = pubkeyByAccountInfo(info);
|
||||
const pubkey = pubkeyToString(info.accountId);
|
||||
await process(
|
||||
{
|
||||
pubkey,
|
||||
|
@ -21,9 +19,3 @@ export const onChangeAccount =
|
|||
all,
|
||||
);
|
||||
};
|
||||
|
||||
const pubkeyByAccountInfo = (info: KeyedAccountInfo) => {
|
||||
return typeof info.accountId === 'string'
|
||||
? info.accountId
|
||||
: info.accountId.toBase58();
|
||||
};
|
|
@ -1,19 +1,19 @@
|
|||
import {
|
||||
AuctionParser,
|
||||
cache,
|
||||
ParsedAccount,
|
||||
AuctionData,
|
||||
AuctionDataExtended,
|
||||
AuctionDataExtendedParser,
|
||||
AuctionParser,
|
||||
BidderMetadata,
|
||||
BidderMetadataParser,
|
||||
BidderPot,
|
||||
BidderPotParser,
|
||||
BIDDER_METADATA_LEN,
|
||||
BIDDER_POT_LEN,
|
||||
AuctionDataExtended,
|
||||
MAX_AUCTION_DATA_EXTENDED_SIZE,
|
||||
AuctionDataExtendedParser,
|
||||
AUCTION_ID,
|
||||
} from '@oyster/common';
|
||||
} from '../../actions';
|
||||
import { AUCTION_ID } from '../../utils';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { cache } from '../accounts/cache';
|
||||
import { CheckAccountFunc, ProcessAccountsFunc } from './types';
|
||||
|
||||
export const processAuctions: ProcessAccountsFunc = (
|
|
@ -1,18 +1,18 @@
|
|||
import {
|
||||
decodeMetadata,
|
||||
decodeEdition,
|
||||
decodeMasterEdition,
|
||||
Metadata,
|
||||
ParsedAccount,
|
||||
Edition,
|
||||
MasterEditionV1,
|
||||
MasterEditionV2,
|
||||
MetadataKey,
|
||||
METADATA_PROGRAM_ID,
|
||||
} from '@oyster/common';
|
||||
import { AccountInfo } from '@solana/web3.js';
|
||||
import { ProcessAccountsFunc } from './types';
|
||||
import { isValidHttpUrl } from '../../utils/isValidHttpUrl';
|
||||
import {
|
||||
decodeEdition,
|
||||
decodeMasterEdition,
|
||||
decodeMetadata,
|
||||
Edition,
|
||||
MasterEditionV1,
|
||||
MasterEditionV2,
|
||||
Metadata,
|
||||
MetadataKey,
|
||||
} from '../../actions';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { METADATA_PROGRAM_ID } from '../../utils';
|
||||
|
||||
export const processMetaData: ProcessAccountsFunc = (
|
||||
{ account, pubkey },
|
|
@ -1,13 +1,12 @@
|
|||
import { programIds, cache, ParsedAccount, METAPLEX_ID } from '@oyster/common';
|
||||
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
AuctionManagerV2,
|
||||
BidRedemptionTicket,
|
||||
decodeAuctionManager,
|
||||
decodeBidRedemptionTicket,
|
||||
decodeStore,
|
||||
decodeWhitelistedCreator,
|
||||
getWhitelistedCreator,
|
||||
isCreatorPartOfTheStore,
|
||||
MetaplexKey,
|
||||
Store,
|
||||
WhitelistedCreator,
|
||||
|
@ -20,9 +19,10 @@ import {
|
|||
decodeSafetyDepositConfig,
|
||||
SafetyDepositConfig,
|
||||
} from '../../models/metaplex';
|
||||
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates';
|
||||
import names from '../../config/userNames.json';
|
||||
import { ProcessAccountsFunc } from './types';
|
||||
import { METAPLEX_ID, programIds } from '../../utils';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { cache } from '../accounts/cache';
|
||||
|
||||
export const processMetaplexAccounts: ProcessAccountsFunc = async (
|
||||
{ account, pubkey },
|
||||
|
@ -130,30 +130,33 @@ export const processMetaplexAccounts: ProcessAccountsFunc = async (
|
|||
}
|
||||
|
||||
if (isWhitelistedCreatorV1Account(account)) {
|
||||
const whitelistedCreator = decodeWhitelistedCreator(account.data);
|
||||
const parsedAccount = cache.add(
|
||||
pubkey,
|
||||
account,
|
||||
WhitelistedCreatorParser,
|
||||
false,
|
||||
) as ParsedAccount<WhitelistedCreator>;
|
||||
|
||||
// TODO: figure out a way to avoid generating creator addresses during parsing
|
||||
// should we store store id inside creator?
|
||||
const creatorKeyIfCreatorWasPartOfThisStore = await getWhitelistedCreator(
|
||||
whitelistedCreator.address,
|
||||
);
|
||||
|
||||
if (creatorKeyIfCreatorWasPartOfThisStore === pubkey) {
|
||||
const parsedAccount = cache.add(
|
||||
if (STORE_ID) {
|
||||
const isWhitelistedCreator = await isCreatorPartOfTheStore(
|
||||
parsedAccount.info.address,
|
||||
pubkey,
|
||||
account,
|
||||
WhitelistedCreatorParser,
|
||||
false,
|
||||
) as ParsedAccount<WhitelistedCreator>;
|
||||
|
||||
const nameInfo = (names as any)[parsedAccount.info.address];
|
||||
|
||||
if (nameInfo) {
|
||||
parsedAccount.info = { ...parsedAccount.info, ...nameInfo };
|
||||
);
|
||||
if (isWhitelistedCreator) {
|
||||
setter(
|
||||
'whitelistedCreatorsByCreator',
|
||||
parsedAccount.info.address,
|
||||
parsedAccount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (useAll) {
|
||||
setter(
|
||||
'whitelistedCreatorsByCreator',
|
||||
whitelistedCreator.address,
|
||||
'creators',
|
||||
parsedAccount.info.address + '-' + pubkey,
|
||||
parsedAccount,
|
||||
);
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import { AccountInfo } from '@solana/web3.js';
|
||||
import {
|
||||
ParsedAccount,
|
||||
SafetyDepositBox,
|
||||
VaultKey,
|
||||
decodeSafetyDeposit,
|
||||
decodeVault,
|
||||
SafetyDepositBox,
|
||||
Vault,
|
||||
} from '@oyster/common';
|
||||
import { VAULT_ID } from '@oyster/common/dist/lib/utils/ids';
|
||||
import { AccountInfo } from '@solana/web3.js';
|
||||
VaultKey,
|
||||
} from '../../actions';
|
||||
import { VAULT_ID } from '../../utils';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { ProcessAccountsFunc } from './types';
|
||||
|
||||
export const processVaultData: ProcessAccountsFunc = (
|
|
@ -1,12 +1,10 @@
|
|||
import {
|
||||
Metadata,
|
||||
getMultipleAccounts,
|
||||
cache,
|
||||
MintParser,
|
||||
ParsedAccount,
|
||||
} from '@oyster/common';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { Metadata } from '../../actions';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
import { cache } from '../accounts/cache';
|
||||
import { getMultipleAccounts } from '../accounts/getMultipleAccounts';
|
||||
import { MintParser } from '../accounts/parsesrs';
|
||||
|
||||
export const queryExtendedMetadata = async (
|
||||
connection: Connection,
|
|
@ -0,0 +1,76 @@
|
|||
import { Connection } from '@solana/web3.js';
|
||||
import {
|
||||
AUCTION_ID,
|
||||
METADATA_PROGRAM_ID,
|
||||
METAPLEX_ID,
|
||||
toPublicKey,
|
||||
VAULT_ID,
|
||||
} from '../../utils';
|
||||
import { makeSetter, metadataByMintUpdater } from './loadAccounts';
|
||||
import { onChangeAccount } from './onChangeAccount';
|
||||
import { processAuctions } from './processAuctions';
|
||||
import { processMetaData } from './processMetaData';
|
||||
import { processMetaplexAccounts } from './processMetaplexAccounts';
|
||||
import { processVaultData } from './processVaultData';
|
||||
import { MetaState, UpdateStateValueFunc } from './types';
|
||||
|
||||
export const subscribeAccountsChange = (
|
||||
connection: Connection,
|
||||
all: boolean,
|
||||
getState: () => MetaState,
|
||||
setState: (v: MetaState) => void,
|
||||
) => {
|
||||
const subscriptions: number[] = [];
|
||||
|
||||
const updateStateValue: UpdateStateValueFunc = (prop, key, value) => {
|
||||
const state = getState();
|
||||
const nextState = makeSetter({ ...state })(prop, key, value);
|
||||
setState(nextState);
|
||||
};
|
||||
|
||||
subscriptions.push(
|
||||
connection.onProgramAccountChange(
|
||||
toPublicKey(VAULT_ID),
|
||||
onChangeAccount(processVaultData, updateStateValue, all),
|
||||
),
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
connection.onProgramAccountChange(
|
||||
toPublicKey(AUCTION_ID),
|
||||
onChangeAccount(processAuctions, updateStateValue, all),
|
||||
),
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
connection.onProgramAccountChange(
|
||||
toPublicKey(METAPLEX_ID),
|
||||
onChangeAccount(processMetaplexAccounts, updateStateValue, all),
|
||||
),
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
connection.onProgramAccountChange(
|
||||
toPublicKey(METADATA_PROGRAM_ID),
|
||||
onChangeAccount(
|
||||
processMetaData,
|
||||
async (prop, key, value) => {
|
||||
if (prop === 'metadataByMint') {
|
||||
const state = getState();
|
||||
const nextState = await metadataByMintUpdater(value, state, all);
|
||||
setState(nextState);
|
||||
} else {
|
||||
updateStateValue(prop, key, value);
|
||||
}
|
||||
},
|
||||
all,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(subscriptionId => {
|
||||
connection.removeProgramAccountChangeListener(subscriptionId);
|
||||
});
|
||||
};
|
||||
};
|
|
@ -1,29 +1,29 @@
|
|||
import {
|
||||
Metadata,
|
||||
ParsedAccount,
|
||||
Edition,
|
||||
AuctionData,
|
||||
SafetyDepositBox,
|
||||
BidderMetadata,
|
||||
BidderPot,
|
||||
Vault,
|
||||
AuctionDataExtended,
|
||||
MasterEditionV1,
|
||||
MasterEditionV2,
|
||||
PublicKeyStringAndAccount,
|
||||
} from '@oyster/common';
|
||||
import { AccountInfo } from '@solana/web3.js';
|
||||
import {
|
||||
AuctionData,
|
||||
AuctionDataExtended,
|
||||
BidderMetadata,
|
||||
BidderPot,
|
||||
Edition,
|
||||
MasterEditionV1,
|
||||
MasterEditionV2,
|
||||
Metadata,
|
||||
SafetyDepositBox,
|
||||
Vault,
|
||||
} from '../../actions';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
AuctionManagerV2,
|
||||
BidRedemptionTicket,
|
||||
Store,
|
||||
WhitelistedCreator,
|
||||
BidRedemptionTicketV2,
|
||||
PayoutTicket,
|
||||
PrizeTrackingTicket,
|
||||
AuctionManagerV2,
|
||||
SafetyDepositConfig,
|
||||
BidRedemptionTicketV2,
|
||||
Store,
|
||||
WhitelistedCreator,
|
||||
} from '../../models/metaplex';
|
||||
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates';
|
||||
import { PublicKeyStringAndAccount } from '../../utils';
|
||||
import { ParsedAccount } from '../accounts/types';
|
||||
|
||||
export interface MetaState {
|
||||
metadata: ParsedAccount<Metadata>[];
|
||||
|
@ -72,6 +72,7 @@ export interface MetaState {
|
|||
>;
|
||||
payoutTickets: Record<string, ParsedAccount<PayoutTicket>>;
|
||||
stores: Record<string, ParsedAccount<Store>>;
|
||||
creators: Record<string, ParsedAccount<WhitelistedCreator>>;
|
||||
}
|
||||
|
||||
export interface MetaContextState extends MetaState {
|
|
@ -1 +1,2 @@
|
|||
export * from './account';
|
||||
export * from './metaplex';
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import {
|
||||
getAuctionExtended,
|
||||
getBidderPotKey,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '@oyster/common';
|
||||
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { getAuctionKeys, ClaimBidArgs, SCHEMA } from '.';
|
||||
import { getBidderPotKey, getAuctionExtended } from '../../actions';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function claimBid(
|
||||
acceptPayment: StringPublicKey,
|
|
@ -1,8 +1,8 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { DecommissionAuctionManagerArgs, SCHEMA } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function decommissionAuctionManager(
|
||||
auctionManager: StringPublicKey,
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -7,6 +6,7 @@ import {
|
|||
import { serialize } from 'borsh';
|
||||
|
||||
import { getAuctionKeys, SCHEMA } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
import {
|
||||
AuctionManagerSettingsV1,
|
||||
DeprecatedInitAuctionManagerV1Args,
|
|
@ -1,15 +1,13 @@
|
|||
import {
|
||||
programIds,
|
||||
VAULT_PREFIX,
|
||||
getAuctionExtended,
|
||||
findProgramAddress,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '@oyster/common';
|
||||
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { SCHEMA } from '.';
|
||||
import { getAuctionExtended, VAULT_PREFIX } from '../../actions';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '../../utils';
|
||||
import { DeprecatedPopulateParticipationPrintingAccountArgs } from './deprecatedStates';
|
||||
|
||||
export async function deprecatedPopulateParticipationPrintingAccount(
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -12,6 +11,7 @@ import {
|
|||
SCHEMA,
|
||||
getSafetyDepositConfig,
|
||||
} from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
import { DeprecatedRedeemParticipationBidArgs } from './deprecatedStates';
|
||||
|
||||
export async function deprecatedRedeemParticipationBid(
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, findProgramAddress, toPublicKey } from '@oyster/common';
|
||||
import BN from 'bn.js';
|
||||
import {
|
||||
AuctionManagerStatus,
|
||||
|
@ -9,6 +8,7 @@ import {
|
|||
WinningConfigType,
|
||||
WinningConstraint,
|
||||
} from '.';
|
||||
import { findProgramAddress, programIds, toPublicKey } from '../../utils';
|
||||
|
||||
export const MAX_BID_REDEMPTION_TICKET_V1_SIZE = 3;
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -7,6 +6,7 @@ import {
|
|||
import { serialize } from 'borsh';
|
||||
|
||||
import { SCHEMA } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
import { DeprecatedValidateParticipationArgs } from './deprecatedStates';
|
||||
|
||||
export async function deprecatedValidateParticipation(
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -7,6 +6,7 @@ import {
|
|||
import { serialize } from 'borsh';
|
||||
|
||||
import { getAuctionKeys, getOriginalAuthority, SCHEMA } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
import {
|
||||
getSafetyDepositBoxValidationTicket,
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -13,6 +12,7 @@ import {
|
|||
getSafetyDepositConfig,
|
||||
SCHEMA,
|
||||
} from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function emptyPaymentAccount(
|
||||
acceptPayment: StringPublicKey,
|
|
@ -1,25 +1,25 @@
|
|||
import {
|
||||
AUCTION_PREFIX,
|
||||
programIds,
|
||||
METADATA,
|
||||
AccountParser,
|
||||
findProgramAddress,
|
||||
AuctionData,
|
||||
ParsedAccount,
|
||||
Vault,
|
||||
Metadata,
|
||||
MasterEditionV1,
|
||||
SafetyDepositBox,
|
||||
MasterEditionV2,
|
||||
toPublicKey,
|
||||
StringPublicKey,
|
||||
getAuctionExtended,
|
||||
} from '@oyster/common';
|
||||
import { AccountInfo, SystemProgram } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
import { deserializeUnchecked } from 'borsh';
|
||||
import bs58 from 'bs58';
|
||||
import { AuctionViewItem } from '../../hooks';
|
||||
import { deserializeUnchecked } from 'borsh';
|
||||
import {
|
||||
AuctionData,
|
||||
AUCTION_PREFIX,
|
||||
MasterEditionV1,
|
||||
MasterEditionV2,
|
||||
METADATA,
|
||||
Metadata,
|
||||
SafetyDepositBox,
|
||||
Vault,
|
||||
getAuctionExtended,
|
||||
} from '../../actions';
|
||||
import { AccountParser, ParsedAccount } from '../../contexts';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
toPublicKey,
|
||||
StringPublicKey,
|
||||
} from '../../utils';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
BidRedemptionTicketV1,
|
||||
|
@ -36,6 +36,7 @@ export * from './deprecatedValidateSafetyDepositBoxV1';
|
|||
export * from './redeemParticipationBidV3';
|
||||
export * from './redeemPrintingV2Bid';
|
||||
export * from './withdrawMasterEdition';
|
||||
export * from './deprecatedStates';
|
||||
|
||||
export const METAPLEX_PREFIX = 'metaplex';
|
||||
export const TOTALS = 'totals';
|
||||
|
@ -556,6 +557,14 @@ export interface BidRedemptionTicket {
|
|||
|
||||
getBidRedeemed(order: number): boolean;
|
||||
}
|
||||
|
||||
export interface AuctionViewItem {
|
||||
winningConfigType: WinningConfigType;
|
||||
amount: BN;
|
||||
metadata: ParsedAccount<Metadata>;
|
||||
safetyDeposit: ParsedAccount<SafetyDepositBox>;
|
||||
masterEdition?: ParsedAccount<MasterEditionV1 | MasterEditionV2>;
|
||||
}
|
||||
export class BidRedemptionTicketV2 implements BidRedemptionTicket {
|
||||
key: MetaplexKey = MetaplexKey.BidRedemptionTicketV2;
|
||||
winnerIndex: BN | null;
|
||||
|
@ -1116,9 +1125,22 @@ export async function getOriginalAuthority(
|
|||
)[0];
|
||||
}
|
||||
|
||||
export async function getWhitelistedCreator(creator: string) {
|
||||
export const isCreatorPartOfTheStore = async (
|
||||
creatorAddress: StringPublicKey,
|
||||
pubkey: StringPublicKey,
|
||||
store?: StringPublicKey,
|
||||
) => {
|
||||
const creatorKeyInStore = await getWhitelistedCreator(creatorAddress, store);
|
||||
|
||||
return creatorKeyInStore === pubkey;
|
||||
};
|
||||
|
||||
export async function getWhitelistedCreator(
|
||||
creator: StringPublicKey,
|
||||
storeId?: StringPublicKey,
|
||||
) {
|
||||
const PROGRAM_IDS = programIds();
|
||||
const store = PROGRAM_IDS.store;
|
||||
const store = storeId || PROGRAM_IDS.store;
|
||||
if (!store) {
|
||||
throw new Error('Store not initialized');
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
SCHEMA,
|
||||
TupleNumericType,
|
||||
} from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function initAuctionManagerV2(
|
||||
vault: StringPublicKey,
|
|
@ -1,11 +1,3 @@
|
|||
import {
|
||||
findProgramAddress,
|
||||
getAuctionExtended,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
VAULT_PREFIX,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -22,6 +14,13 @@ import {
|
|||
RedeemUnusedWinningConfigItemsAsAuctioneerArgs,
|
||||
SCHEMA,
|
||||
} from '.';
|
||||
import { VAULT_PREFIX, getAuctionExtended } from '../../actions';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '../../utils';
|
||||
|
||||
export async function redeemBid(
|
||||
vault: StringPublicKey,
|
|
@ -1,11 +1,3 @@
|
|||
import {
|
||||
programIds,
|
||||
getAuctionExtended,
|
||||
VAULT_PREFIX,
|
||||
findProgramAddress,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -22,6 +14,13 @@ import {
|
|||
RedeemUnusedWinningConfigItemsAsAuctioneerArgs,
|
||||
SCHEMA,
|
||||
} from '.';
|
||||
import { VAULT_PREFIX, getAuctionExtended } from '../../actions';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '../../utils';
|
||||
|
||||
export async function redeemFullRightsTransferBid(
|
||||
vault: StringPublicKey,
|
|
@ -1,12 +1,3 @@
|
|||
import {
|
||||
getEdition,
|
||||
programIds,
|
||||
getMetadata,
|
||||
getEditionMarkPda,
|
||||
getAuctionExtended,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -23,6 +14,13 @@ import {
|
|||
getPrizeTrackingTicket,
|
||||
getSafetyDepositConfig,
|
||||
} from '.';
|
||||
import {
|
||||
getAuctionExtended,
|
||||
getEdition,
|
||||
getEditionMarkPda,
|
||||
getMetadata,
|
||||
} from '../../actions';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function redeemParticipationBidV3(
|
||||
vault: StringPublicKey,
|
|
@ -1,12 +1,3 @@
|
|||
import {
|
||||
getEdition,
|
||||
getEditionMarkPda,
|
||||
getMetadata,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
getAuctionExtended,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -23,6 +14,8 @@ import {
|
|||
SCHEMA,
|
||||
getSafetyDepositConfig,
|
||||
} from '.';
|
||||
import { getEdition, getEditionMarkPda, getMetadata, getAuctionExtended } from '../../actions';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function redeemPrintingV2Bid(
|
||||
vault: StringPublicKey,
|
|
@ -1,8 +1,8 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { SCHEMA, SetStoreArgs } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function setStore(
|
||||
isPublic: boolean,
|
|
@ -1,8 +1,8 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { getWhitelistedCreator, SCHEMA, SetWhitelistedCreatorArgs } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function setWhitelistedCreator(
|
||||
creator: StringPublicKey,
|
|
@ -1,8 +1,8 @@
|
|||
import { programIds, StringPublicKey, toPublicKey } from '@oyster/common';
|
||||
import { SYSVAR_CLOCK_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
import { getAuctionKeys, SCHEMA, StartAuctionArgs } from '.';
|
||||
import { programIds, StringPublicKey, toPublicKey } from '../../utils';
|
||||
|
||||
export async function startAuction(
|
||||
vault: StringPublicKey,
|
|
@ -1,4 +1,3 @@
|
|||
import { programIds, toPublicKey, StringPublicKey } from '@oyster/common';
|
||||
import {
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
SCHEMA,
|
||||
ValidateSafetyDepositBoxV2Args,
|
||||
} from '.';
|
||||
import { programIds, toPublicKey, StringPublicKey } from '../../utils';
|
||||
|
||||
export async function validateSafetyDepositBoxV2(
|
||||
vault: StringPublicKey,
|
|
@ -1,12 +1,3 @@
|
|||
import {
|
||||
AUCTION_PREFIX,
|
||||
EXTENDED,
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
VAULT_PREFIX,
|
||||
} from '@oyster/common';
|
||||
import { SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
|
||||
import { serialize } from 'borsh';
|
||||
|
||||
|
@ -17,6 +8,13 @@ import {
|
|||
getPrizeTrackingTicket,
|
||||
getSafetyDepositConfig,
|
||||
} from '.';
|
||||
import { AUCTION_PREFIX, EXTENDED, VAULT_PREFIX } from '../../actions';
|
||||
import {
|
||||
findProgramAddress,
|
||||
programIds,
|
||||
toPublicKey,
|
||||
StringPublicKey,
|
||||
} from '../../utils';
|
||||
|
||||
export async function withdrawMasterEdition(
|
||||
vault: StringPublicKey,
|
|
@ -36,6 +36,10 @@ export const toPublicKey = (key: string | PublicKey) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
export const pubkeyToString = (key: PublicKey | null | string = '') => {
|
||||
return typeof key === 'string' ? key : key?.toBase58() || '';
|
||||
};
|
||||
|
||||
export interface PublicKeyStringAndAccount<T> {
|
||||
pubkey: string;
|
||||
account: AccountInfo<T>;
|
||||
|
|
|
@ -4,6 +4,8 @@ export * from './programIds';
|
|||
export * as Layout from './layout';
|
||||
export * from './notifications';
|
||||
export * from './utils';
|
||||
export * from './useLocalStorage';
|
||||
export * from './strings';
|
||||
export * as shortvec from './shortvec';
|
||||
export * from './isValidHttpUrl';
|
||||
export * from './borsh';
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
type UseStorageReturnValue = {
|
||||
getItem: (key: string) => string;
|
||||
setItem: (key: string, value: string) => boolean;
|
||||
removeItem: (key: string) => void;
|
||||
};
|
||||
|
||||
export const useLocalStorage = (): UseStorageReturnValue => {
|
||||
const isBrowser: boolean = ((): boolean => typeof window !== 'undefined')();
|
||||
|
||||
const getItem = (key: string): string => {
|
||||
return isBrowser ? window.localStorage[key] : '';
|
||||
};
|
||||
|
||||
const setItem = (key: string, value: string): boolean => {
|
||||
if (isBrowser) {
|
||||
window.localStorage.setItem(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const removeItem = (key: string): void => {
|
||||
window.localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
return {
|
||||
getItem,
|
||||
setItem,
|
||||
removeItem,
|
||||
};
|
||||
};
|
|
@ -6,6 +6,7 @@ import { PublicKey } from '@solana/web3.js';
|
|||
import BN from 'bn.js';
|
||||
import { WAD, ZERO } from '../constants';
|
||||
import { TokenInfo } from '@solana/spl-token-registry';
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
|
||||
export type KnownTokenMap = Map<string, TokenInfo>;
|
||||
|
||||
|
@ -16,6 +17,7 @@ export const formatPriceNumber = new Intl.NumberFormat('en-US', {
|
|||
});
|
||||
|
||||
export function useLocalStorageState(key: string, defaultState?: string) {
|
||||
const localStorage = useLocalStorage();
|
||||
const [state, setState] = useState(() => {
|
||||
// NOTE: Not sure if this is ok
|
||||
const storedState = localStorage.getItem(key);
|
||||
|
@ -52,6 +54,7 @@ export const findProgramAddress = async (
|
|||
seeds: (Buffer | Uint8Array)[],
|
||||
programId: PublicKey,
|
||||
) => {
|
||||
const localStorage = useLocalStorage();
|
||||
const key =
|
||||
'pda-' +
|
||||
seeds.reduce((agg, item) => agg + item.toString('hex'), '') +
|
||||
|
|
|
@ -36,8 +36,7 @@
|
|||
"react-masonry-css": "^1.0.16",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"three": "^0.128.0",
|
||||
"use-wallet": "^0.8.1"
|
||||
"three": "^0.128.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "npm-link-shared ../common/node_modules/ . react",
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
utils,
|
||||
actions,
|
||||
models,
|
||||
findProgramAddress,
|
||||
MetadataKey,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import { SafetyDepositConfig } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { approve } from '@oyster/common/dist/lib/models/account';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import {
|
||||
addTokenToInactiveVault,
|
||||
VAULT_PREFIX,
|
||||
} from '@oyster/common/dist/lib/actions/vault';
|
||||
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import BN from 'bn.js';
|
||||
import { SafetyDepositDraft } from './createAuctionManager';
|
||||
import { SafetyDepositConfig } from '../models/metaplex';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
const { createTokenAccount, addTokenToInactiveVault, VAULT_PREFIX } = actions;
|
||||
const { approve } = models;
|
||||
|
||||
export interface SafetyDepositInstructionTemplate {
|
||||
box: {
|
||||
|
|
|
@ -16,7 +16,10 @@ import {
|
|||
import { AccountLayout } from '@solana/spl-token';
|
||||
import { TransactionInstruction, Keypair, Connection } from '@solana/web3.js';
|
||||
import { AuctionView } from '../hooks';
|
||||
import { BidRedemptionTicket, PrizeTrackingTicket } from '../models/metaplex';
|
||||
import {
|
||||
BidRedemptionTicket,
|
||||
PrizeTrackingTicket,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { claimUnusedPrizes } from './claimUnusedPrizes';
|
||||
import { setupPlaceBid } from './sendPlaceBid';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
ParsedAccount,
|
||||
TokenAccount,
|
||||
SafetyDepositBox,
|
||||
|
@ -16,7 +15,7 @@ import {
|
|||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { AccountLayout, MintLayout } from '@solana/spl-token';
|
||||
import { AuctionView, AuctionViewItem } from '../hooks';
|
||||
import { AuctionView } from '../hooks';
|
||||
import {
|
||||
WinningConfigType,
|
||||
redeemBid,
|
||||
|
@ -25,13 +24,14 @@ import {
|
|||
BidRedemptionTicket,
|
||||
getBidRedemption,
|
||||
PrizeTrackingTicket,
|
||||
} from '../models/metaplex';
|
||||
AuctionViewItem,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import {
|
||||
eligibleForParticipationPrizeGivenWinningIndex,
|
||||
setupRedeemParticipationInstructions,
|
||||
setupRedeemPrintingV2Instructions,
|
||||
} from './sendRedeemBid';
|
||||
const { createTokenAccount } = actions;
|
||||
|
||||
export async function findEligibleParticipationBidsForRedemption(
|
||||
auctionView: AuctionView,
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
models,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { StringPublicKey, toPublicKey, WalletSigner } from '@oyster/common';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import {
|
||||
activateVault,
|
||||
combineVault,
|
||||
} from '@oyster/common/dist/lib/actions/vault';
|
||||
import { approve } from '@oyster/common/dist/lib/models/account';
|
||||
|
||||
import { AccountLayout } from '@solana/spl-token';
|
||||
import BN from 'bn.js';
|
||||
const { createTokenAccount, activateVault, combineVault } = actions;
|
||||
const { approve } = models;
|
||||
|
||||
// This command "closes" the vault, by activating & combining it in one go, handing it over to the auction manager
|
||||
// authority (that may or may not exist yet.)
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
SystemProgram,
|
||||
} from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
Metadata,
|
||||
ParsedAccount,
|
||||
MasterEditionV1,
|
||||
|
@ -41,7 +40,8 @@ import {
|
|||
TupleNumericType,
|
||||
SafetyDepositConfig,
|
||||
ParticipationStateV2,
|
||||
} from '../models/metaplex';
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import { createVault } from './createVault';
|
||||
import { closeVault } from './closeVault';
|
||||
import {
|
||||
|
@ -50,15 +50,13 @@ import {
|
|||
} from './addTokensToVault';
|
||||
import { makeAuction } from './makeAuction';
|
||||
import { createExternalPriceAccount } from './createExternalPriceAccount';
|
||||
import { deprecatedValidateParticipation } from '../models/metaplex/deprecatedValidateParticipation';
|
||||
import { deprecatedValidateParticipation } from '@oyster/common/dist/lib/models/metaplex/deprecatedValidateParticipation';
|
||||
import { deprecatedCreateReservationListForTokens } from './deprecatedCreateReservationListsForTokens';
|
||||
import { deprecatedPopulatePrintingTokens } from './deprecatedPopulatePrintingTokens';
|
||||
import { setVaultAndAuctionAuthorities } from './setVaultAndAuctionAuthorities';
|
||||
import { markItemsThatArentMineAsSold } from './markItemsThatArentMineAsSold';
|
||||
import { validateSafetyDepositBoxV2 } from '../models/metaplex/validateSafetyDepositBoxV2';
|
||||
import { initAuctionManagerV2 } from '../models/metaplex/initAuctionManagerV2';
|
||||
|
||||
const { createTokenAccount } = actions;
|
||||
import { validateSafetyDepositBoxV2 } from '@oyster/common/dist/lib/models/metaplex/validateSafetyDepositBoxV2';
|
||||
import { initAuctionManagerV2 } from '@oyster/common/dist/lib/models/metaplex/initAuctionManagerV2';
|
||||
|
||||
interface normalPattern {
|
||||
instructions: TransactionInstruction[];
|
||||
|
|
|
@ -4,22 +4,22 @@ import {
|
|||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
|
||||
import {
|
||||
utils,
|
||||
actions,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import BN from 'bn.js';
|
||||
import { QUOTE_MINT } from '../constants';
|
||||
|
||||
const {
|
||||
import {
|
||||
updateExternalPriceAccount,
|
||||
ExternalPriceAccount,
|
||||
MAX_EXTERNAL_ACCOUNT_SIZE,
|
||||
} = actions;
|
||||
} from '@oyster/common/dist/lib/actions/vault';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { QUOTE_MINT } from '../constants';
|
||||
|
||||
// This command creates the external pricing oracle
|
||||
export async function createExternalPriceAccount(
|
||||
|
|
|
@ -6,19 +6,22 @@ import {
|
|||
} from '@solana/web3.js';
|
||||
import {
|
||||
utils,
|
||||
actions,
|
||||
createMint,
|
||||
findProgramAddress,
|
||||
StringPublicKey,
|
||||
toPublicKey,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
initVault,
|
||||
MAX_VAULT_SIZE,
|
||||
VAULT_PREFIX,
|
||||
} from '@oyster/common/dist/lib/actions/vault';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
|
||||
import { AccountLayout, MintLayout } from '@solana/spl-token';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
|
||||
const { createTokenAccount, initVault, MAX_VAULT_SIZE, VAULT_PREFIX } = actions;
|
||||
|
||||
// This command creates the external pricing oracle a vault
|
||||
// This gets the vault ready for adding the tokens.
|
||||
export async function createVault(
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { AuctionView } from '../hooks';
|
||||
import { AuctionManagerStatus } from '../models/metaplex';
|
||||
import { decommissionAuctionManager } from '../models/metaplex/decommissionAuctionManager';
|
||||
import { AuctionManagerStatus } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { decommissionAuctionManager } from '@oyster/common/dist/lib/models/metaplex/decommissionAuctionManager';
|
||||
import { unwindVault } from './unwindVault';
|
||||
|
||||
export async function decommAuctionManagerAndReturnPrizes(
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { SafetyDepositInstructionTemplate } from './addTokensToVault';
|
||||
import { WinningConfigType } from '../models/metaplex';
|
||||
import { WinningConfigType } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
|
||||
const BATCH_SIZE = 10;
|
||||
// This command batches out creating reservation lists for those tokens who are being sold in PrintingV1 mode.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Keypair, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
utils,
|
||||
actions,
|
||||
findProgramAddress,
|
||||
IPartialCreateAuctionArgs,
|
||||
CreateAuctionArgs,
|
||||
|
@ -9,10 +8,12 @@ import {
|
|||
toPublicKey,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import {
|
||||
AUCTION_PREFIX,
|
||||
createAuction,
|
||||
} from '@oyster/common/dist/lib/actions/auction';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
|
||||
const { AUCTION_PREFIX, createAuction } = actions;
|
||||
|
||||
// This command makes an auction
|
||||
export async function makeAuction(
|
||||
wallet: WalletSigner,
|
||||
|
|
|
@ -5,10 +5,10 @@ import {
|
|||
sendTransactionWithRetry,
|
||||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import { WhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { setStore } from '@oyster/common/dist/lib/models/metaplex/setStore';
|
||||
import { setWhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/setWhitelistedCreator';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { WhitelistedCreator } from '../models/metaplex';
|
||||
import { setStore } from '../models/metaplex/setStore';
|
||||
import { setWhitelistedCreator } from '../models/metaplex/setWhitelistedCreator';
|
||||
|
||||
// TODO if this becomes very slow move to batching txns like we do with settle.ts
|
||||
// but given how little this should be used keep it simple
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
sendTransactionWithRetry,
|
||||
placeBid,
|
||||
models,
|
||||
cache,
|
||||
TokenAccount,
|
||||
ensureWrappedAccount,
|
||||
toLamports,
|
||||
ParsedAccount,
|
||||
|
@ -13,15 +10,16 @@ import {
|
|||
WalletSigner,
|
||||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { approve } from '@oyster/common/dist/lib/models/account';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import { TokenAccount } from '@oyster/common/dist/lib/models/account';
|
||||
|
||||
import { AccountLayout, MintInfo } from '@solana/spl-token';
|
||||
import { AuctionView } from '../hooks';
|
||||
import BN from 'bn.js';
|
||||
import { setupCancelBid } from './cancelBid';
|
||||
import { QUOTE_MINT } from '../constants';
|
||||
|
||||
const { createTokenAccount } = actions;
|
||||
const { approve } = models;
|
||||
|
||||
export async function sendPlaceBid(
|
||||
connection: Connection,
|
||||
wallet: WalletSigner,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { Keypair, Connection, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
actions,
|
||||
ParsedAccount,
|
||||
programIds,
|
||||
models,
|
||||
TokenAccount,
|
||||
createMint,
|
||||
SafetyDepositBox,
|
||||
|
@ -28,8 +26,10 @@ import {
|
|||
} from '@oyster/common';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
|
||||
import { AuctionView, AuctionViewItem } from '../hooks';
|
||||
import { AuctionView } from '../hooks';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
ParticipationStateV1,
|
||||
WinningConfigType,
|
||||
NonWinningConstraint,
|
||||
redeemBid,
|
||||
|
@ -41,21 +41,18 @@ import {
|
|||
PrizeTrackingTicket,
|
||||
getPrizeTrackingTicket,
|
||||
BidRedemptionTicket,
|
||||
} from '../models/metaplex';
|
||||
import { claimBid } from '../models/metaplex/claimBid';
|
||||
AuctionViewItem,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { claimBid } from '@oyster/common/dist/lib/models/metaplex/claimBid';
|
||||
import { approve } from '@oyster/common/dist/lib/models/account';
|
||||
import { createTokenAccount } from '@oyster/common/dist/lib/actions/account';
|
||||
import { setupCancelBid } from './cancelBid';
|
||||
import { deprecatedPopulateParticipationPrintingAccount } from '../models/metaplex/deprecatedPopulateParticipationPrintingAccount';
|
||||
import { deprecatedPopulateParticipationPrintingAccount } from '@oyster/common/dist/lib/models/metaplex/deprecatedPopulateParticipationPrintingAccount';
|
||||
import { setupPlaceBid } from './sendPlaceBid';
|
||||
import { claimUnusedPrizes } from './claimUnusedPrizes';
|
||||
import { createMintAndAccountWithOne } from './createMintAndAccountWithOne';
|
||||
import { BN } from 'bn.js';
|
||||
import { QUOTE_MINT } from '../constants';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
ParticipationStateV1,
|
||||
} from '../models/metaplex/deprecatedStates';
|
||||
const { createTokenAccount } = actions;
|
||||
const { approve } = models;
|
||||
|
||||
export function eligibleForParticipationPrizeGivenWinningIndex(
|
||||
winnerIndex: number | null,
|
||||
|
|
|
@ -16,8 +16,8 @@ import {
|
|||
|
||||
import { AuctionView } from '../hooks';
|
||||
|
||||
import { claimBid } from '../models/metaplex/claimBid';
|
||||
import { emptyPaymentAccount } from '../models/metaplex/emptyPaymentAccount';
|
||||
import { claimBid } from '@oyster/common/dist/lib/models/metaplex/claimBid';
|
||||
import { emptyPaymentAccount } from '@oyster/common/dist/lib/models/metaplex/emptyPaymentAccount';
|
||||
import { QUOTE_MINT } from '../constants';
|
||||
import { setupPlaceBid } from './sendPlaceBid';
|
||||
import { WalletNotConnectedError } from '@solana/wallet-adapter-base';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Connection, Keypair, TransactionInstruction } from '@solana/web3.js';
|
||||
import { startAuction } from '../models/metaplex';
|
||||
import { startAuction } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { notify, sendTransactionWithRetry, WalletSigner } from '@oyster/common';
|
||||
import { AuctionView } from '../hooks';
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React, { Ref, useCallback, useEffect, useState } from 'react';
|
||||
import { Image } from 'antd';
|
||||
import { MetadataCategory, MetadataFile } from '@oyster/common';
|
||||
import { MetadataCategory, MetadataFile, pubkeyToString } from '@oyster/common';
|
||||
import { MeshViewer } from '../MeshViewer';
|
||||
import { ThreeDots } from '../MyLoader';
|
||||
import { useCachedImage, useExtendedArt } from '../../hooks';
|
||||
import { Stream, StreamPlayerApi } from '@cloudflare/stream-react';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { getLast } from '../../utils/utils';
|
||||
import { pubkeyToString } from '../../utils/pubkeyToString';
|
||||
|
||||
const MeshArtContent = ({
|
||||
uri,
|
||||
|
|
|
@ -45,7 +45,7 @@ import {
|
|||
BidRedemptionTicket,
|
||||
MAX_PRIZE_TRACKING_TICKET_SIZE,
|
||||
WinningConfigType,
|
||||
} from '../../models/metaplex';
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
|
||||
async function calculateTotalCostOfRedeemingOtherPeoplesBids(
|
||||
connection: Connection,
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './meta';
|
||||
export * from '@oyster/common/dist/lib/contexts/meta/meta';
|
||||
export * from './coingecko';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './meta';
|
|
@ -9,11 +9,12 @@ import {
|
|||
Metadata,
|
||||
ParsedAccount,
|
||||
StringPublicKey,
|
||||
useLocalStorage,
|
||||
pubkeyToString,
|
||||
} from '@oyster/common';
|
||||
import { WhitelistedCreator } from '../models/metaplex';
|
||||
import { WhitelistedCreator } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { Cache } from 'three';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { pubkeyToString } from '../utils/pubkeyToString';
|
||||
|
||||
const metadataToArt = (
|
||||
info: Metadata | undefined,
|
||||
|
@ -159,6 +160,7 @@ export const useExtendedArt = (id?: StringPublicKey) => {
|
|||
|
||||
const [data, setData] = useState<IMetadataExtension>();
|
||||
const { ref, inView } = useInView();
|
||||
const localStorage = useLocalStorage();
|
||||
|
||||
const key = pubkeyToString(id);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { useMeta } from '../contexts';
|
|||
import {
|
||||
AuctionManager,
|
||||
AuctionManagerStatus,
|
||||
AuctionManagerV1,
|
||||
AuctionManagerV2,
|
||||
BidRedemptionTicket,
|
||||
BidRedemptionTicketV2,
|
||||
|
@ -26,8 +27,8 @@ import {
|
|||
MetaplexKey,
|
||||
SafetyDepositConfig,
|
||||
WinningConfigType,
|
||||
} from '../models/metaplex';
|
||||
import { AuctionManagerV1 } from '../models/metaplex/deprecatedStates';
|
||||
AuctionViewItem,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
|
||||
export enum AuctionViewState {
|
||||
Live = '0',
|
||||
|
@ -37,14 +38,6 @@ export enum AuctionViewState {
|
|||
Defective = '-1',
|
||||
}
|
||||
|
||||
export interface AuctionViewItem {
|
||||
winningConfigType: WinningConfigType;
|
||||
amount: BN;
|
||||
metadata: ParsedAccount<Metadata>;
|
||||
safetyDeposit: ParsedAccount<SafetyDepositBox>;
|
||||
masterEdition?: ParsedAccount<MasterEditionV1 | MasterEditionV2>;
|
||||
}
|
||||
|
||||
// Flattened surface item for easy display
|
||||
export interface AuctionView {
|
||||
// items 1:1 with winning configs FOR NOW
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { StringPublicKey } from '@oyster/common';
|
||||
import { StringPublicKey, pubkeyToString } from '@oyster/common';
|
||||
import { useMeta } from '../contexts';
|
||||
import { pubkeyToString } from '../utils/pubkeyToString';
|
||||
|
||||
export const useCreator = (id?: StringPublicKey) => {
|
||||
const { whitelistedCreatorsByCreator } = useMeta();
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
ParticipationConfigV2,
|
||||
WinningConfigType,
|
||||
WinningConstraint,
|
||||
} from '../models/metaplex';
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { useMeta } from './../contexts';
|
||||
|
||||
export const useUserArts = (): SafetyDepositDraft[] => {
|
||||
|
|
|
@ -3,19 +3,17 @@ import {
|
|||
ConnectionProvider,
|
||||
StoreProvider,
|
||||
WalletProvider,
|
||||
MetaProvider,
|
||||
} from '@oyster/common';
|
||||
import { FC } from 'react';
|
||||
import { UseWalletProvider } from 'use-wallet';
|
||||
import { ConfettiProvider } from './components/Confetti';
|
||||
import { AppLayout } from './components/Layout';
|
||||
import { MetaProvider } from './contexts/meta';
|
||||
import { CoingeckoProvider } from './contexts/coingecko';
|
||||
|
||||
export const Providers: FC = ({ children }) => {
|
||||
return (
|
||||
<ConnectionProvider>
|
||||
<WalletProvider>
|
||||
<UseWalletProvider chainId={5}>
|
||||
<AccountsProvider>
|
||||
<CoingeckoProvider>
|
||||
<StoreProvider
|
||||
|
@ -30,7 +28,6 @@ export const Providers: FC = ({ children }) => {
|
|||
</StoreProvider>
|
||||
</CoingeckoProvider>
|
||||
</AccountsProvider>
|
||||
</UseWalletProvider>
|
||||
</WalletProvider>
|
||||
</ConnectionProvider>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useLocalStorage } from '@oyster/common';
|
||||
import { TokenInfo } from '@solana/spl-token-registry';
|
||||
|
||||
export const LAMPORT_MULTIPLIER = 10 ** 9;
|
||||
|
@ -8,6 +9,7 @@ export const filterModalSolTokens = (tokens: TokenInfo[]) => {
|
|||
};
|
||||
|
||||
export async function getAssetCostToStore(files: File[]) {
|
||||
const localStorage = useLocalStorage();
|
||||
const totalBytes = files.reduce((sum, f) => (sum += f.size), 0);
|
||||
console.log('Total bytes', totalBytes);
|
||||
const txnFeeInWinstons = parseInt(
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const pubkeyToString = (key: PublicKey | string = '') => {
|
||||
return typeof key === 'string' ? key : key?.toBase58() || '';
|
||||
};
|
|
@ -12,7 +12,10 @@ import {
|
|||
Divider,
|
||||
} from 'antd';
|
||||
import { useMeta } from '../../contexts';
|
||||
import { Store, WhitelistedCreator } from '../../models/metaplex';
|
||||
import {
|
||||
Store,
|
||||
WhitelistedCreator,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import {
|
||||
MasterEditionV1,
|
||||
notify,
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import React, { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { Layout, Button, Col, Spin } from 'antd';
|
||||
import { useMeta } from '../../contexts';
|
||||
import { AuctionManagerV2, WinningConfigType } from '../../models/metaplex';
|
||||
import {
|
||||
AuctionManagerV1,
|
||||
AuctionManagerV2,
|
||||
WinningConfigType,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { Pie, Bar } from 'react-chartjs-2';
|
||||
import {
|
||||
AuctionDataExtended,
|
||||
|
@ -16,7 +20,6 @@ import {
|
|||
import { AuctionView, useAuctions } from '../../hooks';
|
||||
import { QUOTE_MINT } from '../../constants';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
import { AuctionManagerV1 } from '../../models/metaplex/deprecatedStates';
|
||||
|
||||
const { Content } = Layout;
|
||||
export const AnalyticsView = () => {
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
NonWinningConstraint,
|
||||
PayoutTicket,
|
||||
WinningConstraint,
|
||||
} from '../../models/metaplex';
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { settle } from '../../actions/settle';
|
||||
import { MintInfo } from '@solana/spl-token';
|
||||
|
|
|
@ -3,9 +3,9 @@ import { useParams } from 'react-router-dom';
|
|||
import { Row, Col, Button, Skeleton, Carousel, List, Card } from 'antd';
|
||||
import { AuctionCard } from '../../components/AuctionCard';
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { AuctionViewItem } from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import {
|
||||
AuctionView as Auction,
|
||||
AuctionViewItem,
|
||||
useArt,
|
||||
useAuction,
|
||||
useBidsForAuction,
|
||||
|
|
|
@ -36,7 +36,11 @@ import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|||
import { useWallet } from '@solana/wallet-adapter-react';
|
||||
import { MintLayout } from '@solana/spl-token';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { WinningConfigType, AmountRange } from '../../models/metaplex';
|
||||
import { capitalize } from 'lodash';
|
||||
import {
|
||||
WinningConfigType,
|
||||
AmountRange,
|
||||
} from '@oyster/common/dist/lib/models/metaplex/index';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
createAuctionManager,
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
useConnection,
|
||||
useStore,
|
||||
useWalletModal,
|
||||
WalletSigner,
|
||||
WhitelistedCreator,
|
||||
} from '@oyster/common';
|
||||
import { useWallet } from '@solana/wallet-adapter-react';
|
||||
import { Button } from 'antd';
|
||||
|
@ -10,9 +10,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
import { saveAdmin } from '../../actions/saveAdmin';
|
||||
import { useMeta } from '../../contexts';
|
||||
import { WhitelistedCreator } from '../../models/metaplex';
|
||||
import { SetupVariables } from '../../components/SetupVariables';
|
||||
import { WalletAdapter } from '@solana/wallet-adapter-base';
|
||||
|
||||
export const SetupView = () => {
|
||||
const [isInitalizingStore, setIsInitalizingStore] = useState(false);
|
||||
|
|
3253
js/yarn.lock
3253
js/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -2,14 +2,14 @@
|
|||
url = "https://anchor.projectserum.com"
|
||||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
cluster = "devnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.mainnet]
|
||||
nft_candy_machine = "cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ"
|
||||
|
||||
[scripts]
|
||||
test = "mocha -t 1000000 tests/"
|
||||
test = "ts-mocha -p ./tsconfig.json -t 1000000 test/*.ts"
|
||||
|
||||
[workspace]
|
||||
members = ["nft-candy-machine"]
|
||||
|
|
|
@ -121,7 +121,7 @@ pub mod nft_candy_machine {
|
|||
for c in &config.data.creators {
|
||||
creators.push(spl_token_metadata::state::Creator {
|
||||
address: c.address,
|
||||
verified: true,
|
||||
verified: false,
|
||||
share: c.share,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue