github.com/status-im/status-go@v1.1.0/services/wallet/transfer/swap_identifier.go (about) 1 package transfer 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 8 "github.com/ethereum/go-ethereum/accounts/abi/bind" 9 "github.com/ethereum/go-ethereum/common" 10 "github.com/ethereum/go-ethereum/common/hexutil" 11 "github.com/ethereum/go-ethereum/core/types" 12 13 uniswapv2 "github.com/status-im/status-go/contracts/uniswapV2" 14 uniswapv3 "github.com/status-im/status-go/contracts/uniswapV3" 15 "github.com/status-im/status-go/rpc/chain" 16 w_common "github.com/status-im/status-go/services/wallet/common" 17 "github.com/status-im/status-go/services/wallet/token" 18 ) 19 20 const ETHSymbol string = "ETH" 21 const WETHSymbol string = "WETH" 22 23 func fetchUniswapV2PairInfo(ctx context.Context, client chain.ClientInterface, pairAddress common.Address) (*common.Address, *common.Address, error) { 24 caller, err := uniswapv2.NewUniswapv2Caller(pairAddress, client) 25 if err != nil { 26 return nil, nil, err 27 } 28 29 token0Address, err := caller.Token0(&bind.CallOpts{ 30 Context: ctx, 31 }) 32 if err != nil { 33 return nil, nil, err 34 } 35 36 token1Address, err := caller.Token1(&bind.CallOpts{ 37 Context: ctx, 38 }) 39 if err != nil { 40 return nil, nil, err 41 } 42 43 return &token0Address, &token1Address, nil 44 } 45 46 func fetchUniswapV3PoolInfo(ctx context.Context, client chain.ClientInterface, poolAddress common.Address) (*common.Address, *common.Address, error) { 47 caller, err := uniswapv3.NewUniswapv3Caller(poolAddress, client) 48 if err != nil { 49 return nil, nil, err 50 } 51 52 token0Address, err := caller.Token0(&bind.CallOpts{ 53 Context: ctx, 54 }) 55 if err != nil { 56 return nil, nil, err 57 } 58 59 token1Address, err := caller.Token1(&bind.CallOpts{ 60 Context: ctx, 61 }) 62 if err != nil { 63 return nil, nil, err 64 } 65 66 return &token0Address, &token1Address, nil 67 } 68 69 func identifyUniswapV2Asset(tokenManager *token.Manager, chainID uint64, amount0 *big.Int, contractAddress0 common.Address, amount1 *big.Int, contractAddress1 common.Address) (token *token.Token, amount *big.Int, err error) { 70 // Either amount0 or amount1 should be 0 71 if amount1.Sign() == 0 && amount0.Sign() != 0 { 72 token = tokenManager.FindTokenByAddress(chainID, contractAddress0) 73 if token == nil { 74 err = fmt.Errorf("couldn't find symbol for token0 %v", contractAddress0) 75 return 76 } 77 amount = amount0 78 } else if amount0.Sign() == 0 && amount1.Sign() != 0 { 79 token = tokenManager.FindTokenByAddress(chainID, contractAddress1) 80 if token == nil { 81 err = fmt.Errorf("couldn't find symbol for token1 %v", contractAddress1) 82 return 83 } 84 amount = amount1 85 } else { 86 err = fmt.Errorf("couldn't identify token %v %v %v %v", contractAddress0, amount0, contractAddress1, amount1) 87 return 88 } 89 90 return 91 } 92 93 func fetchUniswapV2Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { 94 pairAddress, _, _, amount0In, amount1In, amount0Out, amount1Out, err := w_common.ParseUniswapV2Log(log) 95 if err != nil { 96 return 97 } 98 99 token0ContractAddress, token1ContractAddress, err := fetchUniswapV2PairInfo(ctx, client, pairAddress) 100 if err != nil { 101 return 102 } 103 104 fromToken, fromAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0In, *token0ContractAddress, amount1In, *token1ContractAddress) 105 if err != nil { 106 // "Soft" error, allow to continue with unknown asset 107 fromAsset = "" 108 fromAmount = (*hexutil.Big)(big.NewInt(0)) 109 } else { 110 fromAsset = fromToken.Symbol 111 fromAmount = (*hexutil.Big)(fromAmountInt) 112 } 113 114 toToken, toAmountInt, err := identifyUniswapV2Asset(tokenManager, client.NetworkID(), amount0Out, *token0ContractAddress, amount1Out, *token1ContractAddress) 115 if err != nil { 116 // "Soft" error, allow to continue with unknown asset 117 toAsset = "" 118 toAmount = (*hexutil.Big)(big.NewInt(0)) 119 } else { 120 toAsset = toToken.Symbol 121 toAmount = (*hexutil.Big)(toAmountInt) 122 } 123 124 err = nil 125 return 126 } 127 128 func identifyUniswapV3Assets(tokenManager *token.Manager, chainID uint64, amount0 *big.Int, contractAddress0 common.Address, amount1 *big.Int, contractAddress1 common.Address) (fromToken *token.Token, fromAmount *big.Int, toToken *token.Token, toAmount *big.Int, err error) { 129 token0 := tokenManager.FindTokenByAddress(chainID, contractAddress0) 130 if token0 == nil { 131 err = fmt.Errorf("couldn't find symbol for token0 %v", contractAddress0) 132 return 133 } 134 135 token1 := tokenManager.FindTokenByAddress(chainID, contractAddress1) 136 if token1 == nil { 137 err = fmt.Errorf("couldn't find symbol for token1 %v", contractAddress1) 138 return 139 } 140 141 // amount0 and amount1 are the balance deltas of the pool 142 // The positive amount is how much the sender spent 143 // The negative amount is how much the recipent got 144 if amount0.Sign() > 0 && amount1.Sign() < 0 { 145 fromToken = token0 146 fromAmount = amount0 147 toToken = token1 148 toAmount = new(big.Int).Neg(amount1) 149 } else if amount0.Sign() < 0 && amount1.Sign() > 0 { 150 fromToken = token1 151 fromAmount = amount1 152 toToken = token0 153 toAmount = new(big.Int).Neg(amount0) 154 } else { 155 err = fmt.Errorf("couldn't identify tokens %v %v %v %v", contractAddress0, amount0, contractAddress1, amount1) 156 return 157 } 158 159 return 160 } 161 162 func fetchUniswapV3Info(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { 163 poolAddress, _, _, amount0, amount1, err := w_common.ParseUniswapV3Log(log) 164 if err != nil { 165 return 166 } 167 168 token0ContractAddress, token1ContractAddress, err := fetchUniswapV3PoolInfo(ctx, client, poolAddress) 169 if err != nil { 170 return 171 } 172 173 fromToken, fromAmountInt, toToken, toAmountInt, err := identifyUniswapV3Assets(tokenManager, client.NetworkID(), amount0, *token0ContractAddress, amount1, *token1ContractAddress) 174 if err != nil { 175 // "Soft" error, allow to continue with unknown asset 176 err = nil 177 fromAsset = "" 178 fromAmount = (*hexutil.Big)(big.NewInt(0)) 179 toAsset = "" 180 toAmount = (*hexutil.Big)(big.NewInt(0)) 181 } else { 182 fromAsset = fromToken.Symbol 183 fromAmount = (*hexutil.Big)(fromAmountInt) 184 toAsset = toToken.Symbol 185 toAmount = (*hexutil.Big)(toAmountInt) 186 } 187 188 return 189 } 190 191 func fetchUniswapInfo(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, log *types.Log, logType w_common.EventType) (fromAsset string, fromAmount *hexutil.Big, toAsset string, toAmount *hexutil.Big, err error) { 192 switch logType { 193 case w_common.UniswapV2SwapEventType: 194 return fetchUniswapV2Info(ctx, client, tokenManager, log) 195 case w_common.UniswapV3SwapEventType: 196 return fetchUniswapV3Info(ctx, client, tokenManager, log) 197 } 198 err = fmt.Errorf("wrong log type %s", logType) 199 return 200 } 201 202 // Build a Swap multitransaction from a list containing one or several uniswapV2/uniswapV3 subTxs 203 // We only care about the first and last swap to identify the input/output token and amounts 204 func buildUniswapSwapMultitransaction(ctx context.Context, client chain.ClientInterface, tokenManager *token.Manager, transfer *Transfer) (*MultiTransaction, error) { 205 multiTransaction := MultiTransaction{ 206 Type: MultiTransactionSwap, 207 FromNetworkID: transfer.NetworkID, 208 FromTxHash: transfer.Receipt.TxHash, 209 FromAddress: transfer.Address, 210 ToNetworkID: transfer.NetworkID, 211 ToTxHash: transfer.Receipt.TxHash, 212 ToAddress: transfer.Address, 213 Timestamp: transfer.Timestamp, 214 } 215 216 var firstSwapLog, lastSwapLog *types.Log 217 var firstSwapLogType, lastSwapLogType w_common.EventType 218 hasWETHDepositLog := false 219 hasWETHWithdrawalLog := false 220 221 for _, ethlog := range transfer.Receipt.Logs { 222 logType := w_common.GetEventType(ethlog) 223 switch logType { 224 case w_common.WETHDepositEventType: 225 hasWETHDepositLog = true 226 case w_common.WETHWithdrawalEventType: 227 hasWETHWithdrawalLog = true 228 case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType: 229 if firstSwapLog == nil { 230 firstSwapLog = ethlog 231 firstSwapLogType = logType 232 } 233 lastSwapLog = ethlog 234 lastSwapLogType = logType 235 } 236 } 237 238 var err error 239 240 multiTransaction.FromAsset, multiTransaction.FromAmount, multiTransaction.ToAsset, multiTransaction.ToAmount, err = fetchUniswapInfo(ctx, client, tokenManager, firstSwapLog, firstSwapLogType) 241 if err != nil { 242 return nil, err 243 } 244 245 if firstSwapLog != lastSwapLog { 246 _, _, multiTransaction.ToAsset, multiTransaction.ToAmount, err = fetchUniswapInfo(ctx, client, tokenManager, lastSwapLog, lastSwapLogType) 247 if err != nil { 248 return nil, err 249 } 250 } 251 252 // WETH and ETH have same decimals value, no need to change From/To Amount 253 if multiTransaction.FromAsset == WETHSymbol && hasWETHDepositLog { 254 multiTransaction.FromAsset = ETHSymbol 255 } 256 257 if multiTransaction.ToAsset == WETHSymbol && hasWETHWithdrawalLog { 258 multiTransaction.ToAsset = ETHSymbol 259 } 260 261 return &multiTransaction, nil 262 }