Skip to content

Commit

Permalink
feat: move fungibleTokens to composable
Browse files Browse the repository at this point in the history
  • Loading branch information
konradodo authored and peronczyk committed Oct 16, 2023
1 parent 7121589 commit f90eecf
Show file tree
Hide file tree
Showing 34 changed files with 520 additions and 497 deletions.
306 changes: 306 additions & 0 deletions src/composables/fungibleTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import { ref, watch } from 'vue';
import camelCaseKeysDeep from 'camelcase-keys-deep';
import BigNumber from 'bignumber.js';
import { Encoded, Encoding } from '@aeternity/aepp-sdk';
import { fetchAllPages, handleUnknownError, toShiftedBigNumber } from '@/utils';
import type {
BigNumberPublic, IDefaultComposableOptions,
IToken,
ITokenList,
ITransaction,
TokenPair,
} from '@/types';
import { PROTOCOL_AETERNITY, TX_DIRECTION } from '@/constants';
import { aettosToAe, calculateSupplyAmount, categorizeContractCallTxObject } from '@/protocols/aeternity/helpers';
import FungibleTokenFullInterfaceACI from '@/lib/contracts/FungibleTokenFullInterfaceACI.json';
import AedexV2PairACI from '@/lib/contracts/AedexV2PairACI.json';
import ZeitTokenACI from '@/lib/contracts/FungibleTokenFullACI.json';
import { AE_SYMBOL } from '@/protocols/aeternity/config';
import { useAccounts } from './accounts';
import { useAeSdk } from './aeSdk';
import { useMiddleware } from './middleware';
import { useTippingContracts } from './tippingContracts';
import { createNetworkWatcher } from './networks';
import { createPollingBasedOnMountedComponents } from './composablesHelpers';

const availableTokens = ref<ITokenList>({});
const tokenBalances = ref<Record<string, IToken[]>>({});

const { onNetworkChange } = createNetworkWatcher();
const availableTokensPooling = createPollingBasedOnMountedComponents(60000);
const tokenBalancesPooling = createPollingBasedOnMountedComponents(10000);

export function useFungibleTokens({ store }: IDefaultComposableOptions) {
const { getAeSdk } = useAeSdk({ store });
const { fetchFromMiddleware } = useMiddleware();
const { tippingContractAddresses } = useTippingContracts({ store });
const {
isLoggedIn,
aeAccounts,
getLastActiveProtocolAccount,
} = useAccounts();

function getTokenBalance(address?: string) {
const account = getLastActiveProtocolAccount(PROTOCOL_AETERNITY);
return tokenBalances.value[address || account?.address!] || [];
}

async function loadAvailableTokens() {
const response: IToken[] = camelCaseKeysDeep(await fetchAllPages(
() => fetchFromMiddleware('/v2/aex9?by=name&limit=100&direction=forward'),
fetchFromMiddleware,
));

if (!response.length) {
availableTokens.value = {};
}

availableTokens.value = response.reduce((accumulator, token) => {
const { contractId, ...other } = token;

return {
...accumulator,
[contractId]: { contractId, ...other },
};
}, {});
}

async function loadTokenBalances() {
if (!isLoggedIn.value) {
return;
}
const addresses = aeAccounts.value.map((account) => account.address);

await Promise.all(addresses.map(async (address) => {
try {
const tokens: IToken[] = camelCaseKeysDeep(await fetchAllPages(
() => fetchFromMiddleware(`/v2/aex9/account-balances/${address}?limit=100`),
fetchFromMiddleware,
));
if (!tokens.length) {
return;
}

const balances: IToken[] = (tokens)
.filter((token) => availableTokens.value[token.contractId])
.map((token) => {
const availableToken = availableTokens.value[token.contractId];
const balance = toShiftedBigNumber(token.amount!, -availableToken.decimals);
const convertedBalance = Number(balance.toFixed(2));

return {
...availableToken,
value: token.contractId,
text: `${convertedBalance} ${availableToken.symbol}`,
contractId: token.contractId,
balance,
convertedBalance,
};
});

tokenBalances.value[address] = balances;
} catch (error) {
handleUnknownError(error);
}
}));
}

async function createOrChangeAllowance(contractId: string, amount: number | string) {
const aeSdk = await getAeSdk();
const account = getLastActiveProtocolAccount(PROTOCOL_AETERNITY);
const selectedToken = tokenBalances.value?.[account?.address!]
?.find((token) => token?.contractId === contractId);

const tokenContract = await aeSdk.initializeContract({
aci: FungibleTokenFullInterfaceACI,
address: selectedToken?.contractId as any,
});

const { decodedResult } = await tokenContract.allowance({
from_account: account?.address,
for_account: tippingContractAddresses?.value?.tippingV2?.replace('ct_', 'ak_'),
});

const allowanceAmount = (decodedResult !== undefined)
? new BigNumber(decodedResult)
.multipliedBy(-1)
.plus(toShiftedBigNumber(amount, selectedToken?.decimals!))
.toNumber()
: toShiftedBigNumber(amount, selectedToken?.decimals!)
.toNumber();

const getContractFunction = (tokenContract.methods as any)[
decodedResult !== undefined ? 'change_allowance' : 'create_allowance'
];

return getContractFunction(
tippingContractAddresses.value?.tippingV2?.replace(
`${Encoding.ContractAddress}_`,
`${Encoding.AccountAddress}_`,
),
allowanceAmount,
);
}

async function getContractTokenPairs(
address: Encoded.ContractAddress,
): Promise<Partial<TokenPair> & Record<string, any>> {
try {
const aeSdk = await getAeSdk();
const account = getLastActiveProtocolAccount(PROTOCOL_AETERNITY);
const tokenContract = await aeSdk.initializeContract({
aci: AedexV2PairACI,
address,
});

const [
{ decodedResult: balances },
{ decodedResult: balance },
{ decodedResult: token0 },
{ decodedResult: token1 },
{ decodedResult: reserves },
{ decodedResult: totalSupply },
] = await Promise.all([
tokenContract.balances(),
tokenContract.balance(account?.address),
tokenContract.token0(),
tokenContract.token1(),
tokenContract.get_reserves(),
tokenContract.total_supply(),
]);

return {
token0: {
...availableTokens.value?.[token0],
amount: calculateSupplyAmount(
balance,
totalSupply,
reserves.reserve0,
),
},
token1: {
...availableTokens.value?.[token1],
amount: calculateSupplyAmount(
balance,
totalSupply,
reserves.reserve1,
),
},
totalSupply,
balance,
balances,
};
} catch (error) {
return {};
}
}

async function transferToken(
tokenContractId: Encoded.ContractAddress,
toAccount: Encoded.AccountAddress,
amount: number,
option: {
waitMined: boolean,
modal: boolean,
},
) {
const aeSdk = await getAeSdk();
const tokenContract = await aeSdk.initializeContract({
aci: FungibleTokenFullInterfaceACI,
address: tokenContractId,
});
return tokenContract.transfer(toAccount, amount.toFixed(), option);
}

async function burnTriggerPoS(
address: Encoded.ContractAddress,
posAddress: string,
invoiceId: string,
amount: number,
option: {
waitMined: boolean,
modal: boolean,
},
) {
const aeSdk = await getAeSdk();
const tokenContract = await aeSdk.initializeContract({
aci: ZeitTokenACI,
address,
});
return tokenContract.burn_trigger_pos(
amount.toFixed(),
posAddress,
invoiceId,
option,
);
}

function getTxSymbol(transaction?: ITransaction) {
if (transaction?.pendingTokenTx) {
return availableTokens.value[transaction.tx.contractId]?.symbol;
}
const contractCallData = transaction?.tx && categorizeContractCallTxObject(transaction);
return availableTokens.value[contractCallData?.token!]?.symbol || AE_SYMBOL;
}

function getTxAmountTotal(
transaction: ITransaction,
direction: string = TX_DIRECTION.sent,
) {
const contractCallData = transaction.tx && categorizeContractCallTxObject(transaction);
if (contractCallData && availableTokens.value[contractCallData.token!]) {
return +toShiftedBigNumber(
contractCallData.amount || 0,
-availableTokens.value[contractCallData.token!].decimals,
);
}
const isReceived = direction === TX_DIRECTION.received;

const rawAmount = (
transaction.tx?.amount
|| (transaction.tx?.tx?.tx as any)?.amount
|| transaction.tx?.nameFee
|| 0
);

const amount: BigNumberPublic = (typeof rawAmount === 'object')
? rawAmount
: new BigNumber(Number(rawAmount));

return +aettosToAe(amount
.plus(isReceived ? 0 : transaction.tx?.fee || 0)
.plus(isReceived ? 0 : transaction.tx?.tx?.tx?.fee || 0));
}

onNetworkChange(async (network, oldNetwork) => {
const newMiddlewareUrl = network.protocols[PROTOCOL_AETERNITY].middlewareUrl;
const oldMiddlewareUrl = oldNetwork?.protocols?.[PROTOCOL_AETERNITY]?.middlewareUrl;
if (newMiddlewareUrl !== oldMiddlewareUrl) {
await loadAvailableTokens();
await loadTokenBalances();
}
});

watch(aeAccounts, (val, oldVal) => {
if (val !== oldVal) {
loadTokenBalances();
}
});

availableTokensPooling(() => loadAvailableTokens());
tokenBalancesPooling(() => loadTokenBalances());

return {
availableTokens,
tokenBalances,
getTokenBalance,
createOrChangeAllowance,
getTxSymbol,
getTxAmountTotal,
transferToken,
burnTriggerPoS,
getContractTokenPairs,
loadTokenBalances,
loadAvailableTokens,
};
}
1 change: 1 addition & 0 deletions src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from './transactionTokens';
export * from './transactionList';
export * from './ui';
export * from './viewport';
export * from './fungibleTokens';
export * from './coinTokensProps';
export * from './scrollTransactions';
export * from './languages';
6 changes: 3 additions & 3 deletions src/composables/latestTransactionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@/utils';
import { ProtocolAdapterFactory } from '@/lib/ProtocolAdapterFactory';
import { AE_MDW_TO_NODE_APPROX_DELAY_TIME } from '@/protocols/aeternity/config';
import { useFungibleTokens } from './fungibleTokens';
import { useAccounts } from './accounts';
import { useBalances } from './balances';
import { useTransactionTx } from './transactionTx';
Expand All @@ -40,10 +41,9 @@ export function useLatestTransactionList({ store }: IDefaultComposableOptions) {
fetchTransactions,
} = useTransactionList({ store });

const { tokenBalances } = useFungibleTokens({ store });
const btcTransactions = ref<ITransaction[]>([]);

const tokens = computed(() => store.state.fungibleTokens.tokens);

const latestTransactions = computed(() => {
const aeTransactions = Object.entries(transactions.value)
.map(([
Expand Down Expand Up @@ -131,7 +131,7 @@ export function useLatestTransactionList({ store }: IDefaultComposableOptions) {
);

watch(
tokens,
tokenBalances,
(oldTokens, newTokens) => {
if (!isEqual(oldTokens, newTokens)) {
updateTransactionListData();
Expand Down
Loading

0 comments on commit f90eecf

Please sign in to comment.