-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add nano contract transaction components
- Loading branch information
1 parent
ee0be8a
commit a0caced
Showing
4 changed files
with
494 additions
and
1 deletion.
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
src/components/NanoContract/NanoContractTransactionBalanceList.component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* Copyright (c) Hathor Labs and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import React, { useEffect, useState } from 'react'; | ||
import { StyleSheet, View, FlatList } from 'react-native'; | ||
import { useSelector } from 'react-redux'; | ||
import { transactionUtils } from '@hathor/wallet-lib'; | ||
import { t } from 'ttag'; | ||
|
||
import { COLORS } from '../../styles/themes'; | ||
import { NanoContractTransactionBalanceListItem } from './NanoContractTransactionBalanceListItem.component'; | ||
import { HathorFlatList } from '../HathorFlatList.component'; | ||
|
||
/** | ||
* Calculate the balance of a transaction | ||
* | ||
* @param tx Transaction to get balance from | ||
* @param storage Storage to get metadata from | ||
* @returns {Promise<Record<string, IBalance>>} Balance of the transaction | ||
*/ | ||
async function getTxBalance(tx, storage) { | ||
const balance = {}; | ||
const getEmptyBalance = () => ({ | ||
tokens: { locked: 0, unlocked: 0 }, | ||
authorities: { | ||
mint: { locked: 0, unlocked: 0 }, | ||
melt: { locked: 0, unlocked: 0 }, | ||
}, | ||
}); | ||
|
||
const nowTs = Math.floor(Date.now() / 1000); | ||
const nowHeight = await storage.getCurrentHeight(); | ||
const rewardLock = storage.version?.reward_spend_min_blocks; | ||
const isHeightLocked = this.isHeightLocked(tx.height, nowHeight, rewardLock); | ||
|
||
for (const output of tx.outputs) { | ||
// Removed isAddressMine filter | ||
if (!balance[output.token]) { | ||
balance[output.token] = getEmptyBalance(); | ||
} | ||
const isLocked = this.isOutputLocked(output, { refTs: nowTs }) || isHeightLocked; | ||
|
||
if (this.isAuthorityOutput(output)) { | ||
if (this.isMint(output)) { | ||
if (isLocked) { | ||
balance[output.token].authorities.mint.locked += 1; | ||
} else { | ||
balance[output.token].authorities.mint.unlocked += 1; | ||
} | ||
} | ||
if (this.isMelt(output)) { | ||
if (isLocked) { | ||
balance[output.token].authorities.melt.locked += 1; | ||
} else { | ||
balance[output.token].authorities.melt.unlocked += 1; | ||
} | ||
} | ||
} else if (isLocked) { | ||
balance[output.token].tokens.locked += output.value; | ||
} else { | ||
balance[output.token].tokens.unlocked += output.value; | ||
} | ||
} | ||
|
||
for (const input of tx.inputs) { | ||
// Removed isAddressMine filter | ||
if (!balance[input.token]) { | ||
balance[input.token] = getEmptyBalance(); | ||
} | ||
|
||
if (this.isAuthorityOutput(input)) { | ||
if (this.isMint(input)) { | ||
balance[input.token].authorities.mint.unlocked -= 1; | ||
} | ||
if (this.isMelt(input)) { | ||
balance[input.token].authorities.melt.unlocked -= 1; | ||
} | ||
} else { | ||
balance[input.token].tokens.unlocked -= input.value; | ||
} | ||
} | ||
|
||
return balance; | ||
} | ||
|
||
async function getTokensBalance(tx, wallet) { | ||
const tokensBalance = await getTxBalance.bind(transactionUtils)(tx, wallet.storage); | ||
const balances = []; | ||
for (const [key, balance] of Object.entries(tokensBalance)) { | ||
const tokenBalance = { | ||
tokenUid: key, | ||
available: balance.tokens.unlocked, | ||
locked: balance.tokens.locked | ||
}; | ||
balances.push(tokenBalance); | ||
} | ||
return balances; | ||
} | ||
|
||
export const NanoContractTransactionBalanceList = ({ tx }) => { | ||
const wallet = useSelector((state) => state.wallet); | ||
const [tokensBalance, setTokensBalance] = useState([]); | ||
|
||
useEffect(() => { | ||
const fetchTokensBalance = async () => { | ||
const balance = await getTokensBalance(tx, wallet); | ||
console.log('tokens balance', balance); | ||
setTokensBalance(balance); | ||
}; | ||
fetchTokensBalance(); | ||
}, []); | ||
|
||
return ( | ||
<Wrapper> | ||
<HathorFlatList | ||
data={tokensBalance} | ||
renderItem={({ item, index }) => ( | ||
<NanoContractTransactionBalanceListItem | ||
item={item} | ||
index={index} | ||
/> | ||
)} | ||
keyExtractor={(item) => item.tokenUid} | ||
/> | ||
</Wrapper> | ||
); | ||
}; | ||
|
||
const Wrapper = ({ children }) => ( | ||
<View style={styles.wrapper}> | ||
{children} | ||
</View> | ||
); | ||
|
||
const styles = StyleSheet.create({ | ||
wrapper: { | ||
flex: 1, | ||
alignSelf: 'stretch', | ||
backgroundColor: COLORS.lowContrastDetail, // Defines an outer area on the main list content | ||
}, | ||
}); |
169 changes: 169 additions & 0 deletions
169
src/components/NanoContract/NanoContractTransactionBalanceListItem.component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/** | ||
* Copyright (c) Hathor Labs and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { | ||
TouchableHighlight, | ||
StyleSheet, | ||
View, | ||
Text, | ||
} from 'react-native'; | ||
import { useSelector } from 'react-redux'; | ||
import { t } from 'ttag'; | ||
|
||
import { COLORS } from '../../styles/themes'; | ||
import { getShortHash, isTokenNFT, renderValue } from '../../utils'; | ||
import { ReceivedIcon } from '../Icon/Received.icon'; | ||
import { SentIcon } from '../Icon/Sent.icon'; | ||
|
||
function getBalanceType(value) { | ||
if (value < 0) { | ||
return 'sent'; | ||
} | ||
return 'received'; | ||
} | ||
|
||
function getTokenValue(tokenUid, tokens) { | ||
const registeredToken = Object.values(tokens).filter((token) => token.uid === tokenUid).pop(); | ||
if (registeredToken) { | ||
return registeredToken.symbol; | ||
} | ||
return getShortHash(tokenUid, 7); | ||
} | ||
|
||
/** | ||
* Renders each item of Nano Contract Transactions List. | ||
* | ||
* @param {Object} ncItem | ||
* @property {Object} ncItem.item registered Nano Contract data | ||
* @property {number} ncItem.index position in the list | ||
*/ | ||
export const NanoContractTransactionBalanceListItem = ({ item, index }) => { | ||
const balance = item.available + item.locked; | ||
const tokens = useSelector((state) => state.tokens) || {}; | ||
const tokenValue = getTokenValue(item.tokenUid, tokens); | ||
const type = getBalanceType(balance); | ||
const tokensMetadata = useSelector((state) => state.tokenMetadata); | ||
const isNft = isTokenNFT(item.tokenUid, tokensMetadata); | ||
|
||
return ( | ||
<Wrapper index={index}> | ||
<Icon type={type} /> | ||
<ContentWrapper tokenValue={tokenValue} type={type} /> | ||
<BalanceValue balance={balance} isNft={isNft} /> | ||
</Wrapper> | ||
); | ||
}; | ||
|
||
const Wrapper = ({ index, children }) => { | ||
const isFirstItem = index === 0; | ||
return ( | ||
<TouchableHighlight | ||
style={[isFirstItem && styles.firstItem]} | ||
underlayColor={COLORS.primaryOpacity30} | ||
> | ||
<View style={styles.wrapper}>{children}</View> | ||
</TouchableHighlight> | ||
); | ||
}; | ||
|
||
const Icon = ({ type }) => { | ||
const iconMap = { | ||
sent: SentIcon({ type: 'default' }), | ||
received: ReceivedIcon({ type: 'default' }), | ||
}; | ||
|
||
return (iconMap[type]); | ||
}; | ||
|
||
/** | ||
* Renders item core content. | ||
* | ||
* @param {Object} ncItem | ||
* @property {Obeject} ncItem.nc registered Nano Contract data | ||
*/ | ||
const ContentWrapper = ({ tokenValue, type }) => { | ||
const contentMap = { | ||
sent: t`Sent ${tokenValue}`, | ||
received: t`Received ${tokenValue}`, | ||
}; | ||
|
||
return ( | ||
<View style={styles.contentWrapper}> | ||
<Text style={[styles.text, styles.property]}>{contentMap[type]}</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const BalanceValue = ({ balance, isNft }) => { | ||
const isReceivedType = getBalanceType(balance) === 'received'; | ||
const balanceValue = renderValue(balance, isNft); | ||
|
||
return ( | ||
<View style={styles.balanceWrapper}> | ||
<Text style={[ | ||
styles.balance, | ||
isReceivedType && styles.balanceReceived, | ||
]} | ||
> | ||
{balanceValue} | ||
</Text> | ||
</View> | ||
) | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
wrapper: { | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
justifyContent: 'space-between', | ||
flexWrap: 'wrap', | ||
width: '100%', | ||
paddingVertical: 24, | ||
paddingHorizontal: 16, | ||
}, | ||
firstItem: { | ||
borderTopLeftRadius: 16, | ||
borderTopRightRadius: 16, | ||
}, | ||
contentWrapper: { | ||
maxWidth: '80%', | ||
flexDirection: 'column', | ||
alignItems: 'flex-start', | ||
justifyContent: 'space-between', | ||
marginRight: 'auto', | ||
paddingHorizontal: 16, | ||
}, | ||
icon: { | ||
alignSelf: 'flex-start', | ||
}, | ||
text: { | ||
fontSize: 14, | ||
lineHeight: 20, | ||
paddingBottom: 6, | ||
color: 'hsla(0, 0%, 38%, 1)', | ||
}, | ||
property: { | ||
paddingBottom: 4, | ||
fontWeight: 'bold', | ||
color: 'black', | ||
}, | ||
padding0: { | ||
paddingBottom: 0, | ||
}, | ||
balanceWrapper: { | ||
marginLeft: 'auto', | ||
}, | ||
balance: { | ||
fontSize: 16, | ||
lineHeight: 20, | ||
}, | ||
balanceReceived: { | ||
color: 'hsla(180, 85%, 34%, 1)', | ||
fontWeight: 'bold', | ||
}, | ||
}); |
Oops, something went wrong.