Pular para o conteúdo
Escalibur Wallet
Auditoria

A lista de contratos da troca

Kit/Sources/EscaliburChains/Trade/TradeAllowlist.swift

A allowlist de routers, compilada.

218 linhasSHA-256 3ee36741229cbe11…Abrir no GitHub, no commit 015f5f5
1import EscaliburCore
2import Foundation
3
4// A allowlist de routers, compilada.
5//
6// Para cada rede e provedor: o `to` aceito, o spender que recebe o approve exato, as
7// funcoes aceitas (seletores tirados do ABI verificado) e como conferir que o codigo
8// atras do endereco ainda e o que foi auditado. A allowlist vive no binario: a config
9// remota so pode desligar provedor, nunca acrescentar endereco (docs/blockchain.md 3.5).
10//
11// Conferencia feita em 25/09/2026, para cada endereco e cada uma das 7 redes:
12// - `eth_getCode` nao vazio em dois RPCs;
13// - codigo verificado no Sourcify (sourcify.dev/server/v2/contract/<chainId>/<endereco>):
14// AugustusV6 (solc 0.8.22), MetaAggregationRouterV2, GenericSwapFacetV3,
15// OpenOceanExchange (implementacao atras do proxy);
16// - o endereco devolvido pela API ao vivo (`to` da transacao e `approvalAddress`/
17// `tokenTransferProxy`/`routerAddress`) bateu com o daqui.
18//
19// Segunda leva, conferida em 26/09/2026 do mesmo jeito (codigo em dois RPCs, Sourcify ou
20// explorador, `to` da API ao vivo): Plasma, Linea, Unichain e Sonic. Cada provedor so
21// entra onde tudo fechou; o que ficou de fora esta dito na lista do provedor.
22
23/// Como conferir, antes de assinar, que o codigo atras do router e o que foi lido.
24public enum TradeRouterPin: Sendable, Equatable {
25 /// Contrato sem proxy: o codigo e imutavel. A Augustus V6.2 e um diamond, mas
26 /// `swapExactAmountIn` esta no bytecode principal (Routers.sol), e funcao declarada
27 /// tem precedencia sobre o fallback de facetas.
28 case immutable
29 /// Proxy EIP-1967: a implementacao no slot
30 /// `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` tem de ser
31 /// esta. Upgrade do dono do proxy desliga o provedor ate a proxima versao do app.
32 case eip1967(implementation: EVMAddress)
33 /// Diamond EIP-2535: `facetAddress(seletor)` tem de devolver esta faceta para cada
34 /// seletor aceito.
35 case diamondFacet(EVMAddress)
36
37 /// `keccak256("eip1967.proxy.implementation") - 1`.
38 public static let eip1967ImplementationSlot = [UInt8](hex: "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")!
39
40 /// `facetAddress(bytes4)` do DiamondLoupe (EIP-2535).
41 public static let facetAddressFunction = try! ABIFunction("facetAddress(bytes4)") // cdffacc6
42
43 /// O endereco que a leitura na cadeia tem de devolver; `nil` se nao ha o que ler.
44 public var expected: EVMAddress? {
45 switch self {
46 case .immutable: return nil
47 case .eip1967(let implementation): return implementation
48 case .diamondFacet(let facet): return facet
49 }
50 }
51}
52
53/// Um router aceito numa rede.
54public struct TradeRouter: Sendable, Equatable {
55 public let provider: TradeProvider
56 public let chainID: UInt64
57 /// O `to` da transacao de troca.
58 public let address: EVMAddress
59 /// Quem recebe o `approve` exato do token vendido.
60 public let spender: EVMAddress
61 public let pin: TradeRouterPin
62 /// As funcoes aceitas. Seletor fora desta lista e recusado antes de decodificar.
63 public let functions: [ABIFunction]
64 /// LI.FI: o contrato de taxa que pode aparecer dentro do `swapData`.
65 public let feeForwarder: EVMAddress?
66
67 public var selectors: Set<[UInt8]> { Set(functions.map(\.selector)) }
68
69 /// O router puxa o token pelo Permit2 (Uniswap): o approve exato vai ao Permit2, e o
70 /// plano soma a permissao do Permit2 a este router (`EVMPermit2Approval`).
71 public var viaPermit2: Bool { spender == EIP712ValidatedMessage.permit2 }
72
73 /// A calldata de `facetAddress(seletor)` para cada seletor aceito (so diamond).
74 public var facetQueries: [[UInt8]] {
75 guard case .diamondFacet = pin else { return [] }
76 return functions.compactMap { function in
77 try? TradeRouterPin.facetAddressFunction.encodeCall([.fixedBytes(function.selector)])
78 }
79 }
80}
81
82public enum TradeAllowlist {
83 static func address(_ hex: String) -> EVMAddress {
84 EVMAddress(uncheckedBytes: [UInt8](hex: hex)!)
85 }
86
87 /// As redes EVM com troca, pelo chainId compilado: as 7 da v1 e Plasma, Linea,
88 /// Unichain e Sonic.
89 static let evmChainIDs: [UInt64] = [1, 42161, 8453, 10, 137, 56, 43114, 9745, 59144, 130, 146]
90
91 // MARK: Velora (ex-ParaSwap), Augustus V6.2
92
93 /// Mesmo endereco em todas as redes da Velora. Fonte: developers.velora.xyz,
94 /// "Augustus v6.2 smart contracts"; Sourcify "AugustusV6" em cada uma;
95 /// `contractAddress` e `tokenTransferProxy` da API ao vivo. O spender e o proprio
96 /// router (v6.2 nao tem TokenTransferProxy separado).
97 static let velora = address("6a000f20005980200259b80c5102003040001068")
98 /// As 7 da v1 e a Unichain. A pagina da Velora nao lista mais Plasma nem Sonic, e a
99 /// API responde 503 para as duas.
100 static let veloraChainIDs: Set<UInt64> = [1, 42161, 8453, 10, 137, 56, 43114, 130]
101
102 // MARK: KyberSwap, MetaAggregationRouterV2
103
104 /// Mesmo endereco em todas as redes. Fonte: docs.kyberswap.com (Aggregator API,
105 /// "router address"); Sourcify "MetaAggregationRouterV2" em cada uma (solc 0.8.9 ou
106 /// 0.8.17 conforme a rede); `routerAddress` da API ao vivo. Sem proxy: `Ownable` so
107 /// mexe em whitelist e resgate.
108 static let kyber = address("6131b5fae19ea4f9d964eac0408e4408b66337b5")
109 /// Todas as redes com troca: a API atende `plasma`, `linea`, `unichain` e `sonic`
110 /// (nao atende X Layer nem Celo).
111 static let kyberChainIDs: Set<UInt64> = [1, 42161, 8453, 10, 137, 56, 43114, 9745, 59144, 130, 146]
112
113 // MARK: LI.FI, LiFiDiamond
114
115 /// Mesmo diamond nas 7 redes e na Sonic. Fonte: github.com/lifinance/contracts,
116 /// deployments/<rede>.json (`LiFiDiamond`, `GenericSwapFacetV3`, `FeeForwarder`),
117 /// conferido com `facetAddress(seletor)` na cadeia em 25/09/2026. O
118 /// `swapTokensGeneric` antigo nao esta mais no diamond (facetAddress devolve zero),
119 /// entao a v1 so aceita as seis funcoes da GenericSwapFacetV3.
120 ///
121 /// Sonic (26/09/2026): diamond e faceta `0xf24c...1715` com Sourcify, os seis
122 /// seletores devolvem a faceta em dois RPCs, FeeForwarder com o mesmo bytecode da
123 /// Base. Plasma, Linea e Unichain ficam de fora: la o diamond e o FeeForwarder estao
124 /// em outros enderecos (deployments/plasma.json, linea.json, unichain.json), e esta
125 /// allowlist so conhece um diamond.
126 static let lifiDiamond = address("1231deb6f5749ef6ce6943a275a1d3e7486f4eae")
127 /// FeeForwarder 2.0.0, mesmo endereco e mesmo codehash nas 7 redes e na Sonic
128 /// (Sourcify na Base).
129 static let lifiFeeForwarder = address("ce40449b773a3e6e5e769adb4e567179d4828cbd")
130 static let lifiFacets: [UInt64: EVMAddress] = [
131 1: address("8c9dba771220ed09580b77f0765e7153fbde7790"),
132 10: address("8dfdaebb42655a4a4e2b89687dd117074ae8c665"),
133 42161: address("31a9b1835864706af10103b31ea2b79bdb995f5f"),
134 8453: address("31a9b1835864706af10103b31ea2b79bdb995f5f"),
135 137: address("31a9b1835864706af10103b31ea2b79bdb995f5f"),
136 56: address("31a9b1835864706af10103b31ea2b79bdb995f5f"),
137 43114: address("31a9b1835864706af10103b31ea2b79bdb995f5f"),
138 146: address("f24c9914d3a89f16ad9c87fa549ff24359a31715"),
139 ]
140
141 // MARK: De¹ (ex-OpenOcean), OpenOceanExchange
142
143 /// Proxy transparente, mesmo endereco em todas as redes; a implementacao muda por
144 /// rede e fica fixada aqui. So entram as redes em que a implementacao esta
145 /// verificada: Sourcify em Ethereum, Arbitrum, Base, OP, BNB e Linea; Blockscout na
146 /// Polygon e na Unichain, todas "OpenOceanExchange" com solc 0.8.9. Ficam de fora a
147 /// Avalanche (`0x6a27...f993`) e a Plasma (`0xed85...4615`), com implementacao sem
148 /// codigo verificado em nenhum explorador que conferimos, e a Sonic: la a
149 /// implementacao `0x8d2b...be74` esta verificada (SonicScan), mas a API devolve a
150 /// venda de S sem o endereco 0xEeee e com `flags` 0, e a validacao recusa a calldata
151 /// (conferido ao vivo em 26/09/2026).
152 static let de1Proxy = address("6352a56caadc4f1e25cd6c75970fa768a3304e64")
153 static let de1Implementations: [UInt64: EVMAddress] = [
154 1: address("e29b6b6e96befe1e9c6f948bcb5a3f71058bda9b"),
155 42161: address("94e8da4a7707b2edd1688c1ab3b09fd0fb8267eb"),
156 8453: address("201263cea08e8f1d6e2fdd1fd2ca44bf6145e2af"),
157 10: address("dcbf4cb83d27c408b30dd7f39bfcabd7176b1ba3"),
158 137: address("2691f337abeb0146f16441ca4f82f363275851d5"),
159 56: address("86f2058ba5e1466260055d1f3dab38b15f98be09"),
160 59144: address("1aa298ae7c53d8dafa200ed49608649bfa76a446"),
161 130: address("170100a288dc3d7e83fea20441f98166b15b6df0"),
162 ]
163
164 // MARK: Consulta
165
166 /// O router de um provedor numa rede, ou `nil` se o provedor nao atende ali.
167 public static func router(for provider: TradeProvider, on chain: Chain) -> TradeRouter? {
168 guard chain.family == .evm, let chainID = chain.evmChainID, evmChainIDs.contains(chainID) else { return nil }
169 switch provider {
170 case .velora:
171 guard veloraChainIDs.contains(chainID) else { return nil }
172 return TradeRouter(provider: .velora, chainID: chainID, address: velora, spender: velora,
173 pin: .immutable, functions: VeloraCalldata.functions, feeForwarder: nil)
174 case .kyberSwap:
175 guard kyberChainIDs.contains(chainID) else { return nil }
176 return TradeRouter(provider: .kyberSwap, chainID: chainID, address: kyber, spender: kyber,
177 pin: .immutable, functions: KyberCalldata.functions, feeForwarder: nil)
178 case .lifi:
179 guard let facet = lifiFacets[chainID] else { return nil }
180 return TradeRouter(provider: .lifi, chainID: chainID, address: lifiDiamond, spender: lifiDiamond,
181 pin: .diamondFacet(facet), functions: LiFiCalldata.functions, feeForwarder: lifiFeeForwarder)
182 case .de1:
183 guard let implementation = de1Implementations[chainID] else { return nil }
184 return TradeRouter(provider: .de1, chainID: chainID, address: de1Proxy, spender: de1Proxy,
185 pin: .eip1967(implementation: implementation), functions: De1Calldata.functions, feeForwarder: nil)
186 case .uniswap:
187 // O token vendido e aprovado ao Permit2, e o Permit2 da ao router a permissao
188 // exata de 30 minutos (`TradeRouter.viaPermit2`). Router sem proxy.
189 guard let deployment = UniswapDeployment.all[chainID] else { return nil }
190 return TradeRouter(provider: .uniswap, chainID: chainID, address: deployment.router, spender: EIP712ValidatedMessage.permit2,
191 pin: .immutable, functions: UniswapCalldata.functions, feeForwarder: nil)
192 case .pancakeswap:
193 // SmartRouter sem proxy, com o approve exato a ele mesmo. O `Ownable` dele so
194 // mexe na rota de stable swap, que a carteira nao monta.
195 guard let deployment = PancakeDeployment.all[chainID] else { return nil }
196 return TradeRouter(provider: .pancakeswap, chainID: chainID, address: deployment.router, spender: deployment.router,
197 pin: .immutable, functions: PancakeCalldata.functions, feeForwarder: nil)
198 case .shibaswap:
199 // UniswapV2Router02 sem proxy e sem dono, com o approve exato a ele mesmo.
200 guard let deployment = ShibaSwapDeployment.all[chainID] else { return nil }
201 return TradeRouter(provider: .shibaswap, chainID: chainID, address: deployment.router, spender: deployment.router,
202 pin: .immutable, functions: ShibaSwapCalldata.functions, feeForwarder: nil)
203 }
204 }
205
206 /// Todos os routers aceitos numa rede.
207 public static func routers(on chain: Chain) -> [TradeRouter] {
208 TradeProvider.allCases.compactMap { router(for: $0, on: chain) }
209 }
210
211 /// Os spenders de troca de uma rede, para a tela de aprovacoes vigentes e revogacao
212 /// (docs/seguranca.md 4.4), incluindo o VaultRelayer da CoW.
213 public static func spenders(on chain: Chain) -> Set<EVMAddress> {
214 var out = Set(routers(on: chain).map(\.spender))
215 if CoWProtocol.supports(chain) { out.insert(CoWProtocol.vaultRelayer) }
216 return out
217 }
218}