Skip to content

Commit

Permalink
Merge pull request #568 from telosnetwork/develop
Browse files Browse the repository at this point in the history
v2.3.0-rc
  • Loading branch information
donnyquixotic authored Oct 2, 2023
2 parents 3a5bbeb + 62b7991 commit e2a4128
Show file tree
Hide file tree
Showing 112 changed files with 4,587 additions and 960 deletions.
6 changes: 4 additions & 2 deletions env.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const TESTNET = {
HYPERION_ENDPOINT: 'https://testnet.telos.net',
NETWORK_EXPLORER: 'https://explorer-test.telos.net',
CHAIN_NAME: 'telos-testnet',
APP_OREID_APP_ID: 't_75a4d9233ec441d18c4221e92b379197',
OREID_APP_ID: 't_75a4d9233ec441d18c4221e92b379197',
OREID_APP_ID_NATIVE: 't_a61e9926d5204387a9ac113dfce7cbc5',
};

const MAINNET = {
Expand All @@ -36,7 +37,8 @@ const MAINNET = {
HYPERION_ENDPOINT: 'https://mainnet.telos.net',
NETWORK_EXPLORER: 'https://explorer.telos.net',
CHAIN_NAME: 'telos',
APP_OREID_APP_ID: 'p_e5b81fcc20a04339993b0cc80df7e3fd',
OREID_APP_ID: 'p_e5b81fcc20a04339993b0cc80df7e3fd',
OREID_APP_ID_NATIVE: 'p_751f87258d5b40998b55c626d612fd4e',
};

const env = process.env.NETWORK === 'mainnet' ? MAINNET : TESTNET;
Expand Down
3 changes: 3 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
to = "/"
status = 200

[context.environment]
NETWORK = "testnet"

[build]
publish = "dist/spa"
command = "quasar build"
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "telos-web-wallet",
"version": "2.2.0",
"version": "2.3.0",
"description": "A Web Wallet for Telos",
"productName": "Telos Web Wallet",
"private": true,
Expand Down Expand Up @@ -38,7 +38,7 @@
"node-polyfill-webpack-plugin": "^2.0.1",
"numeral": "^2.0.6",
"oreid-js": "^4.7.1",
"oreid-webpopup": "^2.3.0",
"oreid-webpopup": "^2.4.0",
"pinia": "^2.0.33",
"ptokens": "^0.14.0",
"qrcanvas-vue": "^3.0.0",
Expand All @@ -47,6 +47,7 @@
"rxjs": "^7.8.0",
"ual-anchor": "^1.1.2",
"ual-ledger": "^0.3.0",
"ual-oreid": "^1.0.0",
"ual-wombat": "^0.3.3",
"universal-authenticator-library": "^0.3.0",
"vue": "3",
Expand Down
152 changes: 130 additions & 22 deletions src/antelope/chains/EVMChainSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ import {
NFTContractClass,
IndexerNftItemResult,
NFTItemClass,
addressString,
IndexerTransfersFilter,
IndexerAccountTransfersResponse,
} from 'src/antelope/types';
import EvmContract from 'src/antelope/stores/utils/contracts/EvmContract';
import { ethers } from 'ethers';
import { toStringNumber } from 'src/antelope/stores/utils/currency-utils';
import { dateIsWithinXMinutes } from 'src/antelope/stores/utils/date-utils';
import { getAntelope } from 'src/antelope';
import { WEI_PRECISION } from 'src/antelope/stores/utils';


export default abstract class EVMChainSettings implements ChainSettings {
Expand All @@ -41,6 +46,9 @@ export default abstract class EVMChainSettings implements ChainSettings {
// External query API support
protected hyperion: AxiosInstance = axios.create({ baseURL: this.getHyperionEndpoint() });

// External query API support
protected api: AxiosInstance = axios.create({ baseURL: this.getApiEndpoint() });

// External trusted metadata bucket for EVM contracts
protected contractsBucket: AxiosInstance = axios.create({ baseURL: this.getTrustedContractsBucket() });

Expand All @@ -60,7 +68,10 @@ export default abstract class EVMChainSettings implements ChainSettings {
tokenListPromise: Promise<TokenClass[]> | null = null;

// EvmContracts cache mapped by address
protected contracts: Record<string, EvmContract | false> = {};
protected contracts: Record<string, {
promise: Promise<EvmContract | false>;
resolve?: (value: EvmContract | false) => void;
}> = {};

constructor(network: string) {
this.network = network;
Expand Down Expand Up @@ -100,8 +111,8 @@ export default abstract class EVMChainSettings implements ChainSettings {

// Check indexer health state periodically
this.initPromise = new Promise((resolve) => {
this.updateIndexerHealthState().then(() => {
// we resolve the promise that will be returned by init()
this.updateIndexerHealthState().finally(() => {
// we resolve the promise (in any case) that will be returned by init()
resolve();
});
});
Expand All @@ -120,8 +131,13 @@ export default abstract class EVMChainSettings implements ChainSettings {

// this setTimeout is a work arround because we can't call getAntelope() function before it initializes
setTimeout(() => {
setInterval(() => {
this.updateIndexerHealthState();
const timer = setInterval(async () => {
try {
await this.updateIndexerHealthState();
} catch (e) {
clearInterval(timer);
console.error('Indexer API not working for this chain:', this.getNetwork(), e);
}
}, getAntelope().config.indexerHealthCheckInterval);
}, 1000);

Expand Down Expand Up @@ -159,11 +175,7 @@ export default abstract class EVMChainSettings implements ChainSettings {
this.indexer.get('/v1/health') :
Promise.resolve({ data: this.deathHealthResponse } as AxiosResponse<IndexerHealthResponse>),
)
.then(response => response.data as unknown as IndexerHealthResponse)
.catch((error) => {
console.error('Indexer API not working for this chain:', this.getNetwork(), error);
return this.deathHealthResponse as IndexerHealthResponse;
});
.then(response => response.data as unknown as IndexerHealthResponse);

// initial state
this._indexerHealthState = {
Expand Down Expand Up @@ -194,6 +206,11 @@ export default abstract class EVMChainSettings implements ChainSettings {
return false;
}

// only testnet chains should override this
isTestnet() {
return false;
}

getNetwork(): string {
return this.network;
}
Expand All @@ -209,10 +226,12 @@ export default abstract class EVMChainSettings implements ChainSettings {
abstract getSystemToken(): TokenClass;
abstract getStakedSystemToken(): TokenClass;
abstract getWrappedSystemToken(): TokenClass;
abstract getEscrowContractAddress(): addressString;
abstract getChainId(): string;
abstract getDisplay(): string;
abstract getHyperionEndpoint(): string;
abstract getRPCEndpoint(): RpcEndpoint;
abstract getApiEndpoint(): string;
abstract getPriceData(): Promise<PriceChartData>;
abstract getUsdPrice(): Promise<number>;
abstract getBuyMoreOfTokenLink(): string;
Expand All @@ -225,6 +244,11 @@ export default abstract class EVMChainSettings implements ChainSettings {
abstract hasIndexerSupport(): boolean;
abstract trackAnalyticsEvent(params: Record<string, unknown>): void;

async getApy(): Promise<string> {
const response = await this.api.get('apy/evm');
return response.data as string;
}

async getBalances(account: string): Promise<TokenBalance[]> {
if (!this.hasIndexerSupport()) {
console.error('Indexer API not supported for this chain:', this.getNetwork());
Expand Down Expand Up @@ -268,8 +292,10 @@ export default abstract class EVMChainSettings implements ChainSettings {
const balance = ethers.BigNumber.from(result.balance);
const tokenBalance = new TokenBalance(token, balance);
tokens.push(tokenBalance);
// If we have market data we use it
if (typeof contractData.calldata === 'object') {
const priceUpdatedWithinTenMins = !!contractData.calldata.marketdata_updated && dateIsWithinXMinutes(+contractData.calldata.marketdata_updated, 10);

// If we have market data we use it, as long as the price was updated within the last 10 minutes
if (typeof contractData.calldata === 'object' && priceUpdatedWithinTenMins) {
const price = (+(contractData.calldata.price ?? 0)).toFixed(12);
const marketInfo = { ...contractData.calldata, price } as MarketSourceInfo;
const marketData = new TokenMarketData(marketInfo);
Expand Down Expand Up @@ -324,6 +350,11 @@ export default abstract class EVMChainSettings implements ChainSettings {

}
const contract_source = response.contracts[item_source.contract];

if (!contract_source) {
// this case only happens if the indexer fails to index contract data
continue;
}
const contract = new NFTContractClass(contract_source);
const item = new NFTItemClass(item_source, contract);
const nft = new NFTClass(item);
Expand All @@ -348,23 +379,55 @@ export default abstract class EVMChainSettings implements ChainSettings {
return `${token.symbol}-${token.address}-${this.getNetwork()}`;
}

getContract(address: string): EvmContract | false | null {
/**
* This method returns the cached value for the requested contract which can be one of three values:
* - Promise<EvmContract> if the contract is already cached
* - Promise<null> if the contract was never requested before
* - Promise<false> if the contract was requested, not found and set as not existing (to avoid requesting it again)
* @param address contract requested
* @returns Promise for the requested contract or false if it doesn't exist
*/
async getContract(address: string): Promise<EvmContract | false | null> {
const key = address.toLowerCase();
return this.contracts[key] ?? null;
const returnValue = this.contracts[key]?.promise ?? null;
if (!this.contracts[key]) {
this.contracts[key] = {
promise: Promise.resolve(false),
};
this.contracts[key].promise = new Promise((resolve) => {
this.contracts[key].resolve = resolve;
});
}
return returnValue;
}

addContract(address: string, contract: EvmContract) {
/**
* This method adds a contract to the cache and resolves the promise for it
* @param address address of the contract
* @param contract contract instance to be cached
*/
addContract(address: string, contract: EvmContract | false) {
const key = address.toLowerCase();
if (!this.contracts[key]) {
this.contracts[key] = contract;
this.contracts[key] = {
promise: Promise.resolve(contract),
};
} else {
if (this.contracts[key].resolve) {
this.contracts[key].resolve?.(contract);
} else {
console.error('Error: Contract already exists', address);
}
}
}

/**
* This method sets a contract as not existing and resolves the promise to false for it.
* This is done to distinguish between a contract that was never requested before and one that was requested and not found.
* @param address address of the contract
*/
setContractAsNotExisting(address: string) {
const key = address.toLowerCase();
if (!this.contracts[key]) {
this.contracts[key] = false;
}
return this.addContract(address, false);
}

async getEVMTransactions(filter: IndexerTransactionsFilter): Promise<IndexerAccountTransactionsResponse> {
Expand Down Expand Up @@ -411,6 +474,51 @@ export default abstract class EVMChainSettings implements ChainSettings {
.then(response => response.data as IndexerAccountTransactionsResponse);
}

async getEvmNftTransfers({
account,
type,
limit,
offset,
includePagination,
endBlock,
startBlock,
contract,
includeAbi,
}: IndexerTransfersFilter): Promise<IndexerAccountTransfersResponse> {
let aux = {};

if (limit !== undefined) {
aux = { limit, ...aux };
}
if (offset !== undefined) {
aux = { offset, ...aux };
}
if (includeAbi !== undefined) {
aux = { includeAbi, ...aux };
}
if (type !== undefined) {
aux = { type, ...aux };
}
if (includePagination !== undefined) {
aux = { includePagination, ...aux };
}
if (endBlock !== undefined) {
aux = { endBlock, ...aux };
}
if (startBlock !== undefined) {
aux = { startBlock, ...aux };
}
if (contract !== undefined) {
aux = { contract, ...aux };
}

const params = aux as AxiosRequestConfig;
const url = `v1/account/${account}/transfers`;

return this.indexer.get(url, { params })
.then(response => response.data as IndexerAccountTransfersResponse);
}

async getTokenList(): Promise<TokenClass[]> {
if (this.tokenListPromise) {
return this.tokenListPromise;
Expand Down Expand Up @@ -477,10 +585,10 @@ export default abstract class EVMChainSettings implements ChainSettings {
async getEstimatedGas(limit: number): Promise<{ system:ethers.BigNumber, fiat:ethers.BigNumber }> {
const gasPrice: ethers.BigNumber = await this.getGasPrice();
const tokenPrice: number = await this.getUsdPrice();
const price = ethers.utils.parseUnits(toStringNumber(tokenPrice), 18);
const price = ethers.utils.parseUnits(toStringNumber(tokenPrice), WEI_PRECISION);
const system = gasPrice.mul(limit);
const fiatDouble = system.mul(price);
const fiat = fiatDouble.div(ethers.utils.parseUnits('1', 18));
const fiat = fiatDouble.div(ethers.utils.parseUnits('1', WEI_PRECISION));
return { system, fiat };
}
async getLatestBlock(): Promise<ethers.BigNumber> {
Expand Down
5 changes: 5 additions & 0 deletions src/antelope/chains/NativeChainSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ export default abstract class NativeChainSettings implements ChainSettings {
return true;
}

// only testnet chains should override this
isTestnet() {
return false;
}

getNetwork(): string {
return this.network;
}
Expand Down
19 changes: 16 additions & 3 deletions src/antelope/chains/evm/telos-evm-testnet/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import EVMChainSettings from 'src/antelope/chains/EVMChainSettings';
import { RpcEndpoint } from 'universal-authenticator-library';
import { api } from 'src/api';
import { NativeCurrencyAddress, PriceChartData } from 'src/antelope/types';
import { NativeCurrencyAddress, PriceChartData, addressString } from 'src/antelope/types';
import { TokenClass, TokenSourceInfo } from 'src/antelope/types';
import { useUserStore } from 'src/antelope';
import { getFiatPriceFromIndexer } from 'src/api/price';
Expand Down Expand Up @@ -50,7 +50,8 @@ const RPC_ENDPOINT = {
port: 443,
path: '/evm',
};

const ESCROW_CONTRACT_ADDRESS = '0x7E9cF9fBc881652B05BB8F26298fFAB538163b6f';
const API_ENDPOINT = 'https://api-dev.telos.net/v1';
const WEI_PRECISION = 18;
const EXPLORER_URL = 'https://testnet.teloscan.io';
const ECOSYSTEM_URL = 'https://www.telos.net/ecosystem';
Expand All @@ -61,6 +62,10 @@ const CONTRACTS_BUCKET = 'https://verified-evm-contracts-testnet.s3.amazonaws.co
declare const fathom: { trackGoal: (eventId: string, value: 0) => void };

export default class TelosEVMTestnet extends EVMChainSettings {
isTestnet() {
return true;
}

getNetwork(): string {
return NETWORK;
}
Expand All @@ -81,6 +86,10 @@ export default class TelosEVMTestnet extends EVMChainSettings {
return RPC_ENDPOINT;
}

getApiEndpoint(): string {
return API_ENDPOINT;
}

getPriceData(): Promise<PriceChartData> {
return api.getCoingeckoPriceChartData('telos');
}
Expand All @@ -97,8 +106,12 @@ export default class TelosEVMTestnet extends EVMChainSettings {
return W_TOKEN;
}

getEscrowContractAddress(): addressString {
return ESCROW_CONTRACT_ADDRESS;
}

async getUsdPrice(): Promise<number> {
if (this.hasIndexerSupport()) {
if (this.hasIndexerSupport() && this.isIndexerHealthy()) {
const nativeTokenSymbol = this.getSystemToken().symbol;
const fiatCode = useUserStore().fiatCurrency;
const fiatPrice = await getFiatPriceFromIndexer(nativeTokenSymbol, NativeCurrencyAddress, fiatCode, this.indexer, this);
Expand Down
Loading

0 comments on commit e2a4128

Please sign in to comment.