Theme

Features

Execute Asset Signing

The MPL Core Execute instruction introduces the concept of Asset Signers to MPL Core Assets.

These Asset Signers act as Signers on behalf of the Asset itself which unlocks the ability for MPL Core Assets

  • to transfer out Solana and SPL Tokens.
  • to become the authority of other accounts.
  • to perform other actions and validations that have been assigned to the assetSignerPda that require transaction/instruction/CPI signing.

MPL Core Assets have the ability to sign and submit transactions/CPIs to the blockchain. This effectively gives the Core Asset it's own wallet in the form of an assetSigner.

Asset Signer PDA

Assets are now able to access the assetSignerPda account/address which allows the execute instruction on the MPL Core program to pass through additional instructions sent to it to sign the CPI instructions with the assetSignerPda.

This allows the assetSignerPda account to effectively own and execute account instructions on behalf of the current asset owner.

You can think of the assetSignerPda as a wallet attached to a Core Asset.

findAssetSignerPda()

const assetId = publickey('11111111111111111111111111111111')

const assetSignerPda = findAssetSignerPda(umi, { asset: assetId })

Execute Instruction

Overview

The execute instruction allows users to pass in the Core Asset and also some pass through instructions that will get signed by the AssetSigner when it hits the MPL Core programs execute instruction on chain.

An overview of the execute instruction and it's args.

const executeIx = await execute(umi, {
    {
        // The asset via `fetchAsset()` that is signing the transaction.
        asset: AssetV1,
        // The collection via `fetchCollection()`
        collection?: CollectionV1,
        // Either a TransactionBuilder | Instruction[]
        instructions: ExecuteInput,
        // Additional Signers that will be required for the transaction/instructions.
        signers?: Signer[]
    }
})

Validation

assetSignerPda Validation

The MPL Core Execute instruction will validate that the current Asset owner has also signed the transaction. This insures only the current Asset Owner can execute transactions while using the assetSignerPda with the execute instruction.

Examples

Transferring SOL From the Asset Signer

In the following example we transfer SOL that had been sent to the assetSignerPda to a destination of our choice.

import {
  execute,
  findAssetSignerPda,
  fetchAsset,
  fetchCollection,
} from '@metaplex-foundation/mpl-core'
import { transferSol } from '@metaplex-foundation/mpl-toolbox'
import { publickey, createNoopSigner, sol } from '@metaplex-foundation/umi'

const assetId = publickey('11111111111111111111111111111111')

const asset = await fetchAsset(umi, assetId)

// Optional - If Asset is part of collection fetch the collection object
const collection =
  asset.updateAuthority.type == 'Collection' && asset.updateAuthority.address
    ? await fetchCollection(umi, asset.updateAuthority.address)
    : undefined

// Asset signer has a balance of 1 SOL in the account.
const assetSignerPda = findAssetSignerPda(umi, { asset: assetId })

// Destination account we wish to transfer the SOL to.
const destination = publickey('2222222222222222222222222222222222')

// A standard `transferSol()` transactionBuilder.
const transferSolIx = transferSol(umi, {
  // Create a noopSigner as the assetSigner will sign later during CPI
  source: createNoopSigner(publicKey(assetSigner)),
  // Destination address
  destination,
  // Amount you wish to transfer
  amount: sol(0.5),
})

// Call the `execute` instruction and send to the chain.
const res = await execute(umi, {
  // Execute instruction(s) with this asset
  asset,
  // If Asset is part of collection pass in collection object via `fetchCollection()`
  collection,
  // The transactionBuilder/instruction[] to execute
  instructions: transferSolIx,
}).sendAndConfirm(umi)

console.log({ res })

Transferring SPL Tokens From the Asset Signer

In the following example we transfer some of our SPL Token balance from the assetSignerPda account to a destination.

This example is based on the best practices in regards to derived tokens accounts for a base wallet address. If tokens are not in their correctly derived token account based on the assetSignerPda address then this example will need adjusting.

import {
  execute,
  findAssetSignerPda,
  fetchAsset,
  fetchCollection,
} from '@metaplex-foundation/mpl-core'
import {
  transferTokens,
  findAssociatedTokenPda,
} from '@metaplex-foundation/mpl-toolbox'
import { publickey } from '@metaplex-foundation/umi'

const assetId = publickey('11111111111111111111111111111111')

const asset = await fetchAsset(umi, assetId)

// Optional - If Asset is part of collection fetch the collection object
const collection =
  asset.updateAuthority.type == 'Collection' && asset.updateAuthority.address
    ? await fetchCollection(umi, asset.updateAuthority.address)
    : undefined

const splTokenMint = publickey('2222222222222222222222222222222222')

// Asset signer has a balance of tokens.
const assetSignerPda = findAssetSignerPda(umi, { asset: assetId })

// Destination wallet we wish to transfer the SOL to.
const destinationWallet = publickey('3333333333333333333333333333333')

// A standard `transferTokens()` transactionBuilder.
const transferTokensIx = transferTokens(umi, {
  // Source is the `assetSignerPda` derived Token Account
  source: findAssociatedTokenPda(umi, {
    mint: splTokenMint,
    owner: assetSignerPda,
  }),
  // Destination is the `destinationWallet` derived Token Account
  destination: findAssociatedTokenPda(umi, {
    mint: splTokenMint,
    owner: destinationWallet,
  }),
  // Amount to send in lamports.
  amount: 5000,
})

// Call the `execute` instruction and send to the chain.
const res = await execute(umi, {
  // Execute instruction(s) with this asset
  asset,
  // If Asset is part of collection pass in collection object via `fetchCollection()`
  collection,
  // The transactionBuilder/instruction[] to execute
  instructions: transferTokensIx,
}).sendAndConfirm(umi)

console.log({ res })

Transferring Ownership of an Asset to Another Asset

In the following example we transfer a Core Asset that is owned by another Core Asset, to another.

import {
  execute,
  fetchAsset,
  fetchCollection,
  findAssetSignerPda,
  transfer,
} from '@metaplex-foundation/mpl-core'
import { publickey } from '@metaplex-foundation/umi'

// Asset we wish to transfer.
const assetId = publickey('11111111111111111111111111111111')
const asset = await fetchAsset(assetId)

// Optional - If Asset is part of collection fetch the collection object
const collection =
  asset.updateAuthority.type == 'Collection' && asset.updateAuthority.address
    ? await fetchCollection(umi, asset.updateAuthority.address)
    : undefined

// Asset ID that owns the Asset we wish to transfer.
const sourceAssetId = publickey('2222222222222222222222222222222222')
// The source Asset object.
const sourceAsset = fetchAsset(umi, sourceAssetId)
// Asset signer has a balance of 1 SOL in the account.
const sourceAssetSignerPda = findAssetSignerPda(umi, { asset: assetId })

// Destination account we wish to transfer the SOL to.
const destinationAssetId = publickey('33333333333333333333333333333333')
// Destination Asset signer we wish to transfer the Asset to.
const destinationAssetSignerPda = findAssetSignerPda(umi, {
  asset: destinationAssetId,
})

const transferAssetIx = transfer(umi, {
  // Asset object via `fetchAsset()`.
  asset,
  // Optional - Collection object via `fetchCollection()`
  collection,
  // New Owner of the Asset.
  newOwner: destinationAssetSignerPda,
}).sendAndConfirm(umi)

const res = await execute(umi, {
  // Execute instruction(s) with this asset
  asset,
  // If Asset is part of collection pass in collection object via `fetchCollection()`
  collection,
  // The transactionBuilder/instruction[] to execute
  instructions: transferAssetIx,
}).sendAndConfirm(umi)

console.log({ res })
Previous
Collection Management