Skip to content
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
1 change: 1 addition & 0 deletions python/coinbase-agentkit/changelog.d/824.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added optional custom rpc url
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class CdpEvmWalletProviderConfig(BaseModel):
network_id: str | None = Field(None, description="The network id")
address: str | None = Field(None, description="The address to use")
idempotency_key: str | None = Field(None, description="The idempotency key for wallet creation")
rpc_url: str | None = Field(None, description="Optional RPC URL to override default chain RPC")


class CdpEvmWalletProvider(EvmWalletProvider):
Expand Down Expand Up @@ -54,7 +55,7 @@ def __init__(self, config: CdpEvmWalletProviderConfig):
self._idempotency_key = config.idempotency_key or os.getenv("IDEMPOTENCY_KEY") or None

chain = NETWORK_ID_TO_CHAIN[network_id]
rpc_url = chain.rpc_urls["default"].http[0]
rpc_url = config.rpc_url or os.getenv("RPC_URL") or chain.rpc_urls["default"].http[0]

self._network = Network(
protocol_family="evm",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CdpSmartWalletProviderConfig(BaseModel):
paymaster_url: str | None = Field(
None, description="Optional paymaster URL for gasless transactions"
)
rpc_url: str | None = Field(None, description="Optional RPC URL to override default chain RPC")


class CdpSmartWalletProvider(EvmWalletProvider):
Expand Down Expand Up @@ -65,7 +66,7 @@ def __init__(self, config: CdpSmartWalletProviderConfig):
network_id = config.network_id or os.getenv("NETWORK_ID", "base-sepolia")

chain = NETWORK_ID_TO_CHAIN[network_id]
rpc_url = chain.rpc_urls["default"].http[0]
rpc_url = config.rpc_url or os.getenv("RPC_URL") or chain.rpc_urls["default"].http[0]

self._network = Network(
protocol_family="evm",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Eth account wallet provider."""

import os
from decimal import Decimal
from typing import Any

Expand Down Expand Up @@ -45,6 +46,7 @@ def __init__(self, config: EthAccountWalletProviderConfig):
network_id = ""
rpc_url = config.rpc_url

rpc_url = config.rpc_url or os.getenv("RPC_URL")
if rpc_url is None:
chain = NETWORK_ID_TO_CHAIN[CHAIN_ID_TO_NETWORK_ID[config.chain_id]]
network_id = CHAIN_ID_TO_NETWORK_ID[config.chain_id]
Expand Down
5 changes: 4 additions & 1 deletion python/examples/langchain-cdp-chatbot/.env.local
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ NETWORK_ID=base-sepolia
ADDRESS=

# Optional - Set IDEMPOTENCY_KEY to create a new wallet and seed the randomness
IDEMPOTENCY_KEY=
IDEMPOTENCY_KEY=

# Optional - RPC endpoint
RPC_URL=
2 changes: 2 additions & 0 deletions python/examples/langchain-cdp-chatbot/chatbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def initialize_agent(config: CdpEvmWalletProviderConfig):
network_id=config.network_id, # Network ID - Optional, will default to 'base-sepolia'
address=config.address, # Wallet Address - Optional, will trigger idempotency flow if not provided
idempotency_key=config.idempotency_key, # Idempotency Key - Optional, seeds generation of a new wallet
rpc_url=config.rpc_url, # Optional RPC URL override
)
)

Expand Down Expand Up @@ -134,6 +135,7 @@ def setup():
address=wallet_address,
# Only include idempotency_key if we need to create a new wallet
idempotency_key=(os.getenv("IDEMPOTENCY_KEY") if not wallet_address else None),
rpc_url=os.getenv("RPC_URL"),
)

# Initialize the agent and get the wallet provider
Expand Down
5 changes: 5 additions & 0 deletions typescript/.changeset/beige-readers-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added getPublicClient() for EvmWalletProviders and custom RPC_URL
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ describe("FlaunchActionProvider", () => {
return undefined;
}),
signTypedData: jest.fn().mockResolvedValue("0xsignature" as Hex),
getPublicClient: jest.fn().mockReturnValue({
simulateContract: jest.fn().mockResolvedValue({
result: [BigInt(1000000000000000000)],
}),
}),
} as unknown as jest.Mocked<EvmWalletProvider>;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import {
encodeFunctionData,
decodeEventLog,
parseEther,
createPublicClient,
http,
zeroAddress,
Address,
formatEther,
Expand Down Expand Up @@ -367,12 +365,7 @@ It takes:
permitSingle = message;
}

const viemPublicClient = createPublicClient({
chain: NETWORK_ID_TO_VIEM_CHAIN[networkId],
transport: http(),
});

const quoteResult = await viemPublicClient.simulateContract({
const quoteResult = await walletProvider.getPublicClient().simulateContract({
address: QuoterAddress[chainId],
abi: QUOTER_ABI,
functionName: "quoteExactInput",
Expand Down Expand Up @@ -488,11 +481,6 @@ It takes:
}

try {
const viemPublicClient = createPublicClient({
chain: NETWORK_ID_TO_VIEM_CHAIN[networkId],
transport: http(),
});

let amountIn: bigint | undefined;
let amountOutMin: bigint | undefined;
let amountOut: bigint | undefined;
Expand All @@ -501,7 +489,7 @@ It takes:
if (swapType === "EXACT_IN") {
amountIn = parseEther(swapParams.amountIn!);

const quoteResult = await viemPublicClient.simulateContract({
const quoteResult = await walletProvider.getPublicClient().simulateContract({
address: QuoterAddress[chainId],
abi: QUOTER_ABI,
functionName: "quoteExactInput",
Expand Down Expand Up @@ -537,7 +525,7 @@ It takes:
// EXACT_OUT
amountOut = parseEther(swapParams.amountOut!);

const quoteResult = await viemPublicClient.simulateContract({
const quoteResult = await walletProvider.getPublicClient().simulateContract({
address: QuoterAddress[chainId],
abi: QUOTER_ABI,
functionName: "quoteExactOutput",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,12 @@ import { truemarketsActionProvider, TrueMarketsActionProvider } from "./truemark
import { EvmWalletProvider } from "../../wallet-providers";
import { Network } from "../../network";
import { USDC_ADDRESS } from "./constants";
import { Hex, createPublicClient } from "viem";

// Mock viem's createPublicClient
jest.mock("viem", () => {
const originalModule = jest.requireActual("viem");
return {
...originalModule,
createPublicClient: jest.fn().mockImplementation(() => ({
// Mock public client methods as needed
multicall: jest.fn().mockImplementation(({ contracts }) => {
// Create mock responses with success status
return contracts.map(() => ({
status: "success",
result: "mock result",
}));
}),
readContract: jest.fn(),
})),
http: jest.fn().mockImplementation(url => ({ url })),
};
});
import { Hex } from "viem";

describe("TrueMarketsActionProvider", () => {
let provider: TrueMarketsActionProvider;
let mockWallet: jest.Mocked<EvmWalletProvider>;
let publicClientMock: { multicall: jest.Mock; readContract: jest.Mock };

// Mock addresses and data for tests
const MOCK_MARKET_ADDRESS = "0x1234567890123456789012345678901234567890" as Hex;
Expand All @@ -40,32 +21,27 @@ describe("TrueMarketsActionProvider", () => {
const MOCK_STATUS_NUM = 0n; // Created status
const MOCK_END_OF_TRADING = 1717171717n; // Unix timestamp

describe("constructor", () => {
it("should use the provided RPC_URL for the public client", () => {
const customRpcUrl = "https://custom-rpc.example.com";
truemarketsActionProvider({ RPC_URL: customRpcUrl });

// Verify createPublicClient was called with the correct URL
expect(createPublicClient).toHaveBeenCalledWith(
expect.objectContaining({
chain: expect.anything(),
transport: expect.objectContaining({ url: customRpcUrl }),
}),
);
});
});

beforeEach(() => {
jest.clearAllMocks();

provider = truemarketsActionProvider();

publicClientMock = {
multicall: jest
.fn()
.mockImplementation(({ contracts }) =>
contracts.map(() => ({ status: "success", result: "mock result" })),
),
readContract: jest.fn(),
};

mockWallet = {
readContract: jest.fn(),
getName: jest.fn().mockReturnValue("evm_wallet_provider"),
getNetwork: jest.fn().mockReturnValue({
networkId: "base-mainnet",
}),
getPublicClient: jest.fn().mockReturnValue(publicClientMock),
} as unknown as jest.Mocked<EvmWalletProvider>;
});

Expand Down Expand Up @@ -161,11 +137,12 @@ describe("TrueMarketsActionProvider", () => {
});

describe("getPredictionMarketDetails", () => {
let mockPublicClient: { multicall: jest.Mock };
let mockPublicClient: { multicall: jest.Mock; readContract: jest.Mock };

beforeEach(() => {
// Access the mocked public client
mockPublicClient = (createPublicClient as jest.Mock).mock.results[0].value;
// Use the shared mocked public client
mockPublicClient = publicClientMock;
mockPublicClient.multicall.mockReset();

// Setup multicall mock responses
mockPublicClient.multicall
Expand Down Expand Up @@ -225,7 +202,7 @@ describe("TrueMarketsActionProvider", () => {

it("should handle errors", async () => {
const error = new Error("Failed to fetch market details");
mockWallet.readContract.mockReset();
(mockWallet.readContract as jest.Mock).mockReset();
mockPublicClient.multicall.mockReset();
mockPublicClient.multicall.mockRejectedValueOnce(error);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,18 @@ import {
} from "./constants";
import { abi as ERC20ABI } from "../erc20/constants";
import { EvmWalletProvider } from "../../wallet-providers";
import { Hex, formatUnits, createPublicClient, http, PublicClient } from "viem";
import { Hex, formatUnits } from "viem";
import { TruthMarket, Slot0Result } from "./utils";
import { base } from "viem/chains";

/**
* Configuration options for the TrueMarketsActionProvider.
*/
export interface TrueMarketsActionProviderConfig {
/**
* RPC URL for creating the Viem public client
*/
RPC_URL?: string;
}

/**
* TrueMarketsActionProvider provides actions to interact with TrueMarkets contracts.
* Action provider for TrueMarkets interactions.
*/
export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider> {
#publicClient: PublicClient;

/**
* Constructor for the TrueMarketsActionProvider.
*
* @param config - The configuration options for the TrueMarketsActionProvider.
* Creates a new TrueMarkets action provider.
*/
constructor(config?: TrueMarketsActionProviderConfig) {
constructor() {
super("truemarkets", []);
this.#publicClient = createPublicClient({
chain: base,
transport: config?.RPC_URL ? http(config.RPC_URL) : http(),
}) as PublicClient;
}

/**
Expand Down Expand Up @@ -120,7 +101,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
args: [BigInt(index)],
}));

const marketAddresses = await this.#publicClient.multicall({
const marketAddresses = await walletProvider.getPublicClient().multicall({
contracts: addressCalls,
});

Expand All @@ -143,7 +124,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
functionName: "marketQuestion",
}));

const marketQuestionsResult = await this.#publicClient.multicall({
const marketQuestionsResult = await walletProvider.getPublicClient().multicall({
contracts: questionCalls,
});

Expand Down Expand Up @@ -216,7 +197,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
}

try {
marketAddress = (await this.#publicClient.readContract({
marketAddress = (await walletProvider.getPublicClient().readContract({
address: TruthMarketManager_ADDRESS as Hex,
abi: TruthMarketManagerABI,
functionName: "getActiveMarketAddress",
Expand Down Expand Up @@ -269,7 +250,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
},
];

const basicInfoResults = await this.#publicClient.multicall({
const basicInfoResults = await walletProvider.getPublicClient().multicall({
contracts: basicInfoCalls,
});

Expand Down Expand Up @@ -326,7 +307,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
},
];

const poolInfoResults = await this.#publicClient.multicall({
const poolInfoResults = await walletProvider.getPublicClient().multicall({
contracts: poolInfoCalls,
});

Expand Down Expand Up @@ -386,7 +367,7 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
},
];

const balanceResults = await this.#publicClient.multicall({
const balanceResults = await walletProvider.getPublicClient().multicall({
contracts: balanceCalls,
});

Expand Down Expand Up @@ -515,5 +496,4 @@ export class TrueMarketsActionProvider extends ActionProvider<EvmWalletProvider>
supportsNetwork = (network: Network) => network.networkId === "base-mainnet";
}

export const truemarketsActionProvider = (config?: TrueMarketsActionProviderConfig) =>
new TrueMarketsActionProvider(config);
export const truemarketsActionProvider = () => new TrueMarketsActionProvider();
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ export class CdpEvmWalletProvider extends EvmWalletProvider implements WalletPro
? cdpClient.evm.getAccount({ address: config.address as Address })
: cdpClient.evm.createAccount({ idempotencyKey }));

const rpcUrl = config.rpcUrl || process.env.RPC_URL;
const publicClient = createPublicClient({
chain: NETWORK_ID_TO_VIEM_CHAIN[networkId],
transport: http(),
transport: rpcUrl ? http(rpcUrl) : http(),
});

return new CdpEvmWalletProvider({
Expand Down Expand Up @@ -235,6 +236,15 @@ export class CdpEvmWalletProvider extends EvmWalletProvider implements WalletPro
return this.#cdp;
}

/**
* Gets the Viem PublicClient used for read-only operations.
*
* @returns The Viem PublicClient instance used for read-only operations.
*/
getPublicClient(): PublicClient {
return this.#publicClient;
}

/**
* Gets the balance of the wallet.
*
Expand Down
6 changes: 6 additions & 0 deletions typescript/agentkit/src/wallet-providers/cdpShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export interface CdpWalletProviderConfig extends CdpProviderConfig {
* The idempotency key of the wallet. Only used when creating a new account.
*/
idempotencyKey?: string;

/**
* Optional RPC URL for Viem public client HTTP transport.
* Falls back to process.env.RPC_URL when not provided.
*/
rpcUrl?: string;
}

export interface CdpSmartWalletProviderConfig extends CdpWalletProviderConfig {
Expand Down
Loading
Loading