Skip to content

Commit e2665de

Browse files
authored
feat: support non-vbank purses (#37)
* feat: support non-vbank purses * fix: backwards compatible without boardAux
1 parent f8674ce commit e2665de

File tree

3 files changed

+189
-49
lines changed

3 files changed

+189
-49
lines changed

packages/rpc/src/chainStorageWatcher.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,37 @@ export const makeAgoricChainStorageWatcher = (
201201
return () => stopWatching(pathKey, subscriber as Subscriber<unknown>);
202202
};
203203

204+
const queryOnce = <T>(path: [AgoricChainStoragePathKind, string]) =>
205+
new Promise<T>((res, rej) => {
206+
const stop = watchLatest<T>(
207+
path,
208+
val => {
209+
stop();
210+
res(val);
211+
},
212+
e => rej(e),
213+
);
214+
});
215+
216+
// Assumes argument is an unserialized presence.
217+
const presenceToSlot = (o: unknown) => marshaller.toCapData(o).slots[0];
218+
219+
const queryBoardAux = <T>(boardObjects: unknown[]) => {
220+
const boardIds = boardObjects.map(presenceToSlot);
221+
return boardIds.map(id =>
222+
queryOnce<T>([
223+
AgoricChainStoragePathKind.Data,
224+
`published.boardAux.${id}`,
225+
]),
226+
);
227+
};
228+
204229
return {
205230
watchLatest,
206231
chainId,
207232
rpcAddr,
208233
marshaller,
234+
queryOnce,
235+
queryBoardAux,
209236
};
210237
};

packages/rpc/test/chainStorageWatcher.test.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('makeAgoricChainStorageWatcher', () => {
4343
it('can handle multiple paths at once', async () => {
4444
const expected1 = 'test result';
4545
const expected2 = ['child1', 'child2'];
46-
const path = 'published.vitest.fakePath';
46+
const path = 'vitest.fakePath';
4747

4848
fetch.mockResolvedValue(
4949
createFetchResponse([
@@ -91,7 +91,7 @@ describe('makeAgoricChainStorageWatcher', () => {
9191

9292
it('can handle unserialized values', async () => {
9393
const expected = 126560000000;
94-
const path = 'published.vitest.unserializedValue';
94+
const path = 'vitest.unserializedValue';
9595

9696
fetch.mockResolvedValue(
9797
createUnserializedFetchResponse([
@@ -110,9 +110,32 @@ describe('makeAgoricChainStorageWatcher', () => {
110110
expect(await value).toEqual(expected);
111111
});
112112

113+
it('can do single queries', async () => {
114+
const expected = 126560000000;
115+
const path = 'vitest.unserializedValue';
116+
117+
fetch.mockResolvedValue(
118+
createUnserializedFetchResponse([
119+
{ value: expected, kind: AgoricChainStoragePathKind.Data, id: 0 },
120+
]),
121+
);
122+
123+
const value = watcher.queryOnce<string>([
124+
AgoricChainStoragePathKind.Data,
125+
path,
126+
]);
127+
128+
vi.advanceTimersToNextTimer();
129+
expect(await value).toEqual(expected);
130+
expect(fetch).toHaveBeenCalledOnce();
131+
132+
vi.advanceTimersToNextTimer();
133+
expect(fetch).toHaveBeenCalledOnce();
134+
});
135+
113136
it('notifies for changed data values', async () => {
114137
const expected1 = 'test result';
115-
const path = 'published.vitest.fakePath';
138+
const path = 'vitest.fakePath';
116139

117140
fetch.mockResolvedValue(
118141
createFetchResponse([
@@ -159,7 +182,7 @@ describe('makeAgoricChainStorageWatcher', () => {
159182

160183
it('notifies for changed children values', async () => {
161184
const expected1 = ['child1', 'child2'];
162-
const path = 'published.vitest.fakePath';
185+
const path = 'vitest.fakePath';
163186

164187
fetch.mockResolvedValue(
165188
createFetchResponse([
@@ -204,7 +227,7 @@ describe('makeAgoricChainStorageWatcher', () => {
204227

205228
it('handles errors', async () => {
206229
const expected = 'test error log';
207-
const path = 'published.vitest.fakePath';
230+
const path = 'vitest.fakePath';
208231

209232
fetch.mockResolvedValue(
210233
createFetchResponse([
@@ -232,7 +255,7 @@ describe('makeAgoricChainStorageWatcher', () => {
232255

233256
it('can unsubscribe from paths', async () => {
234257
const expected1 = ['child1', 'child2'];
235-
const path = 'published.vitest.fakePath';
258+
const path = 'vitest.fakePath';
236259

237260
fetch.mockResolvedValue(
238261
createFetchResponse([

packages/web-components/src/wallet-connection/watchWallet.js

Lines changed: 133 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { queryBankBalances } from '../queryBankBalances.js';
77

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

10+
/** @typedef {import('@keplr-wallet/types').Coin} Coin */
11+
1012
/**
1113
* @typedef {{
1214
* brand?: unknown,
@@ -17,6 +19,17 @@ import { queryBankBalances } from '../queryBankBalances.js';
1719
* }} PurseInfo
1820
*/
1921

22+
/**
23+
* @typedef {[
24+
* string,
25+
* {
26+
* brand: unknown,
27+
* issuerName: string,
28+
* displayInfo: unknown
29+
* }
30+
* ][]} VBankAssets
31+
*/
32+
2033
const POLL_INTERVAL_MS = 6000;
2134

2235
/**
@@ -82,51 +95,128 @@ export const watchWallet = async (chainStorageWatcher, address) => {
8295

8396
const watchChainBalances = () => {
8497
const brandToPurse = new Map();
85-
let vbankAssets;
86-
let bank;
87-
88-
const possiblyUpdateBankPurses = () => {
89-
if (!vbankAssets || !bank) return;
90-
91-
const bankMap = new Map(bank.map(({ denom, amount }) => [denom, amount]));
92-
93-
vbankAssets.forEach(([denom, info]) => {
94-
const amount = bankMap.get(denom) ?? 0n;
95-
const purseInfo = {
96-
brand: info.brand,
97-
currentAmount: AmountMath.make(info.brand, BigInt(amount)),
98-
brandPetname: info.issuerName,
99-
pursePetname: info.issuerName,
100-
displayInfo: info.displayInfo,
101-
};
102-
brandToPurse.set(info.brand, purseInfo);
103-
});
104-
105-
updatePurses(brandToPurse);
106-
};
107-
108-
const watchBank = async () => {
109-
const balances = await queryBankBalances(
110-
address,
111-
chainStorageWatcher.rpcAddr,
112-
);
113-
bank = balances;
114-
possiblyUpdateBankPurses();
115-
setTimeout(watchBank, POLL_INTERVAL_MS);
116-
};
11798

118-
const watchVbankAssets = async () => {
119-
chainStorageWatcher.watchLatest(
120-
['data', 'published.agoricNames.vbankAsset'],
121-
value => {
122-
vbankAssets = value;
123-
possiblyUpdateBankPurses();
124-
},
125-
);
126-
};
99+
{
100+
/** @type {VBankAssets} */
101+
let vbankAssets;
102+
/** @type {Coin[]} */
103+
let bank;
104+
105+
const possiblyUpdateBankPurses = () => {
106+
if (!vbankAssets || !bank) return;
107+
108+
const bankMap = new Map(
109+
bank.map(({ denom, amount }) => [denom, amount]),
110+
);
111+
112+
vbankAssets.forEach(([denom, info]) => {
113+
const amount = bankMap.get(denom) ?? 0n;
114+
const purseInfo = {
115+
brand: info.brand,
116+
currentAmount: AmountMath.make(info.brand, BigInt(amount)),
117+
brandPetname: info.issuerName,
118+
pursePetname: info.issuerName,
119+
displayInfo: info.displayInfo,
120+
};
121+
brandToPurse.set(info.brand, purseInfo);
122+
});
123+
124+
updatePurses(brandToPurse);
125+
};
126+
127+
const watchBank = async () => {
128+
const balances = await queryBankBalances(
129+
address,
130+
chainStorageWatcher.rpcAddr,
131+
);
132+
bank = balances;
133+
possiblyUpdateBankPurses();
134+
setTimeout(watchBank, POLL_INTERVAL_MS);
135+
};
136+
137+
const watchVbankAssets = () => {
138+
chainStorageWatcher.watchLatest(
139+
['data', 'published.agoricNames.vbankAsset'],
140+
value => {
141+
vbankAssets = value;
142+
possiblyUpdateBankPurses();
143+
},
144+
);
145+
};
146+
147+
void watchVbankAssets();
148+
void watchBank();
149+
}
127150

128-
void watchVbankAssets();
129-
void watchBank();
151+
{
152+
/** @type { [string, unknown][] } */
153+
let agoricBrands;
154+
/** @type { {balance: unknown, brand: unknown}[] } */
155+
let nonBankPurses;
156+
/** @type { Map<unknown, { displayInfo: unknown }> } */
157+
let brandToBoardAux;
158+
159+
const possiblyUpdateNonBankPurses = () => {
160+
if (!agoricBrands || !nonBankPurses || !brandToBoardAux) return;
161+
162+
nonBankPurses.forEach(({ balance, brand }) => {
163+
const petname = agoricBrands
164+
?.find(([_petname, b]) => b === brand)
165+
?.at(0);
166+
const { displayInfo } = brandToBoardAux.get(brand) ?? {};
167+
const purseInfo = {
168+
brand,
169+
currentAmount: balance,
170+
brandPetname: petname,
171+
pursePetname: petname,
172+
displayInfo,
173+
};
174+
brandToPurse.set(brand, purseInfo);
175+
});
176+
177+
updatePurses(brandToPurse);
178+
};
179+
180+
const watchBrands = () => {
181+
chainStorageWatcher.watchLatest(
182+
['data', 'published.agoricNames.brand'],
183+
value => {
184+
agoricBrands = value;
185+
possiblyUpdateNonBankPurses();
186+
},
187+
);
188+
};
189+
190+
const watchPurses = () =>
191+
chainStorageWatcher.watchLatest(
192+
['data', `published.wallet.${address}.current`],
193+
async value => {
194+
const { purses } = value;
195+
if (nonBankPurses === purses) return;
196+
197+
await null;
198+
if (purses.length !== nonBankPurses?.length) {
199+
const brands = purses.map(p => p.brand);
200+
try {
201+
const boardAux = await Promise.all(
202+
chainStorageWatcher.queryBoardAux(brands),
203+
);
204+
brandToBoardAux = new Map(
205+
brands.map((brand, index) => [brand, boardAux[index]]),
206+
);
207+
} catch (e) {
208+
console.error('Error getting boardAux for brands', brands, e);
209+
}
210+
}
211+
212+
nonBankPurses = purses;
213+
possiblyUpdateNonBankPurses();
214+
},
215+
);
216+
217+
void watchBrands();
218+
void watchPurses();
219+
}
130220
};
131221

132222
const watchWalletUpdates = async () => {

0 commit comments

Comments
 (0)