Skip to content

Commit 87ad236

Browse files
committed
chore: add testing framework
1 parent 01b49cf commit 87ad236

24 files changed

+677
-212
lines changed

.env.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
TEST_PRIVATE_KEY=
1+
TEST_PRIVATE_KEY=
2+
RUN_PAID_TESTS=false

.github/actions/build/action.yml

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ runs:
77
- name: Set up Bun
88
uses: oven-sh/setup-bun@v1
99

10+
- name: Set up foundry
11+
uses: foundry-rs/foundry-toolchain@v1
12+
1013
- name: Install dependencies
1114
shell: bash
1215
run: bun install --frozen-lockfile

.github/actions/install-dependencies/action.yml

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ runs:
77
- name: Set up Bun
88
uses: oven-sh/setup-bun@v1
99

10+
- name: Set up foundry
11+
uses: foundry-rs/foundry-toolchain@v1
12+
1013
- name: Install dependencies
1114
shell: bash
1215
run: |

.github/workflows/funded-tests.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: funded-tests
2+
on:
3+
workflow_dispatch:
4+
pull_request_review:
5+
types: [submitted]
6+
jobs:
7+
funded-tests:
8+
name: funded-tests
9+
permissions: write-all
10+
runs-on: ubuntu-latest
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}-funded-tests
13+
cancel-in-progress: true
14+
steps:
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 22
18+
19+
- uses: actions/checkout@v4
20+
21+
- name: Install dependencies
22+
uses: ./.github/actions/install-dependencies
23+
24+
- name: Run the tests
25+
run: bun run test
26+
env:
27+
TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }}
28+
CHAIN_ID: ${{ secrets.CHAIN_ID }}
29+
CI: true
30+
RUN_PAID_TESTS: true
31+

.github/workflows/unit-tests.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ jobs:
2222
uses: ./.github/actions/install-dependencies
2323

2424
- name: Run the tests
25-
run: bun test
25+
run: bun run test
2626
env:
2727
TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }}
28-
CI: true
28+
CHAIN_ID: ${{ secrets.CHAIN_ID }}
29+
RUN_PAID_TESTS: false

README.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Then in your linked project, update your package.json dependencies to point to t
2121
"@biconomy/abstractjs": "file:../../abstractjs"
2222
}
2323
}
24+
```
2425

2526
This will run the package in watch mode, and will automatically update the package in your linked project.
2627

@@ -38,8 +39,23 @@ Currently the only tests which this project has interact with testnets, due to s
3839
yet been set-up in a local environment.
3940
- For tests to work, an `.env` file needs to contain the `TEST_PRIVATE_KEY` variable!
4041

41-
```sh
42-
bun i --frozen-lockfile && bun test
42+
**Prerequisites:**
43+
- [Node.js](https://nodejs.org/en/download/package-manager) *(v22 or higher)*
44+
- [Bun](https://bun.sh/) package manager
45+
- [Foundry](https://book.getfoundry.sh/getting-started/installation)
46+
47+
**Setup:**
48+
```bash
49+
bun install --frozen-lockfile
50+
```
51+
52+
**Running Tests:**
53+
```bash
54+
# Run all tests
55+
bun run test
56+
57+
# Run tests for a specific module
58+
bun run test -t=explorer
4359
```
4460

4561

bun.lockb

49.3 KB
Binary file not shown.

package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
},
2222
"files": ["dist"],
2323
"scripts": {
24+
"test": "vitest -c ./tests/vitest.config.ts",
2425
"dev": "bun link && bun run build:types && bun run build:watch",
2526
"changeset": "changeset",
2627
"changeset:release": "bun run build && changeset publish",
@@ -44,10 +45,16 @@
4445
"@size-limit/esbuild-why": "^11.1.6",
4546
"@size-limit/preset-small-lib": "^11.1.6",
4647
"@types/bun": "latest",
48+
"@vitest/coverage-istanbul": "^2.1.8",
49+
"dotenv": "^16.4.7",
50+
"get-port": "^7.1.0",
4751
"gh-pages": "^6.3.0",
52+
"prool": "^0.0.16",
4853
"semver": "^7.6.3",
4954
"size-limit": "^11.1.6",
50-
"tsup": "^8.3.5"
55+
"tsup": "^8.3.5",
56+
"viem-deal": "^2.0.4",
57+
"vitest": "^2.1.8"
5158
},
5259
"peerDependencies": {
5360
"typescript": "^5.0.0",

src/account-vendors/account.test.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { beforeAll, describe, expect, inject, test } from "vitest"
2+
import {
3+
type Chain,
4+
zeroAddress,
5+
type LocalAccount,
6+
isHex,
7+
isAddress,
8+
http
9+
} from "viem"
10+
import { base, baseSepolia, optimism, optimismSepolia } from "viem/chains"
11+
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"
12+
import { mcUSDC } from "../commons/tokens/stablecoins"
13+
import type { MultichainSmartAccount } from "./account"
14+
import { toMultichainNexusAccount } from "./nexus/multichain-nexus.account"
15+
import { toMeeCompliantNexusAccount } from "./nexus/nexus-mee-compliant"
16+
17+
describe("accounts", async () => {
18+
let eoa: LocalAccount
19+
let mcNexusTestnet: MultichainSmartAccount
20+
let mcNexusMainnet: MultichainSmartAccount
21+
22+
beforeAll(async () => {
23+
eoa = privateKeyToAccount(generatePrivateKey())
24+
25+
mcNexusTestnet = await toMultichainNexusAccount({
26+
chains: [baseSepolia, optimismSepolia],
27+
signer: eoa
28+
})
29+
30+
mcNexusMainnet = await toMultichainNexusAccount({
31+
chains: [base, optimism],
32+
signer: eoa
33+
})
34+
})
35+
36+
test("should have configured accounts correctly", async () => {
37+
expect(mcNexusMainnet.deployments.length).toEqual(2)
38+
expect(mcNexusTestnet.deployments.length).toEqual(2)
39+
expect(mcNexusTestnet.deploymentOn(baseSepolia.id).address).toEqual(
40+
mcNexusTestnet.deploymentOn(optimismSepolia.id).address
41+
)
42+
})
43+
44+
test("should sign message using MEE Compliant Nexus Account", async () => {
45+
const nexus = await toMeeCompliantNexusAccount({
46+
chain: optimism,
47+
signer: eoa,
48+
transport: http()
49+
})
50+
51+
expect(isAddress(nexus.address)).toBeTruthy()
52+
53+
const signed = await nexus.signMessage({ message: { raw: "0xABC" } })
54+
expect(isHex(signed)).toBeTruthy()
55+
})
56+
57+
test("should read usdc balance on mainnet", async () => {
58+
const readAddress = mcNexusMainnet.deploymentOn(optimism.id).address
59+
const usdcBalanceOnChains = await mcUSDC.read({
60+
account: mcNexusMainnet,
61+
functionName: "balanceOf",
62+
args: [readAddress],
63+
onChains: [base, optimism]
64+
})
65+
66+
expect(usdcBalanceOnChains.length).toEqual(2)
67+
})
68+
})

src/account-vendors/nexus/multichain-nexus.account.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import type { Signer } from "@biconomy/sdk"
2-
import { http, type Chain, type Transport } from "viem"
3-
import type { NonEmptyArray } from "../../utils/types/util.type"
2+
import { http, type Chain } from "viem"
43
import { MultichainSmartAccount } from "../account"
54
import { toMeeCompliantNexusAccount } from "./nexus-mee-compliant"
65

76
export type MeeNexusParams = {
87
signer: Signer
9-
chains: NonEmptyArray<Chain>
8+
chains: Chain[]
109
}
1110

1211
export async function toMultichainNexusAccount(

src/commons/tokens/stablecoins.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { erc20Abi } from "viem"
2-
import { arbitrum, avalanche, base, optimism, polygon } from "viem/chains"
3-
import { address } from "../../primitives"
42
import {
5-
MultichainContract,
6-
getMultichainContract
7-
} from "../../utils/contract/getMultichainContract"
3+
anvil,
4+
arbitrum,
5+
avalanche,
6+
base,
7+
baseSepolia,
8+
optimism,
9+
polygon
10+
} from "viem/chains"
11+
import { getMultichainContract } from "../../utils/contract/getMultichainContract"
812

913
export const mcUSDC = getMultichainContract<typeof erc20Abi>({
1014
abi: erc20Abi,
1115
deployments: [
1216
["0xaf88d065e77c8cC2239327C5EDb3A432268e5831", arbitrum.id],
1317
["0x0b2c639c533813f4aa9d7837caf62653d097ff85", optimism.id],
1418
["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", base.id],
19+
["0x036CbD53842c5426634e7929541eC2318f3dCF7e", baseSepolia.id],
20+
["0x036CbD53842c5426634e7929541eC2318f3dCF7e", anvil.id],
1521
["0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", avalanche.id],
1622
["0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", polygon.id]
1723
]

src/mee.service.test.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { type LocalAccount, type Chain, zeroAddress, isHex } from "viem"
2+
import { base } from "viem/chains"
3+
import { inject, describe, beforeAll, test, expect } from "vitest"
4+
import { initNetwork } from "../tests/config"
5+
import {
6+
type MultichainSmartAccount,
7+
toMultichainNexusAccount
8+
} from "./account-vendors"
9+
import { createMeeService } from "./mee.service"
10+
import {
11+
supertransaction,
12+
type SupportedFeeChainId,
13+
signMeeQuote
14+
} from "./utils"
15+
import { getExplorerTxLink } from "./utils/explorer/explorer"
16+
import { buildMeeUserOp } from "./workflow"
17+
18+
const runPaidTests = inject("runPaidTests")
19+
20+
describe.runIf(runPaidTests)("meeService", async (args) => {
21+
let eoa: LocalAccount
22+
let paymentChain: Chain
23+
let mcNexusMainnet: MultichainSmartAccount
24+
25+
const meeService = createMeeService()
26+
27+
beforeAll(async () => {
28+
const network = await initNetwork("NETWORK_FROM_ENV")
29+
eoa = network.eoa
30+
paymentChain = network.paymentChain
31+
32+
mcNexusMainnet = await toMultichainNexusAccount({
33+
chains: [base, paymentChain],
34+
signer: eoa
35+
})
36+
})
37+
38+
test("should get a quote", async () => {
39+
const mcNexusMainnet = await toMultichainNexusAccount({
40+
chains: [base, paymentChain],
41+
signer: eoa
42+
})
43+
44+
const quote = await supertransaction()
45+
.injectAccount(mcNexusMainnet)
46+
.payGasWith("USDC", { on: paymentChain.id as SupportedFeeChainId })
47+
.addInstructions(
48+
buildMeeUserOp({
49+
calls: {
50+
to: zeroAddress,
51+
gasLimit: 50_000n,
52+
value: 0n
53+
},
54+
chainId: base.id
55+
})
56+
)
57+
.getQuote(meeService)
58+
59+
expect(quote.hash.startsWith("0x")).toBeTruthy()
60+
61+
const receipt = await meeService.execute(
62+
await signMeeQuote({
63+
executionMode: "direct-to-mee",
64+
quote: quote,
65+
signer: eoa
66+
})
67+
)
68+
69+
expect(isHex(receipt.hash)).toBeTruthy()
70+
const explorerUrl = getExplorerTxLink(receipt.hash)
71+
72+
expect(explorerUrl).toEqual(
73+
`https://meescan.biconomy.io/details/${receipt.hash}`
74+
)
75+
console.log(`Supertransaction: ${explorerUrl}`)
76+
})
77+
})

src/mee.service.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,8 @@ export function formatMeeSignature(parameters: {
243243
export function createMeeService(params?: MeeServiceInitParams) {
244244
console.warn(`
245245
--------------------------- READ ----------------------------------------------
246-
You are using the Developer Preview of the Biconomy MEE! The SDK has not been
247-
thoroughly tested and the underlying contracts are still in the auditing process.
248-
The interface, package name and developer flow might change significantly from now
249-
until the release data.
250-
251-
This Developer preview is meant only as a demonstrator of the capabilities
252-
for the MEE stack. Do not use in commercial projects!
246+
You are using the Developer Preview of the Biconomy MEE. The underlying
247+
contracts are still being audited.
253248
-------------------------------------------------------------------------------
254249
`)
255250
return new MeeService(params)

0 commit comments

Comments
 (0)