Bläddra i källkod

Merge pull request #311 from metaplex-foundation/core/execute

added execute instruction page that outlines execute and asset signing
blockiosaurus 9 månader sedan
förälder
incheckning
74b60cbec6
2 ändrade filer med 343 tillägg och 81 borttagningar
  1. 86 81
      src/components/products/core/index.js
  2. 257 0
      src/pages/core/execute-asset-signing.md

+ 86 - 81
src/components/products/core/index.js

@@ -2,10 +2,10 @@ import {
   changelogSection,
   documentationSection,
   guidesSection,
-  referencesSection
-} from '@/shared/sections';
-import { StopCircleIcon } from '@heroicons/react/24/solid';
-import { Hero } from './Hero';
+  referencesSection,
+} from '@/shared/sections'
+import { StopCircleIcon } from '@heroicons/react/24/solid'
+import { Hero } from './Hero'
 
 export const core = {
   name: 'Core',
@@ -24,32 +24,33 @@ export const core = {
         {
           title: 'Introduction',
           links: [
-            { 
-              title: 'Overview', 
-              href: '/core' 
+            {
+              title: 'Overview',
+              href: '/core',
             },
-            { 
-              title: 'What is an Asset?', 
-              href: '/core/what-is-an-asset' 
+            {
+              title: 'What is an Asset?',
+              href: '/core/what-is-an-asset',
             },
-            { 
-              title: 'JSON Schema', 
-              href: '/core/json-schema' 
+            {
+              title: 'JSON Schema',
+              href: '/core/json-schema',
             },
             {
               title: 'Token Metadata Differences',
               href: '/core/tm-differences',
             },
-            { 
-              title: 'Ecosystem Support', 
-              href: '/core/ecosystem-support' },
-            { 
-              title: 'Anchor', 
-              href: '/core/using-core-in-anchor' 
+            {
+              title: 'Ecosystem Support',
+              href: '/core/ecosystem-support',
+            },
+            {
+              title: 'Anchor',
+              href: '/core/using-core-in-anchor',
             },
-            { 
-              title: 'FAQ', 
-              href: '/core/faq' 
+            {
+              title: 'FAQ',
+              href: '/core/faq',
             },
           ],
         },
@@ -63,56 +64,60 @@ export const core = {
             {
               title: 'Rust SDK',
               href: '/core/sdk/rust',
-            }
-          ]
+            },
+          ],
         },
         {
           title: 'Features',
           links: [
-            { 
-              title: 'Creating Assets', 
-              href: '/core/create-asset' 
+            {
+              title: 'Creating Assets',
+              href: '/core/create-asset',
             },
-            { 
-              title: 'Fetching Assets', 
-              href: '/core/fetch'
+            {
+              title: 'Fetching Assets',
+              href: '/core/fetch',
             },
-            { 
-              title: 'Updating Assets', 
-              href: '/core/update' 
+            {
+              title: 'Updating Assets',
+              href: '/core/update',
             },
-            { 
-              title: 'Transferring Assets', 
-              href: '/core/transfer' 
+            {
+              title: 'Transferring Assets',
+              href: '/core/transfer',
             },
-            { 
-              title: 'Burning Assets', 
-              href: '/core/burn' 
+            {
+              title: 'Burning Assets',
+              href: '/core/burn',
             },
             {
               title: 'Collection Management',
               href: '/core/collections',
             },
-            { 
-              title: 'Helpers', 
-              href: '/core/helpers' 
+            {
+              title: 'Execute Asset Signing',
+              href: '/core/execute-asset-signing',
             },
-            { 
-              title: 'Deserializing Assets', 
-              href: '/core/deserialization' 
+            {
+              title: 'Helpers',
+              href: '/core/helpers',
+            },
+            {
+              title: 'Deserializing Assets',
+              href: '/core/deserialization',
             },
           ],
         },
         {
           title: 'Plugins',
           links: [
-            { 
-              title: 'Overview', 
-              href: '/core/plugins' 
+            {
+              title: 'Overview',
+              href: '/core/plugins',
             },
-            { 
-              title: 'Adding Plugins', 
-              href: '/core/plugins/adding-plugins' 
+            {
+              title: 'Adding Plugins',
+              href: '/core/plugins/adding-plugins',
             },
             {
               title: 'Removing Plugins',
@@ -138,26 +143,26 @@ export const core = {
               title: 'Burn Delegate Plugin',
               href: '/core/plugins/burn-delegate',
             },
-            { 
-              title: 'Royalties Plugin', 
-              href: '/core/plugins/royalties' 
+            {
+              title: 'Royalties Plugin',
+              href: '/core/plugins/royalties',
             },
             {
               title: 'Update Delegate Plugin',
               href: '/core/plugins/update-delegate',
-              updated: "06-19-2024"
+              updated: '06-19-2024',
             },
-            { 
-              title: 'Attribute Plugin', 
-              href: '/core/plugins/attribute' 
+            {
+              title: 'Attribute Plugin',
+              href: '/core/plugins/attribute',
             },
-            { 
-              title: 'AddBlocker Plugin', 
-              href: '/core/plugins/addBlocker' 
+            {
+              title: 'AddBlocker Plugin',
+              href: '/core/plugins/addBlocker',
             },
-            { 
-              title: 'Edition Plugin', 
-              href: '/core/plugins/edition' 
+            {
+              title: 'Edition Plugin',
+              href: '/core/plugins/edition',
             },
             {
               title: 'Immutable Metadata Plugin',
@@ -213,7 +218,7 @@ export const core = {
             {
               title: 'AppData Plugin',
               href: '/core/external-plugins/app-data',
-              created: "2024-06-19"
+              created: '2024-06-19',
             },
           ],
         },
@@ -225,23 +230,23 @@ export const core = {
         {
           title: 'General',
           links: [
-            { 
-              title: 'Overview', 
-              href: '/core/guides' 
+            {
+              title: 'Overview',
+              href: '/core/guides',
             },
-            { 
-              title: 'Immutability', 
-              href: '/core/guides/immutability' 
+            {
+              title: 'Immutability',
+              href: '/core/guides/immutability',
             },
-            { 
-              title: 'Soulbound Assets', 
+            {
+              title: 'Soulbound Assets',
               href: '/core/guides/create-soulbound-nft-asset',
               created: '2024-12-06',
               updated: null, // null means it's never been updated
             },
-            { 
-              title: 'Print Editions', 
-              href: '/core/guides/print-editions'
+            {
+              title: 'Print Editions',
+              href: '/core/guides/print-editions',
             },
             {
               title: 'Oracle Plugin Example',
@@ -264,9 +269,9 @@ export const core = {
               title: 'How to Create a Core Collection with JavaScript',
               href: '/core/guides/javascript/how-to-create-a-core-collection-with-javascript',
             },
-            { 
-              title: 'Web2 typescript Staking Example', 
-              href: '/core/guides/javascript/web2-typescript-staking-example' 
+            {
+              title: 'Web2 typescript Staking Example',
+              href: '/core/guides/javascript/web2-typescript-staking-example',
             },
           ],
         },
@@ -282,8 +287,8 @@ export const core = {
               href: '/core/guides/anchor/how-to-create-a-core-collection-with-anchor',
             },
             {
-              title: 'Anchor Staking Example', 
-              href: '/core/guides/anchor/anchor-staking-example' 
+              title: 'Anchor Staking Example',
+              href: '/core/guides/anchor/anchor-staking-example',
             },
           ],
         },

+ 257 - 0
src/pages/core/execute-asset-signing.md

@@ -0,0 +1,257 @@
+---
+title: Execute Asset Signing
+metaTitle: Execute and Asset Signer | Core
+description: Learn how MPL Core Assets can use the Execute instruction and sign instructions and transactions.
+---
+
+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()
+
+```ts
+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.
+
+```ts
+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
+
+{% callout title="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.
+{% /callout %}
+
+## 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.
+
+```js
+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.
+
+```js
+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.
+
+```js
+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 })
+```