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 chooseNoOffset
for theValidationResultsOffset
. - If the Oracle account only contains the
OracleValidation
struct but is managed by an Anchor program, selectAnchor
forValidationResultsOffset
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 theCustom
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/mpl-core'
const collectionSigner = generateSigner(umi)
const oracleAccount = publicKey('11111111111111111111111111111111')
const asset = await create(umi, {
... CreateAssetArgs,
plugins: [
{
type: 'Oracle',
resultsOffset: {
type: 'Anchor',
},
baseAddress: oracleAccount,
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/mpl-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)
Default Oracles deployed by Metaplex
In some rare cases like Soulbound NFT it might be useful to have Oracles that always Deny or Approve a lifecycle event. For those the following Oracles have been deployed and can be used by anyone:
Transfer Oracle: Always denies transferring.
AwPRxL5f6GDVajyE1bBcfSWdQT58nWMoS36A1uFtpCZY
Update Oracle: Always denies updating.
6cKyMV4toCVCEtvh6Sh5RQ1fevynvBDByaQP4ufz1Zj6
Create Oracle: Always denies creating.
2GhRFi9RhqmtEFWCwrAHC61Lti3jEKuCKPcZTfuujaGr
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.