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/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/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',