Skip to content

Commit 69b9eb8

Browse files
Add test for sending userops (#714)
## Changes - Linear: https://linear.app/thirdweb/issue/INF-171/ - Here is the problem being solved. - Here are the changes made. ## How this PR will be tested - [ ] Open the dashboard and click X. Result: A modal should appear. - [ ] Call the /foo/bar API. Result: Returns 200 with "baz" in the response body. ## Output (Example: Screenshot/GIF for UI changes, cURL output for API changes) <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the error handling in the `pollTransactionStatus` function and adding user operation tests in the `userop.test.ts` file. It introduces an `errorMessage` property and improves transaction error logging. ### Detailed summary - Added `errorMessage` property to the `Timing` type in `transactions.ts`. - Changed `timing` to be a constant in `pollTransactionStatus`. - Improved error logging in `pollTransactionStatus`. - Added a suite of user operation tests in `userop.test.ts`. - Implemented tests for sending NFT claims and handling permission errors. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent d17c414 commit 69b9eb8

File tree

2 files changed

+112
-2
lines changed

2 files changed

+112
-2
lines changed

test/e2e/tests/userop.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { beforeAll, describe, expect, test } from "bun:test";
2+
import { type Address, getAddress } from "thirdweb";
3+
import { sepolia } from "thirdweb/chains";
4+
import { DEFAULT_ACCOUNT_FACTORY_V0_6 } from "thirdweb/wallets/smart";
5+
import type { ApiError } from "../../../sdk/dist/declarations/src/core/ApiError";
6+
import type { setupEngine } from "../utils/engine";
7+
import { pollTransactionStatus } from "../utils/transactions";
8+
import { setup } from "./setup";
9+
10+
describe("Userop Tests", () => {
11+
let engine: ReturnType<typeof setupEngine>;
12+
let backendWallet: Address;
13+
let accountAddress: Address;
14+
15+
const accountFactoryAddress = DEFAULT_ACCOUNT_FACTORY_V0_6;
16+
17+
beforeAll(async () => {
18+
const { engine: _engine, backendWallet: _backendWallet } = await setup();
19+
engine = _engine;
20+
backendWallet = _backendWallet as Address;
21+
22+
const addr = await engine.accountFactory.predictAccountAddress(
23+
backendWallet,
24+
sepolia.id.toString(),
25+
accountFactoryAddress,
26+
);
27+
28+
accountAddress = getAddress(addr.result);
29+
});
30+
31+
test("Should send a nft claim userop", async () => {
32+
const writeRes = await engine.erc1155.erc1155ClaimTo(
33+
sepolia.id.toString(),
34+
"0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8",
35+
backendWallet,
36+
{
37+
quantity: "1",
38+
receiver: accountAddress,
39+
tokenId: "0",
40+
},
41+
false,
42+
undefined,
43+
accountAddress,
44+
accountFactoryAddress,
45+
);
46+
47+
expect(writeRes.result.queueId).toBeDefined();
48+
49+
const writeTransactionStatus = await pollTransactionStatus(
50+
engine,
51+
writeRes.result.queueId,
52+
true,
53+
);
54+
55+
expect(writeTransactionStatus.minedAt).toBeDefined();
56+
});
57+
58+
test("Should throw decoded error with simulate false", async () => {
59+
const res = await engine.contractRoles.grantContractRole(
60+
sepolia.id.toString(),
61+
"0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8",
62+
backendWallet,
63+
{
64+
address: accountAddress,
65+
role: "minter",
66+
},
67+
false,
68+
undefined,
69+
accountAddress,
70+
accountFactoryAddress,
71+
);
72+
73+
expect(res.result.queueId).toBeDefined();
74+
75+
const writeTransactionStatus = await pollTransactionStatus(
76+
engine,
77+
res.result.queueId,
78+
true,
79+
);
80+
81+
expect(writeTransactionStatus.errorMessage).toBe(
82+
`Error - Permissions: account ${accountAddress.toLowerCase()} is missing role 0x0000000000000000000000000000000000000000000000000000000000000000`,
83+
);
84+
});
85+
86+
test("Should throw decoded error with simulate true", async () => {
87+
try {
88+
await engine.contractRoles.grantContractRole(
89+
sepolia.id.toString(),
90+
"0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8",
91+
backendWallet,
92+
{
93+
address: accountAddress,
94+
role: "minter",
95+
},
96+
true,
97+
undefined,
98+
accountAddress,
99+
accountFactoryAddress,
100+
);
101+
} catch (e) {
102+
expect((e as ApiError).body?.error?.message).toBe(
103+
`Simulation failed: TransactionError: Error - Permissions: account ${accountAddress.toLowerCase()} is missing role 0x0000000000000000000000000000000000000000000000000000000000000000`,
104+
);
105+
}
106+
});
107+
});

test/e2e/utils/transactions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type Timing = {
77
queuedAt?: number;
88
sentAt?: number;
99
minedAt?: number;
10+
errorMessage?: string;
1011
};
1112

1213
export const sendNoOpTransaction = async (
@@ -65,7 +66,7 @@ export const pollTransactionStatus = async (
6566
log = false,
6667
) => {
6768
let mined = false;
68-
let timing = {} as Timing;
69+
const timing: Timing = {};
6970

7071
while (!mined) {
7172
await sleep(Math.random() * CONFIG.STAGGER_MAX);
@@ -77,7 +78,8 @@ export const pollTransactionStatus = async (
7778
}
7879

7980
if (res.result.errorMessage) {
80-
console.error(`Transaction Error:`, res.result.errorMessage);
81+
console.error("Transaction Error:", res.result.errorMessage);
82+
timing.errorMessage = res.result.errorMessage;
8183
return timing;
8284
}
8385

@@ -96,6 +98,7 @@ export const pollTransactionStatus = async (
9698

9799
if (res.result.status === "errored") {
98100
console.error("Transaction errored:", res.result.errorMessage);
101+
timing.errorMessage = res.result.errorMessage ?? undefined;
99102
return timing;
100103
}
101104

0 commit comments

Comments
 (0)