Skip to content

Commit

Permalink
plugin: tensor (goat-sdk#38)
Browse files Browse the repository at this point in the history
* save

* package

* tweak

* lint
  • Loading branch information
mPaella authored and youssefea committed Dec 17, 2024
1 parent 194c213 commit 7fbba2b
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 2 deletions.
32 changes: 32 additions & 0 deletions typescript/packages/plugins/tensor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@goat-sdk/plugin-tensor",
"version": "0.0.1",
"files": ["dist/**/*", "README.md", "package.json"],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "vitest run --passWithNoTests"
},
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"dependencies": {
"@goat-sdk/core": "workspace:*",
"@solana/web3.js": "catalog:",
"zod": "catalog:"
},
"peerDependencies": {
"@goat-sdk/core": "workspace:*"
},
"homepage": "https://ohmygoat.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/goat-sdk/goat.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/goat-sdk/goat/issues"
},
"keywords": ["ai", "agents", "web3"]
}
1 change: 1 addition & 0 deletions typescript/packages/plugins/tensor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./plugin";
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { SolanaWalletClient } from "@goat-sdk/core";
import type { Connection } from "@solana/web3.js";
import type { z } from "zod";
import type { getBuyListingTransactionResponseSchema } from "../parameters";
import { deserializeTxResponseToInstructions } from "../utils/deserializeTxResponseToInstructions";
import { getNftInfo } from "./getNftInfo";

export async function getBuyListingTransaction({
walletClient,
mintHash,
apiKey,
connection,
}: { walletClient: SolanaWalletClient; mintHash: string; apiKey: string; connection: Connection }) {
const nftInfo = await getNftInfo({ mintHash, apiKey });

const price = nftInfo.listing?.price;
const owner = nftInfo.owner;

if (!price || !owner) {
throw new Error(`No listing found for ${mintHash}`);
}

const queryParams = new URLSearchParams({
buyer: walletClient.getAddress(),
mint: mintHash,
owner,
maxPrice: price,
blockhash: "11111111111111111111111111111111",
});

let data: z.infer<typeof getBuyListingTransactionResponseSchema>;
try {
const response = await fetch(`https://api.mainnet.tensordev.io/api/v1/tx/buy?${queryParams.toString()}`, {
headers: {
"Content-Type": "application/json",
"x-tensor-api-key": apiKey,
},
});

data = (await response.json()) as z.infer<typeof getBuyListingTransactionResponseSchema>;
console.log(data);
} catch (error) {
throw new Error(`Failed to get buy listing transaction: ${error}`);
}

const { versionedTransaction, instructions } = await deserializeTxResponseToInstructions(connection, data);
const lookupTableAddresses = versionedTransaction.message.addressTableLookups.map((lookup) => lookup.accountKey);
return { versionedTransaction, instructions, lookupTableAddresses };
}
20 changes: 20 additions & 0 deletions typescript/packages/plugins/tensor/src/methods/getNftInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { z } from "zod";
import type { getNftInfoResponseSchema } from "../parameters";

export async function getNftInfo({ mintHash, apiKey }: { mintHash: string; apiKey: string }) {
let nftInfo: z.infer<typeof getNftInfoResponseSchema>;
try {
const response = await fetch(`https://api.mainnet.tensordev.io/api/v1/mint?mints=${mintHash}`, {
headers: {
"Content-Type": "application/json",
"x-tensor-api-key": apiKey,
},
});

nftInfo = (await response.json()) as z.infer<typeof getNftInfoResponseSchema>;
} catch (error) {
throw new Error(`Failed to get NFT info: ${error}`);
}

return nftInfo[0];
}
65 changes: 65 additions & 0 deletions typescript/packages/plugins/tensor/src/parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { z } from "zod";

export const getNftInfoParametersSchema = z.object({
mintHash: z.string(),
});

export const getNftInfoResponseSchema = z.array(
z.object({
onchainId: z.string(),
attributes: z.array(z.any()),
imageUri: z.string().optional(),
lastSale: z
.object({
price: z.string(),
priceUnit: z.string(),
})
.optional(),
metadataUri: z.string().optional(),
name: z.string().optional(),
rarityRankTT: z.number().optional(),
rarityRankTTStat: z.number().optional(),
rarityRankHrtt: z.number().optional(),
rarityRankStat: z.number().optional(),
sellRoyaltyFeeBPS: z.number().optional(),
tokenEdition: z.string().optional(),
tokenStandard: z.string().optional(),
hidden: z.boolean().optional(),
compressed: z.boolean().optional(),
verifiedCollection: z.string().optional(),
owner: z.string().optional(),
inscription: z.string().optional(),
tokenProgram: z.string().optional(),
metadataProgram: z.string().optional(),
transferHookProgram: z.string().optional(),
listingNormalizedPrice: z.string().optional(),
hybridAmount: z.string().optional(),
listing: z
.object({
price: z.string(),
txId: z.string(),
seller: z.string(),
source: z.string(),
})
.optional(),
slugDisplay: z.string().optional(),
collId: z.string().optional(),
collName: z.string().optional(),
numMints: z.number().optional(),
}),
);

export const getBuyListingTransactionResponseSchema = z.object({
txs: z.array(
z.object({
tx: z.object({
type: z.string(),
data: z.array(z.number()),
}),
txV0: z.object({
type: z.string(),
data: z.array(z.number()),
}),
}),
),
});
12 changes: 12 additions & 0 deletions typescript/packages/plugins/tensor/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Plugin, SolanaWalletClient } from "@goat-sdk/core";
import type { Connection } from "@solana/web3.js";
import { getTools } from "./tools";

export function tensor(params: { connection: Connection; apiKey: string }): Plugin<SolanaWalletClient> {
return {
name: "tensor",
supportsSmartWallets: () => false,
supportsChain: (chain) => chain.type === "solana",
getTools: async () => getTools(params),
};
}
29 changes: 29 additions & 0 deletions typescript/packages/plugins/tensor/src/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { SolanaWalletClient } from "@goat-sdk/core";

import type { DeferredTool } from "@goat-sdk/core";
import type { Connection } from "@solana/web3.js";
import { getBuyListingTransaction } from "./methods/getBuyListingTransaction";
import { getNftInfo } from "./methods/getNftInfo";
import { getNftInfoParametersSchema } from "./parameters";

export function getTools({
apiKey,
connection,
}: { apiKey: string; connection: Connection }): DeferredTool<SolanaWalletClient>[] {
const getNftInfoTool: DeferredTool<SolanaWalletClient> = {
name: "get_nft_info",
description: "Gets information about a Solana NFT, from the Tensor API",
parameters: getNftInfoParametersSchema,
method: async (walletClient, parameters) => getNftInfo({ mintHash: parameters.mintHash, apiKey }),
};

const buyListingTool: DeferredTool<SolanaWalletClient> = {
name: "get_buy_listing_transaction",
description: "Gets a transaction to buy an NFT from a listing from the Tensor API",
parameters: getNftInfoParametersSchema,
method: async (walletClient, parameters) =>
getBuyListingTransaction({ walletClient, mintHash: parameters.mintHash, apiKey, connection }),
};

return [getNftInfoTool, buyListingTool];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type DecompileArgs, TransactionMessage } from "@solana/web3.js";

import type { Connection, VersionedTransaction } from "@solana/web3.js";

export async function decompileVersionedTransactionToInstructions(
connection: Connection,
versionedTransaction: VersionedTransaction,
) {
const lookupTableAddresses = versionedTransaction.message.addressTableLookups.map((lookup) => lookup.accountKey);
const addressLookupTableAccounts = await Promise.all(
lookupTableAddresses.map((address) =>
connection.getAddressLookupTable(address).then((lookupTable) => lookupTable.value),
),
);
const nonNullAddressLookupTableAccounts = addressLookupTableAccounts.filter((lookupTable) => lookupTable != null);
const decompileArgs: DecompileArgs = {
addressLookupTableAccounts: nonNullAddressLookupTableAccounts,
};
return TransactionMessage.decompile(versionedTransaction.message, decompileArgs).instructions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Connection, VersionedTransaction } from "@solana/web3.js";
import type { z } from "zod";
import type { getBuyListingTransactionResponseSchema } from "../parameters";
import { decompileVersionedTransactionToInstructions } from "./decompileVersionedTransactionToInstructions";

export async function deserializeTxResponseToInstructions(
connection: Connection,
txResponse: z.infer<typeof getBuyListingTransactionResponseSchema>,
) {
const firstTransaction = txResponse.txs[0];
if (firstTransaction == null) {
throw new Error("No transaction in response");
}
const txV0 = firstTransaction.txV0;
if (txV0 == null) {
throw new Error("No txV0 in response");
}
const versionedTransaction = VersionedTransaction.deserialize(Buffer.from(txV0.data));
const instructions = await decompileVersionedTransactionToInstructions(connection, versionedTransaction);
return { versionedTransaction, instructions };
}
6 changes: 6 additions & 0 deletions typescript/packages/plugins/tensor/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
6 changes: 6 additions & 0 deletions typescript/packages/plugins/tensor/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "tsup";
import { treeShakableConfig } from "../../../tsup.config.base";

export default defineConfig({
...treeShakableConfig,
});
18 changes: 16 additions & 2 deletions typescript/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7fbba2b

Please sign in to comment.