Skip to content

Commit d11dd4e

Browse files
authored
Add EVM contract address to scripts (#2121)
1 parent e3dae26 commit d11dd4e

File tree

3 files changed

+111
-55
lines changed

3 files changed

+111
-55
lines changed

packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {mockUser} from "../__mocks__/fcl"
33
import * as fcl from "@onflow/fcl"
44
import * as rlp from "@onflow/rlp"
55
import {CurrentUser} from "@onflow/typedefs"
6+
import {ChainIdStore, NetworkManager} from "../network/network-manager"
7+
import {BehaviorSubject, Subject} from "../util/observable"
68

79
jest.mock("@onflow/fcl", () => {
810
const fcl = jest.requireActual("@onflow/fcl")
@@ -24,35 +26,41 @@ const mockFcl = jest.mocked(fcl)
2426
const mockQuery = jest.mocked(fcl.query)
2527

2628
describe("AccountManager", () => {
29+
let networkManager: jest.Mocked<NetworkManager>
30+
let userMock: ReturnType<typeof mockUser>
31+
2732
beforeEach(() => {
2833
jest.clearAllMocks()
34+
35+
const chainId$ = new BehaviorSubject<number | null>(747)
36+
networkManager = {
37+
$chainId: chainId$,
38+
getChainId: () => chainId$.getValue(),
39+
} as any as jest.Mocked<NetworkManager>
40+
userMock = mockUser()
2941
})
3042

3143
it("should initialize with null COA address", async () => {
32-
const user = mockUser()
33-
const accountManager = new AccountManager(user.mock)
44+
const accountManager = new AccountManager(userMock.mock, networkManager)
3445
expect(await accountManager.getCOAAddress()).toBeNull()
3546
expect(await accountManager.getAccounts()).toEqual([])
3647
})
3748

3849
it("should reset state when the user is not logged in", async () => {
39-
const user = mockUser()
40-
41-
const accountManager = new AccountManager(user.mock)
50+
const accountManager = new AccountManager(userMock.mock, networkManager)
4251

4352
expect(await accountManager.getCOAAddress()).toBeNull()
4453
expect(await accountManager.getAccounts()).toEqual([])
4554
})
4655

4756
it("should fetch and update COA address when user logs in", async () => {
48-
const user = mockUser()
4957
mockQuery.mockResolvedValue("0x123")
5058

51-
const accountManager = new AccountManager(user.mock)
59+
const accountManager = new AccountManager(userMock.mock, networkManager)
5260

5361
expect(await accountManager.getCOAAddress()).toBe(null)
5462

55-
user.set!({addr: "0x1"} as CurrentUser)
63+
userMock.set!({addr: "0x1"} as CurrentUser)
5664

5765
expect(await accountManager.getCOAAddress()).toBe("0x123")
5866
expect(await accountManager.getAccounts()).toEqual(["0x123"])
@@ -63,26 +71,24 @@ describe("AccountManager", () => {
6371
})
6472

6573
it("should not update COA address if user has not changed", async () => {
66-
const user = mockUser()
6774
mockQuery.mockResolvedValue("0x123")
6875

69-
const accountManager = new AccountManager(user.mock)
76+
const accountManager = new AccountManager(userMock.mock, networkManager)
7077

71-
user.set!({addr: "0x1"} as CurrentUser)
78+
userMock.set!({addr: "0x1"} as CurrentUser)
7279

7380
await new Promise(setImmediate)
7481

7582
expect(await accountManager.getCOAAddress()).toBe("0x123")
7683
expect(fcl.query).toHaveBeenCalledTimes(1)
7784

78-
user.set!({addr: "0x1"} as CurrentUser)
85+
userMock.set!({addr: "0x1"} as CurrentUser)
7986

8087
expect(await accountManager.getCOAAddress()).toBe("0x123")
8188
expect(fcl.query).toHaveBeenCalledTimes(1) // Should not have fetched again
8289
})
8390

8491
it("should not update COA address if fetch is outdated when user changes", async () => {
85-
const user = mockUser()
8692
mockQuery.mockResolvedValue("0x123")
8793

8894
mockQuery
@@ -93,39 +99,36 @@ describe("AccountManager", () => {
9399
// 2nd fetch: immediate
94100
.mockResolvedValueOnce("0x456")
95101

96-
const accountManager = new AccountManager(user.mock)
102+
const accountManager = new AccountManager(userMock.mock, networkManager)
97103

98-
await user.set!({addr: "0x1"} as CurrentUser)
99-
await user.set!({addr: "0x2"} as CurrentUser)
104+
await userMock.set!({addr: "0x1"} as CurrentUser)
105+
await userMock.set!({addr: "0x2"} as CurrentUser)
100106

101107
// The second fetch (for address 0x2) is the latest, so "0x456"
102108
expect(await accountManager.getCOAAddress()).toBe("0x456")
103109
})
104110

105111
it("should throw if COA address fetch fails", async () => {
106-
const user = mockUser()
107112
mockQuery.mockRejectedValueOnce(new Error("Fetch failed"))
108113

109-
const accountManager = new AccountManager(user.mock)
114+
const accountManager = new AccountManager(userMock.mock, networkManager)
110115

111-
await user.set!({addr: "0x1"} as CurrentUser)
116+
await userMock.set!({addr: "0x1"} as CurrentUser)
112117

113118
await expect(accountManager.getCOAAddress()).rejects.toThrow("Fetch failed")
114119
})
115120

116121
it("should handle user changes correctly", async () => {
117-
const user = mockUser()
118-
119122
mockQuery
120123
.mockResolvedValueOnce("0x123") // for user 0x1
121124
.mockResolvedValueOnce("0x456") // for user 0x2
122125

123-
const accountManager = new AccountManager(user.mock)
126+
const accountManager = new AccountManager(userMock.mock, networkManager)
124127

125-
await user.set({addr: "0x1"} as CurrentUser)
128+
await userMock.set({addr: "0x1"} as CurrentUser)
126129
expect(await accountManager.getCOAAddress()).toBe("0x123")
127130

128-
await user.set({addr: "0x2"} as CurrentUser)
131+
await userMock.set({addr: "0x2"} as CurrentUser)
129132

130133
await new Promise(setImmediate)
131134
expect(await accountManager.getCOAAddress()).toBe("0x456")
@@ -134,14 +137,12 @@ describe("AccountManager", () => {
134137
it("should call the callback with updated accounts in subscribe", async () => {
135138
mockQuery.mockResolvedValue("0x123")
136139

137-
const user = mockUser()
138-
139-
const accountManager = new AccountManager(user.mock)
140+
const accountManager = new AccountManager(userMock.mock, networkManager)
140141

141142
const callback = jest.fn()
142143
accountManager.subscribe(callback)
143144

144-
user.set({addr: "0x1"} as CurrentUser)
145+
userMock.set({addr: "0x1"} as CurrentUser)
145146

146147
await new Promise(setImmediate)
147148

@@ -150,11 +151,10 @@ describe("AccountManager", () => {
150151

151152
it("should reset accounts in subscribe if user is not authenticated", async () => {
152153
mockQuery.mockResolvedValue("0x123")
153-
const user = mockUser()
154154

155155
const callback = jest.fn()
156156

157-
const accountManager = new AccountManager(user.mock)
157+
const accountManager = new AccountManager(userMock.mock, networkManager)
158158

159159
accountManager.subscribe(callback)
160160

@@ -166,11 +166,11 @@ describe("AccountManager", () => {
166166
it("should call the callback when COA address is updated", async () => {
167167
const callback = jest.fn()
168168

169-
const user = mockUser({addr: "0x1"} as CurrentUser)
170-
171169
mockQuery.mockResolvedValueOnce("0x123")
172170

173-
const accountManager = new AccountManager(user.mock)
171+
const accountManager = new AccountManager(userMock.mock, networkManager)
172+
173+
userMock.set({addr: "0x1"} as CurrentUser)
174174

175175
accountManager.subscribe(callback)
176176

@@ -180,23 +180,31 @@ describe("AccountManager", () => {
180180
})
181181

182182
it("should return an empty array when COA address is null", async () => {
183-
const {mock: user} = mockUser()
184-
const accountManager = new AccountManager(user)
183+
const accountManager = new AccountManager(userMock.mock, networkManager)
185184
expect(await accountManager.getAccounts()).toEqual([])
186185
})
187186

188187
it("should return COA address array when available", async () => {
189188
mockQuery.mockResolvedValueOnce("0x123")
190-
const {mock: user} = mockUser({addr: "0x1"} as CurrentUser)
189+
userMock.set({addr: "0x1"} as CurrentUser)
191190

192-
const accountManager = new AccountManager(user)
191+
const accountManager = new AccountManager(userMock.mock, networkManager)
193192

194193
expect(await accountManager.getAccounts()).toEqual(["0x123"])
195194
})
196195
})
197196

198197
describe("send transaction", () => {
198+
let networkManager: jest.Mocked<NetworkManager>
199+
let $mockChainId: BehaviorSubject<number | null>
200+
199201
beforeEach(() => {
202+
$mockChainId = new BehaviorSubject<number | null>(747)
203+
networkManager = {
204+
$chainId: $mockChainId,
205+
getChainId: () => $mockChainId.getValue(),
206+
} as any as jest.Mocked<NetworkManager>
207+
200208
jest.clearAllMocks()
201209
})
202210

@@ -219,7 +227,7 @@ describe("send transaction", () => {
219227
jest.mocked(fcl.query).mockResolvedValue("0x1234")
220228

221229
const user = mockUser({addr: "0x4444"} as CurrentUser).mock
222-
const accountManager = new AccountManager(user)
230+
const accountManager = new AccountManager(user, networkManager)
223231

224232
const tx = {
225233
to: "0x1234",
@@ -248,6 +256,9 @@ describe("send transaction", () => {
248256
})
249257

250258
test("send transaction testnet", async () => {
259+
// Set chainId to testnet
260+
$mockChainId.next(646)
261+
251262
const mockTxResult = {
252263
onceExecuted: jest.fn().mockResolvedValue({
253264
events: [
@@ -266,7 +277,7 @@ describe("send transaction", () => {
266277
jest.mocked(fcl.query).mockResolvedValue("0x1234")
267278

268279
const user = mockUser({addr: "0x4444"} as CurrentUser)
269-
const accountManager = new AccountManager(user.mock)
280+
const accountManager = new AccountManager(user.mock, networkManager)
270281

271282
const tx = {
272283
to: "0x1234",
@@ -306,7 +317,7 @@ describe("send transaction", () => {
306317
jest.mocked(fcl.query).mockResolvedValue("0x1234")
307318

308319
const user = mockUser({addr: "0x4444"} as CurrentUser)
309-
const accountManager = new AccountManager(user.mock)
320+
const accountManager = new AccountManager(user.mock, networkManager)
310321

311322
const tx = {
312323
to: "0x1234",
@@ -315,7 +326,7 @@ describe("send transaction", () => {
315326
data: "0x1234",
316327
nonce: "0",
317328
gas: "0",
318-
chainId: "646",
329+
chainId: "747",
319330
}
320331

321332
await expect(accountManager.sendTransaction(tx)).rejects.toThrow(
@@ -326,7 +337,7 @@ describe("send transaction", () => {
326337
test("throws error if from address does not match user address", async () => {
327338
jest.mocked(fcl.query).mockResolvedValue("0x1234")
328339
const user = mockUser({addr: "0x4444"} as CurrentUser)
329-
const accountManager = new AccountManager(user.mock)
340+
const accountManager = new AccountManager(user.mock, networkManager)
330341

331342
const tx = {
332343
to: "0x1234",
@@ -339,14 +350,15 @@ describe("send transaction", () => {
339350
}
340351

341352
await expect(accountManager.sendTransaction(tx)).rejects.toThrow(
342-
`From address does not match authenticated user address.\nUser: 0x1234\nFrom: 0x4567`
353+
`Chain ID does not match the current network. Expected: 747, Received: 646`
343354
)
344355

345356
expect(fcl.mutate).not.toHaveBeenCalled()
346357
})
347358
})
348359

349360
describe("signMessage", () => {
361+
let networkManager: jest.Mocked<NetworkManager>
350362
let accountManager: AccountManager
351363
let user: ReturnType<typeof mockUser>["mock"]
352364
let updateUser: ReturnType<typeof mockUser>["set"]
@@ -355,7 +367,12 @@ describe("signMessage", () => {
355367
jest.clearAllMocks()
356368
;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser))
357369
jest.mocked(fcl.query).mockResolvedValue("0xCOA1")
358-
accountManager = new AccountManager(user)
370+
const $mockChainId = new BehaviorSubject<number | null>(747)
371+
networkManager = {
372+
$chainId: $mockChainId,
373+
getChainId: () => $mockChainId.getValue(),
374+
} as any as jest.Mocked<NetworkManager>
375+
accountManager = new AccountManager(user, networkManager)
359376
})
360377

361378
it("should throw an error if the COA address is not available", async () => {
@@ -400,10 +417,7 @@ describe("signMessage", () => {
400417
})
401418

402419
it("should throw an error if signUserMessage returns an empty array", async () => {
403-
accountManager["coaAddress"] = "0xCOA1"
404-
405-
user.signUserMessage = jest.fn().mockResolvedValue([])
406-
420+
user.signUserMessage.mockResolvedValue([])
407421
await expect(
408422
accountManager.signMessage("Test message", "0xCOA1")
409423
).rejects.toThrow("Failed to sign message")

packages/fcl-ethereum-provider/src/accounts/account-manager.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
switchMap,
2525
} from "../util/observable"
2626
import {EthSignatureResponse} from "../types/eth"
27+
import {NetworkManager} from "../network/network-manager"
28+
import {formatChainId, getContractAddress} from "../util/eth"
2729

2830
export class AccountManager {
2931
private $addressStore = new BehaviorSubject<{
@@ -36,7 +38,10 @@ export class AccountManager {
3638
error: null,
3739
})
3840

39-
constructor(private user: typeof fcl.currentUser) {
41+
constructor(
42+
private user: typeof fcl.currentUser,
43+
private networkManager: NetworkManager
44+
) {
4045
// Create an observable from the user
4146
const $user = new Observable<CurrentUser>(subscriber => {
4247
return this.user.subscribe((currentUser: CurrentUser, error?: Error) => {
@@ -91,8 +96,13 @@ export class AccountManager {
9196
}
9297

9398
private async fetchCOAFromFlowAddress(flowAddr: string): Promise<string> {
99+
const chainId = await this.networkManager.getChainId()
100+
if (!chainId) {
101+
throw new Error("No active chain")
102+
}
103+
94104
const cadenceScript = `
95-
import EVM
105+
import EVM from ${getContractAddress(ContractType.EVM, chainId)}
96106
97107
access(all)
98108
fun main(address: Address): String? {
@@ -156,17 +166,22 @@ export class AccountManager {
156166
chainId: string
157167
}) {
158168
// Find the Flow network based on the chain ID
169+
const parsedChainId = parseInt(chainId)
159170
const flowNetwork = Object.entries(FLOW_CHAINS).find(
160-
([, chain]) => chain.eip155ChainId === parseInt(chainId)
171+
([, chain]) => chain.eip155ChainId === parsedChainId
161172
)?.[0] as FlowNetwork | undefined
162173

163174
if (!flowNetwork) {
164175
throw new Error("Flow network not found for chain ID")
165176
}
166177

167-
const evmContractAddress = fcl.withPrefix(
168-
FLOW_CONTRACTS[ContractType.EVM][flowNetwork]
169-
)
178+
// Validate the chain ID
179+
const currentChainId = await this.networkManager.getChainId()
180+
if (parsedChainId !== currentChainId) {
181+
throw new Error(
182+
`Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${parsedChainId}`
183+
)
184+
}
170185

171186
// Check if the from address matches the authenticated COA address
172187
const expectedCOAAddress = await this.getCOAAddress()
@@ -177,7 +192,7 @@ export class AccountManager {
177192
}
178193

179194
const txId = await fcl.mutate({
180-
cadence: `import EVM from ${evmContractAddress}
195+
cadence: `import EVM from ${getContractAddress(ContractType.EVM, parsedChainId)}
181196
182197
/// Executes the calldata from the signer's COA
183198
///

0 commit comments

Comments
 (0)