Skip to content

Commit

Permalink
feat: add nano contract transaction components
Browse files Browse the repository at this point in the history
chore: rename NanoContractTransactionScreen.js

chore: some tweaks

lint: comply with rules

fix: small fixes
  • Loading branch information
alexruzenhack committed May 30, 2024
1 parent fdd7480 commit a946e8f
Show file tree
Hide file tree
Showing 10 changed files with 637 additions and 24 deletions.
4 changes: 2 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ import { COLORS, HathorTheme } from './styles/themes';
import { NetworkSettingsFlowNav, NetworkSettingsFlowStack } from './screens/NetworkSettings';
import { NetworkStatusBar } from './components/NetworkSettings/NetworkStatusBar';
import { NanoContractTransactionsScreen } from './screens/NanoContract/NanoContractTransactionsScreen';
import { NanoContractTransaction } from './screens/NanoContract/NanoContractTransaction.screen';
import { NanoContractTransactionScreen } from './screens/NanoContract/NanoContractTransactionScreen';

/**
* This Stack Navigator is exhibited when there is no wallet initialized on the local storage.
Expand Down Expand Up @@ -379,7 +379,7 @@ const AppStack = () => {
component={TabNavigator}
/>
<Stack.Screen name='NanoContractTransactionsScreen' component={NanoContractTransactionsScreen} />
<Stack.Screen name='NanoContractTransaction' component={NanoContractTransaction} />
<Stack.Screen name='NanoContractTransactionScreen' component={NanoContractTransactionScreen} />
<Stack.Screen name='About' component={About} />
<Stack.Screen name='Security' component={Security} />
<Stack.Screen name='WalletConnectList' component={WalletConnectList} />
Expand Down
31 changes: 23 additions & 8 deletions src/components/Icons/Received.icon.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
/**
* 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 * as React from 'react'
import Svg, { G, Path, Defs, ClipPath } from 'react-native-svg'
import { COLORS } from '../../styles/themes';
import { BaseIcon } from './Base.icon';
import { DEFAULT_ICON_SIZE } from './constants';
import { getScale, getViewBox } from './helper';

/**
* @param {SvgProps|{type: 'default'|'outline'|'fill'}} props
* @param {object} props
* @param {'default'|'outline'|'fill'} props.type
* @property {number} props.size
* @property {string} props.color
* @property {string} props.backgroundColor
*
* @description
* Svg converted from Figma using transaformer at https://react-svgr.com/playground/?native=true
*/
export const ReceivedIcon = (props) => (
<BaseIcon type={props.type || 'default'}>
export const ReceivedIcon = ({ type = 'default', size = DEFAULT_ICON_SIZE, color = 'hsla(180, 85%, 34%, 1)', backgroundColor = COLORS.white }) => (
<BaseIcon type={type}>
<Svg
xmlns='http://www.w3.org/2000/svg'
width={24}
height={24}
width={DEFAULT_ICON_SIZE}
height={DEFAULT_ICON_SIZE}
viewBox={getViewBox(size)}
transform={getScale(size, DEFAULT_ICON_SIZE)}
fill='none'
{...props}
>
<G clipPath='url(#a)'>
<Path
fill={props.color || 'hsla(180, 85%, 34%, 1)'}
fill={color}
fillRule='evenodd'
d='M7.822 17.77h5.924a.75.75 0 1 1 0 1.5H5.261v-8.486a.75.75 0 0 1 1.5 0v5.925L17.678 5.79a.75.75 0 0 1 1.06 1.061L7.823 17.769Z'
clipRule='evenodd'
Expand All @@ -28,7 +43,7 @@ export const ReceivedIcon = (props) => (
<Defs>
<ClipPath id='a'>
<Path
fill={props.color || '#fff'}
fill={backgroundColor}
d='M0 0h24v24H0z'
/>
</ClipPath>
Expand Down
31 changes: 23 additions & 8 deletions src/components/Icons/Sent.icon.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
/**
* 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 * as React from 'react'
import Svg, { G, Path, Defs, ClipPath } from 'react-native-svg'
import { COLORS } from '../../styles/themes';
import { BaseIcon } from './Base.icon';
import { DEFAULT_ICON_SIZE } from './constants';
import { getScale, getViewBox } from './helper';

/**
* @param {SvgProps|{type: 'default'|'outline'|'fill'}} props
* @param {object} props
* @param {'default'|'outline'|'fill'} props.type
* @property {number} props.size
* @property {string} props.color
* @property {string} props.backgroundColor
*
* @description
* Svg converted from Figma using transaformer at https://react-svgr.com/playground/?native=true
*/
export const SentIcon = (props) => (
<BaseIcon type={props.type || 'default'}>
export const SentIcon = ({ type = 'default', size = DEFAULT_ICON_SIZE, color = COLORS.black, backgroundColor = COLORS.white }) => (
<BaseIcon type={type}>
<Svg
xmlns='http://www.w3.org/2000/svg'
width={24}
height={24}
width={DEFAULT_ICON_SIZE}
height={DEFAULT_ICON_SIZE}
viewBox={getViewBox(size)}
transform={getScale(size, DEFAULT_ICON_SIZE)}
fill='none'
{...props}
>
<G clipPath='url(#a)'>
<Path
fill={props.color || '#000'}
fill={color}
fillRule='evenodd'
d='M18.739 13.216V4.731h-8.485a.75.75 0 1 0 0 1.5h5.924L5.261 17.148a.75.75 0 1 0 1.06 1.06L17.24 7.293v5.924a.75.75 0 1 0 1.5 0Z'
clipRule='evenodd'
Expand All @@ -28,7 +43,7 @@ export const SentIcon = (props) => (
<Defs>
<ClipPath id='a'>
<Path
fill={props.color || '#fff'}
fill={backgroundColor}
d='M0 0h24v24H0z'
/>
</ClipPath>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* 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 } from 'react-native';
import { useSelector } from 'react-redux';
import { transactionUtils } from '@hathor/wallet-lib';
import { COLORS } from '../../styles/themes';
import { NanoContractTransactionBalanceListItem } from './NanoContractTransactionBalanceListItem.component';
import { HathorFlatList } from '../HathorFlatList';

/**
* Calculate the balance of a transaction for all the addresses found,
* not only the ones from the wallet.
*
* @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;
}

/**
* Retrives transaction's balance per token.
*
* @param {Object} tx Transaction data
* @param {Object} wallet Wallet from redux store
*
* @returns {{
* tokenUid: string;
* available: number;
* locked: number;
* }[]} Array token's 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;
}

/**
* It presents a list of balance for tokens input and ouput of a transaction.
*
* @param {Object} props
* @param {Object} props.tx Transaction data
*/
export const NanoContractTransactionBalanceList = ({ tx }) => {
const wallet = useSelector((state) => state.wallet);
const [tokensBalance, setTokensBalance] = useState([]);

useEffect(() => {
const fetchTokensBalance = async () => {
const balance = await getTokensBalance(tx, wallet);
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
},
});
Loading

0 comments on commit a946e8f

Please sign in to comment.