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

feat: add safe wallet provider (ts) #330

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add safeWalletProvider
  • Loading branch information
phdargen committed Mar 8, 2025
commit a361e6b719ceeb57daeea496aad3de8ff2171bce
2 changes: 2 additions & 0 deletions typescript/agentkit/src/action-providers/safe/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from "./schemas";
export * from "./safeActionProvider";
export * from "./safeWalletActionProvider";
export * from "./safeApiActionProvider";
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { z } from "zod";
import { CreateAction } from "../actionDecorator";
import { ActionProvider } from "../actionProvider";
import { EvmWalletProvider } from "../../wallet-providers";
import { SafeInfoSchema } from "./schemas";
import { Network, NETWORK_ID_TO_VIEM_CHAIN } from "../../network";

import { Chain, formatEther } from "viem";
import SafeApiKit from "@safe-global/api-kit";

/**
* Configuration options for the SafeActionProvider.
*/
export interface SafeApiActionProviderConfig {
/**
* The network ID to use for the SafeActionProvider.
*/
networkId?: string;
}

/**
* SafeApiActionProvider is an action provider for Safe.
*
* This provider is used for any action that uses the Safe API, but does not require a Safe Wallet.
*/
export class SafeApiActionProvider extends ActionProvider<EvmWalletProvider> {
private readonly chain: Chain;
private apiKit: SafeApiKit;

/**
* Constructor for the SafeActionProvider class.
*
* @param config - The configuration options for the SafeActionProvider.
*/
constructor(config: SafeApiActionProviderConfig = {}) {
super("safe", []);

// Initialize chain
this.chain = NETWORK_ID_TO_VIEM_CHAIN[config.networkId || "base-sepolia"];
if (!this.chain) throw new Error(`Unsupported network: ${config.networkId}`);

// Initialize apiKit with chain ID from Viem chain
this.apiKit = new SafeApiKit({
chainId: BigInt(this.chain.id),
});
}

/**
* Connects to an existing Safe smart account.
*
* @param walletProvider - The wallet provider to use for the action.
* @param args - The input arguments for connecting to a Safe.
* @returns A message containing the connection details.
*/
@CreateAction({
name: "safe_info",
description: `
Gets information about an existing Safe smart account.
Takes the following input:
- safeAddress: Address of the existing Safe to connect to

Important notes:
- The Safe must already be deployed
`,
schema: SafeInfoSchema,
})
async safeInfo(
walletProvider: EvmWalletProvider,
args: z.infer<typeof SafeInfoSchema>,
): Promise<string> {
try {
// Get Safe info
const safeInfo = await this.apiKit.getSafeInfo(args.safeAddress);

const owners = safeInfo.owners;
const threshold = safeInfo.threshold;
const modules = safeInfo.modules;
const nonce = safeInfo.nonce;

// Get balance
const ethBalance = formatEther(
await walletProvider.getPublicClient().getBalance({ address: args.safeAddress }),
);

// Get pending transactions
const pendingTransactions = await this.apiKit.getPendingTransactions(args.safeAddress);
const pendingTxDetails = pendingTransactions.results
.filter(tx => !tx.isExecuted)
.map(tx => {
const confirmations = tx.confirmations?.length || 0;
const needed = tx.confirmationsRequired;
const confirmedBy = tx.confirmations?.map(c => c.owner).join(", ") || "none";
return `\n- Transaction ${tx.safeTxHash} (${confirmations}/${needed} confirmations, confirmed by: ${confirmedBy})`;
})
.join("");

return `Safe info:
- Safe at address: ${args.safeAddress}
- Chain: ${this.chain.name}
- ${owners.length} owners: ${owners.join(", ")}
- Threshold: ${threshold}
- Nonce: ${nonce}
- Modules: ${modules.join(", ")}
- Balance: ${ethBalance} ETH
- Pending transactions: ${pendingTransactions.count}${pendingTxDetails}`;
} catch (error) {
return `Safe info: Error connecting to Safe: ${error instanceof Error ? error.message : String(error)}`;
}
}

/**
* Checks if the Safe action provider supports the given network.
*
* @param network - The network to check.
* @returns True if the Safe action provider supports the network, false otherwise.
*/
supportsNetwork = (network: Network) => network.protocolFamily === "evm";
}

export const safeApiActionProvider = (config: SafeApiActionProviderConfig = {}) =>
new SafeApiActionProvider(config);
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { z } from "zod";
import { CreateAction } from "../actionDecorator";
import { ActionProvider } from "../actionProvider";
import { SafeWalletProvider } from "../../wallet-providers";
import { AddSignerSchema } from "./schemas";
import { Network } from "../../network";

/**
* SafeWalletActionProvider provides actions for managing Safe multi-sig wallets.
*/
export class SafeWalletActionProvider extends ActionProvider<SafeWalletProvider> {
/**
* Constructor for the SafeWalletActionProvider class.
*
*/
constructor() {
super("safe_wallet", []);
}

/**
* Adds a new signer to the Safe multi-sig wallet.
*
* @param walletProvider - The SafeWalletProvider instance to use
* @param args - The input arguments for creating a Safe.
* @returns A Promise that resolves to the transaction hash.
*/
@CreateAction({
name: "add_signer",
description: "Add a new signer to the Safe multi-sig wallet",
schema: AddSignerSchema,
})
async addSigner(
walletProvider: SafeWalletProvider,
args: z.infer<typeof AddSignerSchema>,
): Promise<string> {
try {
// Create and propose/execute the transaction
const addOwnerTx = await walletProvider.addOwnerWithThreshold(
args.newSigner,
args.newThreshold,
);

return addOwnerTx;
} catch (error) {
throw new Error(
`Failed to add signer: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

/**
* Checks if the Safe action provider supports the given network.
*
* @param network - The network to check.
* @returns True if the Safe action provider supports the network, false otherwise.
*/
supportsNetwork = (network: Network) => network.protocolFamily === "evm";
}

/**
* Creates a new SafeWalletActionProvider instance.
*
* @returns A new SafeWalletActionProvider instance
*/
export const safeWalletActionProvider = () => new SafeWalletActionProvider();
1 change: 1 addition & 0 deletions typescript/agentkit/src/wallet-providers/index.ts
Original file line number Diff line number Diff line change
@@ -8,3 +8,4 @@ export * from "./solanaKeypairWalletProvider";
export * from "./privyWalletProvider";
export * from "./privyEvmWalletProvider";
export * from "./privySvmWalletProvider";
export * from "./safeWalletProvider";
Loading