diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d68e1f5..00fa5499d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- added: Implement `getTokenDetails` in Ethereum and Zano tools - added: (Zano) Support infoServerTokens - changed: Allow Zano to spend to multiple destinations - fixed: Update builtinTokens with infoServerPayload when creating tools diff --git a/package.json b/package.json index efa5fc58b..ab5b0f839 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "chai": "^4.2.0", "clipanion": "^4.0.0-rc.2", "crypto-browserify": "^3.12.0", - "edge-core-js": "^2.28.0", + "edge-core-js": "^2.29.0", "esbuild-loader": "^2.20.0", "eslint": "^8.19.0", "eslint-config-standard-kit": "0.15.1", diff --git a/src/ethereum/EthereumTools.ts b/src/ethereum/EthereumTools.ts index fc55cd666..1c1ad4539 100644 --- a/src/ethereum/EthereumTools.ts +++ b/src/ethereum/EthereumTools.ts @@ -5,6 +5,7 @@ import { EdgeCurrencyInfo, EdgeCurrencyTools, EdgeEncodeUri, + EdgeGetTokenDetailsFilter, EdgeIo, EdgeMetaToken, EdgeParsedUri, @@ -381,6 +382,79 @@ export class EthereumTools implements EdgeCurrencyTools { return Object.keys(ethereumPlugins).map(plugin => `wallet:${plugin}`) } + async getTokenDetails( + filter: EdgeGetTokenDetailsFilter + ): Promise { + const { contractAddress } = filter + if (contractAddress == null) return [] + + const valid = EthereumUtil.isValidAddress(contractAddress) + if (!valid) return [] + + const { networkAdapterConfigs } = this.networkInfo + + const networkAdapterConfig = networkAdapterConfigs.find( + (networkAdapterConfig): networkAdapterConfig is RpcAdapterConfig => + networkAdapterConfig.type === 'rpc' + ) + + if (networkAdapterConfig == null) return [] + + const rpcServers = networkAdapterConfig.servers + + const ethProviders: ethers.providers.JsonRpcProvider[] = rpcServers.map( + rpcServer => + new ethers.providers.JsonRpcProvider( + rpcServer, + this.networkInfo.chainParams.chainId + ) + ) + const ERC20_ABI = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)' + ] + + interface ERC20Details { + name: string + symbol: string + decimals: number + } + + const details: ERC20Details | undefined = await multicastEthProviders< + ERC20Details, + ethers.providers.JsonRpcProvider + >({ + func: async (ethProvider: ethers.providers.JsonRpcProvider) => { + const contract = new ethers.Contract( + contractAddress, + ERC20_ABI, + ethProvider + ) + const [name, symbol, decimals] = await Promise.all([ + contract.name(), + contract.symbol(), + contract.decimals() + ]) + return { name, symbol, decimals } + }, + providers: ethProviders + }).catch(() => undefined) + if (details == null) return [] + + const out: EdgeToken = { + currencyCode: details.symbol, + displayName: details.name, + networkLocation: { + contractAddress + }, + denominations: [ + { name: details.symbol, multiplier: '1' + '0'.repeat(details.decimals) } + ] + } + return [out] + } + async getTokenId(token: EdgeToken): Promise { validateToken(token) const cleanLocation = asMaybeContractLocation(token.networkLocation) diff --git a/src/zano/ZanoTools.ts b/src/zano/ZanoTools.ts index ec9ea1da1..f826702ad 100644 --- a/src/zano/ZanoTools.ts +++ b/src/zano/ZanoTools.ts @@ -1,9 +1,10 @@ import { div } from 'biggystring' -import { asString } from 'cleaners' +import { asMaybe, asString } from 'cleaners' import { EdgeCurrencyInfo, EdgeCurrencyTools, EdgeEncodeUri, + EdgeGetTokenDetailsFilter, EdgeIo, EdgeLog, EdgeMetaToken, @@ -23,6 +24,7 @@ import { encodeUriCommon, parseUriCommon } from '../common/uriHelpers' import { getLegacyDenomination, mergeDeeply } from '../common/utils' import { asSafeZanoWalletInfo, + asZanoAssetDetails, asZanoPrivateKeys, ZanoImportPrivateKeyOpts, ZanoInfoPayload, @@ -215,6 +217,62 @@ export class ZanoTools implements EdgeCurrencyTools { return encodedUri } + async getTokenDetails( + filter: EdgeGetTokenDetailsFilter + ): Promise { + const { contractAddress } = filter + if (contractAddress == null) return [] + + const hex64Regex = /^[0-9a-fA-F]{64}$/ + if (!hex64Regex.test(contractAddress)) { + return [] + } + + const opts = { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({ + method: 'get_asset_info', + params: { asset_id: contractAddress } + }) + } + + const response = await this.io.fetch( + `${this.networkInfo.walletRpcAddress}/json_rpc`, + opts + ) + + if (!response.ok) { + const message = await response.text() + throw new Error(message) + } + + const json: unknown = await response.json() + + const assetDetails = asMaybe(asZanoAssetDetails)(json) + if (assetDetails == null) return [] + + const { + ticker, + full_name: displayName, + decimal_point: decimals + } = assetDetails.result.asset_descriptor + const out: EdgeToken = { + currencyCode: ticker, + denominations: [ + { + name: ticker, + multiplier: '1' + '0'.repeat(decimals) + } + ], + displayName, + networkLocation: { contractAddress } + } + return [out] + } + async getTokenId(token: EdgeToken): Promise { validateToken(token) const cleanLocation = asMaybeContractLocation(token.networkLocation) diff --git a/src/zano/zanoTypes.ts b/src/zano/zanoTypes.ts index bea8e5abf..9b7425ce6 100644 --- a/src/zano/zanoTypes.ts +++ b/src/zano/zanoTypes.ts @@ -93,3 +93,20 @@ export const asZanoTransferParams = asObject({ export const createZanoTokenId = (token: EdgeToken): string => { return createTokenIdFromContractAddress(token).toLowerCase() } + +export const asZanoAssetDetails = asObject({ + result: asObject({ + asset_descriptor: asObject({ + // current_supply: 2100000000000000, + decimal_point: asNumber, // 8, + full_name: asString, // 'Get Edgy ', + // hidden_supply: false, + // meta_info: '', + // owner: '7e924fb7f5f6f7f6e683e3f210f9fa4047c9978f7b9e5aa19a10afea0249d8fe', + // owner_eth_pub_key: '', + ticker: asString // 'EDGE' + // total_max_supply: 2100000000000000 + }) + // status: 'OK' + }) +}) diff --git a/yarn.lock b/yarn.lock index bfca9fd72..0462c7d5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3928,10 +3928,10 @@ ed25519@0.0.4: bindings "^1.2.1" nan "^2.0.9" -edge-core-js@^2.28.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.28.0.tgz#3ec5549c1b821ba7cea369c7ff36ed980553b1f3" - integrity sha512-c6oU43fMu9L53kMy9qhsqKHkJ05QqKEeSc80FhwfDTeBtlG9/hn+tdie50wREki3p4amLMvXdqVoqX/m+RwWUQ== +edge-core-js@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.29.0.tgz#36945c029bac568025ff5b22878d850df3803304" + integrity sha512-7HQwlLZELlG0dmjymyVABWOA2+9x5MvLcVtrTubNBGE24s4nQEtcsO/+WAF08J+Zm220E9GtlxBD3uScDoibMw== dependencies: aes-js "^3.1.0" base-x "^4.0.0"