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).
| 1 | import EscaliburCore |
| 2 | import 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. |
| 27 | public 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 | |
| 100 | public 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 | } |