From 647ced9b2f9d063beef4fede9a60162e5995e18b Mon Sep 17 00:00:00 2001 From: samsiegart Date: Thu, 26 Jan 2023 12:40:46 -0800 Subject: [PATCH] feat(web-components): read purses from bank instead of smart wallet --- .../src/keplr-connection/KeplrConnection.js | 6 +- .../{getChainId.js => getChainInfo.js} | 8 +- .../src/keplr-connection/queryBankBalances.js | 20 +++++ .../src/keplr-connection/watchWallet.js | 78 ++++++++++++++----- 4 files changed, 87 insertions(+), 25 deletions(-) rename packages/web-components/src/keplr-connection/{getChainId.js => getChainInfo.js} (65%) create mode 100644 packages/web-components/src/keplr-connection/queryBankBalances.js diff --git a/packages/web-components/src/keplr-connection/KeplrConnection.js b/packages/web-components/src/keplr-connection/KeplrConnection.js index 1017a81387c..1cd085f1ec0 100644 --- a/packages/web-components/src/keplr-connection/KeplrConnection.js +++ b/packages/web-components/src/keplr-connection/KeplrConnection.js @@ -2,7 +2,7 @@ import { makeLeader } from '@agoric/casting'; import { makeImportContext } from '@agoric/smart-wallet/src/marshal-contexts.js'; import { getKeplrAddress } from './getKeplrAddress'; -import { getChainId } from './getChainId'; +import { getChainInfo } from './getChainInfo'; import { watchWallet } from './watchWallet'; // TODO: We need a way to detect the appropriate network-config, and default it @@ -13,11 +13,11 @@ export const makeAgoricKeplrConnection = async ( networkConfig = DEFAULT_NETWORK_CONFIG, context = makeImportContext(), ) => { - const chainId = await getChainId(networkConfig); + const { chainId, rpcs } = await getChainInfo(networkConfig); const address = await getKeplrAddress(chainId); const leader = makeLeader(networkConfig); - const walletNotifiers = await watchWallet(leader, address, context); + const walletNotifiers = await watchWallet(leader, address, context, rpcs); return { address, diff --git a/packages/web-components/src/keplr-connection/getChainId.js b/packages/web-components/src/keplr-connection/getChainInfo.js similarity index 65% rename from packages/web-components/src/keplr-connection/getChainId.js rename to packages/web-components/src/keplr-connection/getChainInfo.js index b5aac46e8cc..ff5e59deb86 100644 --- a/packages/web-components/src/keplr-connection/getChainId.js +++ b/packages/web-components/src/keplr-connection/getChainInfo.js @@ -1,17 +1,19 @@ // @ts-check import { Errors } from './errors'; -export const getChainId = async networkConfig => { +export const getChainInfo = async networkConfig => { let chainId; + let rpcs; try { // eslint-disable-next-line @jessie.js/no-nested-await const res = await fetch(networkConfig); // eslint-disable-next-line @jessie.js/no-nested-await - const { chainName } = await res.json(); + const { chainName, rpcAddrs } = await res.json(); chainId = chainName; + rpcs = rpcAddrs; } catch { throw new Error(Errors.networkConfig); } - return chainId; + return { chainId, rpcs }; }; diff --git a/packages/web-components/src/keplr-connection/queryBankBalances.js b/packages/web-components/src/keplr-connection/queryBankBalances.js new file mode 100644 index 00000000000..16230372e79 --- /dev/null +++ b/packages/web-components/src/keplr-connection/queryBankBalances.js @@ -0,0 +1,20 @@ +import { QueryClient, createProtobufRpcClient } from '@cosmjs/stargate'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; +import { QueryClientImpl } from 'cosmjs-types/cosmos/bank/v1beta1/query'; + +/** + * @param {string} address + * @param {string} rpc + */ +export const queryBankBalances = async (address, rpc) => { + const tendermint = await Tendermint34Client.connect(rpc); + const queryClient = new QueryClient(tendermint); + const rpcClient = createProtobufRpcClient(queryClient); + const bankQueryService = new QueryClientImpl(rpcClient); + + const { balances } = await bankQueryService.AllBalances({ + address, + }); + + return balances; +}; diff --git a/packages/web-components/src/keplr-connection/watchWallet.js b/packages/web-components/src/keplr-connection/watchWallet.js index 1e0a3c58a45..c1d29b9d5b2 100644 --- a/packages/web-components/src/keplr-connection/watchWallet.js +++ b/packages/web-components/src/keplr-connection/watchWallet.js @@ -1,14 +1,16 @@ // @ts-check -import { makeFollower } from '@agoric/casting'; +import { makeFollower, iterateLatest } from '@agoric/casting'; import { makeNotifierKit } from '@agoric/notifier'; - -import { fetchCurrent } from './fetchCurrent'; -import { followLatest } from './followLatest'; +import { AmountMath } from '@agoric/ertp'; +import { assertHasData } from '@agoric/smart-wallet/src/utils'; import { Errors } from './errors'; +import { queryBankBalances } from './queryBankBalances'; /** @typedef {import('./fetchCurrent').PurseInfo} PurseInfo */ -export const watchWallet = async (leader, address, context) => { +const POLL_INTERVAL_MS = 6000; + +export const watchWallet = async (leader, address, context, rpcs) => { const followPublished = path => makeFollower(`:published.${path}`, leader, { unserializer: context.fromMyWallet, @@ -30,21 +32,59 @@ export const watchWallet = async (leader, address, context) => { pursesNotifierKit.updater.updateState(harden(purses)); }; - const currentFollower = followPublished(`wallet.${address}.current`); - const { blockHeight, brandToPurse } = await fetchCurrent( - currentFollower, - ).catch(() => { + const currentFollower = await followPublished(`wallet.${address}.current`); + try { + // eslint-disable-next-line @jessie.js/no-nested-await + await assertHasData(currentFollower); + } catch { throw new Error(Errors.noSmartWallet); - }); - updatePurses(brandToPurse); - - const latestFollower = followPublished(`wallet.${address}`); - followLatest({ - startingHeight: blockHeight, - latestFollower, - updatePurses, - brandToPurse, - }); + } + + const watchChainBalances = () => { + const brandToPurse = new Map(); + let vbankAssets; + let bank; + + const possiblyUpdateBankPurses = () => { + if (!vbankAssets || !bank) return; + + const bankMap = new Map(bank.map(({ denom, amount }) => [denom, amount])); + + vbankAssets.forEach(([denom, info]) => { + const amount = bankMap.get(denom) ?? 0n; + const purseInfo = { + brand: info.brand, + currentAmount: AmountMath.make(info.brand, BigInt(amount)), + brandPetname: info.issuerName, + pursePetname: info.issuerName, + displayInfo: info.displayInfo, + }; + brandToPurse.set(info.brand, purseInfo); + }); + + updatePurses(brandToPurse); + }; + + const watchBank = async () => { + const balances = await queryBankBalances(address, rpcs[0]); + bank = balances; + possiblyUpdateBankPurses(); + setTimeout(watchBank, POLL_INTERVAL_MS); + }; + + const watchVbankAssets = async () => { + const vbankAssetsFollower = followPublished('agoricNames.vbankAsset'); + for await (const { value } of iterateLatest(vbankAssetsFollower)) { + vbankAssets = value; + possiblyUpdateBankPurses(); + } + }; + + void watchVbankAssets(); + void watchBank(); + }; + + watchChainBalances(); return { pursesNotifier: pursesNotifierKit.notifier,