Skip to content

Commit

Permalink
feat: display nft tx (#67)
Browse files Browse the repository at this point in the history
* feat: display nft tx

* fix: sorting of tx

* feat: modal for nft buy operation

* fix: redundancy

* fix: redundancy

* fix: transfer amount

* fix: undefined errors

* display last 10 txs

---------

Co-authored-by: matjazonline <matjazonline@gmail.com>
  • Loading branch information
anukulpandey and matjazonline committed Mar 5, 2024
1 parent e6769e1 commit ac9ce9e
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 16 deletions.
179 changes: 165 additions & 14 deletions src/pages/dashboard/Activity/Activity.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEmptyTokenWithAmount, hooks } from '@reef-chain/react-lib';
import { REEF_TOKEN, createEmptyTokenWithAmount, hooks } from '@reef-chain/react-lib';
import Uik from '@reef-chain/ui-kit';
import React, { useContext, useEffect, useState } from 'react';
import './activity.css';
Expand All @@ -10,6 +10,10 @@ import ActivityDetails from './ActivityDetails';
import ReefSigners from '../../../context/ReefSigners';
import SwapActivityItem from './SwapActivityItem';
import SwapActivityDetails from './SwapActivityDetails';
import NftActivityItem from './NftActivityItem';
import { Network } from '@reef-chain/util-lib/dist/dts/network';
import axios from 'axios';
import {BigNumber} from 'ethers';

const noActivityTokenDisplay = createEmptyTokenWithAmount();
noActivityTokenDisplay.address = '0x';
Expand All @@ -21,54 +25,177 @@ interface CummulativeTransfers extends tokenUtil.TokenTransfer{
token1?:tokenUtil.TokenTransfer;
token2?:tokenUtil.TokenTransfer;
fees?:tokenUtil.TokenTransfer;
isNftBuyOperation?: boolean;
isNftSellOperation?: boolean;
}

export interface SwapPair {
pair:string;
token1: tokenUtil.TokenTransfer;
token2: tokenUtil.TokenTransfer;
fees: tokenUtil.TokenTransfer;
isNftBuyOperation?:boolean;
}

const parseTokenTransfers = (transfers:tokenUtil.TokenTransfer[]):CummulativeTransfers[] => {
const fetchFees = async (blockId:string,index:number,nwContext:Network)=>{

Check failure on line 40 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Missing return type on function
const query = `query MyQuery {
transfers(where: {blockHash_contains: "${blockId}", AND: {extrinsicIndex_eq: ${index}}}, limit: 1) {
signedData
}
}
`
const response = await axios.post(nwContext.graphqlExplorerUrl.replace('wss','https'),{query});

return BigNumber.from(response.data.data.transfers[0].signedData.fee.partialFee);
}

const parseTokenTransfers = async(transfers:tokenUtil.TokenTransfer[],nwContext:Network):Promise<CummulativeTransfers[]> => {
const updatedTxArray: CummulativeTransfers[] = [];
const swapsIdx = [-1];
const nftPurchasesIdx = [-1];
const nftSalesIdx = [-1];
const updatedTxIdx = [-1];

transfers.forEach((tx, idx) => {
for(let idx=0;idx<transfers.length;idx++){

Check failure on line 59 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Unary operator '++' used
const tx = transfers[idx];
if (tx.reefswapAction === 'Swap' && !swapsIdx.includes(idx)) {
swapsIdx.push(idx);
const swapPair = transfers.find((t) => t.extrinsic.id === tx.extrinsic.id && t.reefswapAction === 'Swap' && t.token !== tx.token);
const swapPairIdx = transfers.indexOf(swapPair!);
swapsIdx.push(swapPairIdx);
const feesIdx = swapPairIdx + 1;
swapsIdx.push(feesIdx);
if (feesIdx <= transfers.length) {
swapsIdx.push(feesIdx);
updatedTxArray.push({
isSwap: true,
token1: tx,
token2: swapPair!,
fees: transfers[feesIdx],
timestamp: tx.timestamp,
} as CummulativeTransfers);
updatedTxIdx.push(idx);
}
} else if (tx.reefswapAction === 'Swap' || swapsIdx.includes(idx)) {
} else if (tx.reefswapAction === 'Swap' || swapsIdx.includes(idx) || nftPurchasesIdx.includes(idx)|| nftSalesIdx.includes(idx)) {
// @ts-ignore
} else {
updatedTxArray.push({
...tx,
isSwap: false,
});
}
});
return updatedTxArray.slice(0, 10);
else {
if(tx.type === "ERC1155"){
// buying nft or receiving
if(tx.inbound){

Check failure on line 84 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Expected '===' and instead saw '=='
// bought / minted

Check failure on line 85 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Unexpected any. Specify a different type
if(tx.from == "0x"){

Check failure on line 86 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations
const nftBuyPairs:any = [];

Check failure on line 87 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Expected '===' and instead saw '=='
for (const transfer of transfers) {
if (transfer.extrinsic.id === tx.extrinsic.id && transfer.token !== tx.token && tx.to == transfer.from) {
nftBuyPairs.push(transfer);
}

Check failure on line 91 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations
}

Check failure on line 92 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

'idx' is already declared in the upper scope
for (const nftBuyPair of nftBuyPairs) {
const idx = transfers.indexOf(nftBuyPair);
nftPurchasesIdx.push(idx);
}

if(nftBuyPairs.length){

Check failure on line 98 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Unexpected `await` inside a loop
// purchased NFT
const fees = await fetchFees(nftBuyPairs[0].extrinsic.blockId,nftBuyPairs[0].extrinsic.index,nwContext);

if(fees){
const feesToken = {
'token':{
...REEF_TOKEN,
balance:fees,
}
};

updatedTxArray.push({
isNftBuyOperation: true,
token1: tx,
token2: nftBuyPairs[0],
fees: feesToken,
timestamp: tx.timestamp,
} as CummulativeTransfers);
}else{
updatedTxArray.push({
isSwap: false,
...tx,
} as CummulativeTransfers);
}
updatedTxIdx.push(idx);
}
}else{
// received NFT
updatedTxArray.push({
...tx,
isSwap: false,
});
updatedTxIdx.push(idx);

Check failure on line 131 in src/pages/dashboard/Activity/Activity.tsx

View workflow job for this annotation

GitHub Actions / unit-lint

Closing curly brace does not appear on the same line as the subsequent block
}
}
// selling or sending nft
else{
const nftSellPairs:any = [];
for (const transfer of transfers) {
if (transfer.extrinsic.id === tx.extrinsic.id && transfer.token !== tx.token) {
nftSellPairs.push(transfer);
}
}

// sold nft
if(tx.to=="0x"){
if(nftSellPairs.length){
updatedTxArray.push({
...tx,
isSwap: false,
isNftSellOperation:true,
});

}else{
updatedTxArray.push({
...tx,
isSwap: false,
});
}
updatedTxIdx.push(idx);
}
// sent nft
else{
updatedTxArray.push({
...tx,
isSwap: false,
});
updatedTxIdx.push(idx);
}
}
}
}
}

for(let i=0;i<transfers.length;i++){
if(!swapsIdx.includes(i) && !nftPurchasesIdx.includes(i) && !nftSalesIdx.includes(i) && !updatedTxIdx.includes(i)){
updatedTxArray.push({
...transfers[i],
isSwap: false,
});
}
}
//@ts-ignore
updatedTxArray.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));

return updatedTxArray.reverse().slice(0, 10);
};

export const Activity = (): JSX.Element => {
const [unparsedTransfers, loading] :[tokenUtil.TokenTransfer[], boolean] = hooks.useTxHistory();
const [transfers, setTransfers] = useState([]);
const {network:nwContext} = useContext(ReefSigners);

useEffect(() => {
setTransfers(parseTokenTransfers(unparsedTransfers));
}, [unparsedTransfers]);
const parseTokens = async()=>{
const parsedTxs = await parseTokenTransfers(unparsedTransfers,nwContext);
setTransfers(parsedTxs as any);
}
parseTokens();
}, [unparsedTransfers,nwContext]);

const {
selectedSigner, network,
Expand Down Expand Up @@ -129,6 +256,7 @@ export const Activity = (): JSX.Element => {
token1: item.token1,
token2: item.token2,
fees: item.fees,

} as SwapPair);
setSwapActivityModalOpen(!isSwapActivityModalOpen);
}}
Expand All @@ -137,6 +265,28 @@ export const Activity = (): JSX.Element => {
</div>
);
}
if (item.isNftBuyOperation) {
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
return (
<div
role="button"
key={`item-wrapper-${item.timestamp + index.toString()}`}
onClick={() => {
setSwapPair({
pair: `${item.token1!.token.name}-${item.token2!.token.name}`,
token1: item.token1,
token2: item.token2,
fees: item.fees,
isNftBuyOperation: item.isNftBuyOperation,
} as SwapPair);
setSwapActivityModalOpen(!isSwapActivityModalOpen);
console.log(item.timestamp)
}}
>
<NftActivityItem fees={item.fees!} token1={item.token1!} token2={item.token2!} isBought = {true}/>
</div>
);
}
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
Expand All @@ -151,6 +301,7 @@ export const Activity = (): JSX.Element => {
timestamp={item.timestamp}
token={item.token}
inbound={item.inbound}
isNftSellOperation={item.isNftSellOperation}
/>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions src/pages/dashboard/Activity/ActivityItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface Props {
timestamp: number;
inbound: boolean;
token: Token | NFT;
isNftSellOperation?: boolean;
}

const formatDate = (timestamp: number): string => {
Expand All @@ -34,6 +35,7 @@ const TokenActivityItem = ({
token,
timestamp,
inbound,
isNftSellOperation,
}: Props): JSX.Element => {
const {
symbol,
Expand All @@ -55,6 +57,7 @@ const TokenActivityItem = ({
};

const action = actionMap[type];
if(isNftSellOperation) return `Put ${symbol || name} on sale`
return `${action} ${symbol || name}`;
}, [type, symbol, name]);

Expand Down
73 changes: 73 additions & 0 deletions src/pages/dashboard/Activity/NftActivityItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Uik from '@reef-chain/ui-kit';
import React from 'react';
import './activity-item.css';
import {
faRepeat,
} from '@fortawesome/free-solid-svg-icons';
import '../loading-animation.css';
import { tokenUtil } from '@reef-chain/util-lib';
import SwapDetails from './SwapDetails';

interface Props {
token1: tokenUtil.TokenTransfer;
token2: tokenUtil.TokenTransfer;
fees: tokenUtil.TokenTransfer;
isBought: boolean;
}

const formatDate = (timestamp: number): string => {
let date = new Date(timestamp);
const offset = date.getTimezoneOffset();
date = new Date(date.getTime() - offset * 60 * 1000);
const formattedDate = date
.toISOString()
.split('T')[0]
.split('-')
.reverse()
.join('-');
const formattedTime = date
.toISOString()
.split('T')[1]
.split(':')
.slice(0, 2)
.join(':');

return `${formattedDate}, ${formattedTime}`;
};

const NftActivityItem = ({ token1, token2, fees, isBought }: Props): JSX.Element => (
<>
<div
key={token1.timestamp}
className={`
activity-item
activity-item--send
`}
>
<div className="activity-item__indicator">
<Uik.Icon className="activity-item__indicator-icon" icon={faRepeat} />
</div>

<div className="activity-item__content">
<div
style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}
>
<div style={{ width: '100%' }}>
<div
className="activity-item__title"
title={`${isBought? "Purchased":"Sold"} ${token1.token.name}`}
>
{`${isBought? "Purchased":"Sold"} ${token1.token.name}`}
</div>
<SwapDetails token1={token1} token2={token2} fees={fees} />
<div className="activity-item__date">
{formatDate(token1.timestamp)}
</div>
</div>
</div>
</div>
</div>
</>
);

export default NftActivityItem;
19 changes: 17 additions & 2 deletions src/pages/dashboard/Activity/SwapActivityDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface Props{

function SwapActivityDetails({ isOpen, onClose, swapPair }:Props):JSX.Element {
return (
<OverlayAction isOpen={isOpen} onClose={onClose} className="overlay-swap" title={`Swap ${swapPair.pair}`}>
<OverlayAction isOpen={isOpen} onClose={onClose} className="overlay-swap" title={`${swapPair.isNftBuyOperation? `Purchased ${swapPair.token1.token.name}`:`Swap ${swapPair.pair}`}`}>
<div className="transfer-asset__container">
<div className="transfer-asset-summary">
<div
Expand All @@ -29,11 +29,23 @@ function SwapActivityDetails({ isOpen, onClose, swapPair }:Props):JSX.Element {
>
<div className="transfer-asset__content-ntf">
<div className="transfer-asset__block">
{swapPair.isNftBuyOperation ?
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-around', width: '100%',

}}
>
<div style={{
<img src={swapPair.token1.token.iconUrl} alt={swapPair.token1.token.name} style={{
maxWidth: "250px",
borderRadius: "20px",
marginTop: "15px",
}}/>
</div>
:<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-around', width: '100%',
}}
>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-around', flexDirection: 'column',
}}
>
Expand Down Expand Up @@ -64,7 +76,10 @@ function SwapActivityDetails({ isOpen, onClose, swapPair }:Props):JSX.Element {
</div>

</div>

</div>
}

</div>
<Uik.Text text="Transfer Details" type="light" className="mt-2" />
<div
Expand Down

0 comments on commit ac9ce9e

Please sign in to comment.