diff --git a/test/e2e/tests/userop.test.ts b/test/e2e/tests/userop.test.ts new file mode 100644 index 000000000..269349afb --- /dev/null +++ b/test/e2e/tests/userop.test.ts @@ -0,0 +1,107 @@ +import { beforeAll, describe, expect, test } from "bun:test"; +import { type Address, getAddress } from "thirdweb"; +import { sepolia } from "thirdweb/chains"; +import { DEFAULT_ACCOUNT_FACTORY_V0_6 } from "thirdweb/wallets/smart"; +import type { ApiError } from "../../../sdk/dist/declarations/src/core/ApiError"; +import type { setupEngine } from "../utils/engine"; +import { pollTransactionStatus } from "../utils/transactions"; +import { setup } from "./setup"; + +describe("Userop Tests", () => { + let engine: ReturnType; + let backendWallet: Address; + let accountAddress: Address; + + const accountFactoryAddress = DEFAULT_ACCOUNT_FACTORY_V0_6; + + beforeAll(async () => { + const { engine: _engine, backendWallet: _backendWallet } = await setup(); + engine = _engine; + backendWallet = _backendWallet as Address; + + const addr = await engine.accountFactory.predictAccountAddress( + backendWallet, + sepolia.id.toString(), + accountFactoryAddress, + ); + + accountAddress = getAddress(addr.result); + }); + + test("Should send a nft claim userop", async () => { + const writeRes = await engine.erc1155.erc1155ClaimTo( + sepolia.id.toString(), + "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + backendWallet, + { + quantity: "1", + receiver: accountAddress, + tokenId: "0", + }, + false, + undefined, + accountAddress, + accountFactoryAddress, + ); + + expect(writeRes.result.queueId).toBeDefined(); + + const writeTransactionStatus = await pollTransactionStatus( + engine, + writeRes.result.queueId, + true, + ); + + expect(writeTransactionStatus.minedAt).toBeDefined(); + }); + + test("Should throw decoded error with simulate false", async () => { + const res = await engine.contractRoles.grantContractRole( + sepolia.id.toString(), + "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + backendWallet, + { + address: accountAddress, + role: "minter", + }, + false, + undefined, + accountAddress, + accountFactoryAddress, + ); + + expect(res.result.queueId).toBeDefined(); + + const writeTransactionStatus = await pollTransactionStatus( + engine, + res.result.queueId, + true, + ); + + expect(writeTransactionStatus.errorMessage).toBe( + `Error - Permissions: account ${accountAddress.toLowerCase()} is missing role 0x0000000000000000000000000000000000000000000000000000000000000000`, + ); + }); + + test("Should throw decoded error with simulate true", async () => { + try { + await engine.contractRoles.grantContractRole( + sepolia.id.toString(), + "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + backendWallet, + { + address: accountAddress, + role: "minter", + }, + true, + undefined, + accountAddress, + accountFactoryAddress, + ); + } catch (e) { + expect((e as ApiError).body?.error?.message).toBe( + `Simulation failed: TransactionError: Error - Permissions: account ${accountAddress.toLowerCase()} is missing role 0x0000000000000000000000000000000000000000000000000000000000000000`, + ); + } + }); +}); diff --git a/test/e2e/utils/transactions.ts b/test/e2e/utils/transactions.ts index 968fa8484..7e51b5a6c 100644 --- a/test/e2e/utils/transactions.ts +++ b/test/e2e/utils/transactions.ts @@ -7,6 +7,7 @@ type Timing = { queuedAt?: number; sentAt?: number; minedAt?: number; + errorMessage?: string; }; export const sendNoOpTransaction = async ( @@ -65,7 +66,7 @@ export const pollTransactionStatus = async ( log = false, ) => { let mined = false; - let timing = {} as Timing; + const timing: Timing = {}; while (!mined) { await sleep(Math.random() * CONFIG.STAGGER_MAX); @@ -77,7 +78,8 @@ export const pollTransactionStatus = async ( } if (res.result.errorMessage) { - console.error(`Transaction Error:`, res.result.errorMessage); + console.error("Transaction Error:", res.result.errorMessage); + timing.errorMessage = res.result.errorMessage; return timing; } @@ -96,6 +98,7 @@ export const pollTransactionStatus = async ( if (res.result.status === "errored") { console.error("Transaction errored:", res.result.errorMessage); + timing.errorMessage = res.result.errorMessage ?? undefined; return timing; }