diff --git a/.env.base b/.env.base index 331de5c7eee..79e65a27ccc 100644 --- a/.env.base +++ b/.env.base @@ -56,6 +56,7 @@ REACT_APP_FEATURE_READ_ONLY_ASSETS=true # swapper feature flags - other .env files will override these REACT_APP_FEATURE_COWSWAP=true REACT_APP_FEATURE_COWSWAP_GNOSIS=true +REACT_APP_FEATURE_COWSWAP_ARBITRUM=false REACT_APP_FEATURE_LIFI_SWAP=true REACT_APP_FEATURE_ONE_INCH=false REACT_APP_FEATURE_THOR_SWAP=true diff --git a/.env.dev b/.env.dev index a331dd2d1ef..c5ea4359294 100644 --- a/.env.dev +++ b/.env.dev @@ -2,6 +2,7 @@ REACT_APP_FEATURE_RFOX=true REACT_APP_FEATURE_RFOX_REWARDS_TAB=true REACT_APP_FEATURE_ARBITRUM_BRIDGE=true +REACT_APP_FEATURE_COWSWAP_ARBITRUM=true # logging REACT_APP_REDUX_WINDOW=false diff --git a/.env.develop b/.env.develop index 65a59c83015..d6c9efa8196 100644 --- a/.env.develop +++ b/.env.develop @@ -3,6 +3,7 @@ REACT_APP_FEATURE_CHATWOOT=true REACT_APP_FEATURE_RFOX=true REACT_APP_FEATURE_RFOX_REWARDS_TAB=true REACT_APP_FEATURE_ARBITRUM_BRIDGE=true +REACT_APP_FEATURE_COWSWAP_ARBITRUM=true # mixpanel REACT_APP_MIXPANEL_TOKEN=1c1369f6ea23a6404bac41b42817cc4b diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/arbitrum.test.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/arbitrum.test.ts index b476ec68ea4..d8e4212f3da 100644 --- a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/arbitrum.test.ts +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/arbitrum.test.ts @@ -6,13 +6,21 @@ import type { Trade, Transfer } from '../../../../types' import { Dex, TradeType, TransferType, TxStatus } from '../../../../types' import type { ParsedTx } from '../../../parser' import { V1Api } from '../../index' -import { TransactionParser, ZRX_ARBITRUM_PROXY_CONTRACT } from '../index' +import { + RFOX_PROXY_CONTRACT_ADDRESS, + TransactionParser, + ZRX_ARBITRUM_PROXY_CONTRACT, +} from '../index' import erc20Approve from './mockData/erc20Approve' import erc721 from './mockData/erc721' import erc1155 from './mockData/erc1155' import ethSelfSend from './mockData/ethSelfSend' import ethStandard from './mockData/ethStandard' -import { usdcToken } from './mockData/tokens' +import rfoxSetRuneAddress from './mockData/rfoxSetRuneAddress' +import rfoxStake from './mockData/rfoxStake' +import rfoxUnstake from './mockData/rfoxUnstake' +import rfoxWithdraw from './mockData/rfoxWithdraw' +import { foxToken, usdcToken } from './mockData/tokens' import tokenSelfSend from './mockData/tokenSelfSend' import tokenStandard from './mockData/tokenStandard' import zrxTradeEthToUsdc from './mockData/zrxTradeEthToUsdc' @@ -878,4 +886,150 @@ describe('parseTx', () => { expect(actual).toEqual(expected) }) }) + + describe('rfox', () => { + it('should be able to stake', async () => { + const { tx } = rfoxStake + const address = '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986' + + const expected: ParsedTx = { + txid: tx.txid, + blockHeight: tx.blockHeight, + blockTime: tx.timestamp, + blockHash: tx.blockHash, + address, + chainId: arbitrumChainId, + confirmations: tx.confirmations, + fee: { + assetId: arbitrumAssetId, + value: '3013123887600', + }, + data: { + method: 'stake', + parser: 'rfox', + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + runeAddress: 'thor125dwsa39yeylqc7pn59l079dur502nsleyrgup', + }, + status: TxStatus.Confirmed, + transfers: [ + { + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + components: [{ value: '1000000000000000000' }], + from: address, + to: RFOX_PROXY_CONTRACT_ADDRESS, + token: foxToken, + totalValue: '1000000000000000000', + type: TransferType.Send, + }, + ], + } + + const actual = await txParser.parse(tx, address) + + expect(actual).toEqual(expected) + }) + + it('should be able to unstake', async () => { + const { tx } = rfoxUnstake + const address = '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986' + + const expected: ParsedTx = { + txid: tx.txid, + blockHeight: tx.blockHeight, + blockTime: tx.timestamp, + blockHash: tx.blockHash, + address, + chainId: arbitrumChainId, + confirmations: tx.confirmations, + fee: { + assetId: arbitrumAssetId, + value: '44360779710690', + }, + data: { + method: 'unstakeRequest', + parser: 'rfox', + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + value: '3273624096679687500', + }, + status: TxStatus.Confirmed, + transfers: [], + } + + const actual = await txParser.parse(tx, address) + + expect(actual).toEqual(expected) + }) + + it('should be able to withdraw claim', async () => { + const { tx } = rfoxWithdraw + const address = '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986' + + const expected: ParsedTx = { + txid: tx.txid, + blockHeight: tx.blockHeight, + blockTime: tx.timestamp, + blockHash: tx.blockHash, + address, + chainId: arbitrumChainId, + confirmations: tx.confirmations, + fee: { + assetId: arbitrumAssetId, + value: '41698790000000', + }, + data: { + method: 'withdraw', + parser: 'rfox', + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + claimIndex: 0, + }, + status: TxStatus.Confirmed, + transfers: [ + { + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + components: [{ value: '4364832128906250000' }], + from: RFOX_PROXY_CONTRACT_ADDRESS, + to: address, + token: foxToken, + totalValue: '4364832128906250000', + type: TransferType.Receive, + }, + ], + } + + const actual = await txParser.parse(tx, address) + + expect(actual).toEqual(expected) + }) + + it('should be able to set RUNE address', async () => { + const { tx } = rfoxSetRuneAddress + const address = '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986' + + const expected: ParsedTx = { + txid: tx.txid, + blockHeight: tx.blockHeight, + blockTime: tx.timestamp, + blockHash: tx.blockHash, + address, + chainId: arbitrumChainId, + confirmations: tx.confirmations, + fee: { + assetId: arbitrumAssetId, + value: '39355169490000', + }, + data: { + method: 'setRuneAddress', + parser: 'rfox', + assetId: 'eip155:42161/erc20:0xf929de51d91c77e42f5090069e0ad7a09e513c73', + runeAddress: 'thor1clpczglrkrvdq9xtcsmj9a8ayrjeet2llcqufl', + }, + status: TxStatus.Confirmed, + transfers: [], + } + + const actual = await txParser.parse(tx, address) + + expect(actual).toEqual(expected) + }) + }) }) diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxSetRuneAddress.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxSetRuneAddress.ts new file mode 100644 index 00000000000..3cd8adaa1e2 --- /dev/null +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxSetRuneAddress.ts @@ -0,0 +1,22 @@ +import type { Tx } from '../../../..' + +const tx: Tx = { + txid: '0x0145c782916e9e8683caac3b24c6c93a22b738cd2e019d7c43cd3d60c82e6b9b', + blockHash: '0x76787a0a3d5b6a43a308b828b58edb841d13b6671f366ad1308fb7bdaaf5cc6a', + blockHeight: 221516124, + timestamp: 1718305602, + status: 1, + from: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + to: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + confirmations: 303953, + value: '0', + fee: '39355169490000', + gasLimit: '145286', + gasUsed: '126711', + gasPrice: '310590000', + inputData: + '0xf6d0b2ed0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b74686f7231636c70637a676c726b7276647139787463736d6a3961386179726a656574326c6c637175666c000000000000000000000000000000000000000000', + internalTxs: [], +} + +export default { tx } diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxStake.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxStake.ts new file mode 100644 index 00000000000..4f3e8a21ace --- /dev/null +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxStake.ts @@ -0,0 +1,34 @@ +import type { Tx } from '../../../..' + +const tx: Tx = { + txid: '0x38532538617c6db07af876e0fbe070526e549d0d48cd5c73b67fad0bc5551fd2', + blockHash: '0x54a56f8a3162328adfc8c7c796aa6bce78d2c21dcca9fd2d18cf1938014c243b', + blockHeight: 221776349, + timestamp: 1718370601, + status: 1, + from: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + to: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + confirmations: 41943, + value: '0', + fee: '3013123887600', + gasLimit: '236001', + gasUsed: '205844', + gasPrice: '14637900', + inputData: + '0xe7e4e1f70000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002b74686f723132356477736133397965796c716337706e35396c3037396475723530326e736c657972677570000000000000000000000000000000000000000000', + tokenTransfers: [ + { + contract: '0xf929de51D91C77E42f5090069E0AD7A09e513c73', + decimals: 18, + name: 'FOX', + symbol: 'FOX', + type: 'ERC20', + from: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + to: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + value: '1000000000000000000', + }, + ], + internalTxs: [], +} + +export default { tx } diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxUnstake.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxUnstake.ts new file mode 100644 index 00000000000..6496ce52a39 --- /dev/null +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxUnstake.ts @@ -0,0 +1,21 @@ +import type { Tx } from '../../../..' + +const tx: Tx = { + txid: '0x971e268f63572aee723b2ecd1a08aa8cdb873927fba6fc55146516bc26fc319a', + blockHash: '0x086aa823ff9e888ab8d7c3026a45de90f5a0df69fdb78ebd8b35868573387dbc', + blockHeight: 221510033, + timestamp: 1718304084, + status: 1, + from: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + to: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + confirmations: 309306, + value: '0', + fee: '44360779710690', + gasLimit: '234173', + gasUsed: '210690', + gasPrice: '210550001', + inputData: '0x2e17de780000000000000000000000000000000000000000000000002d6e3fc582c0754c', + internalTxs: [], +} + +export default { tx } diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxWithdraw.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxWithdraw.ts new file mode 100644 index 00000000000..8651bf85223 --- /dev/null +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/rfoxWithdraw.ts @@ -0,0 +1,33 @@ +import type { Tx } from '../../../..' + +const tx: Tx = { + txid: '0x6f800e60876460c749e8a9e6dbccd4d166ffc8243a9a63c0c323327ab9764d81', + blockHash: '0x34e3fdfac2e0780254ab3e1f10135bfd2097402c472862a00305b609d65cf4f5', + blockHeight: 221510152, + timestamp: 1718304113, + status: 1, + from: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + to: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + confirmations: 309455, + value: '0', + fee: '41698790000000', + gasLimit: '195355', + gasUsed: '159460', + gasPrice: '261500000', + inputData: '0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000000', + tokenTransfers: [ + { + contract: '0xf929de51D91C77E42f5090069E0AD7A09e513c73', + decimals: 18, + name: 'FOX', + symbol: 'FOX', + type: 'ERC20', + from: '0xd612B64A134f3D4830542B7463CE8ca8a29D7268', + to: '0x5daF465a9cCf64DEB146eEaE9E7Bd40d6761c986', + value: '4364832128906250000', + }, + ], + internalTxs: [], +} + +export default { tx } diff --git a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/tokens.ts b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/tokens.ts index 9e8378c0f95..685e68b33a1 100644 --- a/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/tokens.ts +++ b/packages/unchained-client/src/evm/arbitrum/parser/__tests__/mockData/tokens.ts @@ -6,3 +6,10 @@ export const usdcToken: Token = { name: 'USD Coin', symbol: 'USDC', } + +export const foxToken: Token = { + contract: '0xf929de51D91C77E42f5090069E0AD7A09e513c73', + decimals: 18, + name: 'FOX', + symbol: 'FOX', +} diff --git a/packages/unchained-client/src/evm/arbitrum/parser/index.ts b/packages/unchained-client/src/evm/arbitrum/parser/index.ts index c6738c0860b..e486bd50e76 100644 --- a/packages/unchained-client/src/evm/arbitrum/parser/index.ts +++ b/packages/unchained-client/src/evm/arbitrum/parser/index.ts @@ -1,11 +1,15 @@ +import { foxOnArbitrumOneAssetId } from '@shapeshiftoss/caip' + import type { Tx } from '../../../generated/arbitrum' import type { BaseTransactionParserArgs } from '../../parser' import { BaseTransactionParser } from '../../parser' import * as erc20 from '../../parser/erc20' import * as nft from '../../parser/nft' +import * as rfox from '../../parser/rfox' import * as zrx from '../../parser/zrx' export const ZRX_ARBITRUM_PROXY_CONTRACT = '0xDef1C0ded9bec7F1a1670819833240f027b25EfF' +export const RFOX_PROXY_CONTRACT_ADDRESS = '0xd612B64A134f3D4830542B7463CE8ca8a29D7268' export class TransactionParser extends BaseTransactionParser { constructor(args: BaseTransactionParserArgs) { @@ -19,6 +23,10 @@ export class TransactionParser extends BaseTransactionParser { }), new erc20.Parser({ chainId: this.chainId, provider: this.provider }), new zrx.Parser({ proxyContract: ZRX_ARBITRUM_PROXY_CONTRACT }), + new rfox.Parser({ + proxyContract: RFOX_PROXY_CONTRACT_ADDRESS, + stakingAssetId: foxOnArbitrumOneAssetId, + }), ]) } } diff --git a/packages/unchained-client/src/evm/parser/abi/rfox.ts b/packages/unchained-client/src/evm/parser/abi/rfox.ts new file mode 100644 index 00000000000..d3b1b7018d9 --- /dev/null +++ b/packages/unchained-client/src/evm/parser/abi/rfox.ts @@ -0,0 +1,539 @@ +import type { InterfaceAbi } from 'ethers' + +export const RFOX_ABI: InterfaceAbi = [ + { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, + { + type: 'function', + inputs: [], + name: 'REWARD_RATE', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'UPGRADE_INTERFACE_VERSION', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'WAD', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: 'total', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'cooldownPeriod', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'earned', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'account', internalType: 'address', type: 'address' }, + { name: 'index', internalType: 'uint256', type: 'uint256' }, + ], + name: 'getUnstakingRequest', + outputs: [ + { + name: '', + internalType: 'struct UnstakingRequest', + type: 'tuple', + components: [ + { + name: 'unstakingBalance', + internalType: 'uint256', + type: 'uint256', + }, + { name: 'cooldownExpiry', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'getUnstakingRequestCount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'stakingTokenAddress', internalType: 'address', type: 'address' }], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'lastUpdateTimestamp', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'owner', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'pause', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'pauseStaking', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'pauseUnstaking', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'pauseWithdrawals', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'paused', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'proxiableUUID', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'rewardPerToken', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'rewardPerTokenStored', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'newCooldownPeriod', internalType: 'uint256', type: 'uint256' }], + name: 'setCooldownPeriod', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'runeAddress', internalType: 'string', type: 'string' }], + name: 'setRuneAddress', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'runeAddress', internalType: 'string', type: 'string' }, + ], + name: 'stake', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: '', internalType: 'address', type: 'address' }], + name: 'stakingInfo', + outputs: [ + { name: 'stakingBalance', internalType: 'uint256', type: 'uint256' }, + { name: 'unstakingBalance', internalType: 'uint256', type: 'uint256' }, + { name: 'earnedRewards', internalType: 'uint256', type: 'uint256' }, + { + name: 'rewardPerTokenStored', + internalType: 'uint256', + type: 'uint256', + }, + { name: 'runeAddress', internalType: 'string', type: 'string' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'stakingPaused', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'stakingToken', + outputs: [{ name: '', internalType: 'contract IERC20', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'totalCoolingDown', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'totalStaked', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unpause', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unpauseStaking', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unpauseUnstaking', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unpauseWithdrawals', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'amount', internalType: 'uint256', type: 'uint256' }], + name: 'unstake', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unstakingPaused', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'newImplementation', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'upgradeToAndCall', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [], + name: 'version', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'index', internalType: 'uint256', type: 'uint256' }], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'withdrawalsPaused', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'version', + internalType: 'uint64', + type: 'uint64', + indexed: false, + }, + ], + name: 'Initialized', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'previousOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'Paused', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'oldRuneAddress', + internalType: 'string', + type: 'string', + indexed: true, + }, + { + name: 'newRuneAddress', + internalType: 'string', + type: 'string', + indexed: true, + }, + ], + name: 'SetRuneAddress', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + { + name: 'runeAddress', + internalType: 'string', + type: 'string', + indexed: true, + }, + ], + name: 'Stake', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'Unpaused', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + { + name: 'cooldownExpiry', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'Unstake', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'newCooldownPeriod', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'UpdateCooldownPeriod', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'implementation', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'Upgraded', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'account', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'Withdraw', + }, + { + type: 'error', + inputs: [{ name: 'target', internalType: 'address', type: 'address' }], + name: 'AddressEmptyCode', + }, + { + type: 'error', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'AddressInsufficientBalance', + }, + { + type: 'error', + inputs: [{ name: 'implementation', internalType: 'address', type: 'address' }], + name: 'ERC1967InvalidImplementation', + }, + { type: 'error', inputs: [], name: 'ERC1967NonPayable' }, + { type: 'error', inputs: [], name: 'EnforcedPause' }, + { type: 'error', inputs: [], name: 'ExpectedPause' }, + { type: 'error', inputs: [], name: 'FailedInnerCall' }, + { type: 'error', inputs: [], name: 'InvalidInitialization' }, + { type: 'error', inputs: [], name: 'NotInitializing' }, + { + type: 'error', + inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], + name: 'OwnableInvalidOwner', + }, + { + type: 'error', + inputs: [{ name: 'account', internalType: 'address', type: 'address' }], + name: 'OwnableUnauthorizedAccount', + }, + { type: 'error', inputs: [], name: 'ReentrancyGuardReentrantCall' }, + { + type: 'error', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'SafeERC20FailedOperation', + }, + { type: 'error', inputs: [], name: 'UUPSUnauthorizedCallContext' }, + { + type: 'error', + inputs: [{ name: 'slot', internalType: 'bytes32', type: 'bytes32' }], + name: 'UUPSUnsupportedProxiableUUID', + }, +] diff --git a/packages/unchained-client/src/evm/parser/rfox.ts b/packages/unchained-client/src/evm/parser/rfox.ts new file mode 100644 index 00000000000..2557a669800 --- /dev/null +++ b/packages/unchained-client/src/evm/parser/rfox.ts @@ -0,0 +1,77 @@ +import type { AssetId } from '@shapeshiftoss/caip' +import { ethers } from 'ethers' + +import type { BaseTxMetadata } from '../../types' +import type { SubParser, TxSpecific } from '.' +import { getSigHash, txInteractsWithContract } from '.' +import { RFOX_ABI } from './abi/rfox' +import type { Tx } from './types' + +export interface TxMetadata extends BaseTxMetadata { + parser: 'rfox' + assetId: AssetId + value?: string + runeAddress?: string + claimIndex?: number +} + +export interface ParserArgs { + proxyContract: string + stakingAssetId: AssetId +} + +export class Parser implements SubParser { + private readonly proxyContract + private readonly stakingAssetId: AssetId + + readonly abiInterface = new ethers.Interface(RFOX_ABI) + + readonly supportedFunctions = { + stake: this.abiInterface.getFunction('stake')!.selector, + unstake: this.abiInterface.getFunction('unstake')!.selector, + withdrawClaim: this.abiInterface.getFunction('withdraw(uint256)')!.selector, + withdraw: this.abiInterface.getFunction('withdraw()')!.selector, + setRuneAddress: this.abiInterface.getFunction('setRuneAddress')!.selector, + } + + constructor(args: ParserArgs) { + this.proxyContract = args.proxyContract + this.stakingAssetId = args.stakingAssetId + } + + async parse(tx: Tx): Promise { + if (!txInteractsWithContract(tx, this.proxyContract)) return + if (!tx.inputData) return + + const txSigHash = getSigHash(tx.inputData) + + if (!Object.values(this.supportedFunctions).some(hash => hash === txSigHash)) return + + const decoded = this.abiInterface.parseTransaction({ data: tx.inputData }) + + if (!decoded) return + + const data: TxMetadata = { + method: decoded.name, + parser: 'rfox', + assetId: this.stakingAssetId, + } + + switch (txSigHash) { + case this.supportedFunctions.unstake: + const amount = decoded.args.amount as BigInt + return await Promise.resolve({ + data: { ...data, method: `${data.method}Request`, value: amount.toString() }, + }) + case this.supportedFunctions.stake: + case this.supportedFunctions.setRuneAddress: + const runeAddress = decoded.args.runeAddress as string + return await Promise.resolve({ data: { ...data, runeAddress } }) + case this.supportedFunctions.withdrawClaim: + const index = decoded.args.index as BigInt + return await Promise.resolve({ data: { ...data, claimIndex: Number(index) } }) + default: + return await Promise.resolve({ data }) + } + } +} diff --git a/packages/unchained-client/src/evm/parser/types.ts b/packages/unchained-client/src/evm/parser/types.ts index 6aa1c3c19d2..ce70e736c18 100644 --- a/packages/unchained-client/src/evm/parser/types.ts +++ b/packages/unchained-client/src/evm/parser/types.ts @@ -9,6 +9,7 @@ import type * as uniV2 from '../ethereum/parser/uniV2' import type * as weth from '../ethereum/parser/weth' import type * as erc20 from '../parser/erc20' import type * as nft from '../parser/nft' +import type * as rfox from '../parser/rfox' import type * as zrx from '../parser/zrx' export type Tx = evm.Tx @@ -23,6 +24,7 @@ export type TxMetadata = | weth.TxMetadata | zrx.TxMetadata | nft.TxMetadata + | rfox.TxMetadata export interface ParsedTx extends StandardTx { data?: TxMetadata diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 185c0f4fab1..b1b10327415 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -640,6 +640,13 @@ "for": "for", "emptyMessage": "Your %{status} transactions will appear here.", "parser": { + "rfox": { + "stake": "Stake", + "withdraw": "Withdraw", + "unstakeRequest": "Unstake Request", + "setRuneAddress": "Set Rune Address" + + }, "weth": { "deposit": "Deposit", "approve": "Approve", diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/HopTransactionStep.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/HopTransactionStep.tsx index 940951f6bca..31209da0320 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/HopTransactionStep.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/HopTransactionStep.tsx @@ -1,9 +1,6 @@ import { Button, Card, CardBody, Link, Tooltip, VStack } from '@chakra-ui/react' -import type { - SupportedTradeQuoteStepIndex, - SwapperName, - TradeQuoteStep, -} from '@shapeshiftoss/swapper' +import type { SupportedTradeQuoteStepIndex, TradeQuoteStep } from '@shapeshiftoss/swapper' +import { SwapperName } from '@shapeshiftoss/swapper' import type { KnownChainIds } from '@shapeshiftoss/types' import { useCallback, useMemo } from 'react' import { useTranslate } from 'react-polyglot' @@ -77,7 +74,7 @@ export const HopTransactionStep = ({ txLink: getTxLink({ name: tradeQuoteStep.source, defaultExplorerBaseUrl: tradeQuoteStep.buyAsset.explorerTxLink, - tradeId: buyTxHash, + txId: buyTxHash, }), txHash: buyTxHash, }) @@ -88,7 +85,13 @@ export const HopTransactionStep = ({ txLink: getTxLink({ name: tradeQuoteStep.source, defaultExplorerBaseUrl: tradeQuoteStep.sellAsset.explorerTxLink, - tradeId: sellTxHash, + ...(tradeQuoteStep.source === SwapperName.CowSwap + ? { + tradeId: sellTxHash, + } + : { + txId: sellTxHash, + }), }), txHash: sellTxHash, }) diff --git a/src/components/TransactionHistoryRows/TransactionDetails/Amount.tsx b/src/components/TransactionHistoryRows/TransactionDetails/Amount.tsx index 42fe3bb3636..d1ec2c9e954 100644 --- a/src/components/TransactionHistoryRows/TransactionDetails/Amount.tsx +++ b/src/components/TransactionHistoryRows/TransactionDetails/Amount.tsx @@ -1,19 +1,33 @@ +import type { AssetId } from '@shapeshiftoss/caip' import { Amount as AmountComponent } from 'components/Amount/Amount' import { fromBaseUnit } from 'lib/math' +import { selectAssetById } from 'state/slices/selectors' +import { useAppSelector } from 'state/store' -export const Amount = ({ - value, - symbol, - precision, -}: { +type AmountArgs = { value: string - symbol: string - precision: number -}) => ( - +} & ( + | { + assetId: AssetId + precision?: never + symbol?: never + } + | { + assetId?: never + precision: number + symbol: string + } ) + +export const Amount = ({ value, symbol, precision, assetId }: AmountArgs) => { + const asset = useAppSelector(state => selectAssetById(state, assetId ?? '')) + + return ( + + ) +} diff --git a/src/components/TransactionHistoryRows/TransactionGenericRow.tsx b/src/components/TransactionHistoryRows/TransactionGenericRow.tsx index fb29323a4fa..2f9e0b3fdd1 100644 --- a/src/components/TransactionHistoryRows/TransactionGenericRow.tsx +++ b/src/components/TransactionHistoryRows/TransactionGenericRow.tsx @@ -1,5 +1,5 @@ import { ChevronRightIcon } from '@chakra-ui/icons' -import { Center, Flex, HStack } from '@chakra-ui/react' +import { Center, Flex, HStack, Tag } from '@chakra-ui/react' import type { AssetId } from '@shapeshiftoss/caip' import type { TxMetadata } from '@shapeshiftoss/chain-adapters' import type { Asset } from '@shapeshiftoss/types' @@ -17,6 +17,7 @@ import { fromBaseUnit } from 'lib/math' import { middleEllipsis } from 'lib/utils' import type { TxId } from 'state/slices/txHistorySlice/txHistorySlice' +import { Amount as CryptoAmount } from './TransactionDetails/Amount' import { ApprovalAmount } from './TransactionDetails/ApprovalAmount' import { TransactionTag } from './TransactionTag' import { TransactionTeaser } from './TransactionTeaser' @@ -175,10 +176,14 @@ export const TransactionGenericRow = ({ // asset(s) label const bottomLeft = useMemo(() => { - if (type === Method.Approve) { + if (type === Method.Approve || txData?.method === Method.UnstakeRequest) { return } + if (txData?.parser === 'rfox' && txData?.method === Method.SetRuneAddress) { + return {txData.runeAddress ?? ''} + } + if (hasNoTransfers) return // send only @@ -263,6 +268,7 @@ export const TransactionGenericRow = ({ uniqueAssets, transfersByType.Receive, transfersByType.Send, + txData, txMetadataWithAssetId?.assetId, type, divider, @@ -281,6 +287,14 @@ export const TransactionGenericRow = ({ ) } + if (txData?.parser === 'rfox' && txData?.method === Method.UnstakeRequest) { + return ( + + + + ) + } + if (hasNoReceiveAssets) return sendAmount if (hasSingleReceiveAsset) { @@ -314,6 +328,7 @@ export const TransactionGenericRow = ({ hasSingleReceiveAsset, hasManyReceiveAssets, transfersByType.Receive, + txData, txMetadataWithAssetId?.assetId, txMetadataWithAssetId?.parser, txMetadataWithAssetId?.value, diff --git a/src/components/TransactionHistoryRows/TransactionTag.tsx b/src/components/TransactionHistoryRows/TransactionTag.tsx index 3bd86a026b0..be9e8875df4 100644 --- a/src/components/TransactionHistoryRows/TransactionTag.tsx +++ b/src/components/TransactionHistoryRows/TransactionTag.tsx @@ -29,6 +29,13 @@ export const TransactionTag: React.FC = ({ txDetails, trans ) } + if (txData && txData.parser === 'rfox') { + return ( + + rFOX + + ) + } if (txData && txData.parser === 'thorchain' && txData.liquidity) { return ( diff --git a/src/config.ts b/src/config.ts index 0b7776913d3..70013f28bbf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -78,6 +78,7 @@ const validators = { REACT_APP_FEATURE_LIFI_SWAP: bool({ default: false }), REACT_APP_FEATURE_COWSWAP: bool({ default: false }), REACT_APP_FEATURE_COWSWAP_GNOSIS: bool({ default: false }), + REACT_APP_FEATURE_COWSWAP_ARBITRUM: bool({ default: false }), REACT_APP_FEATURE_JAYPEGZ: bool({ default: false }), REACT_APP_FEATURE_OPTIMISM: bool({ default: false }), REACT_APP_FEATURE_BNBSMARTCHAIN: bool({ default: false }), diff --git a/src/hooks/useTxDetails/useTxDetails.ts b/src/hooks/useTxDetails/useTxDetails.ts index 6438f7a4154..008af562fa8 100644 --- a/src/hooks/useTxDetails/useTxDetails.ts +++ b/src/hooks/useTxDetails/useTxDetails.ts @@ -49,11 +49,13 @@ export enum Method { Refund = 'refund', RemoveLiquidityEth = 'removeLiquidityETH', Revoke = 'revoke', + SetRuneAddress = 'setRuneAddress', Stake = 'stake', SwapOut = 'swapOut', SwapRefund = 'swapRefund', Transfer = 'transfer', Unstake = 'unstake', + UnstakeRequest = 'unstakeRequest', Withdraw = 'withdraw', WithdrawNative = 'withdrawNative', WithdrawDelegatorReward = 'withdraw_delegator_reward', diff --git a/src/lib/swapper/swappers/CowSwapper/CowSwapper.test.ts b/src/lib/swapper/swappers/CowSwapper/CowSwapper.test.ts index ac6d7a39b0b..68917c4fc7a 100644 --- a/src/lib/swapper/swappers/CowSwapper/CowSwapper.test.ts +++ b/src/lib/swapper/swappers/CowSwapper/CowSwapper.test.ts @@ -6,8 +6,10 @@ import { describe, expect, it, vi } from 'vitest' import { BTC, ETH, + ETH_ARBITRUM, FOX_GNOSIS, FOX_MAINNET, + USDC_ARBITRUM, WBTC, WETH, XDAI, @@ -53,6 +55,8 @@ vi.mock('state/slices/assetsSlice/selectors', async () => { WBTC, WETH, XDAI, + ETH_ARBITRUM, + USDC_ARBITRUM, } = require('lib/swapper/swappers/utils/test-data/assets') const actual = await vi.importActual('state/slices/assetsSlice/selectors') @@ -66,11 +70,13 @@ vi.mock('state/slices/assetsSlice/selectors', async () => { [WBTC.assetId]: WBTC, [WETH.assetId]: WETH, [XDAI.assetId]: XDAI, + [ETH_ARBITRUM.assetId]: ETH_ARBITRUM, + [USDC_ARBITRUM.assetId]: USDC_ARBITRUM, })), } }) -const ASSETS = [ETH, WBTC, WETH, BTC, FOX_MAINNET, XDAI] +const ASSETS = [ETH, WBTC, WETH, BTC, FOX_MAINNET, XDAI, ETH_ARBITRUM, USDC_ARBITRUM] describe('CowSwapper', () => { describe('filterAssetIdsBySellable', () => { @@ -83,15 +89,17 @@ describe('CowSwapper', () => { WBTC.assetId, WETH.assetId, FOX_MAINNET.assetId, + USDC_ARBITRUM.assetId, ]) }) it('returns array filtered out of unsupported tokens', async () => { - const assetIds = [FOX_MAINNET, FOX_GNOSIS, BTC] + const assetIds = [FOX_MAINNET, FOX_GNOSIS, USDC_ARBITRUM, BTC] expect(await cowSwapper.filterAssetIdsBySellable(assetIds)).toEqual([ FOX_MAINNET.assetId, FOX_GNOSIS.assetId, + USDC_ARBITRUM.assetId, ]) }) }) diff --git a/src/lib/swapper/swappers/CowSwapper/getCowSwapTradeQuote/getCowSwapTradeQuote.test.ts b/src/lib/swapper/swappers/CowSwapper/getCowSwapTradeQuote/getCowSwapTradeQuote.test.ts index e25fac85020..beb540eec34 100644 --- a/src/lib/swapper/swappers/CowSwapper/getCowSwapTradeQuote/getCowSwapTradeQuote.test.ts +++ b/src/lib/swapper/swappers/CowSwapper/getCowSwapTradeQuote/getCowSwapTradeQuote.test.ts @@ -5,7 +5,15 @@ import { Ok } from '@sniptt/monads' import type { AxiosResponse } from 'axios' import { describe, expect, it, vi } from 'vitest' -import { ETH, FOX_MAINNET, USDC_GNOSIS, WETH, XDAI } from '../../utils/test-data/assets' +import { + ETH, + ETH_ARBITRUM, + FOX_MAINNET, + USDC_ARBITRUM, + USDC_GNOSIS, + WETH, + XDAI, +} from '../../utils/test-data/assets' import type { CowSwapQuoteResponse } from '../types' import { COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS, @@ -112,6 +120,20 @@ const expectedApiInputUsdcGnosisToXdai: CowSwapSellQuoteApiInput = { validTo: 1656797787, } +const expectedApiInputUsdcToEthArbitrum: CowSwapSellQuoteApiInput = { + appData: + '{"appCode":"shapeshift","metadata":{"orderClass":{"orderClass":"market"},"quote":{"slippageBips":"50"}},"version":"0.9.0"}', + appDataHash: '0x9b3c15b566e3b432f1ba3533bb0b071553fd03cec359caf3e6559b29fec1e62e', + buyToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + from: '0x0000000000000000000000000000000000000000', + kind: 'sell', + partiallyFillable: false, + receiver: '0x0000000000000000000000000000000000000000', + sellAmountBeforeFee: '500000', + sellToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + validTo: 1656797787, +} + const expectedTradeQuoteWethToFox: TradeQuote = { id: '123', receiveAddress: '0x0000000000000000000000000000000000000000', @@ -211,6 +233,39 @@ const expectedTradeQuoteUsdcToXdai: TradeQuote = { ], } +const expectedTradeQuoteUsdcToEthArbitrum: TradeQuote = { + id: '123', + receiveAddress: '0x0000000000000000000000000000000000000000', + affiliateBps: '0', + potentialAffiliateBps: '0', + rate: '0.00028787191526496171', + slippageTolerancePercentageDecimal: '0.005', + steps: [ + { + allowanceContract: '0xc92e8bdf79f0507f65a392b0ab4667716bfe0110', + rate: '0.00028787191526496171', + estimatedExecutionTimeMs: undefined, + feeData: { + networkFeeCryptoBaseUnit: '0', + protocolFees: { + [USDC_ARBITRUM.assetId]: { + amountCryptoBaseUnit: '7944', + requiresBalance: false, + asset: USDC_ARBITRUM, + }, + }, + }, + sellAmountIncludingProtocolFeesCryptoBaseUnit: '500000', + buyAmountBeforeFeesCryptoBaseUnit: '143935957632481', + buyAmountAfterFeesCryptoBaseUnit: '141649103137616', + source: SwapperName.CowSwap, + buyAsset: ETH_ARBITRUM, + sellAsset: USDC_ARBITRUM, + accountNumber: 0, + }, + ], +} + const expectedTradeQuoteSmallAmountWethToFox: TradeQuote = { id: '123', receiveAddress: '0x0000000000000000000000000000000000000000', @@ -403,6 +458,50 @@ describe('getCowTradeQuote', () => { ) }) + it('should call cowService with correct parameters, handle the fees and return the correct trade quote when buying ETH on Arbitrum', async () => { + const input: GetTradeQuoteInput = { + chainId: KnownChainIds.ArbitrumMainnet, + sellAsset: USDC_ARBITRUM, + buyAsset: ETH_ARBITRUM, + sellAmountIncludingProtocolFeesCryptoBaseUnit: '500000', + accountNumber: 0, + receiveAddress: DEFAULT_ADDRESS, + affiliateBps: '0', + potentialAffiliateBps: '0', + supportsEIP1559: false, + allowMultiHop: false, + slippageTolerancePercentageDecimal: '0.005', // 0.5% + } + + mockedCowService.post.mockReturnValue( + Promise.resolve( + Ok({ + data: { + id: 123, + quote: { + ...expectedApiInputUsdcToEthArbitrum, + sellAmountBeforeFee: undefined, + sellAmount: '492056', + buyAmount: '141649103137616', + feeAmount: '7944', + sellTokenBalance: ERC20_TOKEN_BALANCE, + buyTokenBalance: ERC20_TOKEN_BALANCE, + }, + }, + } as unknown as AxiosResponse), + ), + ) + + const maybeTradeQuote = await getCowSwapTradeQuote(input) + + expect(maybeTradeQuote.isOk()).toBe(true) + expect(maybeTradeQuote.unwrap()).toEqual(expectedTradeQuoteUsdcToEthArbitrum) + expect(cowService.post).toHaveBeenCalledWith( + 'https://api.cow.fi/arbitrum_one/api/v1/quote/', + expectedApiInputUsdcToEthArbitrum, + ) + }) + it('should call cowService with correct parameters and return quote with original sellAmount when selling a very small amount of WETH', async () => { const input: GetTradeQuoteInput = { chainId: KnownChainIds.EthereumMainnet, diff --git a/src/lib/swapper/swappers/CowSwapper/types.ts b/src/lib/swapper/swappers/CowSwapper/types.ts index 3ab699d0b30..cd6a84d9ae9 100644 --- a/src/lib/swapper/swappers/CowSwapper/types.ts +++ b/src/lib/swapper/swappers/CowSwapper/types.ts @@ -34,9 +34,13 @@ export type CowSwapQuoteError = { export enum CowNetwork { Mainnet = 'mainnet', Xdai = 'xdai', + ArbitrumOne = 'arbitrum_one', } -export type CowChainId = KnownChainIds.EthereumMainnet | KnownChainIds.GnosisMainnet +export type CowChainId = + | KnownChainIds.EthereumMainnet + | KnownChainIds.GnosisMainnet + | KnownChainIds.ArbitrumMainnet export type CowSwapGetTradesResponse = { txHash: string diff --git a/src/lib/swapper/swappers/CowSwapper/utils/constants.ts b/src/lib/swapper/swappers/CowSwapper/utils/constants.ts index 97b4d4ae9fe..ca275cdea26 100644 --- a/src/lib/swapper/swappers/CowSwapper/utils/constants.ts +++ b/src/lib/swapper/swappers/CowSwapper/utils/constants.ts @@ -3,13 +3,6 @@ import { KnownChainIds } from '@shapeshiftoss/types' import { zeroAddress } from 'viem' import type { SupportedChainIds } from 'lib/swapper/types' -import type { CowChainId } from '../types' - -export const MIN_COWSWAP_USD_TRADE_VALUES_BY_CHAIN_ID: Record = { - [KnownChainIds.EthereumMainnet]: '20', - [KnownChainIds.GnosisMainnet]: '0.01', -} - export const DEFAULT_ADDRESS = zeroAddress export const COW_SWAP_VAULT_RELAYER_ADDRESS = '0xc92e8bdf79f0507f65a392b0ab4667716bfe0110' @@ -24,9 +17,26 @@ export const ERC20_TOKEN_BALANCE = 'erc20' export const COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' export const SUPPORTED_CHAIN_IDS: ChainId[] = [ - KnownChainIds.GnosisMainnet, KnownChainIds.EthereumMainnet, -] + KnownChainIds.GnosisMainnet, + KnownChainIds.ArbitrumMainnet, +].filter(chainId => { + if ( + process.env['REACT_APP_FEATURE_COWSWAP_GNOSIS'] !== 'true' && + chainId === KnownChainIds.GnosisMainnet + ) { + return false + } + + if ( + process.env['REACT_APP_FEATURE_COWSWAP_ARBITRUM'] !== 'true' && + chainId === KnownChainIds.ArbitrumMainnet + ) { + return false + } + + return true +}) export const COW_SWAP_SUPPORTED_CHAIN_IDS: SupportedChainIds = { sell: SUPPORTED_CHAIN_IDS, diff --git a/src/lib/swapper/swappers/CowSwapper/utils/helpers/helpers.ts b/src/lib/swapper/swappers/CowSwapper/utils/helpers/helpers.ts index 08c634032d5..63a2c2675e3 100644 --- a/src/lib/swapper/swappers/CowSwapper/utils/helpers/helpers.ts +++ b/src/lib/swapper/swappers/CowSwapper/utils/helpers/helpers.ts @@ -65,6 +65,8 @@ export const getCowswapNetwork = (chainId: ChainId): Result { vi.hoisted(() => { vi.stubEnv('REACT_APP_FEATURE_MIXPANEL', 'false') + vi.stubEnv('REACT_APP_FEATURE_COWSWAP_GNOSIS', 'true') + vi.stubEnv('REACT_APP_FEATURE_COWSWAP_ARBITRUM', 'true') }) beforeAll(() => { diff --git a/src/state/slices/preferencesSlice/preferencesSlice.ts b/src/state/slices/preferencesSlice/preferencesSlice.ts index 29d3e688df3..4b1cdb20fc8 100644 --- a/src/state/slices/preferencesSlice/preferencesSlice.ts +++ b/src/state/slices/preferencesSlice/preferencesSlice.ts @@ -33,6 +33,7 @@ export type FeatureFlags = { TradeRates: boolean Cowswap: boolean CowswapGnosis: boolean + CowswapArbitrum: boolean ZrxSwap: boolean Mixpanel: boolean LifiSwap: boolean @@ -122,6 +123,7 @@ const initialState: Preferences = { TradeRates: getConfig().REACT_APP_FEATURE_TRADE_RATES, Cowswap: getConfig().REACT_APP_FEATURE_COWSWAP, CowswapGnosis: getConfig().REACT_APP_FEATURE_COWSWAP_GNOSIS, + CowswapArbitrum: getConfig().REACT_APP_FEATURE_COWSWAP_ARBITRUM, ZrxSwap: getConfig().REACT_APP_FEATURE_ZRX_SWAP, LifiSwap: getConfig().REACT_APP_FEATURE_LIFI_SWAP, CovalentJaypegs: getConfig().REACT_APP_FEATURE_COVALENT_JAYPEGS, diff --git a/src/test/mocks/store.ts b/src/test/mocks/store.ts index fd795790d67..0c320ddc71b 100644 --- a/src/test/mocks/store.ts +++ b/src/test/mocks/store.ts @@ -77,6 +77,7 @@ export const mockStore: ReduxState = { ThorSwapStreamingSwaps: false, Cowswap: false, CowswapGnosis: false, + CowswapArbitrum: false, Yat: false, WalletConnectToDapps: false, WalletConnectToDappsV2: false,