Skip to content

Commit 8bed32a

Browse files
committed
improved wallet provider integration
1 parent 3577b27 commit 8bed32a

File tree

8 files changed

+596
-615
lines changed

8 files changed

+596
-615
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { SafeActionProvider } from "./safeActionProvider";
2+
import { EvmWalletProvider } from "../../wallet-providers";
3+
import Safe from "@safe-global/protocol-kit";
4+
import SafeApiKit from "@safe-global/api-kit";
5+
import { waitForTransactionReceipt } from "viem/actions";
6+
import { SafeTransaction } from "@safe-global/safe-core-sdk-types";
7+
8+
jest.mock("@safe-global/protocol-kit");
9+
jest.mock("@safe-global/api-kit");
10+
jest.mock("viem/actions");
11+
12+
describe("Safe Action Provider", () => {
13+
const MOCK_PRIVATE_KEY = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
14+
const MOCK_NETWORK_ID = "base-sepolia";
15+
const MOCK_SAFE_ADDRESS = "0x1234567890123456789012345678901234567890";
16+
const MOCK_SIGNER_ADDRESS = "0x2345678901234567890123456789012345678901";
17+
const MOCK_TX_HASH = "0xabcdef1234567890abcdef1234567890";
18+
19+
const MOCK_TRANSACTION = {
20+
data: {
21+
to: MOCK_SAFE_ADDRESS,
22+
value: "0",
23+
data: "0x",
24+
},
25+
signatures: new Map(),
26+
getSignature: jest.fn(),
27+
addSignature: jest.fn(),
28+
encodedSignatures: jest.fn(),
29+
} as unknown as SafeTransaction;
30+
31+
const MOCK_TX_RESULT = {
32+
hash: MOCK_TX_HASH,
33+
isExecuted: true,
34+
transactionResponse: { hash: MOCK_TX_HASH },
35+
};
36+
37+
let actionProvider: SafeActionProvider;
38+
let mockWallet: jest.Mocked<EvmWalletProvider>;
39+
let mockSafeSDK: jest.Mocked<Safe>;
40+
let mockApiKit: jest.Mocked<SafeApiKit>;
41+
42+
beforeEach(() => {
43+
jest.clearAllMocks();
44+
45+
mockWallet = {
46+
getAddress: jest.fn().mockReturnValue(MOCK_SIGNER_ADDRESS),
47+
getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", networkId: MOCK_NETWORK_ID }),
48+
getPublicClient: jest.fn().mockReturnValue({
49+
transport: {},
50+
chain: {
51+
blockExplorers: {
52+
default: { url: "https://sepolia.basescan.org" },
53+
},
54+
},
55+
getBalance: jest.fn().mockResolvedValue(BigInt(1000000000000000000)), // 1 ETH
56+
}),
57+
} as unknown as jest.Mocked<EvmWalletProvider>;
58+
59+
const mockExternalSigner = {
60+
sendTransaction: jest.fn().mockResolvedValue(MOCK_TX_HASH),
61+
};
62+
63+
const mockReceipt = {
64+
transactionHash: MOCK_TX_HASH,
65+
status: 1,
66+
blockNumber: 123456,
67+
};
68+
69+
(waitForTransactionReceipt as jest.Mock).mockResolvedValue(mockReceipt);
70+
71+
mockSafeSDK = {
72+
getAddress: jest.fn().mockResolvedValue(MOCK_SAFE_ADDRESS),
73+
getOwners: jest.fn().mockResolvedValue([MOCK_SIGNER_ADDRESS]),
74+
getThreshold: jest.fn().mockResolvedValue(1),
75+
createTransaction: jest.fn().mockResolvedValue(MOCK_TRANSACTION),
76+
getTransactionHash: jest.fn(),
77+
signHash: jest.fn().mockResolvedValue({
78+
data: "0x",
79+
signer: MOCK_SIGNER_ADDRESS,
80+
isContractSignature: false,
81+
staticPart: () => "0x",
82+
dynamicPart: () => "0x",
83+
}),
84+
executeTransaction: jest.fn().mockResolvedValue(MOCK_TX_RESULT),
85+
connect: jest.fn().mockReturnThis(),
86+
isSafeDeployed: jest.fn().mockResolvedValue(true),
87+
createSafeDeploymentTransaction: jest.fn().mockResolvedValue({
88+
to: MOCK_SAFE_ADDRESS,
89+
value: "0",
90+
data: "0x",
91+
}),
92+
getSafeProvider: jest.fn().mockReturnValue({
93+
getExternalSigner: jest.fn().mockResolvedValue(mockExternalSigner),
94+
}),
95+
} as unknown as jest.Mocked<Safe>;
96+
97+
(Safe.init as jest.Mock).mockResolvedValue(mockSafeSDK);
98+
99+
mockApiKit = {
100+
getPendingTransactions: jest.fn().mockResolvedValue({ results: [], count: 0 }),
101+
proposeTransaction: jest.fn(),
102+
getTransaction: jest.fn().mockResolvedValue({
103+
safe: MOCK_SAFE_ADDRESS,
104+
to: MOCK_SAFE_ADDRESS,
105+
data: "0x",
106+
value: "0",
107+
operation: 0,
108+
nonce: 0,
109+
safeTxGas: 0,
110+
baseGas: 0,
111+
gasPrice: "0",
112+
gasToken: "0x0000000000000000000000000000000000000000",
113+
refundReceiver: "0x0000000000000000000000000000000000000000",
114+
submissionDate: new Date().toISOString(),
115+
executionDate: new Date().toISOString(),
116+
modified: new Date().toISOString(),
117+
transactionHash: MOCK_TX_HASH,
118+
isExecuted: true,
119+
isSuccessful: true,
120+
safeTxHash: MOCK_TX_HASH,
121+
confirmationsRequired: 1,
122+
confirmations: [
123+
{
124+
owner: MOCK_SIGNER_ADDRESS,
125+
signature: "0x",
126+
signatureType: "EOA",
127+
submissionDate: new Date().toISOString(),
128+
},
129+
],
130+
}),
131+
} as unknown as jest.Mocked<SafeApiKit>;
132+
133+
(SafeApiKit as unknown as jest.Mock).mockImplementation(() => mockApiKit);
134+
135+
actionProvider = new SafeActionProvider({
136+
privateKey: MOCK_PRIVATE_KEY,
137+
networkId: MOCK_NETWORK_ID,
138+
});
139+
});
140+
141+
describe("initializeSafe", () => {
142+
it("should successfully create a new Safe", async () => {
143+
const args = {
144+
signers: ["0x3456789012345678901234567890123456789012"],
145+
threshold: 2,
146+
};
147+
148+
const response = await actionProvider.initializeSafe(mockWallet, args);
149+
150+
expect(Safe.init).toHaveBeenCalled();
151+
expect(response).toContain("Successfully created Safe");
152+
expect(response).toContain(MOCK_SAFE_ADDRESS);
153+
});
154+
155+
it("should handle Safe creation errors", async () => {
156+
const args = {
157+
signers: ["0x3456789012345678901234567890123456789012"],
158+
threshold: 2,
159+
};
160+
161+
(Safe.init as jest.Mock).mockRejectedValue(new Error("Failed to create Safe"));
162+
163+
const response = await actionProvider.initializeSafe(mockWallet, args);
164+
165+
expect(response).toContain("Error creating Safe");
166+
});
167+
});
168+
169+
describe("safeInfo", () => {
170+
it("should successfully get Safe info", async () => {
171+
const args = {
172+
safeAddress: MOCK_SAFE_ADDRESS,
173+
};
174+
175+
const response = await actionProvider.safeInfo(mockWallet, args);
176+
177+
expect(response).toContain(MOCK_SAFE_ADDRESS);
178+
expect(response).toContain("owners:");
179+
expect(response).toContain("Threshold:");
180+
});
181+
182+
it("should handle Safe info errors", async () => {
183+
const args = {
184+
safeAddress: MOCK_SAFE_ADDRESS,
185+
};
186+
187+
mockSafeSDK.getOwners.mockRejectedValue(new Error("Failed to get owners"));
188+
189+
const response = await actionProvider.safeInfo(mockWallet, args);
190+
191+
expect(response).toContain("Error connecting to Safe");
192+
});
193+
});
194+
195+
describe("supportsNetwork", () => {
196+
it("should return true for EVM networks", () => {
197+
const result = actionProvider.supportsNetwork({ protocolFamily: "evm", networkId: "any" });
198+
expect(result).toBe(true);
199+
});
200+
201+
it("should return false for non-EVM networks", () => {
202+
const result = actionProvider.supportsNetwork({
203+
protocolFamily: "bitcoin",
204+
networkId: "any",
205+
});
206+
expect(result).toBe(false);
207+
});
208+
});
209+
});

0 commit comments

Comments
 (0)