소스 검색

add getTokenAccounts & getNftEditions (#383)

* initial new endpoints

* noUmi

* noUmi 2

* fix example format

* Update src/pages/das-api/methods/get-nft-editions.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix examples

* Update src/pages/das-api/methods/get-nft-editions.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* remove X

* Update src/pages/das-api/methods/get-nft-editions.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
MarkSackerberg 3 달 전
부모
커밋
78e1a994a4

+ 1 - 0
markdoc/tags.js

@@ -175,6 +175,7 @@ const tags = {
   apiRenderer: {
     attributes: {
       method: { type: String },
+      noUmi: { type: Boolean },
     },
     render: ApiComponentWrapper,
   },

+ 1 - 0
src/components/apiComponents/apiComponentWrapper.jsx

@@ -219,6 +219,7 @@ const ApiComponentWrapper = (args) => {
             body={body}
             activeEndpoint={activeEndpoint}
             setActiveEndpoint={(endpoint) => setActiveEndpoint(endpoint)}
+            noUmi={args.noUmi}
           />
           <button
             className="hidden min-w-[150px] items-center justify-center rounded-lg border border-gray-200 px-4 py-3 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300 dark:placeholder-neutral-500 2xl:flex"

+ 21 - 16
src/components/apiComponents/exampleSelector.jsx

@@ -29,15 +29,14 @@ const ApiExampleSelector = ({
     : null;
 
   const handleExampleChange = (e) => {
-    const selectedName = e.target.value;
-    if (!selectedName) {
+    const selectedValue = e.target.value;
+    if (!selectedValue) {
       handleSetExample(-1);
       return;
     }
 
-    const example = examples.find(ex => ex.name === selectedName);
-    if (example) {
-      const index = examples.indexOf(example);
+    const index = parseInt(selectedValue, 10);
+    if (!isNaN(index) && index >= 0 && index < examples.length) {
       handleSetExample(index);
     }
   };
@@ -54,7 +53,7 @@ const ApiExampleSelector = ({
         <div className="relative flex h-12 w-full">
           <Select
             onChange={handleExampleChange}
-            value={currentExample?.name || ''}
+            value={selectedExample >= 0 ? selectedExample.toString() : ''}
             className={clsx(
               'dark:white block w-full appearance-none rounded-lg border border-black/10 bg-white/5 px-3 py-1.5 text-sm/6 text-black dark:border-white/15 dark:bg-transparent',
               'focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25',
@@ -64,20 +63,26 @@ const ApiExampleSelector = ({
             <option value="">-</option>
             {devnetExamples.length > 0 && (
               <optgroup label="Solana Devnet">
-                {devnetExamples.map((example) => (
-                  <option key={example.name} value={example.name}>
-                    {example.name}
-                  </option>
-                ))}
+                {devnetExamples.map((example) => {
+                  const originalIndex = examples.indexOf(example);
+                  return (
+                    <option key={`devnet-${originalIndex}`} value={originalIndex.toString()}>
+                      {example.name}
+                    </option>
+                  );
+                })}
               </optgroup>
             )}
             {mainnetExamples.length > 0 && (
               <optgroup label="Solana Mainnet">
-                {mainnetExamples.map((example) => (
-                  <option key={example.name} value={example.name}>
-                    {example.name}
-                  </option>
-                ))}
+                {mainnetExamples.map((example) => {
+                  const originalIndex = examples.indexOf(example);
+                  return (
+                    <option key={`mainnet-${originalIndex}`} value={originalIndex.toString()}>
+                      {example.name}
+                    </option>
+                  );
+                })}
               </optgroup>
             )}
           </Select>

+ 16 - 9
src/components/apiComponents/languageRenderer.jsx

@@ -13,8 +13,8 @@ import SwiftRequestRenderer from './languageComponents/swiftRenderer';
 import UmiRequestRenderer from './languageComponents/umiRequestRenderer';
 import LanguageSelector from './languageSelector';
 
-const LanguageRenderer = ({ api, body, setActiveEndpoint, activeEndpoint }) => {
-  const [activeLanguage, setActiveLanguage] = useState('umi')
+const LanguageRenderer = ({ api, body, setActiveEndpoint, activeEndpoint, noUmi }) => {
+  const [activeLanguage, setActiveLanguage] = useState(noUmi ? 'curl' : 'umi')
 
   function strToTitleCase(str) {
     return str.charAt(0).toUpperCase() + str.slice(1)
@@ -203,13 +203,19 @@ const LanguageRenderer = ({ api, body, setActiveEndpoint, activeEndpoint }) => {
             <div className="-mb-3 text-sm font-medium text-gray-800 dark:text-neutral-400">
               {strToTitleCase(activeLanguage)} Request Example
             </div>
-            <UmiRequestRenderer
-              method={api.method}
-              url={activeEndpoint}
-              headers={headers}
-              bodyMethod={body.method}
-              bodyParams={body.params}
-            />
+            {noUmi ? (
+              <div className="rounded-lg bg-yellow-50 p-4 text-sm text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-200">
+                This endpoint is not yet supported in the UMI-based JS SDK.
+              </div>
+            ) : (
+              <UmiRequestRenderer
+                method={api.method}
+                url={activeEndpoint}
+                headers={headers}
+                bodyMethod={body.method}
+                bodyParams={body.params}
+              />
+            )}
           </div>
         )
     }
@@ -224,6 +230,7 @@ const LanguageRenderer = ({ api, body, setActiveEndpoint, activeEndpoint }) => {
       <LanguageSelector
         activeLanguage={activeLanguage}
         setActiveLanguage={(language) => setActiveLanguage(language)}
+        noUmi={noUmi}
       />
       {renderLanguage(activeLanguage)}
     </div>

+ 25 - 15
src/components/apiComponents/languageSelector.jsx

@@ -15,27 +15,37 @@ const languages = [
   { name: 'csharp', icon: 'CSharpIcon' },
 ]
 
-const LanguageSelector = ({ activeLanguage, setActiveLanguage }) => {
+const LanguageSelector = ({ activeLanguage, setActiveLanguage, noUmi }) => {
   return (
     <div className="scrollbar flex flex-col gap-2">
       <div className="text-sm font-medium text-gray-800 dark:text-neutral-400">
         Language
       </div>
       <div className="flex overflow-auto">
-        {languages.map((language) => (
-          <button
-            key={language.name}
-            className={`-ms-px inline-flex min-w-16 flex-col items-center justify-center gap-1 border border-gray-200 px-10 py-2 text-xs font-medium text-gray-800 shadow-sm transition-colors first:ms-0 first:rounded-s-lg last:rounded-e-lg hover:bg-gray-50 focus:z-10 focus:bg-accent-300 focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white dark:hover:bg-neutral-800 ${
-              activeLanguage === language.name
-                ? 'bg-accent-300 text-black hover:bg-accent-300 dark:bg-accent-300 dark:text-black dark:hover:bg-accent-300'
-                : ''
-            }`}
-            onClick={() => setActiveLanguage(language.name)}
-          >
-            <Icon icon={language.icon} className="h-4 w-4" />
-            {language.name}
-          </button>
-        ))}
+        {languages.map((language) => {
+          const isUmiDisabled = noUmi && language.name === 'umi';
+          return (
+            <button
+              key={language.name}
+              className={`-ms-px inline-flex min-w-16 flex-col items-center justify-center gap-1 border border-gray-200 px-10 py-2 text-xs font-medium text-gray-800 shadow-sm transition-colors first:ms-0 first:rounded-s-lg last:rounded-e-lg hover:bg-gray-50 focus:z-10 focus:bg-accent-300 focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white dark:hover:bg-neutral-800 ${
+                activeLanguage === language.name
+                  ? 'bg-accent-300 text-black hover:bg-accent-300 dark:bg-accent-300 dark:text-black dark:hover:bg-accent-300'
+                  : ''
+              } ${isUmiDisabled ? 'opacity-50 cursor-not-allowed' : ''}`}
+              onClick={() => !isUmiDisabled && setActiveLanguage(language.name)}
+              disabled={isUmiDisabled}
+              title={isUmiDisabled ? 'UMI support coming soon' : ''}
+            >
+              <Icon icon={language.icon} className="h-4 w-4" />
+              <div className="flex flex-col items-center">
+                <span>{language.name}</span>
+                {isUmiDisabled && (
+                  <span className="text-xs text-gray-500 dark:text-neutral-500">Coming Soon</span>
+                )}
+              </div>
+            </button>
+          );
+        })}
       </div>
     </div>
   )

+ 2 - 0
src/components/products/das-api/index.js

@@ -41,6 +41,8 @@ export const das = {
             { title: 'Get Assets By Creator', href: '/das-api/methods/get-assets-by-creator' },
             { title: 'Get Assets By Group', href: '/das-api/methods/get-assets-by-group' },
             { title: 'Get Assets By Owner', href: '/das-api/methods/get-assets-by-owner' },
+            { title: 'Get NFT Editions', href: '/das-api/methods/get-nft-editions' },
+            { title: 'Get Token Accounts', href: '/das-api/methods/get-token-accounts' },
             { title: 'Search Assets', href: '/das-api/methods/search-assets' },
           ],
         },

+ 96 - 0
src/lib/api/aura/das/getNftEditions.js

@@ -0,0 +1,96 @@
+const getNftEditions = {
+  description: 'Get NFT editions for a given mint',
+  method: 'getNftEditions',
+  params: [
+    {
+      type: 'string',
+      description: 'Public key of the (master) edition NFT mint',
+      name: 'mintAddress',
+      placeholder: 'Public key of the (master) edition NFT mint',
+      required: true,
+    },
+    {
+      name: 'limit',
+      type: 'number',
+      description: 'Number of editions to return (default: 10000)',
+    },
+    {
+      name: 'page',
+      type: 'number',
+      description: 'The index of the "page" to retrieve',
+    },
+    {
+      name: 'cursor',
+      type: 'string',
+      description: 'Pagination cursor',
+    },
+    {
+      name: 'before',
+      type: 'string',
+      description: 'Retrieve editions before the specified ID',
+    },
+    {
+      name: 'after',
+      type: 'string',
+      description: 'Retrieve editions after the specified ID',
+    },
+  ],
+  examples: [
+    {
+      name: 'Get NFT Editions',
+      description: 'Get all editions by Edition NFT Address',
+      chain: 'solanaMainnet',
+      body: {
+        params: {
+          mintAddress: '8L1wkbHnLWxUkcAb64yT37g6v5zRLM8LVAc5y4r2Pesq',
+        },
+      },
+    },
+    {
+      name: 'Get NFT Editions',
+      description: 'Get all editions for a Master Edition NFT',
+      chain: 'solanaDevnet',
+      body: {
+        params: {
+          mintAddress: 'J6tXe9TY2eKwj2AYFKU8B5VYcaCPvkwckghDhWUBEQgD',
+        },
+      },
+    },
+  ],
+  exampleResponse: {
+    "jsonrpc": "2.0",
+    "result": {
+      "total": 4,
+      "limit": 1000,
+      "master_edition_address": "FLW3QiKm564dnVXjntKwPsD4mGFZsYCZZRBNnRqfpvWY",
+      "supply": 4,
+      "max_supply": 15,
+      "editions": [
+        {
+          "mint_address": "UwLknqVX35h8BnBy6tqSGsn49x86aQYfLo9XtPvskHi",
+          "edition_address": "2PHWwsa9r5T3paVqFs6t7rhYR7tjxbKNQh8jHgG3Rdhz",
+          "edition_number": 2
+        },
+        {
+          "mint_address": "56YfVQ86XMV57M1NSYP4utDkqc9ZfUVe8yXCSUwEML1c",
+          "edition_address": "5Cy7euqce9RWfR7bSDUAT8ieVz6V4zn14dZtuphEfqhP",
+          "edition_number": 1
+        },
+        {
+          "mint_address": "3t7WXtj7XBek4hA4vPLL1D9Jfi2B3fucbEXU2YwrG2ox",
+          "edition_address": "BHJaMzigGyALnVDAAX6rgTJGig13NB57KdVUCMXtUZfw",
+          "edition_number": 4
+        },
+        {
+          "mint_address": "4LYWNR2GoLiCexEWVLu6QEhN4uPaXzxodLHmYDMc8YfD",
+          "edition_address": "Hdf38V6SMt9UiikxZ5KJxAodf9fiMRAA5oXSEL6oL5Tm",
+          "edition_number": 3
+        }
+      ],
+      "cursor": "Hdf38V6SMt9UiikxZ5KJxAodf9fiMRAA5oXSEL6oL5Tm"
+    },
+    "id": 0
+  }
+}
+
+export default getNftEditions

+ 124 - 42
src/lib/api/aura/das/getTokenAccounts.js

@@ -1,55 +1,137 @@
 const getTokenAccounts = {
-    description: 'Returns the token accounts for a given set of addresses',
-    method: 'getTokenAccounts',
-    params: [
-      {
-          name: 'mint',
-        type: 'string',
-        description: 'Public key of the mint to retrieve',
-        placeholder: 'Public key of the mint',
-      },
-      {
-        name: 'owner',
+  description: 'Returns the token accounts for a given set of addresses',
+  method: 'getTokenAccounts',
+  params: [
+    {
+      name: 'mintAddress',
+      type: 'string',
+      description: 'Public key of the mint to retrieve',
+      placeholder: 'Public key of the mint',
+    },
+    {
+      name: 'ownerAddress',
       type: 'string',
       description: 'Owner public key of the token accounts to retrieve',
       placeholder: 'Owner public key',
     },
     {
-        name: 'limit',
-        type: 'number',
-        description: 'Number of assets to return',
-      },
-      {
-        name: 'page',
-        type: 'number',
-        description: 'The index of the "page" to retrieve.',
-      },
-      {
-        name: 'cursor',
-        type: 'string',
-        description: 'pagination cursor',
+      name: 'limit',
+      type: 'number',
+      description: 'Number of assets to return',
+    },
+    {
+      name: 'page',
+      type: 'number',
+      description: 'The index of the "page" to retrieve',
+    },
+    {
+      name: 'cursor',
+      type: 'string',
+      description: 'Pagination cursor',
+    },
+    {
+      name: 'before',
+      type: 'string',
+      description: 'Retrieve assets before the specified ID',
+    },
+    {
+      name: 'after',
+      type: 'string',
+      description: 'Retrieve assets after the specified ID',
+    },
+    {
+      name: 'options',
+      type: 'object',
+      description: 'Display options',
+      value: {
+        showZeroBalance: {
+          type: 'boolean',
+          description: 'Show zero balance accounts',
+          value: ['false', 'true'],
+        },
       },
-      {
-        name: 'before',
-        type: 'string',
-        description: 'Retrieve assets before the specified ID',
+    },
+  ],
+  examples: [
+    {
+      name: 'Get USDC Token Accounts',
+      description: 'Get all USDC token accounts',
+      chain: 'solanaDevnet',
+      body: {
+        params: {
+          mintAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
+        },
       },
-      {
-        name: 'after',
-        type: 'string',
-        description: 'Retrieve assets after the specified ID',
+    },
+    {
+      name: 'Get USDC Token Accounts',
+      description: 'Get all USDC token accounts',
+      chain: 'solanaMainnet',
+      body: {
+        params: {
+          mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
+        },
       },
-      {
-        name: 'options',
-        type: 'object',
-        value: {
-            showZeroBalance: {
-                type: 'boolean',
-                description: 'Show zero balance accounts',
+    },
+  ],
+  exampleResponse: {
+    "jsonrpc": "2.0",
+    "result": {
+      "total": 50,
+      "limit": 10,
+      "page": 1,
+      "items": [
+        {
+          "interface": "FungibleToken",
+          "id": "TokenAccount123456789abcdef",
+          "content": {
+            "$schema": "https://schema.metaplex.com/nft1.0.json",
+            "json_uri": "https://arweave.net/token-metadata-uri",
+            "files": [
+              {
+                "uri": "https://arweave.net/token-image",
+                "mime": "image/png"
+              }
+            ],
+            "metadata": {
+              "name": "Example Token",
+              "symbol": "EXAMPLE",
+              "description": "An example fungible token"
+            }
+          },
+          "authorities": [
+            {
+              "address": "TokenAuthority123",
+              "scopes": ["full"]
             }
+          ],
+          "compression": {
+            "eligible": false,
+            "compressed": false
+          },
+          "ownership": {
+            "frozen": false,
+            "delegated": false,
+            "delegate": null,
+            "ownership_model": "single",
+            "owner": "1BWutmTvYPwDtmw9abTkS4Ssr8no61spGAvW1X6NDix"
+          },
+          "supply": {
+            "amount": "1000000000",
+            "decimals": 6
+          },
+          "token_info": {
+            "balance": 1000000000,
+            "supply": 1000000000000,
+            "decimals": 6,
+            "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
+            "mint": "So11111111111111111111111111111111111111112"
+          }
         }
-      }
-    ],
-  }
+      ]
+    },
+    "id": "1"
+  },
+}
   
   export default getTokenAccounts

+ 2 - 0
src/lib/api/aura/methods.js

@@ -7,6 +7,7 @@ import getAssetsByCreator from './das/getAssetsByCreator'
 import getAssetsByGroup from './das/getAssetsByGroup'
 import getAssetsByOwner from './das/getAssetsByOwner'
 import getAssetSignatures from './das/getAssetSignatures'
+import getNftEditions from './das/getNftEditions'
 import getTokenAccounts from './das/getTokenAccounts'
 import searchAssets from './das/searchAssest'
 
@@ -23,6 +24,7 @@ const apiMethods = {
   getAssetsByOwner: getAssetsByOwner,
   getAssets: getAssets,
   getAssetProofs: getAssetProofs,
+  getNftEditions: getNftEditions,
   searchAssets: searchAssets,
   getAssetSignatures: getAssetSignatures,
   getTokenAccounts: getTokenAccounts,

+ 35 - 0
src/pages/das-api/methods/get-nft-editions.md

@@ -0,0 +1,35 @@
+---
+title: Get NFT Editions
+metaTitle: Get NFT Editions | DAS API
+description: Get all printable editions of a master edition NFT mint
+---
+
+Returns all printable editions for a master edition NFT mint—including edition numbers, addresses, and supply information. You can also pass an edition address to retrieve the corresponding master edition and its sibling editions.
+
+## Parameters
+
+| Name          | Required | Description                                        |
+| ------------- | :------: | -------------------------------------------------- |
+| `mintAddress` |    ✅    | The mint address of the master edition NFT.       |
+| `cursor`      |         | Cursor for pagination.                             |
+| `page`        |         | Page number for pagination.                        |
+| `limit`       |         | Maximum number of editions to return.              |
+| `before`      |         | Return editions before this cursor.                |
+| `after`       |         | Return editions after this cursor.                 |
+
+## Response
+
+The response includes:
+
+- `editions` - Array of edition objects containing:
+  - `edition_address` - The address of the [edition account](/token-metadata#printing-editions)
+  - `edition_number` - The edition number (1, 2, 3, etc.)
+  - `mint_address` - The mint address of the edition
+- `master_edition_address` - Address of the master edition account
+- `supply` - Current number of editions minted
+- `max_supply` - Maximum number of editions that can be minted (null for unlimited)
+
+
+## Playground
+
+{% apiRenderer method="getNftEditions" noUmi=true /%}

+ 42 - 0
src/pages/das-api/methods/get-token-accounts.md

@@ -0,0 +1,42 @@
+---
+title: Get Token Accounts
+metaTitle: Get Token Accounts | DAS API
+description: Get a list of token accounts by owner or mint
+tableOfContents: false
+---
+
+Returns a list of token accounts filtered by owner address, mint address, or both. Useful for finding all token accounts associated with a wallet or all accounts holding a specific token.
+
+## Parameters
+
+| Name           | Required | Description                                          |
+| -------------- | :------: | ---------------------------------------------------- |
+| `ownerAddress` |    (only required if `mintAddress` is not provided)    | Filter by owner address.                             |
+| `mintAddress`  |    (only required if `ownerAddress` is not provided)    | Filter by mint address.                              |
+| `cursor`       |         | Cursor for pagination.                               |
+| `page`         |         | Page number for pagination.                          |
+| `limit`        |         | Maximum number of token accounts to return.          |
+| `before`       |         | Return accounts before this cursor.                  |
+| `after`        |         | Return accounts after this cursor.                   |
+| `options`      |         | Additional [display options](/das-api/display-options).              |
+
+## Response
+
+The response includes:
+
+- `token_accounts` - Array of token account objects containing:
+  - `address` - The token account address
+  - `amount` - Token balance in the account
+  - `mint` - The mint address of the token
+  - `owner` - The owner address of the account
+  - `delegate` - Delegate address (if any)
+  - `delegated_amount` - Amount delegated to the delegate
+  - `frozen` - Whether the account is frozen
+  - `close_authority` - Close authority address (if any)
+  - `extensions` - Token extensions data
+- `errors` - Array of any errors encountered during processing
+- Pagination fields: `cursor`, `page`, `limit`, `before`, `after`, `total`
+
+## Playground
+
+{% apiRenderer method="getTokenAccounts" noUmi=true /%}