External Plugins

Oracle Plugin

What is an Oracle Plugin?

An Oracle Plugin is an onchain account that is created by the authority externally from the MPL Core Asset or Collection. If an Asset or Collection has an Oracle Adapter enabled and an Oracle Account assigned to it the Oracle Account will be loaded by the MPL Core program for validations against lifecycle events.

The Oracle Plugin stores data relating to 4 lifecycle events of create, transfer, burn, and update and can be configured to perform a Reject validation.

The ability to update and change the Oracle Account provides a powerful and interactive lifecycle experience.

Works With

MPL Core Asset
MPL Core Collection

Allowed Validations

The following validation results can be returned from the Oracle Account to the Oracle Plugin.

Can Approve
Can Reject
Can Pass

On Chain Oracle Account Structure

The Oracle Account should have the following onchain account structure.

On Chain Account Struct of Oracle Account

#[account]
pub struct Validation {
    pub validation: OracleValidation,
}

impl Validation {
    pub fn size() -> usize {
        8 // anchor discriminator
        + 5 // validation
    }
}

pub enum OracleValidation {
    Uninitialized,
    V1 {
        create: ExternalValidationResult,
        transfer: ExternalValidationResult,
        burn: ExternalValidationResult,
        update: ExternalValidationResult,
    },
}

pub enum ExternalValidationResult {
    Approved,
    Rejected,
    Pass,
}

Oracle Account Offset

The account structure will differ slightly between account frameworks (Anchor, Shank, etc.) due to the discriminator sizes needed for accounts:

  • If the OracleValidation struct is located at the beginning of the data section for the Oracle account, then choose NoOffset for the ValidationResultsOffset.
  • If the Oracle account only contains the OracleValidation struct but is managed by an Anchor program, select Anchor for ValidationResultsOffset so that the struct can be located after the Anchor account discriminator.
  • If the OracleValidation struct is located at some other offset in the Oracle account, use the Custom offset.

resultsOffset / result_offset

const resultsOffset: ValidationResultsOffset =
  | { type: 'NoOffset' }
  | { type: 'Anchor' }
  | { type: 'Custom'; offset: bigint };

Updating the Oracle Account

Because the Oracle Account is created and maintained by the creator/developer the OracleValidation struct can be updated at anytime allowing lifecycles to be dynamic.

The Oracle Adapter

The Oracle Adapter accepts the following arguments and data.

On Chain Struct

pub struct Oracle {
    /// The address of the oracle, or if using the `pda` option,
    /// a program ID from which to derive a PDA.
    pub base_address: Pubkey,
    /// Optional account specification (PDA derived from `base_address` or other
    /// available account specifications).  Note that even when this
    /// configuration is used there is still only one
    /// Oracle account specified by the adapter.
    pub base_address_config: Option<ExtraAccount>,
    /// Validation results offset in the Oracle account.
    /// Default is `ValidationResultsOffset::NoOffset`
    pub results_offset: ValidationResultsOffset,
}

Declaring the PDA of an Oracle Plugin

The default behavior of the Oracle Plugin Adapter is to supply the adapter with a static base_address which the adapter can then read from and provide the resulting validation results.

If you wish to get more dynamic with the Oracle Plugin Adapter you can pass in your program_id as the base_address and then an ExtraAccount, which can be used to derive one or more PDAs pointing to Oracle Account addresses. This allows the Oracle Adapter to access data from multiple derived Oracle Accounts. Note that there are other advanced non-PDA specifications also available when using ExtraAccount.

List of ExtraAccounts Options

An example of an extra account that is the same for all assets in a collection is the PreconfiguredCollection PDA, which uses the collection's Pubkey to derive the Oracle account. An example of more dynamic extra account is the PreconfiguredOwner PDA, which uses the owner pubkey to derive the Oracle account.

pub enum ExtraAccount {
    /// Program-based PDA with seeds \["mpl-core"\]
    PreconfiguredProgram {
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// Collection-based PDA with seeds \["mpl-core", collection_pubkey\]
    PreconfiguredCollection {
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// Owner-based PDA with seeds \["mpl-core", owner_pubkey\]
    PreconfiguredOwner {
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// Recipient-based PDA with seeds \["mpl-core", recipient_pubkey\]
    /// If the lifecycle event has no recipient the derivation will fail.
    PreconfiguredRecipient {
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// Asset-based PDA with seeds \["mpl-core", asset_pubkey\]
    PreconfiguredAsset {
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// PDA based on user-specified seeds.
    CustomPda {
        /// Seeds used to derive the PDA.
        seeds: Vec<Seed>,
        /// Program ID if not the base address/program ID for the external plugin.
        custom_program_id: Option<Pubkey>,
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
    /// Directly-specified address.
    Address {
        /// Address.
        address: Pubkey,
        /// Account is a signer
        is_signer: bool,
        /// Account is writable.
        is_writable: bool,
    },
}

Creating and Adding Oracle Plugins

Creating an Asset with the Oracle Plugin

Create a MPL Core Asset with an Oracle Plugin

import { generateSigner, publicKey } from '@metaplex-foundation/umi'
import {
  create,
  CheckResult
} from '@metaplex-foundation/core'

const collectionSigner = generateSigner(umi)

const oracleAccount = publicKey('11111111111111111111111111111111')

const asset = await create(umi, {
    ... CreateAssetArgs,
    plugins: [
        {
        type: 'Oracle',
        resultsOffset: {
          type: 'Anchor',
        },
        baseAddress: oracleAccount,
        authority: {
          type: 'UpdateAuthority',
        },
        lifecycleChecks: {
          update: [CheckResult.CAN_REJECT],
        },
        baseAddressConfig: undefined,
      },,
    ],
  });.sendAndConfirm(umi)

Adding an Oracle Plugin to An Asset

Adding an Oracle Plugin to a Collection

import { publicKey } from '@metaplex-foundation/umi'
import { addPlugin, CheckResult } from '@metaplex-foundation/mpl-core'

const asset = publicKey('11111111111111111111111111111111')
const oracleAccount = publicKey('22222222222222222222222222222222')

addPlugin(umi, {
  asset,
  plugin: {
    type: 'Oracle',
    resultsOffset: {
      type: 'Anchor',
    },
    lifecycleChecks: {
      create: [CheckResult.CAN_REJECT],
    },
    baseAddress: oracleAccount,
  },
})

Creating a Collection with an Oracle Plugin

Creating a Collection with an Oracle Plugin

import { generateSigner, publicKey } from '@metaplex-foundation/umi'
import {
  create,
  CheckResult
  } from '@metaplex-foundation/core'

const collectionSigner = generateSigner(umi)
const oracleAccount = publicKey('11111111111111111111111111111111')

const collection = await createCollection(umi, {
    ... CreateCollectionArgs,
    plugins: [
        {
        type: 'Oracle',
        resultsOffset: {
          type: 'Anchor',
        },
        baseAddress: oracleAccount,
        lifecycleChecks: {
          update: [CheckResult.CAN_REJECT],
        },
        baseAddressConfig: undefined,
      },,
    ],
  });.sendAndConfirm(umi)

Adding an Oracle Plugin to a Collection

Adding an Oracle Plugin to a Collection

import { publicKey } from '@metaplex-foundation/umi'
import { addCollectionPlugin, CheckResult } from '@metaplex-foundation/mpl-core'

const collection = publicKey('11111111111111111111111111111111')
const oracleAccount = publicKey('22222222222222222222222222222222')

await addCollectionPlugin(umi, {
  collection: collection,
  plugin: {
    type: 'Oracle',
    resultsOffset: {
      type: 'Anchor',
    },
    lifecycleChecks: {
      update: [CheckResult.CAN_REJECT],
    },
    baseAddress: oracleAccount,
  },
}).sendAndConfirm(umi)

Example Usage/Ideas

Example 1

Assets to be not transferable during the hours of noon-midnight UTC.

  • Create onchain Oracle Plugin in a program of your choice.
  • Add the Oracle Plugin Adapter to an Asset or Collection specifying the lifecycle events you wish to have rejection validation over.
  • You write a cron that writes and updates to your Oracle Plugin at noon and midnight flipping a bit validation from true/false/true.

Example 2

Assets can only be updated if the floor price is above $10 and the asset has attribute “red hat”.

  • Create onchain Oracle Plugin in a program of your choice.
  • Add the Oracle Plugin Adapter to Asset specifying the lifecycle events you wish to have rejection validation over.
  • Dev writes Anchor program that can write to the Oracle Account that derive the same PRECONFIGURED_ASSET accounts
  • Dev writes web2 script that watches prices on a marketplace, AND with known hashlist of Assets with the 'Red Hat' trait red updates and writes to the relevant Oracle Accounts.
Previous
Removing External Plugin Adapters