-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: split out cdp wallet and cdp api actions
- Loading branch information
Showing
5 changed files
with
319 additions
and
195 deletions.
There are no files selected for viewing
168 changes: 168 additions & 0 deletions
168
cdp-agentkit-core/typescript/src/action-providers/cdp/cdpApiActionProvider.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import { EvmWalletProvider } from "../../wallet-providers"; | ||
import { CdpApiActionProvider } from "./cdpApiActionProvider"; | ||
import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; | ||
|
||
// Mock the entire module | ||
jest.mock("@coinbase/coinbase-sdk"); | ||
|
||
// Get the mocked constructor | ||
const { ExternalAddress } = jest.requireMock("@coinbase/coinbase-sdk"); | ||
|
||
describe("CDP API Action Provider Input Schemas", () => { | ||
describe("Address Reputation Schema", () => { | ||
it("should successfully parse valid input", () => { | ||
const validInput = { | ||
address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", | ||
network: "base-mainnet", | ||
}; | ||
|
||
const result = AddressReputationSchema.safeParse(validInput); | ||
|
||
expect(result.success).toBe(true); | ||
expect(result.data).toEqual(validInput); | ||
}); | ||
|
||
it("should fail parsing invalid address", () => { | ||
const invalidInput = { | ||
address: "invalid-address", | ||
network: "base-mainnet", | ||
}; | ||
const result = AddressReputationSchema.safeParse(invalidInput); | ||
|
||
expect(result.success).toBe(false); | ||
}); | ||
}); | ||
|
||
describe("Request Faucet Funds Schema", () => { | ||
it("should successfully parse with optional assetId", () => { | ||
const validInput = { | ||
assetId: "eth", | ||
}; | ||
|
||
const result = RequestFaucetFundsSchema.safeParse(validInput); | ||
|
||
expect(result.success).toBe(true); | ||
expect(result.data).toEqual(validInput); | ||
}); | ||
|
||
it("should successfully parse without assetId", () => { | ||
const validInput = {}; | ||
const result = RequestFaucetFundsSchema.safeParse(validInput); | ||
|
||
expect(result.success).toBe(true); | ||
expect(result.data).toEqual(validInput); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("CDP API Action Provider", () => { | ||
let actionProvider: CdpApiActionProvider; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
let mockExternalAddressInstance: jest.Mocked<any>; | ||
let mockWallet: jest.Mocked<EvmWalletProvider>; | ||
|
||
beforeEach(() => { | ||
// Reset all mocks before each test | ||
jest.clearAllMocks(); | ||
|
||
actionProvider = new CdpApiActionProvider(); | ||
mockExternalAddressInstance = { | ||
reputation: jest.fn(), | ||
faucet: jest.fn(), | ||
}; | ||
|
||
// Mock the constructor to return our mock instance | ||
(ExternalAddress as jest.Mock).mockImplementation(() => mockExternalAddressInstance); | ||
|
||
mockWallet = { | ||
deployToken: jest.fn(), | ||
deployContract: jest.fn(), | ||
getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"), | ||
getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }), | ||
} as unknown as jest.Mocked<EvmWalletProvider>; | ||
}); | ||
|
||
describe("addressReputation", () => { | ||
it("should successfully check address reputation", async () => { | ||
const args = { | ||
address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", | ||
network: "base-mainnet", | ||
}; | ||
|
||
mockExternalAddressInstance.reputation.mockResolvedValue("Good reputation"); | ||
|
||
const result = await actionProvider.addressReputation(args); | ||
|
||
expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); | ||
expect(ExternalAddress).toHaveBeenCalledTimes(1); | ||
expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); | ||
expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); | ||
expect(result).toBe("Good reputation"); | ||
}); | ||
|
||
it("should handle errors when checking reputation", async () => { | ||
const args = { | ||
address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", | ||
network: "base-mainnet", | ||
}; | ||
|
||
const error = new Error("Reputation check failed"); | ||
mockExternalAddressInstance.reputation.mockRejectedValue(error); | ||
|
||
const result = await actionProvider.addressReputation(args); | ||
|
||
expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); | ||
expect(ExternalAddress).toHaveBeenCalledTimes(1); | ||
expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); | ||
expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); | ||
expect(result).toBe(`Error checking address reputation: ${error}`); | ||
}); | ||
}); | ||
|
||
describe("faucet", () => { | ||
beforeEach(() => { | ||
mockExternalAddressInstance.faucet.mockResolvedValue({ | ||
wait: jest.fn().mockResolvedValue({ | ||
getTransactionLink: jest.fn().mockReturnValue("tx-link"), | ||
}), | ||
}); | ||
}); | ||
|
||
it("should successfully request faucet funds with assetId", async () => { | ||
const args = { | ||
assetId: "eth", | ||
}; | ||
|
||
const result = await actionProvider.faucet(mockWallet, args); | ||
|
||
expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); | ||
expect(ExternalAddress).toHaveBeenCalledTimes(1); | ||
expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith("eth"); | ||
expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); | ||
expect(result).toContain("Received eth from the faucet"); | ||
expect(result).toContain("tx-link"); | ||
}); | ||
|
||
it("should successfully request faucet funds without assetId", async () => { | ||
const args = {}; | ||
|
||
const result = await actionProvider.faucet(mockWallet, args); | ||
|
||
expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); | ||
expect(ExternalAddress).toHaveBeenCalledTimes(1); | ||
expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith(undefined); | ||
expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); | ||
expect(result).toContain("Received ETH from the faucet"); | ||
}); | ||
|
||
it("should handle faucet errors", async () => { | ||
const args = {}; | ||
const error = new Error("Faucet request failed"); | ||
mockExternalAddressInstance.faucet.mockRejectedValue(error); | ||
|
||
const result = await actionProvider.faucet(mockWallet, args); | ||
|
||
expect(result).toBe(`Error requesting faucet funds: ${error}`); | ||
}); | ||
}); | ||
}); |
105 changes: 105 additions & 0 deletions
105
cdp-agentkit-core/typescript/src/action-providers/cdp/cdpApiActionProvider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk"; | ||
import { z } from "zod"; | ||
|
||
import { CreateAction } from "../actionDecorator"; | ||
import { ActionProvider } from "../actionProvider"; | ||
import { Network } from "../../network"; | ||
import { CdpWalletProviderConfig, EvmWalletProvider } from "../../wallet-providers"; | ||
|
||
import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; | ||
|
||
/** | ||
* CdpApiActionProvider is an action provider for CDP API. | ||
* | ||
* This provider is used for any action that uses the CDP API, but does not require a CDP Wallet. | ||
*/ | ||
export class CdpApiActionProvider extends ActionProvider<EvmWalletProvider> { | ||
/** | ||
* Constructor for the CdpApiActionProvider class. | ||
* | ||
* @param config - The configuration options for the CdpApiActionProvider. | ||
*/ | ||
constructor(config: CdpWalletProviderConfig = {}) { | ||
super("cdp_api", []); | ||
|
||
if (config.apiKeyName && config.apiKeyPrivateKey) { | ||
Coinbase.configure({ apiKeyName: config.apiKeyName, privateKey: config.apiKeyPrivateKey }); | ||
} else { | ||
Coinbase.configureFromJson(); | ||
} | ||
} | ||
|
||
/** | ||
* Check the reputation of an address. | ||
* | ||
* @param args - The input arguments for the action | ||
* @returns A string containing reputation data or error message | ||
*/ | ||
@CreateAction({ | ||
name: "address_reputation", | ||
description: ` | ||
This tool checks the reputation of an address on a given network. It takes: | ||
- network: The network to check the address on (e.g. "base-mainnet") | ||
- address: The Ethereum address to check | ||
`, | ||
schema: AddressReputationSchema, | ||
}) | ||
async addressReputation(args: z.infer<typeof AddressReputationSchema>): Promise<string> { | ||
try { | ||
const address = new ExternalAddress(args.network, args.address); | ||
const reputation = await address.reputation(); | ||
return reputation.toString(); | ||
} catch (error) { | ||
return `Error checking address reputation: ${error}`; | ||
} | ||
} | ||
|
||
/** | ||
* Requests test tokens from the faucet for the default address in the wallet. | ||
* | ||
* @param walletProvider - The wallet provider to request funds from. | ||
* @param args - The input arguments for the action. | ||
* @returns A confirmation message with transaction details. | ||
*/ | ||
@CreateAction({ | ||
name: "request_faucet_funds", | ||
description: `This tool will request test tokens from the faucet for the default address in the wallet. It takes the wallet and asset ID as input. | ||
If no asset ID is provided the faucet defaults to ETH. Faucet is only allowed on 'base-sepolia' and can only provide asset ID 'eth' or 'usdc'. | ||
You are not allowed to faucet with any other network or asset ID. If you are on another network, suggest that the user sends you some ETH | ||
from another wallet and provide the user with your wallet details.`, | ||
schema: RequestFaucetFundsSchema, | ||
}) | ||
async faucet( | ||
walletProvider: EvmWalletProvider, | ||
args: z.infer<typeof RequestFaucetFundsSchema>, | ||
): Promise<string> { | ||
try { | ||
const address = new ExternalAddress( | ||
walletProvider.getNetwork().networkId!, | ||
walletProvider.getAddress(), | ||
); | ||
|
||
const faucetTx = await address.faucet(args.assetId || undefined); | ||
|
||
const result = await faucetTx.wait(); | ||
|
||
return `Received ${ | ||
args.assetId || "ETH" | ||
} from the faucet. Transaction: ${result.getTransactionLink()}`; | ||
} catch (error) { | ||
return `Error requesting faucet funds: ${error}`; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the Cdp action provider supports the given network. | ||
* | ||
* @param _ - The network to check. | ||
* @returns True if the Cdp action provider supports the network, false otherwise. | ||
* TODO: Split out into sub providers so network support can be tighter scoped. | ||
*/ | ||
supportsNetwork = (_: Network) => true; | ||
} | ||
|
||
export const cdpApiActionProvider = () => new CdpApiActionProvider(); |
Oops, something went wrong.