Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions src/commands/core/plugins/add.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Args, Flags } from '@oclif/core'

import { addCollectionPlugin, AddCollectionPluginArgsPlugin, addPlugin, AddPluginArgsPlugin, fetchAsset } from '@metaplex-foundation/mpl-core'
import { addCollectionPlugin, AddCollectionPluginArgsPlugin, addPlugin, AddPluginArgsPlugin } from '@metaplex-foundation/mpl-core'
import { publicKey, transactionBuilder } from '@metaplex-foundation/umi'
import { readFileSync } from 'fs'
import ora from 'ora'
import { BaseCommand } from '../../../BaseCommand.js'
Comment thread
MarkSackerberg marked this conversation as resolved.
import { generateCoreExplorerUrl } from '../../../explorers.js'
import resolveCoreAccount from '../../../lib/core/fetch/resolveCoreAccount.js'
import { Plugin } from '../../../lib/types/pluginData.js'
import umiSendAllTransactionsAndConfirm from '../../../lib/umi/sendAllTransactionsAndConfirm.js'
import { txSignatureToString } from '../../../lib/util.js'
Expand All @@ -18,40 +19,43 @@ export default class CorePluginsAdd extends BaseCommand<typeof CorePluginsAdd> {
static override examples = [
'<%= config.bin %> <%= command.id %> <asset or collection public key> --wizard',
'<%= config.bin %> <%= command.id %> <asset or collection public key> ./plugin.json',
'<%= config.bin %> <%= command.id %> <collection public key> ./plugin.json --collection',
]

static override flags = {
wizard: Flags.boolean({ description: 'Wizard mode', default: false }),
collection: Flags.boolean({ description: 'Is this a collection\'s plugin', default: false }),
collection: Flags.boolean({
description: 'Treat the address as a collection (auto-detected if omitted)',
default: false,
}),
}

static override args = {
id: Args.string({ description: 'asset or collection public key', required: true }),
json: Args.file({ description: 'path to a plugin data JSON file', required: false }),
}



public async run(): Promise<unknown> {
const { args, flags } = await this.parse(CorePluginsAdd)

// Auto-detect collection ID if this is an asset operation
const resolveSpinner = ora('Resolving asset or collection...').start()
let isCollection: boolean
let collectionId: string | undefined
if (!flags.collection) {
try {
const asset = await fetchAsset(this.context.umi, publicKey(args.id))

if (asset.updateAuthority.type === 'Collection') {
collectionId = asset.updateAuthority.address
}
} catch (error) {
throw new Error('Unable to fetch asset')
}
try {
const resolved = await resolveCoreAccount(this.context.umi, args.id, {
forceCollection: flags.collection,
})
isCollection = resolved.isCollection
collectionId = resolved.collectionId
resolveSpinner.succeed(`Resolved as ${isCollection ? 'collection' : 'asset'}`)
} catch (error) {
resolveSpinner.fail(error instanceof Error ? error.message : 'Failed to resolve address')
throw error
}

if (flags.wizard) {
const selectedPlugins = await pluginSelector({
filter: flags.collection ? PluginFilterType.Collection : PluginFilterType.Asset,
filter: isCollection ? PluginFilterType.Collection : PluginFilterType.Asset,
type: 'list',
managedBy: PluginFilterType.Authority
Comment thread
MarkSackerberg marked this conversation as resolved.
}) as Plugin[]
Expand All @@ -64,7 +68,7 @@ export default class CorePluginsAdd extends BaseCommand<typeof CorePluginsAdd> {

const pluginsArray = Object.values(wizardPluginData) as (AddPluginArgsPlugin | AddCollectionPluginArgsPlugin)[]
return await this.addPluginsBatch(args.id, pluginsArray, {
isCollection: flags.collection,
isCollection,
collectionId
})
}
Expand All @@ -81,7 +85,7 @@ export default class CorePluginsAdd extends BaseCommand<typeof CorePluginsAdd> {
}

return await this.addPluginsBatch(args.id, jsonData as (AddPluginArgsPlugin | AddCollectionPluginArgsPlugin)[], {
isCollection: flags.collection,
isCollection,
collectionId
})
}
Expand All @@ -91,7 +95,7 @@ export default class CorePluginsAdd extends BaseCommand<typeof CorePluginsAdd> {


private async addPluginsBatch(asset: string, pluginsData: (AddPluginArgsPlugin | AddCollectionPluginArgsPlugin)[], options: { isCollection: boolean, collectionId?: string }): Promise<unknown> {
const { umi, explorer } = this.context
const { umi } = this.context
const { isCollection, collectionId } = options

let transaction = transactionBuilder()
Expand Down
52 changes: 22 additions & 30 deletions src/commands/core/plugins/update.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Args, Flags } from '@oclif/core'

import { updateCollectionPlugin, updatePlugin, UpdatePluginArgsPlugin, UpdateCollectionPluginArgsPlugin, fetchAsset, fetchCollection } from '@metaplex-foundation/mpl-core'
import { updateCollectionPlugin, updatePlugin, UpdatePluginArgsPlugin, UpdateCollectionPluginArgsPlugin } from '@metaplex-foundation/mpl-core'
import { publicKey, transactionBuilder } from '@metaplex-foundation/umi'
import { readFileSync } from 'fs'
import ora from 'ora'
import { BaseCommand } from '../../../BaseCommand.js'
import { generateCoreExplorerUrl } from '../../../explorers.js'
import resolveCoreAccount from '../../../lib/core/fetch/resolveCoreAccount.js'
import { Plugin } from '../../../lib/types/pluginData.js'
import umiSendAllTransactionsAndConfirm from '../../../lib/umi/sendAllTransactionsAndConfirm.js'
import { txSignatureToString } from '../../../lib/util.js'
Expand All @@ -18,11 +19,15 @@ export default class CorePluginsUpdate extends BaseCommand<typeof CorePluginsUpd
static override examples = [
'<%= config.bin %> <%= command.id %> <asset or collection public key> --wizard',
'<%= config.bin %> <%= command.id %> <asset or collection public key> ./plugin.json',
'<%= config.bin %> <%= command.id %> <collection public key> ./plugin.json --collection',
]

static override flags = {
wizard: Flags.boolean({ description: 'Wizard mode', default: false }),
collection: Flags.boolean({ description: 'Is this a collection\'s plugin', default: false }),
collection: Flags.boolean({
description: 'Treat the address as a collection (auto-detected if omitted)',
default: false,
}),
}

static override args = {
Expand All @@ -33,37 +38,24 @@ export default class CorePluginsUpdate extends BaseCommand<typeof CorePluginsUpd
public async run(): Promise<unknown> {
const { args, flags } = await this.parse(CorePluginsUpdate)

// Fetch current asset or collection to validate it exists
const fetchSpinner = ora('Fetching current state...').start()
const resolveSpinner = ora('Resolving asset or collection...').start()
let isCollection: boolean
let collectionId: string | undefined
try {
if (flags.collection) {
await fetchCollection(this.context.umi, publicKey(args.id))
} else {
await fetchAsset(this.context.umi, publicKey(args.id))
}
fetchSpinner.succeed('Successfully fetched current state')
const resolved = await resolveCoreAccount(this.context.umi, args.id, {
forceCollection: flags.collection,
})
isCollection = resolved.isCollection
collectionId = resolved.collectionId
resolveSpinner.succeed(`Resolved as ${isCollection ? 'collection' : 'asset'}`)
} catch (error) {
fetchSpinner.fail(`Failed to fetch ${flags.collection ? 'collection' : 'asset'}: ${error instanceof Error ? error.message : 'Unknown error'}`)
resolveSpinner.fail(error instanceof Error ? error.message : 'Failed to resolve address')
throw error
}

// Auto-detect collection ID if this is an asset operation
let collectionId: string | undefined
if (!flags.collection) {
try {
const asset = await fetchAsset(this.context.umi, publicKey(args.id))

if (asset.updateAuthority.type === 'Collection') {
collectionId = asset.updateAuthority.address
}
} catch (error) {
throw new Error('Unable to fetch asset')
}
}

if (flags.wizard) {
const selectedPlugins = await pluginSelector({
filter: flags.collection ? PluginFilterType.Collection : PluginFilterType.Asset,
filter: isCollection ? PluginFilterType.Collection : PluginFilterType.Asset,
type: 'list',
managedBy: PluginFilterType.Authority
}) as Plugin[]
Expand All @@ -76,7 +68,7 @@ export default class CorePluginsUpdate extends BaseCommand<typeof CorePluginsUpd

const pluginsArray = Object.values(wizardPluginData) as (UpdatePluginArgsPlugin | UpdateCollectionPluginArgsPlugin)[]
return await this.updatePluginsBatch(args.id, pluginsArray, {
isCollection: flags.collection,
isCollection,
collectionId
})
}
Expand All @@ -93,7 +85,7 @@ export default class CorePluginsUpdate extends BaseCommand<typeof CorePluginsUpd
}

return await this.updatePluginsBatch(args.id, jsonData as (UpdatePluginArgsPlugin | UpdateCollectionPluginArgsPlugin)[], {
isCollection: flags.collection,
isCollection,
collectionId
})
}
Expand All @@ -103,7 +95,7 @@ export default class CorePluginsUpdate extends BaseCommand<typeof CorePluginsUpd


private async updatePluginsBatch(assetOrCollection: string, pluginsData: (UpdatePluginArgsPlugin | UpdateCollectionPluginArgsPlugin)[], options: { isCollection: boolean, collectionId?: string }): Promise<unknown> {
const { umi, explorer } = this.context
const { umi } = this.context
const { isCollection, collectionId } = options

// Build a single transaction with all plugin instructions
Expand Down Expand Up @@ -160,4 +152,4 @@ Core Explorer: ${generateCoreExplorerUrl(this.context.chain, assetOrCollection)}
throw error
}
}
}
}
6 changes: 5 additions & 1 deletion src/commands/genesis/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
findGenesisAccountV2Pda,
WRAPPED_SOL_MINT,
} from '@metaplex-foundation/genesis'
import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox'
import { generateSigner, publicKey } from '@metaplex-foundation/umi'
import { Flags } from '@oclif/core'
import { confirm } from '@inquirer/prompts'
import ora from 'ora'

import { TransactionCommand } from '../../TransactionCommand.js'
import { generateExplorerUrl } from '../../explorers.js'
import { GENESIS_CREATE_COMPUTE_UNIT_LIMIT } from '../../lib/genesis/operations.js'
import { txSignatureToString } from '../../lib/util.js'
import umiSendAndConfirmTransaction from '../../lib/umi/sendAndConfirm.js'
import { runApiWizard, runManualWizard, WizardContext, WizardLogger } from '../../lib/genesis/wizard.js'
Expand Down Expand Up @@ -156,7 +158,9 @@ Use --wizard for an interactive guided setup.`
uri: flags.uri,
decimals: flags.decimals,
genesisIndex: flags.genesisIndex,
})
}).prepend(
setComputeUnitLimit(this.context.umi, { units: GENESIS_CREATE_COMPUTE_UNIT_LIMIT }),
)

const result = await umiSendAndConfirmTransaction(this.context.umi, transaction)

Expand Down
76 changes: 76 additions & 0 deletions src/lib/core/fetch/resolveCoreAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
collectionAddress,
deserializeAssetV1,
deserializeCollectionV1,
Key,
} from '@metaplex-foundation/mpl-core'
import { publicKey, Umi } from '@metaplex-foundation/umi'

export type ResolvedCoreAccount = {
id: string
isCollection: boolean
/** Parent collection address when the account is an asset belonging to a collection */
collectionId?: string
Comment thread
MarkSackerberg marked this conversation as resolved.
}

export type ResolveCoreAccountOptions = {
/**
* When true, only accept a Core Collection at this address.
* Mirrors an explicit `--collection` flag override.
*/
forceCollection?: boolean
}

/**
* Resolve whether an address is a Core Asset or Collection.
*
* Fetches the account once so RPC/transport failures propagate. Distinguishes
* Asset vs Collection via the on-chain account key instead of catching
* deserialize errors (which would also hide network failures).
*
* When `forceCollection` is set, only Collection is accepted.
*/
export async function resolveCoreAccount(
umi: Umi,
id: string,
options: ResolveCoreAccountOptions = {},
): Promise<ResolvedCoreAccount> {
const address = publicKey(id)
const account = await umi.rpc.getAccount(address)

if (!account.exists) {
if (options.forceCollection) {
throw new Error(`Unable to fetch collection at address: ${id}`)
}
throw new Error(`Address ${id} is neither a Core Asset nor a Core Collection`)
}

const accountKey = account.data[0]

if (options.forceCollection) {
if (accountKey !== Key.CollectionV1) {
throw new Error(`Unable to fetch collection at address: ${id}`)
}
deserializeCollectionV1(account)
return { id, isCollection: true }
}

if (accountKey === Key.AssetV1) {
const asset = deserializeAssetV1(account)
const parentCollection = collectionAddress(asset)
return {
id,
isCollection: false,
collectionId: parentCollection ? parentCollection.toString() : undefined,
}
}

if (accountKey === Key.CollectionV1) {
deserializeCollectionV1(account)
return { id, isCollection: true }
}

throw new Error(`Address ${id} is neither a Core Asset nor a Core Collection`)
}

export default resolveCoreAccount
8 changes: 7 additions & 1 deletion src/lib/genesis/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
findUnlockedBucketV2Pda,
WRAPPED_SOL_MINT,
} from '@metaplex-foundation/genesis'
import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox'
import {
Umi,
Signer,
Expand All @@ -26,6 +27,9 @@ import {
import umiSendAndConfirmTransaction from '../umi/sendAndConfirm.js'
import { txSignatureToString } from '../util.js'

/** Genesis initialize + token metadata can exceed Solana's 200k default CU budget. */
export const GENESIS_CREATE_COMPUTE_UNIT_LIMIT = 400_000

Comment thread
coderabbitai[bot] marked this conversation as resolved.
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
Expand Down Expand Up @@ -143,7 +147,9 @@ export async function createGenesisAccount(
uri: params.uri,
decimals: params.decimals,
genesisIndex,
})
}).prepend(
setComputeUnitLimit(umi, { units: GENESIS_CREATE_COMPUTE_UNIT_LIMIT }),
)

const result = await umiSendAndConfirmTransaction(umi, transaction)

Expand Down
Loading
Loading