Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.1.0(ts): deploy nft #200

Merged
merged 2 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CdpWalletProvider } from "../../wallet_providers";
import { CdpActionProvider } from "./cdpActionProvider";
import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas";
import { AddressReputationSchema, DeployNftSchema, RequestFaucetFundsSchema } from "./schemas";
import { SmartContract } from "@coinbase/coinbase-sdk";

// Mock the entire module
Expand Down Expand Up @@ -34,6 +34,28 @@ describe("CDP Action Provider Input Schemas", () => {
});
});

describe("Deploy NFT Schema", () => {
it("should successfully parse valid input", () => {
const validInput = {
baseURI: "https://www.test.xyz/metadata/",
name: "Test Token",
symbol: "TEST",
};

const result = DeployNftSchema.safeParse(validInput);

expect(result.success).toBe(true);
expect(result.data).toEqual(validInput);
});

it("should fail parsing empty input", () => {
const emptyInput = {};
const result = DeployNftSchema.safeParse(emptyInput);

expect(result.success).toBe(false);
});
});

describe("Request Faucet Funds Schema", () => {
it("should successfully parse with optional assetId", () => {
const validInput = {
Expand Down Expand Up @@ -120,6 +142,65 @@ describe("CDP Action Provider", () => {
});
});

describe("deployNft", () => {
let mockWallet: jest.Mocked<CdpWalletProvider>;
const MOCK_NFT_BASE_URI = "https://www.test.xyz/metadata/";
const MOCK_NFT_NAME = "Test Token";
const MOCK_NFT_SYMBOL = "TEST";
const CONTRACT_ADDRESS = "0x123456789abcdef";
const NETWORK_ID = "base-sepolia";
const TRANSACTION_HASH = "0xghijkl987654321";
const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`;

beforeEach(() => {
mockWallet = {
deployNFT: jest.fn().mockResolvedValue({
wait: jest.fn().mockResolvedValue({
getContractAddress: jest.fn().mockReturnValue(CONTRACT_ADDRESS),
getTransaction: jest.fn().mockReturnValue({
getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH),
getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK),
}),
}),
}),
getNetwork: jest.fn().mockReturnValue({ networkId: NETWORK_ID }),
} as unknown as jest.Mocked<CdpWalletProvider>;
});

it("should successfully deploy an NFT", async () => {
const args = {
name: MOCK_NFT_NAME,
symbol: MOCK_NFT_SYMBOL,
baseURI: MOCK_NFT_BASE_URI,
};

const result = await actionProvider.deployNFT(mockWallet, args);

expect(mockWallet.deployNFT).toHaveBeenCalledWith(args);
expect(result).toContain(`Deployed NFT Collection ${MOCK_NFT_NAME}:`);
expect(result).toContain(`- to address ${CONTRACT_ADDRESS}`);
expect(result).toContain(`- on network ${NETWORK_ID}`);
expect(result).toContain(`Transaction hash: ${TRANSACTION_HASH}`);
expect(result).toContain(`Transaction link: ${TRANSACTION_LINK}`);
});

it("should handle deployment errors", async () => {
const args = {
name: MOCK_NFT_NAME,
symbol: MOCK_NFT_SYMBOL,
baseURI: MOCK_NFT_BASE_URI,
};

const error = new Error("An error has occurred");
mockWallet.deployNFT.mockRejectedValue(error);

const result = await actionProvider.deployNFT(mockWallet, args);

expect(mockWallet.deployNFT).toHaveBeenCalledWith(args);
expect(result).toBe(`Error deploying NFT: ${error}`);
});
});

describe("deployToken", () => {
beforeEach(() => {
mockWallet = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SolidityVersions } from "./constants";
import {
AddressReputationSchema,
DeployContractSchema,
DeployNftSchema,
DeployTokenSchema,
RequestFaucetFundsSchema,
} from "./schemas";
Expand Down Expand Up @@ -101,6 +102,48 @@ map where the key is the arg name and the value is the arg value. Encode uint/in
}
}

/**
* Deploys an NFT (ERC-721) token collection onchain from the wallet.
*
* @param walletProvider - The wallet provider to deploy the NFT from.
* @param args - The input arguments for the action.
* @returns A message containing the NFT token deployment details.
*/
@CreateAction({
name: "deploy_nft",
description: `This tool will deploy an NFT (ERC-721) contract onchain from the wallet.
It takes the name of the NFT collection, the symbol of the NFT collection, and the base URI for the token metadata as inputs.`,
schema: DeployNftSchema,
})
async deployNFT(
walletProvider: CdpWalletProvider,
args: z.infer<typeof DeployNftSchema>,
): Promise<string> {
try {
const nftContract = await walletProvider.deployNFT({
name: args.name,
symbol: args.symbol,
baseURI: args.baseURI,
});

const result = await nftContract.wait();

const transaction = result.getTransaction()!;
const networkId = walletProvider.getNetwork().networkId;
const contractAddress = result.getContractAddress();

return [
`Deployed NFT Collection ${args.name}:`,
`- to address ${contractAddress}`,
`- on network ${networkId}.`,
`Transaction hash: ${transaction.getTransactionHash()}`,
`Transaction link: ${transaction.getTransactionLink()}`,
].join("\n");
} catch (error) {
return `Error deploying NFT: ${error}`;
}
}

/**
* Deploys a token.
*
Expand Down
12 changes: 12 additions & 0 deletions cdp-agentkit-core/typescript/src/action_providers/cdp/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ export const DeployContractSchema = z
.strip()
.describe("Instructions for deploying an arbitrary contract");

/**
* Input schema for deploy NFT action
*/
export const DeployNftSchema = z
stat marked this conversation as resolved.
Show resolved Hide resolved
.object({
name: z.string().describe("The name of the NFT collection"),
symbol: z.string().describe("The symbol of the NFT collection"),
baseURI: z.string().describe("The base URI for the token metadata"),
})
.strip()
.describe("Instructions for deploying an NFT collection");

/**
* Input schema for deploy token action.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,28 @@ export class CdpWalletProvider extends EvmWalletProvider {

return this.#cdpWallet.deployContract(options);
}

/**
* Deploys a new NFT (ERC-721) smart contract.
*
* @param options - Configuration options for the NFT contract deployment
* @param options.name - The name of the collection
* @param options.symbol - The token symbol for the collection
* @param options.baseURI - The base URI for token metadata.
*
* @returns A Promise that resolves to the deployed SmartContract instance
* @throws Error if the wallet is not properly initialized
* @throws Error if the deployment fails for any reason (network issues, insufficient funds, etc.)
*/
async deployNFT(options: {
name: string;
symbol: string;
baseURI: string;
}): Promise<SmartContract> {
if (!this.#cdpWallet) {
throw new Error("Wallet not initialized");
}

return this.#cdpWallet.deployNFT(options);
}
}