Concepts
OCP uses SPL managed-token as the
base for holding the freeze authority of the spl-token. And managed-token has 1:1
feature parity of the spl-token in terms of the token interfaces like transfer
, approve
, revoke
, burn
, close
,
init_account
and mint_to
.
Action Context​
Action context is the context used to build the json rules engine. OCP's solution is a JSON rules engine DSL to define what can be applied to the context.
pub struct ActionCtx {
pub action: String,
pub program_ids: Vec<String>,
pub mint: String,
pub mint_state: MintStateCtx,
pub mint_account: Option<MintAccountCtx>,
pub metadata: Option<MetadataCtx>,
pub payer: Option<String>,
pub from: Option<String>, // owner of the from_account, and many action's initiator
pub to: Option<String>, // owner of the to_account
pub last_memo_signer: Option<String>,
pub last_memo_data: Option<String>,
}
Onchain Policy Engine​
OCP utilises the JSON Rules Engine package, for more information please see the repository.
Sample Use Cases | Policy (json_rule) |
---|---|
Allow For All | null |
Program IDs Allowlist | { "field": "program_ids", "operator": "string_is_subset", "value": ["1111111111111111111111111111111"]} |
Program IDs Denylist | { "field": "program_ids", "operator": "string_does_not_contain_any", "value": ["1111111111111111111111111111111"]} |
Soulbound Token | { "field": "mint_state/transferred_count", "operator": "int_less_than", "value": 1 } |
Semi Soulbound Token | { "field": "mint_state/transferred_count", "operator": "int_less_than", "value": n } |
Transfer Timestamp Constraint | { "field": "mint_state/derived_datetime/utc_timestamp", "operator": "int_greater_than", "value": 1669881409} |
Transfer Cooldown Token | { "field": "mint_state/derived_cooldown", "operator": "int_greater_than", "value": 3600 } |
Metadata Name Filter | { "field": "metadata/name", "operator": "string_has_substring", "value": "FROZEN"} |
Metadata URI Filter | { "field": "metadata/uri", "operator": "string_has_substring", "value": "IPFS"} |
Single Transfer Destination | { "field": "to", "operator": "string_equals", "value": ["1111111111111111111111111111111"]} |
Here's a full example of how a creator can leverage OCP to personalize the transferability. The logic works like this:
- When the
action
is nottransfer
, pass - When the
action
istransfer
, then one cannot transfer if themetadata/name
contains a keywordFROZEN
- When the
action
istransfer
, then one cannot transfer to a specific address if themetadata/name
doesn't containWINNER
.
Example of the "Magic Mint"'s complex policy can be found on the solana explorer link.
{
"events": [],
"conditions": {
"or": [
{
"field": "action",
"operator": "string_not_equals",
"value": "transfer"
},
{
"and": [
{
"not": {
"field": "metadata/name",
"operator": "string_has_substring",
"value": "FROZEN"
}
},
{
"or": [
{
"field": "to",
"operator": "string_not_equals",
"value": "DWuopEsTrg5qWMSMVT1hoiVTRQG9PkGJZSbXiKAxHYbn"
},
{
"field": "metadata/name",
"operator": "string_has_substring",
"value": "WINNER"
}
]
}
]
}
]
}
}
Mint State​
MintState
determines if a mint (token) is with OCP or not. Mint state is a key PDA that OCP uses to associate a mint account with a policy and some state information related to the mint account.
pub struct MintState {
pub version: u8,
pub bump: [u8; 1],
pub mint: Pubkey,
pub policy: Pubkey,
pub locked_by: Option<Pubkey>,
pub last_approved_at: i64,
pub last_transferred_at: i64,
pub transferred_count: u32,
}
One can use the findMintStatePk
to test if a mint account has a MintState
account, and then leverage what OCP program provides.
export const findMintStatePk = (mint: PublicKey) => {
return PublicKey.findProgramAddressSync(
[utils.bytes.utf8.encode("mint_state"), mint.toBuffer()],
PROGRAM_ID
)[0];
};
const mintStatePk = findMintStatePk(mint);
const mintStateAcc = await conn.getAccountInfo(mintStatePk): // returns null | AccountInfo<Buffer>, and then you can decide if it's OCP going forward.
Dynamic Royalties​
Creators can specify a relationship between an NFT’s sale price and royalty amount via a linear price curve. And more
curve types to be supported in the future. The first dynamic royalty curve OCP supports is DynamicRoyaltyPriceLinear
.
Both start_multiplier_bp
and end_multiplier_bp
are relative multipliers based on the
Metaplex's metadata.seller_fee_basis_points
.
pub struct DynamicRoyaltyPriceLinear {
pub price_mint: Option<Pubkey>,
pub start_price: u64,
pub end_price: u64,
pub start_multiplier_bp: u16,
pub end_multiplier_bp: u16,
}
Specifically, if we note that r is the final multiplier_bp, then:
For example, given the following dynamic royalty setting:
DynamicRoyaltyPriceLinear:
{
start_price: 0 SOL
end_price: 5 SOL
start_multiplier_bp: 100% (10000 bp)
end_multiplier_bp: 50% (5000 bp)
}
Metadata:
seller_fee_basis_points: 5% (500 bp)
price: 0 SOL ===> royalty_bp: 500 (5%)
price: 2.5 SOL ===> royalty_bp: 375 (3.75%)
price: 5 SOL ===> royalty_bp: 250 (2.5%)