1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { getContractInterface as getContractInterfaceBedrock } from '@eth-optimism/contracts-bedrock'
import { ethers, Contract } from 'ethers'
import { toAddress } from './coercion'
import { DeepPartial } from './type-utils'
import { CrossChainMessenger } from '../cross-chain-messenger'
import { StandardBridgeAdapter, ETHBridgeAdapter } from '../adapters'
import {
CONTRACT_ADDRESSES,
DEFAULT_L2_CONTRACT_ADDRESSES,
BRIDGE_ADAPTER_DATA,
} from './chain-constants'
import {
OEContracts,
OEL1Contracts,
OEL2Contracts,
OEContractsLike,
AddressLike,
BridgeAdapters,
BridgeAdapterData,
} from '../interfaces'
/**
* We've changed some contract names in this SDK to be a bit nicer. Here we remap these nicer names
* back to the original contract names so we can look them up.
*/
const NAME_REMAPPING = {
AddressManager: 'Lib_AddressManager' as const,
OVM_L1BlockNumber: 'iOVM_L1BlockNumber' as const,
WETH: 'WETH9' as const,
BedrockMessagePasser: 'L2ToL1MessagePasser' as const,
}
/**
* Returns an ethers.Contract object for the given name, connected to the appropriate address for
* the given L2 chain ID. Users can also provide a custom address to connect the contract to
* instead. If the chain ID is not known then the user MUST provide a custom address or this
* function will throw an error.
*
* @param contractName Name of the contract to connect to.
* @param l2ChainId Chain ID for the L2 network.
* @param opts Additional options for connecting to the contract.
* @param opts.address Custom address to connect to the contract.
* @param opts.signerOrProvider Signer or provider to connect to the contract.
* @returns An ethers.Contract object connected to the appropriate address and interface.
*/
export const getOEContract = (
contractName: keyof OEL1Contracts | keyof OEL2Contracts,
l2ChainId: number,
opts: {
address?: AddressLike
signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {}
): Contract => {
const addresses = CONTRACT_ADDRESSES[l2ChainId]
if (addresses === undefined && opts.address === undefined) {
throw new Error(
`cannot get contract ${contractName} for unknown L2 chain ID ${l2ChainId}, you must provide an address`
)
}
// Bedrock interfaces are backwards compatible. We can prefer Bedrock interfaces over legacy
// interfaces if they exist.
const name = NAME_REMAPPING[contractName] || contractName
let iface: ethers.utils.Interface
try {
iface = getContractInterfaceBedrock(name)
} catch (err) {
iface = getContractInterface(name)
}
return new Contract(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
iface,
opts.signerOrProvider
)
}
/**
* Automatically connects to all contract addresses, both L1 and L2, for the given L2 chain ID. The
* user can provide custom contract address overrides for L1 or L2 contracts. If the given chain ID
* is not known then the user MUST provide custom contract addresses for ALL L1 contracts or this
* function will throw an error.
*
* @param l2ChainId Chain ID for the L2 network.
* @param opts Additional options for connecting to the contracts.
* @param opts.l1SignerOrProvider: Signer or provider to connect to the L1 contracts.
* @param opts.l2SignerOrProvider: Signer or provider to connect to the L2 contracts.
* @param opts.overrides Custom contract address overrides for L1 or L2 contracts.
* @returns An object containing ethers.Contract objects connected to the appropriate addresses on
* both L1 and L2.
*/
export const getAllOEContracts = (
l2ChainId: number,
opts: {
l1SignerOrProvider?: ethers.Signer | ethers.providers.Provider
l2SignerOrProvider?: ethers.Signer | ethers.providers.Provider
overrides?: DeepPartial<OEContractsLike>
} = {}
): OEContracts => {
const addresses = CONTRACT_ADDRESSES[l2ChainId] || {
l1: {
AddressManager: undefined,
L1CrossDomainMessenger: undefined,
L1StandardBridge: undefined,
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
OptimismPortal: undefined,
L2OutputOracle: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
// Attach all L1 contracts.
const l1Contracts = {} as OEL1Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l1)) {
l1Contracts[contractName] = getOEContract(
contractName as keyof OEL1Contracts,
l2ChainId,
{
address: opts.overrides?.l1?.[contractName] || contractAddress,
signerOrProvider: opts.l1SignerOrProvider,
}
)
}
// Attach all L2 contracts.
const l2Contracts = {} as OEL2Contracts
for (const [contractName, contractAddress] of Object.entries(addresses.l2)) {
l2Contracts[contractName] = getOEContract(
contractName as keyof OEL2Contracts,
l2ChainId,
{
address: opts.overrides?.l2?.[contractName] || contractAddress,
signerOrProvider: opts.l2SignerOrProvider,
}
)
}
return {
l1: l1Contracts,
l2: l2Contracts,
}
}
/**
* Gets a series of bridge adapters for the given L2 chain ID.
*
* @param l2ChainId Chain ID for the L2 network.
* @param messenger Cross chain messenger to connect to the bridge adapters
* @param opts Additional options for connecting to the custom bridges.
* @param opts.overrides Custom bridge adapters.
* @returns An object containing all bridge adapters
*/
export const getBridgeAdapters = (
l2ChainId: number,
messenger: CrossChainMessenger,
opts?: {
overrides?: BridgeAdapterData
contracts?: DeepPartial<OEContractsLike>
}
): BridgeAdapters => {
const adapterData: BridgeAdapterData = {
...(CONTRACT_ADDRESSES[l2ChainId]
? {
Standard: {
Adapter: StandardBridgeAdapter,
l1Bridge:
opts.contracts?.l1?.L1StandardBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
l2Bridge: predeploys.L2StandardBridge,
},
ETH: {
Adapter: ETHBridgeAdapter,
l1Bridge:
opts.contracts?.l1?.L1StandardBridge ||
CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge,
l2Bridge: predeploys.L2StandardBridge,
},
}
: {}),
...(BRIDGE_ADAPTER_DATA[l2ChainId] || {}),
...(opts?.overrides || {}),
}
const adapters: BridgeAdapters = {}
for (const [bridgeName, bridgeData] of Object.entries(adapterData)) {
adapters[bridgeName] = new bridgeData.Adapter({
messenger,
l1Bridge: bridgeData.l1Bridge,
l2Bridge: bridgeData.l2Bridge,
})
}
return adapters
}