Skip to content

Commit

Permalink
feat: support non-vbank purses
Browse files Browse the repository at this point in the history
  • Loading branch information
samsiegart committed Aug 25, 2023
1 parent 4fe64c8 commit dfa49e2
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 49 deletions.
23 changes: 23 additions & 0 deletions packages/rpc/src/chainStorageWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,33 @@ export const makeAgoricChainStorageWatcher = (
return () => stopWatching(pathKey, subscriber as Subscriber<unknown>);
};

const queryOnce = <T>(path: [AgoricChainStoragePathKind, string]) =>
new Promise<T>(res => {
const stop = watchLatest<T>(path, val => {
stop();
res(val);
});
});

// Assumes argument is an unserialized presence.
const presenceToSlot = (o: unknown) => marshaller.toCapData(o).slots[0];

const queryBoardAux = <T>(boardObjects: unknown[]) => {
const boardIds = boardObjects.map(presenceToSlot);
return boardIds.map(id =>
queryOnce<T>([
AgoricChainStoragePathKind.Data,
`published.boardAux.${id}`,
]),
);
};

return {
watchLatest,
chainId,
rpcAddr,
marshaller,
queryOnce,
queryBoardAux,
};
};
35 changes: 29 additions & 6 deletions packages/rpc/test/chainStorageWatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('makeAgoricChainStorageWatcher', () => {
it('can handle multiple paths at once', async () => {
const expected1 = 'test result';
const expected2 = ['child1', 'child2'];
const path = 'published.vitest.fakePath';
const path = 'vitest.fakePath';

fetch.mockResolvedValue(
createFetchResponse([
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('makeAgoricChainStorageWatcher', () => {

it('can handle unserialized values', async () => {
const expected = 126560000000;
const path = 'published.vitest.unserializedValue';
const path = 'vitest.unserializedValue';

fetch.mockResolvedValue(
createUnserializedFetchResponse([
Expand All @@ -110,9 +110,32 @@ describe('makeAgoricChainStorageWatcher', () => {
expect(await value).toEqual(expected);
});

it('can do single queries', async () => {
const expected = 126560000000;
const path = 'vitest.unserializedValue';

fetch.mockResolvedValue(
createUnserializedFetchResponse([
{ value: expected, kind: AgoricChainStoragePathKind.Data, id: 0 },
]),
);

const value = watcher.queryOnce<string>([
AgoricChainStoragePathKind.Data,
path,
]);

vi.advanceTimersToNextTimer();
expect(await value).toEqual(expected);
expect(fetch).toHaveBeenCalledOnce();

vi.advanceTimersToNextTimer();
expect(fetch).toHaveBeenCalledOnce();
});

it('notifies for changed data values', async () => {
const expected1 = 'test result';
const path = 'published.vitest.fakePath';
const path = 'vitest.fakePath';

fetch.mockResolvedValue(
createFetchResponse([
Expand Down Expand Up @@ -159,7 +182,7 @@ describe('makeAgoricChainStorageWatcher', () => {

it('notifies for changed children values', async () => {
const expected1 = ['child1', 'child2'];
const path = 'published.vitest.fakePath';
const path = 'vitest.fakePath';

fetch.mockResolvedValue(
createFetchResponse([
Expand Down Expand Up @@ -204,7 +227,7 @@ describe('makeAgoricChainStorageWatcher', () => {

it('handles errors', async () => {
const expected = 'test error log';
const path = 'published.vitest.fakePath';
const path = 'vitest.fakePath';

fetch.mockResolvedValue(
createFetchResponse([
Expand Down Expand Up @@ -232,7 +255,7 @@ describe('makeAgoricChainStorageWatcher', () => {

it('can unsubscribe from paths', async () => {
const expected1 = ['child1', 'child2'];
const path = 'published.vitest.fakePath';
const path = 'vitest.fakePath';

fetch.mockResolvedValue(
createFetchResponse([
Expand Down
172 changes: 129 additions & 43 deletions packages/web-components/src/wallet-connection/watchWallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { queryBankBalances } from '../queryBankBalances.js';

/** @typedef {import('@agoric/smart-wallet/src/types.js').Petname} Petname */

/** @typedef {import('@keplr-wallet/types').Coin} Coin */

/**
* @typedef {{
* brand?: unknown,
Expand All @@ -17,6 +19,17 @@ import { queryBankBalances } from '../queryBankBalances.js';
* }} PurseInfo
*/

/**
* @typedef {[
* string,
* {
* brand: unknown,
* issuerName: string,
* displayInfo: unknown
* }
* ][]} VBankAssets
*/

const POLL_INTERVAL_MS = 6000;

/**
Expand Down Expand Up @@ -82,51 +95,124 @@ export const watchWallet = async (chainStorageWatcher, address) => {

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,
chainStorageWatcher.rpcAddr,
);
bank = balances;
possiblyUpdateBankPurses();
setTimeout(watchBank, POLL_INTERVAL_MS);
};

const watchVbankAssets = async () => {
chainStorageWatcher.watchLatest(
['data', 'published.agoricNames.vbankAsset'],
value => {
vbankAssets = value;
possiblyUpdateBankPurses();
},
);
};
{
/** @type {VBankAssets} */
let vbankAssets;
/** @type {Coin[]} */
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,
chainStorageWatcher.rpcAddr,
);
bank = balances;
possiblyUpdateBankPurses();
setTimeout(watchBank, POLL_INTERVAL_MS);
};

const watchVbankAssets = () => {
chainStorageWatcher.watchLatest(
['data', 'published.agoricNames.vbankAsset'],
value => {
vbankAssets = value;
possiblyUpdateBankPurses();
},
);
};

void watchVbankAssets();
void watchBank();
}

void watchVbankAssets();
void watchBank();
{
/** @type { [string, unknown][] } */
let agoricBrands;
/** @type { {balance: unknown, brand: unknown}[] } */
let nonBankPurses;
/** @type { Map<unknown, { displayInfo: unknown }> } */
let brandToBoardAux;

const possiblyUpdateNonBankPurses = () => {
if (!agoricBrands || !nonBankPurses || !brandToBoardAux) return;

nonBankPurses.forEach(({ balance, brand }) => {
const petname = agoricBrands
?.find(([_petname, b]) => b === brand)
?.at(0);
const { displayInfo } = brandToBoardAux.get(brand) ?? {};
const purseInfo = {
brand,
currentAmount: balance,
brandPetname: petname,
pursePetname: petname,
displayInfo,
};
brandToPurse.set(brand, purseInfo);
});

updatePurses(brandToPurse);
};

const watchBrands = () => {
chainStorageWatcher.watchLatest(
['data', 'published.agoricNames.brand'],
value => {
agoricBrands = value;
possiblyUpdateNonBankPurses();
},
);
};

const watchPurses = () =>
chainStorageWatcher.watchLatest(
['data', `published.wallet.${address}.current`],
async value => {
const { purses } = value;
if (nonBankPurses === purses) return;

await null;
if (purses.length !== nonBankPurses?.length) {
const brands = purses.map(p => p.brand);
const boardAux = await Promise.all(
chainStorageWatcher.queryBoardAux(brands),
);
brandToBoardAux = new Map(
brands.map((brand, index) => [brand, boardAux[index]]),
);
}

nonBankPurses = purses;
possiblyUpdateNonBankPurses();
},
);

void watchBrands();
void watchPurses();
}
};

const watchWalletUpdates = async () => {
Expand Down

0 comments on commit dfa49e2

Please sign in to comment.