@agoric/client-utils 0.1.1-other-dev-3eb1a1d.0 → 0.2.0-u18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/cli.js +20 -0
- package/src/main.js +4 -0
- package/src/network-config.js +42 -0
- package/src/rpc.js +1 -1
- package/src/smart-wallet-kit.js +113 -0
- package/src/vstorage-kit.js +7 -126
- package/src/vstorage.js +125 -0
- package/src/wallet-utils.js +5 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/client-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-u18.0",
|
|
4
4
|
"description": "Utilities for building Agoric clients",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"ava": "^5.3.0",
|
|
26
26
|
"c8": "^10.1.2",
|
|
27
|
-
"ts-blank-space": "^0.4.
|
|
27
|
+
"ts-blank-space": "^0.4.4"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agoric/casting": "0.4.3-
|
|
31
|
-
"@agoric/ertp": "0.16.3-
|
|
32
|
-
"@agoric/internal": "0.
|
|
33
|
-
"@agoric/smart-wallet": "0.5.4-
|
|
34
|
-
"@agoric/vats": "0.
|
|
30
|
+
"@agoric/casting": "^0.4.3-u18.5",
|
|
31
|
+
"@agoric/ertp": "^0.16.3-u18.1",
|
|
32
|
+
"@agoric/internal": "^0.4.0-u18.1",
|
|
33
|
+
"@agoric/smart-wallet": "^0.5.4-u18.5",
|
|
34
|
+
"@agoric/vats": "^0.16.0-u18.5",
|
|
35
35
|
"@cosmjs/stargate": "^0.32.3",
|
|
36
36
|
"@cosmjs/tendermint-rpc": "^0.32.3",
|
|
37
37
|
"@endo/common": "^1.2.8",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
],
|
|
59
59
|
"timeout": "20m"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "f8c45b8a2e29a51522a81a6692af25b2d7f6b50f"
|
|
62
62
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LOCAL_CONFIG_KEY, fetchNetworkConfig } from './network-config.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import {MinimalNetworkConfig} from './network-config.js';
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fetch the network config for the AGORIC_NET environment variable.
|
|
9
|
+
*
|
|
10
|
+
* If none is set or it's 'local', return a local chain config.
|
|
11
|
+
*
|
|
12
|
+
* @param {{ env: typeof process.env, fetch: typeof fetch }} io
|
|
13
|
+
* @returns {Promise<MinimalNetworkConfig>}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const fetchEnvNetworkConfig = async ({ env, fetch }) => {
|
|
17
|
+
const net = env.AGORIC_NET || LOCAL_CONFIG_KEY;
|
|
18
|
+
|
|
19
|
+
return fetchNetworkConfig(net, { fetch });
|
|
20
|
+
};
|
package/src/main.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
export * from './cli.js';
|
|
2
|
+
export * from './network-config.js';
|
|
1
3
|
export * from './rpc.js';
|
|
4
|
+
export * from './smart-wallet-kit.js';
|
|
2
5
|
export * from './sync-tools.js';
|
|
6
|
+
export * from './vstorage.js';
|
|
3
7
|
export * from './vstorage-kit.js';
|
|
4
8
|
export * from './wallet-utils.js';
|
|
5
9
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{ rpcAddrs: string[], chainName: string }} MinimalNetworkConfig
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const toNetworkConfigUrl = agoricNetSubdomain =>
|
|
6
|
+
`https://${agoricNetSubdomain}.agoric.net/network-config`;
|
|
7
|
+
|
|
8
|
+
export const toRpcUrl = agoricNetSubdomain =>
|
|
9
|
+
`https://${agoricNetSubdomain}.rpc.agoric.net:443`;
|
|
10
|
+
|
|
11
|
+
/** @satisfies {MinimalNetworkConfig} */
|
|
12
|
+
export const LOCAL_CONFIG = {
|
|
13
|
+
rpcAddrs: ['http://0.0.0.0:26657'],
|
|
14
|
+
chainName: 'agoriclocal',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const LOCAL_CONFIG_KEY = 'local';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetches the network config for the given network specifier.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} spec
|
|
23
|
+
* @param {{ fetch: typeof fetch }} io
|
|
24
|
+
* @returns {Promise<MinimalNetworkConfig>}
|
|
25
|
+
*/
|
|
26
|
+
export const fetchNetworkConfig = async (spec, { fetch }) => {
|
|
27
|
+
const [netName, chainName] = spec.split(',');
|
|
28
|
+
|
|
29
|
+
if (netName === LOCAL_CONFIG_KEY) {
|
|
30
|
+
return LOCAL_CONFIG;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (chainName) {
|
|
34
|
+
return { chainName, rpcAddrs: [toRpcUrl(netName)] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fetch(toNetworkConfigUrl(netName))
|
|
38
|
+
.then(res => res.json())
|
|
39
|
+
.catch(err => {
|
|
40
|
+
throw Error(`cannot get network config (${spec}): ${err.message}`);
|
|
41
|
+
});
|
|
42
|
+
};
|
package/src/rpc.js
CHANGED
|
@@ -3,7 +3,7 @@ import { StargateClient } from '@cosmjs/stargate';
|
|
|
3
3
|
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @
|
|
6
|
+
* @import {MinimalNetworkConfig} from './network-config.js';
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// TODO distribute load
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js';
|
|
2
|
+
import { pollBlocks } from './chain.js';
|
|
3
|
+
import { makeStargateClient } from './rpc.js';
|
|
4
|
+
import { makeAgoricNames, makeVstorageKit } from './vstorage-kit.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @import {Amount, Brand} from '@agoric/ertp/src/types.js'
|
|
8
|
+
* @import {CurrentWalletRecord, UpdateRecord} from '@agoric/smart-wallet/src/smartWallet.js';
|
|
9
|
+
* @import {MinimalNetworkConfig} from './network-config.js';
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Augment VstorageKit with addtional convenience methods for working with
|
|
14
|
+
* Agoric smart wallets.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} root0
|
|
17
|
+
* @param {typeof globalThis.fetch} root0.fetch
|
|
18
|
+
* @param {(ms: number) => Promise<void>} root0.delay
|
|
19
|
+
* @param {MinimalNetworkConfig} networkConfig
|
|
20
|
+
*/
|
|
21
|
+
export const makeSmartWalletKit = async ({ fetch, delay }, networkConfig) => {
|
|
22
|
+
const vsk = makeVstorageKit({ fetch }, networkConfig);
|
|
23
|
+
|
|
24
|
+
const client = await makeStargateClient(networkConfig, { fetch });
|
|
25
|
+
|
|
26
|
+
const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} from
|
|
30
|
+
* @param {number|string} [minHeight]
|
|
31
|
+
*/
|
|
32
|
+
const storedWalletState = async (from, minHeight = undefined) => {
|
|
33
|
+
const history = await vsk.vstorage.readFully(
|
|
34
|
+
`published.wallet.${from}`,
|
|
35
|
+
minHeight,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/** @type {{ Invitation: Brand<'set'> }} */
|
|
39
|
+
// @ts-expect-error XXX how to narrow AssetKind to set?
|
|
40
|
+
const { Invitation } = agoricNames.brand;
|
|
41
|
+
const coalescer = makeWalletStateCoalescer(Invitation);
|
|
42
|
+
// update with oldest first
|
|
43
|
+
for (const txt of history.reverse()) {
|
|
44
|
+
const { body, slots } = JSON.parse(txt);
|
|
45
|
+
const record = vsk.marshaller.fromCapData({ body, slots });
|
|
46
|
+
coalescer.update(record);
|
|
47
|
+
}
|
|
48
|
+
const coalesced = coalescer.state;
|
|
49
|
+
harden(coalesced);
|
|
50
|
+
return coalesced;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get OfferStatus by id, polling until available.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} from
|
|
57
|
+
* @param {string|number} id
|
|
58
|
+
* @param {number|string} minHeight
|
|
59
|
+
* @param {boolean} [untilNumWantsSatisfied]
|
|
60
|
+
*/
|
|
61
|
+
const pollOffer = async (
|
|
62
|
+
from,
|
|
63
|
+
id,
|
|
64
|
+
minHeight,
|
|
65
|
+
untilNumWantsSatisfied = false,
|
|
66
|
+
) => {
|
|
67
|
+
const poll = pollBlocks({
|
|
68
|
+
client,
|
|
69
|
+
delay,
|
|
70
|
+
retryMessage: 'offer not in wallet at block',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const lookup = async () => {
|
|
74
|
+
const { offerStatuses } = await storedWalletState(from, minHeight);
|
|
75
|
+
const offerStatus = [...offerStatuses.values()].find(s => s.id === id);
|
|
76
|
+
if (!offerStatus) throw Error('retry');
|
|
77
|
+
harden(offerStatus);
|
|
78
|
+
if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) {
|
|
79
|
+
throw Error('retry (no numWantsSatisfied yet)');
|
|
80
|
+
}
|
|
81
|
+
return offerStatus;
|
|
82
|
+
};
|
|
83
|
+
return poll(lookup);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} addr
|
|
88
|
+
* @returns {Promise<UpdateRecord>}
|
|
89
|
+
*/
|
|
90
|
+
const getLastUpdate = addr => {
|
|
91
|
+
return vsk.readPublished(`wallet.${addr}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} addr
|
|
96
|
+
* @returns {Promise<CurrentWalletRecord>}
|
|
97
|
+
*/
|
|
98
|
+
const getCurrentWalletRecord = addr => {
|
|
99
|
+
return vsk.readPublished(`wallet.${addr}.current`);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
// pass along all of VstorageKit
|
|
104
|
+
...vsk,
|
|
105
|
+
agoricNames,
|
|
106
|
+
networkConfig,
|
|
107
|
+
getLastUpdate,
|
|
108
|
+
getCurrentWalletRecord,
|
|
109
|
+
storedWalletState,
|
|
110
|
+
pollOffer,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/** @typedef {Awaited<ReturnType<typeof makeSmartWalletKit>>} SmartWalletKit */
|
package/src/vstorage-kit.js
CHANGED
|
@@ -1,135 +1,18 @@
|
|
|
1
|
-
/* global Buffer */
|
|
2
1
|
import {
|
|
3
2
|
boardSlottingMarshaller,
|
|
4
3
|
makeBoardRemote,
|
|
5
4
|
} from '@agoric/vats/tools/board-utils.js';
|
|
5
|
+
import { assertAllDefined } from '@agoric/internal';
|
|
6
|
+
import { makeVStorage } from './vstorage.js';
|
|
6
7
|
|
|
7
8
|
export { boardSlottingMarshaller };
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* @import {MinimalNetworkConfig} from './
|
|
11
|
+
* @import {MinimalNetworkConfig} from './network-config.js';
|
|
11
12
|
* @import {TypedPublished} from './types.js';
|
|
13
|
+
* @import {VStorage} from './vstorage.js';
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
/**
|
|
15
|
-
* @param {object} powers
|
|
16
|
-
* @param {typeof window.fetch} powers.fetch
|
|
17
|
-
* @param {MinimalNetworkConfig} config
|
|
18
|
-
*/
|
|
19
|
-
export const makeVStorage = (powers, config) => {
|
|
20
|
-
/** @param {string} path */
|
|
21
|
-
const getJSON = path => {
|
|
22
|
-
const url = config.rpcAddrs[0] + path;
|
|
23
|
-
// console.warn('fetching', url);
|
|
24
|
-
return powers.fetch(url, { keepalive: true }).then(res => res.json());
|
|
25
|
-
};
|
|
26
|
-
// height=0 is the same as omitting height and implies the highest block
|
|
27
|
-
const url = (path = 'published', { kind = 'children', height = 0 } = {}) =>
|
|
28
|
-
`/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`;
|
|
29
|
-
|
|
30
|
-
const readStorage = (path = 'published', { kind = 'children', height = 0 }) =>
|
|
31
|
-
getJSON(url(path, { kind, height }))
|
|
32
|
-
.catch(err => {
|
|
33
|
-
throw Error(`cannot read ${kind} of ${path}: ${err.message}`);
|
|
34
|
-
})
|
|
35
|
-
.then(data => {
|
|
36
|
-
const {
|
|
37
|
-
result: { response },
|
|
38
|
-
} = data;
|
|
39
|
-
if (response?.code !== 0) {
|
|
40
|
-
/** @type {any} */
|
|
41
|
-
const err = Error(
|
|
42
|
-
`error code ${response?.code} reading ${kind} of ${path}: ${response.log}`,
|
|
43
|
-
);
|
|
44
|
-
err.code = response?.code;
|
|
45
|
-
err.codespace = response?.codespace;
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
return data;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
url,
|
|
53
|
-
decode({ result: { response } }) {
|
|
54
|
-
const { code } = response;
|
|
55
|
-
if (code !== 0) {
|
|
56
|
-
throw response;
|
|
57
|
-
}
|
|
58
|
-
const { value } = response;
|
|
59
|
-
return Buffer.from(value, 'base64').toString();
|
|
60
|
-
},
|
|
61
|
-
/**
|
|
62
|
-
*
|
|
63
|
-
* @param {string} path
|
|
64
|
-
* @returns {Promise<string>} latest vstorage value at path
|
|
65
|
-
*/
|
|
66
|
-
async readLatest(path = 'published') {
|
|
67
|
-
const raw = await readStorage(path, { kind: 'data' });
|
|
68
|
-
return this.decode(raw);
|
|
69
|
-
},
|
|
70
|
-
async keys(path = 'published') {
|
|
71
|
-
const raw = await readStorage(path, { kind: 'children' });
|
|
72
|
-
return JSON.parse(this.decode(raw)).children;
|
|
73
|
-
},
|
|
74
|
-
/**
|
|
75
|
-
* @param {string} path
|
|
76
|
-
* @param {number} [height] default is highest
|
|
77
|
-
* @returns {Promise<{blockHeight: number, values: string[]}>}
|
|
78
|
-
*/
|
|
79
|
-
async readAt(path, height = undefined) {
|
|
80
|
-
const raw = await readStorage(path, { kind: 'data', height });
|
|
81
|
-
const txt = this.decode(raw);
|
|
82
|
-
/** @type {{ value: string }} */
|
|
83
|
-
const { value } = JSON.parse(txt);
|
|
84
|
-
return JSON.parse(value);
|
|
85
|
-
},
|
|
86
|
-
/**
|
|
87
|
-
* Read values going back as far as available
|
|
88
|
-
*
|
|
89
|
-
* @param {string} path
|
|
90
|
-
* @param {number | string} [minHeight]
|
|
91
|
-
* @returns {Promise<string[]>}
|
|
92
|
-
*/
|
|
93
|
-
async readFully(path, minHeight = undefined) {
|
|
94
|
-
const parts = [];
|
|
95
|
-
// undefined the first iteration, to query at the highest
|
|
96
|
-
let blockHeight;
|
|
97
|
-
await null;
|
|
98
|
-
do {
|
|
99
|
-
// console.debug('READING', { blockHeight });
|
|
100
|
-
let values;
|
|
101
|
-
try {
|
|
102
|
-
({ blockHeight, values } = await this.readAt(
|
|
103
|
-
path,
|
|
104
|
-
blockHeight && Number(blockHeight) - 1,
|
|
105
|
-
));
|
|
106
|
-
// console.debug('readAt returned', { blockHeight });
|
|
107
|
-
} catch (err) {
|
|
108
|
-
if (
|
|
109
|
-
// CosmosSDK ErrInvalidRequest with particular message text;
|
|
110
|
-
// misrepresentation of pruned data
|
|
111
|
-
// TODO replace after incorporating a fix to
|
|
112
|
-
// https://github.com/cosmos/cosmos-sdk/issues/19992
|
|
113
|
-
err.codespace === 'sdk' &&
|
|
114
|
-
err.code === 18 &&
|
|
115
|
-
err.message.match(/pruned/)
|
|
116
|
-
) {
|
|
117
|
-
// console.error(err);
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
parts.push(values);
|
|
123
|
-
// console.debug('PUSHED', values);
|
|
124
|
-
// console.debug('NEW', { blockHeight, minHeight });
|
|
125
|
-
if (minHeight && Number(blockHeight) <= Number(minHeight)) break;
|
|
126
|
-
} while (blockHeight > 0);
|
|
127
|
-
return parts.flat();
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
/** @typedef {ReturnType<typeof makeVStorage>} VStorage */
|
|
132
|
-
|
|
133
16
|
/** @deprecated */
|
|
134
17
|
export const makeFromBoard = () => {
|
|
135
18
|
const cache = new Map();
|
|
@@ -191,6 +74,7 @@ harden(storageHelper);
|
|
|
191
74
|
* @returns {Promise<import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes>}
|
|
192
75
|
*/
|
|
193
76
|
export const makeAgoricNames = async (ctx, vstorage) => {
|
|
77
|
+
assertAllDefined({ ctx, vstorage });
|
|
194
78
|
const reverse = {};
|
|
195
79
|
const entries = await Promise.all(
|
|
196
80
|
['brand', 'instance', 'vbankAsset'].map(async kind => {
|
|
@@ -214,12 +98,10 @@ export const makeAgoricNames = async (ctx, vstorage) => {
|
|
|
214
98
|
* @param {{ fetch: typeof window.fetch }} io
|
|
215
99
|
* @param {MinimalNetworkConfig} config
|
|
216
100
|
*/
|
|
217
|
-
export const makeVstorageKit =
|
|
218
|
-
await null;
|
|
101
|
+
export const makeVstorageKit = ({ fetch }, config) => {
|
|
219
102
|
try {
|
|
220
103
|
const vstorage = makeVStorage({ fetch }, config);
|
|
221
104
|
const fromBoard = makeFromBoard();
|
|
222
|
-
const agoricNames = await makeAgoricNames(fromBoard, vstorage);
|
|
223
105
|
|
|
224
106
|
const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal);
|
|
225
107
|
|
|
@@ -249,7 +131,6 @@ export const makeVstorageKit = async ({ fetch }, config) => {
|
|
|
249
131
|
readLatestHead(`published.${subpath}`);
|
|
250
132
|
|
|
251
133
|
return {
|
|
252
|
-
agoricNames,
|
|
253
134
|
fromBoard,
|
|
254
135
|
marshaller,
|
|
255
136
|
readLatestHead,
|
|
@@ -261,4 +142,4 @@ export const makeVstorageKit = async ({ fetch }, config) => {
|
|
|
261
142
|
throw Error(`RPC failure (${config.rpcAddrs}): ${err.message}`);
|
|
262
143
|
}
|
|
263
144
|
};
|
|
264
|
-
/** @typedef {
|
|
145
|
+
/** @typedef {ReturnType<typeof makeVstorageKit>} VstorageKit */
|
package/src/vstorage.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* global Buffer */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import {MinimalNetworkConfig} from './network-config.js';
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} powers
|
|
9
|
+
* @param {typeof window.fetch} powers.fetch
|
|
10
|
+
* @param {MinimalNetworkConfig} config
|
|
11
|
+
*/
|
|
12
|
+
export const makeVStorage = ({ fetch }, config) => {
|
|
13
|
+
/** @param {string} path */
|
|
14
|
+
const getJSON = path => {
|
|
15
|
+
const url = config.rpcAddrs[0] + path;
|
|
16
|
+
// console.warn('fetching', url);
|
|
17
|
+
return fetch(url, { keepalive: true }).then(res => res.json());
|
|
18
|
+
};
|
|
19
|
+
// height=0 is the same as omitting height and implies the highest block
|
|
20
|
+
const url = (path = 'published', { kind = 'children', height = 0 } = {}) =>
|
|
21
|
+
`/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`;
|
|
22
|
+
|
|
23
|
+
const readStorage = (path = 'published', { kind = 'children', height = 0 }) =>
|
|
24
|
+
getJSON(url(path, { kind, height }))
|
|
25
|
+
.catch(err => {
|
|
26
|
+
throw Error(`cannot read ${kind} of ${path}: ${err.message}`);
|
|
27
|
+
})
|
|
28
|
+
.then(data => {
|
|
29
|
+
const {
|
|
30
|
+
result: { response },
|
|
31
|
+
} = data;
|
|
32
|
+
if (response?.code !== 0) {
|
|
33
|
+
/** @type {any} */
|
|
34
|
+
const err = Error(
|
|
35
|
+
`error code ${response?.code} reading ${kind} of ${path}: ${response.log}`,
|
|
36
|
+
);
|
|
37
|
+
err.code = response?.code;
|
|
38
|
+
err.codespace = response?.codespace;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const vstorage = {
|
|
45
|
+
url,
|
|
46
|
+
decode({ result: { response } }) {
|
|
47
|
+
const { code } = response;
|
|
48
|
+
if (code !== 0) {
|
|
49
|
+
throw response;
|
|
50
|
+
}
|
|
51
|
+
const { value } = response;
|
|
52
|
+
return Buffer.from(value, 'base64').toString();
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @param {string} path
|
|
57
|
+
* @returns {Promise<string>} latest vstorage value at path
|
|
58
|
+
*/
|
|
59
|
+
async readLatest(path = 'published') {
|
|
60
|
+
const raw = await readStorage(path, { kind: 'data' });
|
|
61
|
+
return vstorage.decode(raw);
|
|
62
|
+
},
|
|
63
|
+
async keys(path = 'published') {
|
|
64
|
+
const raw = await readStorage(path, { kind: 'children' });
|
|
65
|
+
return JSON.parse(vstorage.decode(raw)).children;
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} path
|
|
69
|
+
* @param {number} [height] default is highest
|
|
70
|
+
* @returns {Promise<{blockHeight: number, values: string[]}>}
|
|
71
|
+
*/
|
|
72
|
+
async readAt(path, height = undefined) {
|
|
73
|
+
const raw = await readStorage(path, { kind: 'data', height });
|
|
74
|
+
const txt = vstorage.decode(raw);
|
|
75
|
+
/** @type {{ value: string }} */
|
|
76
|
+
const { value } = JSON.parse(txt);
|
|
77
|
+
return JSON.parse(value);
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Read values going back as far as available
|
|
81
|
+
*
|
|
82
|
+
* @param {string} path
|
|
83
|
+
* @param {number | string} [minHeight]
|
|
84
|
+
* @returns {Promise<string[]>}
|
|
85
|
+
*/
|
|
86
|
+
async readFully(path, minHeight = undefined) {
|
|
87
|
+
const parts = [];
|
|
88
|
+
// undefined the first iteration, to query at the highest
|
|
89
|
+
let blockHeight;
|
|
90
|
+
await null;
|
|
91
|
+
do {
|
|
92
|
+
// console.debug('READING', { blockHeight });
|
|
93
|
+
let values;
|
|
94
|
+
try {
|
|
95
|
+
({ blockHeight, values } = await vstorage.readAt(
|
|
96
|
+
path,
|
|
97
|
+
blockHeight && Number(blockHeight) - 1,
|
|
98
|
+
));
|
|
99
|
+
// console.debug('readAt returned', { blockHeight });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (
|
|
102
|
+
// CosmosSDK ErrInvalidRequest with particular message text;
|
|
103
|
+
// misrepresentation of pruned data
|
|
104
|
+
// TODO replace after incorporating a fix to
|
|
105
|
+
// https://github.com/cosmos/cosmos-sdk/issues/19992
|
|
106
|
+
err.codespace === 'sdk' &&
|
|
107
|
+
err.code === 18 &&
|
|
108
|
+
err.message.match(/pruned/)
|
|
109
|
+
) {
|
|
110
|
+
// console.error(err);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
parts.push(values);
|
|
116
|
+
// console.debug('PUSHED', values);
|
|
117
|
+
// console.debug('NEW', { blockHeight, minHeight });
|
|
118
|
+
if (minHeight && Number(blockHeight) <= Number(minHeight)) break;
|
|
119
|
+
} while (blockHeight > 0);
|
|
120
|
+
return parts.flat();
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return vstorage;
|
|
124
|
+
};
|
|
125
|
+
/** @typedef {ReturnType<typeof makeVStorage>} VStorage */
|
package/src/wallet-utils.js
CHANGED
|
@@ -1,113 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import { pollBlocks } from './chain.js';
|
|
3
|
-
import { makeStargateClient } from './rpc.js';
|
|
4
|
-
import { boardSlottingMarshaller, makeVstorageKit } from './vstorage-kit.js';
|
|
1
|
+
/** @file backwards compat */
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
* @import {Amount, Brand} from '@agoric/ertp/src/types.js'
|
|
8
|
-
* @import {CurrentWalletRecord, UpdateRecord} from '@agoric/smart-wallet/src/smartWallet.js';
|
|
9
|
-
* @import {MinimalNetworkConfig} from './rpc.js';
|
|
10
|
-
*/
|
|
3
|
+
import { makeSmartWalletKit } from './smart-wallet-kit.js';
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Augment VstorageKit with addtional convenience methods for working with
|
|
15
|
-
* Agoric smart wallets.
|
|
16
|
-
*
|
|
17
|
-
* @param {object} root0
|
|
18
|
-
* @param {typeof globalThis.fetch} root0.fetch
|
|
19
|
-
* @param {(ms: number) => Promise<void>} root0.delay
|
|
20
|
-
* @param {MinimalNetworkConfig} networkConfig
|
|
21
|
-
*/
|
|
22
|
-
export const makeWalletUtils = async ({ fetch, delay }, networkConfig) => {
|
|
23
|
-
const vsk = await makeVstorageKit({ fetch }, networkConfig);
|
|
5
|
+
/** @typedef {import('./smart-wallet-kit.js').SmartWalletKit} WalletUtils */
|
|
24
6
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const client = await makeStargateClient(networkConfig, { fetch });
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param {string} from
|
|
31
|
-
* @param {number|string} [minHeight]
|
|
32
|
-
*/
|
|
33
|
-
const storedWalletState = async (from, minHeight = undefined) => {
|
|
34
|
-
const history = await vsk.vstorage.readFully(
|
|
35
|
-
`published.wallet.${from}`,
|
|
36
|
-
minHeight,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
/** @type {{ Invitation: Brand<'set'> }} */
|
|
40
|
-
// @ts-expect-error XXX how to narrow AssetKind to set?
|
|
41
|
-
const { Invitation } = vsk.agoricNames.brand;
|
|
42
|
-
const coalescer = makeWalletStateCoalescer(Invitation);
|
|
43
|
-
// update with oldest first
|
|
44
|
-
for (const txt of history.reverse()) {
|
|
45
|
-
const { body, slots } = JSON.parse(txt);
|
|
46
|
-
const record = m.fromCapData({ body, slots });
|
|
47
|
-
coalescer.update(record);
|
|
48
|
-
}
|
|
49
|
-
const coalesced = coalescer.state;
|
|
50
|
-
harden(coalesced);
|
|
51
|
-
return coalesced;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get OfferStatus by id, polling until available.
|
|
56
|
-
*
|
|
57
|
-
* @param {string} from
|
|
58
|
-
* @param {string|number} id
|
|
59
|
-
* @param {number|string} minHeight
|
|
60
|
-
* @param {boolean} [untilNumWantsSatisfied]
|
|
61
|
-
*/
|
|
62
|
-
const pollOffer = async (
|
|
63
|
-
from,
|
|
64
|
-
id,
|
|
65
|
-
minHeight,
|
|
66
|
-
untilNumWantsSatisfied = false,
|
|
67
|
-
) => {
|
|
68
|
-
const poll = pollBlocks({
|
|
69
|
-
client,
|
|
70
|
-
delay,
|
|
71
|
-
retryMessage: 'offer not in wallet at block',
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const lookup = async () => {
|
|
75
|
-
const { offerStatuses } = await storedWalletState(from, minHeight);
|
|
76
|
-
const offerStatus = [...offerStatuses.values()].find(s => s.id === id);
|
|
77
|
-
if (!offerStatus) throw Error('retry');
|
|
78
|
-
harden(offerStatus);
|
|
79
|
-
if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) {
|
|
80
|
-
throw Error('retry (no numWantsSatisfied yet)');
|
|
81
|
-
}
|
|
82
|
-
return offerStatus;
|
|
83
|
-
};
|
|
84
|
-
return poll(lookup);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @param {string} addr
|
|
89
|
-
* @returns {Promise<UpdateRecord>}
|
|
90
|
-
*/
|
|
91
|
-
const getLastUpdate = addr => {
|
|
92
|
-
return vsk.readPublished(`wallet.${addr}`);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @param {string} addr
|
|
97
|
-
* @returns {Promise<CurrentWalletRecord>}
|
|
98
|
-
*/
|
|
99
|
-
const getCurrentWalletRecord = addr => {
|
|
100
|
-
return vsk.readPublished(`wallet.${addr}.current`);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
// pass along all of VstorageKit
|
|
105
|
-
...vsk,
|
|
106
|
-
networkConfig,
|
|
107
|
-
getLastUpdate,
|
|
108
|
-
getCurrentWalletRecord,
|
|
109
|
-
storedWalletState,
|
|
110
|
-
pollOffer,
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
/** @typedef {Awaited<ReturnType<typeof makeWalletUtils>>} WalletUtils */
|
|
7
|
+
/** @deprecated use `makeSmartWalletKit` */
|
|
8
|
+
export const makeWalletUtils = makeSmartWalletKit;
|