Pular para o conteúdo
Escalibur Wallet
Auditoria

A lista de contratos da ponte

Kit/Sources/EscaliburChains/Bridge/BridgeAllowlist.swift

A allowlist da ponte, compilada e rede por rede (docs/seguranca.md 4.12).

270 linhasSHA-256 646a8b8ef8121734…Abrir no GitHub, no commit 015f5f5
1import EscaliburCore
2import Foundation
3
4// A allowlist da ponte, compilada e rede por rede (docs/seguranca.md 4.12).
5//
6// Primeira leva: Ethereum, Arbitrum, Base e Optimism (CCTP V2 rapido, SpokePool do
7// Across, ETH nativo e simulacao em dois RPCs). Segunda leva abaixo. Terceira (14/10/2026):
8// a BNB, so pelo Across. O resto fica de fora ate fechar tudo:
9// - BNB sem CCTP: o TokenMessengerV2 da Ethereum aceita o dominio 17, mas na BNB o USDC
10// nao e cunhado (`getLocalToken` zero para todas as origens, `burnLimitsPerMessage`
11// zero). Uma queima para la se perderia: a BNB nao tem `cctp` aqui, e o app ainda
12// confere `getLocalToken` no destino antes de assinar qualquer queima.
13// - Celo: sem CCTP. Sonic, X Layer e Celo: sem SpokePool.
14// - O SpokePool da Base, 0x09ae...bEC64, tem codigo com o mesmo endereco na X Layer e na
15// Celo, e la nao e SpokePool (`chainId()` reverte). Endereco vale so na rede listada.
16//
17// Conferencia em 11 e 12/10/2026, cada valor em duas leituras independentes (o agente
18// de seguranca e a sessao principal), cada leitura em dois RPCs:
19// - endereco nas fontes oficiais: developers.circle.com (contract-addresses,
20// supported-chains-and-domains, usdc-contract-addresses) e docs.across.to mais
21// across-protocol/contracts broadcast/deployed-addresses.json, que concordam;
22// - `localDomain()` do MessageTransmitterV2 e `chainId()` do SpokePool lidos na cadeia;
23// - a implementacao de cada proxy, no slot EIP-1967. Implementacao diferente da daqui
24// desliga a ponte ate a proxima versao do app (o dono do proxy trocou o codigo).
25
26/// O que a ponte usa numa rede.
27public struct BridgeChain: Sendable, Equatable {
28 public let chain: Chain
29 /// O CCTP V2 da Circle nesta rede; `nil` onde a Circle nao cunha USDC nativo (BNB).
30 public let cctp: CCTP?
31 /// O USDC que a ponte move nesta rede: o nativo da Circle onde ha CCTP; na BNB, o da
32 /// Binance, que so anda pelo Across.
33 public let usdc: EVMAddress
34 /// O Across nesta rede; `nil` onde ele nao tem SpokePool (Sonic).
35 public let across: Across?
36 /// O USDT do emissor, so onde o Across entrega exatamente este contrato.
37 public let usdt: EVMAddress?
38 /// A moeda nativa da rede e o ETH: o Across move ETH nativo de ponta a ponta.
39 public let nativeETH: Bool
40 /// O ETH em token onde a moeda nativa nao e ETH e o Across entrega este contrato (na
41 /// BNB, o da Binance). `nil` onde o ETH e nativo ou a ponte nao move ETH.
42 public let ethToken: EVMAddress?
43 /// Casas do USDC e do USDT nesta rede: 6 nos da Circle e da Tether, 18 nos da Binance.
44 public let stableDecimals: Int
45 /// O USDC desta rede e um proxy fora do CCTP (na BNB, o da Binance): a implementacao
46 /// fixada, conferida como as da ponte. Trocada, a ponte para (revisao de 14/10/2026, B1).
47 public let usdcImplementation: EVMAddress?
48 /// O Across move o USDC desta rede. Na Plasma nao: la ele so leva USDT0, e o USDC
49 /// chega pelo CCTP (`available-routes`, 14/10/2026).
50 public let acrossUSDC: Bool
51 /// O WBTC que o Across entrega nesta rede, so onde e o mesmo contrato da lista curada.
52 public let wbtc: EVMAddress?
53
54 /// A Legacy Mesh da Tether nesta rede (o UsdtOFT), so na Ethereum e na Arbitrum.
55 public var mesh: USDT0Mesh.Source? { USDT0Mesh.source(chain) }
56
57 public struct CCTP: Sendable, Equatable {
58 /// O dominio do CCTP. Nao e o chainId: a Base e 6.
59 public let domain: UInt32
60 public let messengerImplementation: EVMAddress
61 public let transmitterImplementation: EVMAddress
62 }
63
64 public struct Across: Sendable, Equatable {
65 public let spokePool: EVMAddress
66 public let implementation: EVMAddress
67 /// O WETH que o SpokePool embrulha quando entra ETH (`wrappedNativeToken()`).
68 public let wrappedNative: EVMAddress
69 }
70
71 /// Rede com CCTP: USDC e USDT de 6 casas, ETH nativo ou nenhum.
72 public init(
73 chain: Chain, cctpDomain: UInt32, usdc: EVMAddress, messengerImplementation: EVMAddress, transmitterImplementation: EVMAddress,
74 across: Across?, usdt: EVMAddress?, nativeETH: Bool, acrossUSDC: Bool = true, wbtc: EVMAddress? = nil
75 ) {
76 self.init(chain: chain, cctp: CCTP(domain: cctpDomain, messengerImplementation: messengerImplementation,
77 transmitterImplementation: transmitterImplementation),
78 usdc: usdc, across: across, usdt: usdt, nativeETH: nativeETH, ethToken: nil, stableDecimals: 6, acrossUSDC: acrossUSDC,
79 wbtc: wbtc)
80 }
81
82 public init(
83 chain: Chain, cctp: CCTP?, usdc: EVMAddress, across: Across?, usdt: EVMAddress?, nativeETH: Bool, ethToken: EVMAddress?,
84 stableDecimals: Int, usdcImplementation: EVMAddress? = nil, acrossUSDC: Bool = true, wbtc: EVMAddress? = nil
85 ) {
86 self.wbtc = wbtc
87 self.usdcImplementation = usdcImplementation
88 self.acrossUSDC = acrossUSDC
89 self.chain = chain
90 self.cctp = cctp
91 self.usdc = usdc
92 self.across = across
93 self.usdt = usdt
94 self.nativeETH = nativeETH
95 self.ethToken = ethToken
96 self.stableDecimals = stableDecimals
97 }
98}
99
100public enum BridgeAllowlist {
101 static func address(_ hex: String) -> EVMAddress {
102 EVMAddress(uncheckedBytes: [UInt8](hex: hex)!)
103 }
104
105 // CCTP V2: o mesmo endereco nas quatro redes (CREATE2 da Circle), conferido em cada uma.
106 public static let tokenMessenger = address("28b5a0e9C621a5BadaA536219b3a228C8168cf5d")
107 public static let messageTransmitter = address("81D40F21F12A8F0E3252Bccb954D722d4c464B64")
108 public static let tokenMinter = address("fd78EE919681417d192449715b2594ab58f5D002")
109 static let messengerImplementation = address("555e272506c06e7e559d57418563742afe363ec8")
110
111 /// O teto por queima que o TokenMinterV2 impoe ao USDC, lido nas quatro redes:
112 /// 10.000.000 USDC. Valor maior vira mais de uma queima.
113 public static let burnLimitPerMessage = BigUInt(10_000_000_000_000)
114
115 public static let ethereum = BridgeChain(
116 chain: .ethereum, cctpDomain: 0,
117 usdc: address("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
118 messengerImplementation: messengerImplementation,
119 transmitterImplementation: address("a30c41865729c248067704d4db9f38385cbe1186"),
120 across: .init(spokePool: address("5c7BCd6E7De5423a257D81B442095A1a6ced35C5"), implementation: address("456ac26e5ec083ee9889eba0d1a0a582502b8e84"),
121 wrappedNative: address("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")),
122 usdt: address("dAC17F958D2ee523a2206206994597C13D831ec7"), nativeETH: true,
123 wbtc: address("2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599")
124 )
125
126 public static let arbitrum = BridgeChain(
127 chain: .arbitrum, cctpDomain: 3,
128 usdc: address("af88d065e77c8cC2239327C5EDb3A432268e5831"),
129 messengerImplementation: messengerImplementation,
130 transmitterImplementation: address("7b93db2bc72faf642cec0adcac4f91718831a1f4"),
131 across: .init(spokePool: address("e35e9842fceaCA96570B734083f4a58e8F7C5f2A"), implementation: address("cfcda84333431bcc9155f2368b8362f0d1dff8c9"),
132 wrappedNative: address("82aF49447D8a07e3bd95BD0d56f35241523fBab1")),
133 // USDT0, o USDT da Tether na Arbitrum, e o que o Across entrega aqui.
134 usdt: address("Fd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), nativeETH: true,
135 wbtc: address("2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f")
136 )
137
138 public static let base = BridgeChain(
139 chain: .base, cctpDomain: 6,
140 usdc: address("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
141 messengerImplementation: messengerImplementation,
142 transmitterImplementation: address("7db629f6acc20be49a0a7565c21cc178e9ac21e3"),
143 across: .init(spokePool: address("09aea4b2242abC8bb4BB78D537A67a245A7bEC64"), implementation: address("f23c6c04a2b88e8651fe99bbdccbb5c9d306e6b0"),
144 wrappedNative: address("4200000000000000000000000000000000000006")),
145 // O USDT do Across na Base (0xfde4...) e de ponte, nao do emissor.
146 usdt: nil, nativeETH: true
147 )
148
149 public static let optimism = BridgeChain(
150 chain: .optimism, cctpDomain: 2,
151 usdc: address("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"),
152 messengerImplementation: messengerImplementation,
153 transmitterImplementation: address("1f33335414afb7305367452d00a0d024b0184ea8"),
154 across: .init(spokePool: address("6f26Bf09B1C792e3228e5467807a900A503c0281"), implementation: address("60674c53a4cc4ed350659c95fb8020b5ac61ca1d"),
155 wrappedNative: address("4200000000000000000000000000000000000006")),
156 // O Across entrega o USDT de ponte (0x94b0...), nao o USDT0 da lista do app.
157 usdt: nil, nativeETH: true,
158 wbtc: address("68f180fcCe6836688e9084f035309E29Bf0A2095")
159 )
160
161 // Segunda leva, 14/10/2026: lida do mesmo jeito (implementacao no slot EIP-1967,
162 // `localDomain()` e `chainId()` em dois RPCs; dominio e implementacao do
163 // TokenMessengerV2 batem com a leitura independente do agente de seguranca de
164 // 11/10). Cada uma tem `eth_simulateV1` em duas fontes. O que fica de fora em cada:
165 // - Polygon: so USDC. O ETH que o Across entrega la e WETH de ponte, e o USDT da
166 // lista ainda nao tem a conferencia de emissor registrada.
167 // - Linea: USDC e ETH. O USDT da Linea e de ponte (TokenRegistry).
168 // - Unichain: USDC, ETH e USDT0.
169 // - Sonic: so USDC; o Across nao tem SpokePool la.
170 // - Plasma: USDC e USDT0; a moeda nativa e o XPL. O USDC so pelo CCTP: o Across de la
171 // nao leva USDC, e por isso nao ha USDC entre a Plasma e a BNB.
172 //
173 // WBTC (17/10/2026, pedido do dono de liberar toda moeda que der): pelo Across, entre
174 // Ethereum, Arbitrum, OP e Polygon, as redes em que o Across entrega o mesmo contrato da
175 // lista curada (`available-routes` ao vivo). Em dois RPCs de cada uma: codigo igual e
176 // `decimals()` 8. Na Arbitrum e o token padrao da ponte da Arbitrum (proxy de beacon
177 // da propria Arbitrum); na Polygon, o da ponte PoS. A Linea fica de fora: o WBTC que o
178 // Across entrega la nao esta na lista.
179 public static let polygon = BridgeChain(
180 chain: .polygon, cctpDomain: 7,
181 usdc: address("3c499c542cEF5E3811e1192ce70d8cC03d5c3359"),
182 messengerImplementation: messengerImplementation,
183 transmitterImplementation: address("d40518c1a3139e8f1f73f83e5de74e05c88f5ad3"),
184 across: .init(spokePool: address("9295ee1d8C5b022Be115A2AD3c30C72E34e7F096"), implementation: address("304bed95daecd3e680073eab385193da711d59d4"),
185 wrappedNative: address("0d500b1d8e8ef31e21c99d1db9a6444d3adf1270")),
186 usdt: nil, nativeETH: false,
187 wbtc: address("1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6")
188 )
189
190 public static let linea = BridgeChain(
191 chain: .linea, cctpDomain: 11,
192 usdc: address("176211869cA2b568f2A7D4EE941E073a821EE1ff"),
193 messengerImplementation: messengerImplementation,
194 transmitterImplementation: address("cec4e8c3bd65cf40821cb2c63deb0aabbcd9ef1b"),
195 across: .init(spokePool: address("7E63A5f1a8F0B4d0934B2f2327DAED3F6bb2ee75"), implementation: address("263c0e973fd0ca9de57bb22a91c57fc367a81915"),
196 wrappedNative: address("e5d7c2a44ffddf6b295a15c148167daaaf5cf34f")),
197 usdt: nil, nativeETH: true
198 )
199
200 public static let unichain = BridgeChain(
201 chain: .unichain, cctpDomain: 10,
202 usdc: address("078D782b760474a361dDA0AF3839290b0EF57AD6"),
203 messengerImplementation: messengerImplementation,
204 transmitterImplementation: address("ca8b81cc584e1480f7855bf843ce17af511ecd03"),
205 across: .init(spokePool: address("09aea4b2242abC8bb4BB78D537A67a245A7bEC64"), implementation: address("e619bd2ed532b87e353142b73ab8e70cb764ff64"),
206 wrappedNative: address("4200000000000000000000000000000000000006")),
207 usdt: address("9151434b16b9763660705744891fA906F660EcC5"), nativeETH: true
208 )
209
210 public static let sonic = BridgeChain(
211 chain: .sonic, cctpDomain: 13,
212 usdc: address("29219dd400f2Bf60E5a23d13Be72B486D4038894"),
213 messengerImplementation: messengerImplementation,
214 transmitterImplementation: address("824ddd600433ce8b17ad5dd6333c883ac10a3ece"),
215 across: nil, usdt: nil, nativeETH: false
216 )
217
218 public static let plasma = BridgeChain(
219 chain: .plasma, cctpDomain: 33,
220 usdc: address("2d661C89D812261039AF9764eceaAee884f5F67F"),
221 // A Plasma tem outra implementacao do TokenMessengerV2 (lida em dois RPCs, igual a
222 // do agente de seguranca).
223 messengerImplementation: address("1ccafdffbc1b7b5c499c97322f961b7d929a41b4"),
224 transmitterImplementation: address("d683965f427af258f5eec40f950a95a62edc8ca6"),
225 across: .init(spokePool: address("50039fAEfebef707cFD94D6d462fE6D10B39207a"), implementation: address("af10c33de0aee6df661b9bfb25e829871ab87a41"),
226 wrappedNative: address("6100e367285b01f48d07953803a2d8dca5d19873")),
227 usdt: address("B8CE59FC3717ada4C02eaDF9682A9e934F625ebb"), nativeETH: false, acrossUSDC: false
228 )
229
230 /// BNB Chain, so pelo Across. O USDC, o USDT e o ETH daqui sao os da Binance, com 18
231 /// casas, e sao os contratos que o Across entrega (`available-routes`, 14/10/2026).
232 /// Conferido em 14/10/2026: endereco do SpokePool em across-protocol/contracts
233 /// (broadcast/deployed-addresses.json) e na API do Across; na cadeia, em dois RPCs
234 /// (bsc-dataseed.bnbchain.org e bsc-rpc.publicnode.com), `chainId()` 56, a
235 /// implementacao no slot EIP-1967, `wrappedNativeToken()` (o WBNB) e `decimals()` 18
236 /// nos tres tokens.
237 public static let bnb = BridgeChain(
238 chain: .bnb, cctp: nil,
239 usdc: address("8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"),
240 across: .init(spokePool: address("4e8E101924eDE233C13e2D8622DC8aED2872d505"), implementation: address("c7b7029373f504949553106c9eb2dafdd48ef086"),
241 wrappedNative: address("bb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")),
242 usdt: address("55d398326f99059fF775485246999027B3197955"), nativeETH: false,
243 ethToken: address("2170Ed0880ac9A755fd29B2688956BD959F933F8"), stableDecimals: 18,
244 // O USDC da Binance e proxy EIP-1967 com admin numa chave so; o USDT e o ETH de la nao
245 // sao proxy. Implementacao lida em dois RPCs em 14/10/2026.
246 usdcImplementation: address("ba5fe23f8a3a24bed3236f05f2fcf35fd0bf0b5c")
247 )
248
249 public static let chains: [BridgeChain] = [ethereum, arbitrum, base, optimism, polygon, linea, unichain, sonic, plasma, bnb]
250
251 /// A Tron, so como destino do USDT pela Legacy Mesh da Tether (`USDT0Mesh`). Nao e EVM e
252 /// nao entra em `chains` (as redes de origem): sem CCTP nem Across; o `usdc` fica no
253 /// endereco zero e nunca e usado (`BridgeAsset.available` so libera o USDT pela Mesh), e o
254 /// `usdt` sao os 20 bytes do USDT da Tron, TR7NH...Lj6t.
255 public static let tron = BridgeChain(
256 chain: .tron, cctp: nil, usdc: .zero, across: nil, usdt: EVMAddress(uncheckedBytes: USDT0Mesh.tronUSDT.account20),
257 nativeETH: false, ethToken: nil, stableDecimals: 6, acrossUSDC: false
258 )
259
260 /// Os destinos: as redes de origem e a Tron.
261 public static let destinations: [BridgeChain] = chains + [tron]
262
263 public static func chain(_ chain: Chain) -> BridgeChain? {
264 destinations.first { $0.chain.id == chain.id }
265 }
266
267 public static func chain(domain: UInt32) -> BridgeChain? {
268 chains.first { $0.cctp?.domain == domain }
269 }
270}