Integrate Metaplex DAS API (#215)

# Pull Request Description
This PR implements the [Metaplex DAS
API](https://developers.metaplex.com/das-api) to the Solana Agent Kit

## Changes Made
This PR adds the following new tools and actions:
- Get Asset by ID
- Get Assets by Authority
- Get Assets by Creator
- Search/Lookup Assets by different criteria
  
## Implementation Details
- The actions and tools are implemented to their respective files in the
`metaplex/` folders of `actions`, `tools` and `langchain` directories.
- The actions were added as part of the SolanaAgentKit class and
exported under `createSolanaTools` function.
- The actions utilize the
`@metaplex-foundation/digital-asset-standard-api` SDK.

## Prompt Used

[prompts.txt](https://github.com/user-attachments/files/18442209/prompts.txt)

## Checklist
- [x] I have tested these changes locally
- [x] I have updated the documentation
- [x] I have added the prompt used to test it
This commit is contained in:
aryan
2025-01-18 16:31:43 +05:30
committed by GitHub
20 changed files with 1216 additions and 0 deletions

View File

@@ -498,6 +498,12 @@ const signature = await agent.voltrWithdrawStrategy(
)
```
### Get a Solana asset by its ID
```typescript
const asset = await agent.getAsset("41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU")
```
## Examples
### LangGraph Multi-Agent System
@@ -521,6 +527,7 @@ The toolkit relies on several key Solana and Metaplex libraries:
- @solana/web3.js
- @solana/spl-token
- @metaplex-foundation/digital-asset-standard-api
- @metaplex-foundation/mpl-token-metadata
- @metaplex-foundation/mpl-core
- @metaplex-foundation/umi

View File

@@ -36,6 +36,7 @@
"@langchain/openai": "^0.3.16",
"@lightprotocol/compressed-token": "^0.17.1",
"@lightprotocol/stateless.js": "^0.17.1",
"@metaplex-foundation/digital-asset-standard-api": "^1.0.4",
"@metaplex-foundation/mpl-core": "^1.1.1",
"@metaplex-foundation/mpl-token-metadata": "^3.3.0",
"@metaplex-foundation/mpl-toolbox": "^0.9.4",

528
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -72,6 +72,10 @@ import lendAndBorrowAPYAction from "./drift/getLendAndBorrowAPY";
import getVoltrPositionValuesAction from "./voltr/getPositionValues";
import depositVoltrStrategyAction from "./voltr/depositStrategy";
import withdrawVoltrStrategyAction from "./voltr/withdrawStrategy";
import getAssetAction from "./metaplex/getAsset";
import getAssetsByAuthorityAction from "./metaplex/getAssetsByAuthority";
import getAssetsByCreatorAction from "./metaplex/getAssetsByCreator";
import searchAssetsAction from "./metaplex/searchAssets";
export const ACTIONS = {
WALLET_ADDRESS_ACTION: getWalletAddressAction,
@@ -150,6 +154,10 @@ export const ACTIONS = {
GET_VOLTR_POSITION_VALUES_ACTION: getVoltrPositionValuesAction,
DEPOSIT_VOLTR_STRATEGY_ACTION: depositVoltrStrategyAction,
WITHDRAW_VOLTR_STRATEGY_ACTION: withdrawVoltrStrategyAction,
GET_ASSET_ACTION: getAssetAction,
GET_ASSETS_BY_AUTHORITY_ACTION: getAssetsByAuthorityAction,
GET_ASSETS_BY_CREATOR_ACTION: getAssetsByCreatorAction,
SEARCH_ASSETS_ACTION: searchAssetsAction,
};
export type { Action, ActionExample, Handler } from "../types/action";

View File

@@ -0,0 +1,51 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { get_asset } from "../../tools/metaplex";
const getAssetAction: Action = {
name: "GET_ASSET",
similes: [
"fetch asset",
"retrieve asset",
"get asset details",
"fetch asset details",
],
description: `Fetch asset details using the Metaplex DAS API.`,
examples: [
[
{
input: {
assetId: "Asset ID",
},
output: {
status: "success",
message: "Asset retrieved successfully",
result: {
// Example asset details
name: "Example Asset",
symbol: "EXA",
uri: "https://example.com/asset.json",
},
},
explanation: "Fetch details of an asset using its ID",
},
],
],
schema: z.object({
assetId: z.string().min(1, "Asset ID is required"),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
const assetId = input.assetId;
const result = await get_asset(agent, assetId);
return {
status: "success",
message: "Asset retrieved successfully",
result,
};
},
};
export default getAssetAction;

View File

@@ -0,0 +1,103 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { get_assets_by_authority } from "../../tools/metaplex";
const getAssetsByAuthorityAction: Action = {
name: "GET_ASSETS_BY_AUTHORITY",
similes: [
"fetch assets by authority",
"retrieve assets by authority",
"get assets by authority address",
"fetch authority assets",
],
description: `Fetch a list of assets owned by a specific address using the Metaplex DAS API.`,
examples: [
[
{
input: {
authority: "mRdta4rc2RtsxEUDYuvKLamMZAdW6qHcwuq866Skxxv",
limit: 10,
},
output: {
status: "success",
message: "Assets retrieved successfully",
result: {
total: 2,
limit: 10,
items: [
{
interface: "V1_NFT",
id: "ExampleAssetId1",
content: {
json_uri: "https://example.com/asset1.json",
metadata: {
name: "Example Asset 1",
symbol: "EXA1",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
{
interface: "V1_NFT",
id: "ExampleAssetId2",
content: {
json_uri: "https://example.com/asset2.json",
metadata: {
name: "Example Asset 2",
symbol: "EXA2",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
],
},
},
explanation: "Fetch a list of assets owned by a specific address",
},
],
],
schema: z.object({
authority: z.string().min(1, "Authority address is required"),
sortBy: z
.object({
sortBy: z.enum(["created", "updated", "recentAction", "none"]),
sortDirection: z.enum(["asc", "desc"]),
})
.optional(),
limit: z.number().optional(),
page: z.number().optional(),
before: z.string().optional(),
after: z.string().optional(),
}),
handler: async (
agent: SolanaAgentKit,
input: z.infer<typeof getAssetsByAuthorityAction.schema>,
) => {
const result = await get_assets_by_authority(agent, input);
return {
status: "success",
message: "Assets retrieved successfully",
result,
};
},
};
export default getAssetsByAuthorityAction;

View File

@@ -0,0 +1,105 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { get_assets_by_creator } from "../../tools/metaplex";
const getAssetsByCreatorAction: Action = {
name: "GET_ASSETS_BY_CREATOR",
similes: [
"fetch assets by creator",
"retrieve assets by creator",
"get assets by creator address",
"fetch creator assets",
],
description: `Fetch a list of assets created by a specific address using the Metaplex DAS API.`,
examples: [
[
{
input: {
creator: "D3XrkNZz6wx6cofot7Zohsf2KSsu2ArngNk8VqU9cTY3",
onlyVerified: true,
limit: 10,
},
output: {
status: "success",
message: "Assets retrieved successfully",
result: {
total: 2,
limit: 10,
items: [
{
interface: "V1_NFT",
id: "ExampleAssetId1",
content: {
json_uri: "https://example.com/asset1.json",
metadata: {
name: "Example Asset 1",
symbol: "EXA1",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
{
interface: "V1_NFT",
id: "ExampleAssetId2",
content: {
json_uri: "https://example.com/asset2.json",
metadata: {
name: "Example Asset 2",
symbol: "EXA2",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
],
},
},
explanation: "Fetch a list of assets created by a specific address",
},
],
],
schema: z.object({
creator: z.string().min(1, "Creator address is required"),
onlyVerified: z.boolean(),
sortBy: z
.object({
sortBy: z.enum(["created", "updated", "recentAction", "none"]),
sortDirection: z.enum(["asc", "desc"]),
})
.optional(),
limit: z.number().optional(),
page: z.number().optional(),
before: z.string().optional(),
after: z.string().optional(),
}),
handler: async (
agent: SolanaAgentKit,
input: z.infer<typeof getAssetsByCreatorAction.schema>,
) => {
const result = await get_assets_by_creator(agent, input);
return {
status: "success",
message: "Assets retrieved successfully",
result,
};
},
};
export default getAssetsByCreatorAction;

View File

@@ -0,0 +1,113 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { search_assets } from "../../tools/metaplex";
import { publicKey } from "@metaplex-foundation/umi";
const searchAssetsAction: Action = {
name: "SEARCH_ASSETS",
similes: ["search assets", "find assets", "lookup assets", "query assets"],
description: `Search for assets using various criteria with the Metaplex DAS API.`,
examples: [
[
{
input: {
owner: publicKey("N4f6zftYsuu4yT7icsjLwh4i6pB1zvvKbseHj2NmSQw"),
jsonUri:
"https://arweave.net/c9aGs5fOk7gD4wWnSvmzeqgtfxAGRgtI1jYzvl8-IVs/chiaki-violet-azure-common.json",
},
output: {
status: "success",
message: "Assets retrieved successfully",
result: {
total: 2,
limit: 10,
items: [
{
interface: "V1_NFT",
id: "ExampleAssetId1",
content: {
json_uri: "https://example.com/asset1.json",
metadata: {
name: "Example Asset 1",
symbol: "EXA1",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
{
interface: "V1_NFT",
id: "ExampleAssetId2",
content: {
json_uri: "https://example.com/asset2.json",
metadata: {
name: "Example Asset 2",
symbol: "EXA2",
},
},
authorities: [],
compression: {},
grouping: [],
royalty: {},
creators: [],
ownership: {},
supply: {},
mutable: true,
burnt: false,
},
],
},
},
explanation: "Search for assets using various criteria",
},
],
],
schema: z.object({
negate: z.boolean().optional(),
conditionType: z.enum(["all", "any"]).optional(),
interface: z.string().optional(),
jsonUri: z.string().optional(),
owner: z.string().optional(),
ownerType: z.enum(["single", "token"]).optional(),
creator: z.string().optional(),
creatorVerified: z.boolean().optional(),
authority: z.string().optional(),
grouping: z.tuple([z.string(), z.string()]).optional(),
delegate: z.string().optional(),
frozen: z.boolean().optional(),
supply: z.number().optional(),
supplyMint: z.string().optional(),
compressed: z.boolean().optional(),
compressible: z.boolean().optional(),
royaltyModel: z.enum(["creators", "fanout", "single"]).optional(),
royaltyTarget: z.string().optional(),
royaltyAmount: z.number().optional(),
burnt: z.boolean().optional(),
limit: z.number().optional(),
page: z.number().optional(),
before: z.string().optional(),
after: z.string().optional(),
}),
handler: async (
agent: SolanaAgentKit,
input: z.infer<typeof searchAssetsAction.schema>,
) => {
const result = await search_assets(agent, input);
return {
status: "success",
message: "Assets retrieved successfully",
result,
};
},
};
export default searchAssetsAction;

View File

@@ -114,6 +114,10 @@ import {
voltrGetPositionValues,
voltrDepositStrategy,
voltrWithdrawStrategy,
get_asset,
get_assets_by_authority,
get_assets_by_creator,
search_assets,
} from "../tools";
import {
Config,
@@ -131,6 +135,13 @@ import {
HeliusWebhookIdResponse,
HeliusWebhookResponse,
} from "../types";
import {
DasApiAsset,
DasApiAssetList,
GetAssetsByAuthorityRpcInput,
GetAssetsByCreatorRpcInput,
SearchAssetsRpcInput,
} from "@metaplex-foundation/digital-asset-standard-api";
/**
* Main class for interacting with Solana blockchain
@@ -998,4 +1009,21 @@ export class SolanaAgentKit {
async voltrGetPositionValues(vault: PublicKey): Promise<string> {
return voltrGetPositionValues(this, vault);
}
async getAsset(assetId: string): Promise<DasApiAsset> {
return get_asset(this, assetId);
}
async getAssetsByAuthority(
params: GetAssetsByAuthorityRpcInput,
): Promise<DasApiAssetList> {
return get_assets_by_authority(this, params);
}
async getAssetsByCreator(
params: GetAssetsByCreatorRpcInput,
): Promise<DasApiAssetList> {
return get_assets_by_creator(this, params);
}
async searchAssets(params: SearchAssetsRpcInput): Promise<DasApiAssetList> {
return search_assets(this, params);
}
}

View File

@@ -130,6 +130,10 @@ import {
SolanaVoltrGetPositionValues,
SolanaVoltrDepositStrategy,
SolanaVoltrWithdrawStrategy,
SolanaGetAssetTool,
SolanaGetAssetsByAuthorityTool,
SolanaGetAssetsByCreatorTool,
SolanaSearchAssetsTool,
} from "./index";
export function createSolanaTools(solanaKit: SolanaAgentKit) {
@@ -238,5 +242,9 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaVoltrGetPositionValues(solanaKit),
new SolanaVoltrDepositStrategy(solanaKit),
new SolanaVoltrWithdrawStrategy(solanaKit),
new SolanaGetAssetTool(solanaKit),
new SolanaGetAssetsByAuthorityTool(solanaKit),
new SolanaGetAssetsByCreatorTool(solanaKit),
new SolanaSearchAssetsTool(solanaKit),
];
}

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaGetAssetTool extends Tool {
name = "solana_get_asset";
description = `Fetch asset details for a given asset ID using the Metaplex DAS API.
Inputs (input is a string):
eg "8TrvJBRa6Pzb9BDadqroHhWTHxaxK8Ws8r91oZ2jxaVV" (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const result = await this.solanaKit.getAsset(input);
return JSON.stringify({
status: "success",
message: "Asset retrieved successfully",
result,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaGetAssetsByAuthorityTool extends Tool {
name = "solana_get_assets_by_authority";
description = `Fetch a list of assets by a specific authority address using the Metaplex DAS API.
Inputs (input is a JSON string):
authority: string, eg "mRdta4rc2RtsxEUDYuvKLamMZAdW6qHcwuq866Skxxv" (required)
sortBy: { sortBy: "created" | "updated" | "recentAction" | "none", sortDirection: "asc" | "desc" } (optional)
limit: number (optional)
page: number (optional)
before: string (optional)
after: string (optional)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const result = await this.solanaKit.getAssetsByAuthority(parsedInput);
return JSON.stringify({
status: "success",
message: "Assets retrieved successfully",
result,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,40 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaGetAssetsByCreatorTool extends Tool {
name = "solana_get_assets_by_creator";
description = `Fetch a list of assets created by a specific address using the Metaplex DAS API.
Inputs (input is a JSON string):
creator: string, eg "D3XrkNZz6wx6cofot7Zohsf2KSsu2ArngNk8VqU9cTY3" (required)
onlyVerified: boolean (optional)
sortBy: { sortBy: "created" | "updated" | "recentAction" | "none", sortDirection: "asc" | "desc" } (optional)
limit: number (optional)
page: number (optional)
before: string (optional)
after: string (optional)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const result = await this.solanaKit.getAssetsByCreator(parsedInput);
return JSON.stringify({
status: "success",
message: "Assets retrieved successfully",
result,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -1,3 +1,7 @@
export * from "./deploy_collection";
export * from "./mint_nft";
export * from "./deploy_token";
export * from "./get_asset";
export * from "./get_assets_by_authority";
export * from "./get_assets_by_creator";
export * from "./search_assets";

View File

@@ -0,0 +1,57 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaSearchAssetsTool extends Tool {
name = "solana_search_assets";
description = `Search for assets using various criteria with the Metaplex DAS API.
Inputs (input is a JSON string):
negate: boolean (optional)
conditionType: "all" | "any" (optional)
interface: string (optional)
jsonUri: string (optional)
owner: string (optional)
ownerType: "single" | "token" (optional)
creator: string (optional)
creatorVerified: boolean (optional)
authority: string (optional)
grouping: [string, string] (optional)
delegate: string (optional)
frozen: boolean (optional)
supply: number (optional)
supplyMint: string (optional)
compressed: boolean (optional)
compressible: boolean (optional)
royaltyModel: "creators" | "fanout" | "single" (optional)
royaltyTarget: string (optional)
royaltyAmount: number (optional)
burnt: boolean (optional)
limit: number (optional)
page: number (optional)
before: string (optional)
after: string (optional)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const result = await this.solanaKit.searchAssets(parsedInput);
return JSON.stringify({
status: "success",
message: "Assets retrieved successfully",
result,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,28 @@
import { SolanaAgentKit } from "../../index";
import { publicKey } from "@metaplex-foundation/umi";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
dasApi,
DasApiAsset,
} from "@metaplex-foundation/digital-asset-standard-api";
/**
* Fetch asset details using the Metaplex DAS API
* @param agent SolanaAgentKit instance
* @param assetId ID of the asset to fetch
* @returns Asset details
*/
export async function get_asset(
agent: SolanaAgentKit,
assetId: string,
): Promise<DasApiAsset> {
try {
const endpoint = agent.connection.rpcEndpoint;
const umi = createUmi(endpoint).use(dasApi());
return await umi.rpc.getAsset(publicKey(assetId));
} catch (error: any) {
console.error("Error retrieving asset: ", error.message);
throw new Error(`Asset retrieval failed: ${error.message}`);
}
}

View File

@@ -0,0 +1,20 @@
import { SolanaAgentKit } from "../../agent";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
dasApi,
GetAssetsByAuthorityRpcInput,
} from "@metaplex-foundation/digital-asset-standard-api";
/**
* Fetch assets by authority using the Metaplex DAS API
* @param agent SolanaAgentKit instance
* @param params Parameters for fetching assets by authority
* @returns List of assets associated with the given authority
*/
export async function get_assets_by_authority(
agent: SolanaAgentKit,
params: GetAssetsByAuthorityRpcInput,
) {
const umi = createUmi(agent.connection.rpcEndpoint).use(dasApi());
return await umi.rpc.getAssetsByAuthority(params);
}

View File

@@ -0,0 +1,20 @@
import { SolanaAgentKit } from "../../agent";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
dasApi,
GetAssetsByCreatorRpcInput,
} from "@metaplex-foundation/digital-asset-standard-api";
/**
* Fetch assets by creator using the Metaplex DAS API
* @param agent SolanaAgentKit instance
* @param params Parameters for fetching assets by creator
* @returns List of assets created by the specified creator
*/
export async function get_assets_by_creator(
agent: SolanaAgentKit,
params: GetAssetsByCreatorRpcInput,
) {
const umi = createUmi(agent.connection.rpcEndpoint).use(dasApi());
return await umi.rpc.getAssetsByCreator(params);
}

View File

@@ -1,3 +1,7 @@
export * from "./deploy_collection";
export * from "./mint_nft";
export * from "./deploy_token";
export * from "./get_asset";
export * from "./get_assets_by_authority";
export * from "./get_assets_by_creator";
export * from "./search_assets";

View File

@@ -0,0 +1,20 @@
import { SolanaAgentKit } from "../../agent";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import {
dasApi,
SearchAssetsRpcInput,
} from "@metaplex-foundation/digital-asset-standard-api";
/**
* Search for assets using the Metaplex DAS API
* @param agent SolanaAgentKit instance
* @param params Parameters for searching assets
* @returns List of assets matching the search criteria
*/
export async function search_assets(
agent: SolanaAgentKit,
params: SearchAssetsRpcInput,
) {
const umi = createUmi(agent.connection.rpcEndpoint).use(dasApi());
return await umi.rpc.searchAssets(params);
}