agoric 0.18.4 → 0.18.5-dev-800c2bb.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/README.md +73 -0
- package/package.json +37 -30
- package/src/bin-agops.js +68 -6
- package/src/chain-config.js +19 -2
- package/src/commands/auction.js +170 -0
- package/src/commands/ec.js +314 -0
- package/src/commands/inter.js +626 -0
- package/src/commands/oracle.js +212 -0
- package/src/commands/perf.js +32 -22
- package/src/commands/psm.js +75 -210
- package/src/commands/reserve.js +98 -0
- package/src/commands/test-upgrade.js +109 -0
- package/src/commands/vaults.js +161 -0
- package/src/commands/wallet.js +85 -60
- package/src/cosmos.js +5 -9
- package/src/deploy.js +208 -334
- package/src/entrypoint.js +9 -2
- package/src/follow.js +40 -4
- package/src/init.js +11 -10
- package/src/install.js +25 -30
- package/src/json-http-client-node.js +2 -2
- package/src/json.js +1 -1
- package/src/lib/casting.js +4 -2
- package/src/lib/chain.js +147 -33
- package/src/lib/format.js +58 -43
- package/src/lib/rpc.js +106 -101
- package/src/lib/wallet.js +305 -10
- package/src/main-publish.js +3 -5
- package/src/main.js +74 -42
- package/src/open.js +3 -1
- package/src/publish.js +25 -23
- package/src/run.js +20 -0
- package/src/scripts.js +190 -0
- package/src/sdk-package-names.js +6 -2
- package/src/set-defaults.js +2 -2
- package/src/start.js +69 -35
- package/src/lib/psm.js +0 -82
package/src/lib/rpc.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
/* global Buffer, fetch, process */
|
|
4
4
|
|
|
5
5
|
import { NonNullish } from '@agoric/assert';
|
|
6
|
+
import {
|
|
7
|
+
boardSlottingMarshaller,
|
|
8
|
+
makeBoardRemote,
|
|
9
|
+
} from '@agoric/vats/tools/board-utils.js';
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
* @typedef {{boardId: string, iface: string}} RpcRemote
|
|
9
|
-
*/
|
|
11
|
+
export { boardSlottingMarshaller };
|
|
10
12
|
|
|
11
13
|
export const networkConfigUrl = agoricNetSubdomain =>
|
|
12
14
|
`https://${agoricNetSubdomain}.agoric.net/network-config`;
|
|
@@ -29,29 +31,63 @@ const fromAgoricNet = str => {
|
|
|
29
31
|
return fetch(networkConfigUrl(netName)).then(res => res.json());
|
|
30
32
|
};
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {typeof process.env} env
|
|
36
|
+
* @returns {Promise<MinimalNetworkConfig>}
|
|
37
|
+
*/
|
|
38
|
+
export const getNetworkConfig = async env => {
|
|
39
|
+
if (!('AGORIC_NET' in env) || env.AGORIC_NET === 'local') {
|
|
40
|
+
return { rpcAddrs: ['http://0.0.0.0:26657'], chainName: 'agoriclocal' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return fromAgoricNet(NonNullish(env.AGORIC_NET)).catch(err => {
|
|
44
|
+
throw Error(
|
|
45
|
+
`cannot get network config (${env.AGORIC_NET || 'local'}): ${
|
|
46
|
+
err.message
|
|
47
|
+
}`,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
32
52
|
/** @type {MinimalNetworkConfig} */
|
|
33
|
-
export const networkConfig =
|
|
34
|
-
'AGORIC_NET' in process.env && process.env.AGORIC_NET !== 'local'
|
|
35
|
-
? await fromAgoricNet(NonNullish(process.env.AGORIC_NET))
|
|
36
|
-
: { rpcAddrs: ['http://0.0.0.0:26657'], chainName: 'agoriclocal' };
|
|
53
|
+
export const networkConfig = await getNetworkConfig(process.env);
|
|
37
54
|
// console.warn('networkConfig', networkConfig);
|
|
38
55
|
|
|
39
56
|
/**
|
|
40
|
-
*
|
|
41
57
|
* @param {object} powers
|
|
42
58
|
* @param {typeof window.fetch} powers.fetch
|
|
59
|
+
* @param {MinimalNetworkConfig} config
|
|
43
60
|
*/
|
|
44
|
-
export const makeVStorage = powers => {
|
|
61
|
+
export const makeVStorage = (powers, config = networkConfig) => {
|
|
62
|
+
/** @param {string} path */
|
|
45
63
|
const getJSON = path => {
|
|
46
|
-
const url =
|
|
64
|
+
const url = config.rpcAddrs[0] + path;
|
|
47
65
|
// console.warn('fetching', url);
|
|
48
66
|
return powers.fetch(url, { keepalive: true }).then(res => res.json());
|
|
49
67
|
};
|
|
68
|
+
// height=0 is the same as omitting height and implies the highest block
|
|
69
|
+
const url = (path = 'published', { kind = 'children', height = 0 } = {}) =>
|
|
70
|
+
`/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`;
|
|
71
|
+
|
|
72
|
+
const readStorage = (path = 'published', { kind = 'children', height = 0 }) =>
|
|
73
|
+
getJSON(url(path, { kind, height }))
|
|
74
|
+
.catch(err => {
|
|
75
|
+
throw Error(`cannot read ${kind} of ${path}: ${err.message}`);
|
|
76
|
+
})
|
|
77
|
+
.then(data => {
|
|
78
|
+
const {
|
|
79
|
+
result: { response },
|
|
80
|
+
} = data;
|
|
81
|
+
if (response?.code !== 0) {
|
|
82
|
+
throw Error(
|
|
83
|
+
`error code ${response?.code} reading ${kind} of ${path}: ${response.log}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return data;
|
|
87
|
+
});
|
|
50
88
|
|
|
51
89
|
return {
|
|
52
|
-
|
|
53
|
-
url: (path = 'published', { kind = 'children', height = 0 } = {}) =>
|
|
54
|
-
`/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`,
|
|
90
|
+
url,
|
|
55
91
|
decode({ result: { response } }) {
|
|
56
92
|
const { code } = response;
|
|
57
93
|
if (code !== 0) {
|
|
@@ -66,11 +102,11 @@ export const makeVStorage = powers => {
|
|
|
66
102
|
* @returns {Promise<string>} latest vstorage value at path
|
|
67
103
|
*/
|
|
68
104
|
async readLatest(path = 'published') {
|
|
69
|
-
const raw = await
|
|
105
|
+
const raw = await readStorage(path, { kind: 'data' });
|
|
70
106
|
return this.decode(raw);
|
|
71
107
|
},
|
|
72
108
|
async keys(path = 'published') {
|
|
73
|
-
const raw = await
|
|
109
|
+
const raw = await readStorage(path, { kind: 'children' });
|
|
74
110
|
return JSON.parse(this.decode(raw)).children;
|
|
75
111
|
},
|
|
76
112
|
/**
|
|
@@ -79,7 +115,7 @@ export const makeVStorage = powers => {
|
|
|
79
115
|
* @returns {Promise<{blockHeight: number, values: string[]}>}
|
|
80
116
|
*/
|
|
81
117
|
async readAt(path, height = undefined) {
|
|
82
|
-
const raw = await
|
|
118
|
+
const raw = await readStorage(path, { kind: 'data', height });
|
|
83
119
|
const txt = this.decode(raw);
|
|
84
120
|
/** @type {{ value: string }} */
|
|
85
121
|
const { value } = JSON.parse(txt);
|
|
@@ -89,32 +125,34 @@ export const makeVStorage = powers => {
|
|
|
89
125
|
* Read values going back as far as available
|
|
90
126
|
*
|
|
91
127
|
* @param {string} path
|
|
128
|
+
* @param {number | string} [minHeight]
|
|
92
129
|
* @returns {Promise<string[]>}
|
|
93
130
|
*/
|
|
94
|
-
async readFully(path) {
|
|
131
|
+
async readFully(path, minHeight = undefined) {
|
|
95
132
|
const parts = [];
|
|
96
133
|
// undefined the first iteration, to query at the highest
|
|
97
134
|
let blockHeight;
|
|
98
135
|
do {
|
|
99
|
-
console.debug('READING', { blockHeight });
|
|
136
|
+
// console.debug('READING', { blockHeight });
|
|
100
137
|
let values;
|
|
101
138
|
try {
|
|
102
139
|
// eslint-disable-next-line no-await-in-loop
|
|
103
140
|
({ blockHeight, values } = await this.readAt(
|
|
104
141
|
path,
|
|
105
|
-
blockHeight && blockHeight - 1,
|
|
142
|
+
blockHeight && Number(blockHeight) - 1,
|
|
106
143
|
));
|
|
107
|
-
console.debug('readAt returned', { blockHeight });
|
|
144
|
+
// console.debug('readAt returned', { blockHeight });
|
|
108
145
|
} catch (err) {
|
|
109
|
-
if (
|
|
110
|
-
console.error(err);
|
|
146
|
+
if (err.message.match(/unknown request/)) {
|
|
147
|
+
// console.error(err);
|
|
111
148
|
break;
|
|
112
149
|
}
|
|
113
150
|
throw err;
|
|
114
151
|
}
|
|
115
152
|
parts.push(values);
|
|
116
|
-
console.debug('PUSHED', values);
|
|
117
|
-
console.debug('NEW', { blockHeight });
|
|
153
|
+
// console.debug('PUSHED', values);
|
|
154
|
+
// console.debug('NEW', { blockHeight, minHeight });
|
|
155
|
+
if (minHeight && Number(blockHeight) <= Number(minHeight)) break;
|
|
118
156
|
} while (blockHeight > 0);
|
|
119
157
|
return parts.flat();
|
|
120
158
|
},
|
|
@@ -122,76 +160,14 @@ export const makeVStorage = powers => {
|
|
|
122
160
|
};
|
|
123
161
|
/** @typedef {ReturnType<typeof makeVStorage>} VStorage */
|
|
124
162
|
|
|
125
|
-
|
|
126
|
-
* Like makeMarshal but,
|
|
127
|
-
* - slotToVal takes an iface arg
|
|
128
|
-
* - if a part being serialized has a boardId property, it passes through as a slot value whereas the normal marshaller would treat it as a copyRecord
|
|
129
|
-
*
|
|
130
|
-
* @param {(slot: string, iface: string) => any} slotToVal
|
|
131
|
-
* @returns {import('@endo/marshal').Marshal<string>}
|
|
132
|
-
*/
|
|
133
|
-
export const boardSlottingMarshaller = (slotToVal = (s, _i) => s) => ({
|
|
134
|
-
/** @param {{body: string, slots: string[]}} capData */
|
|
135
|
-
unserialize: ({ body, slots }) => {
|
|
136
|
-
const reviver = (_key, obj) => {
|
|
137
|
-
const qclass = obj !== null && typeof obj === 'object' && obj['@qclass'];
|
|
138
|
-
// NOTE: hilbert hotel not impl
|
|
139
|
-
switch (qclass) {
|
|
140
|
-
case 'slot': {
|
|
141
|
-
const { index, iface } = obj;
|
|
142
|
-
return slotToVal(slots[index], iface);
|
|
143
|
-
}
|
|
144
|
-
case 'bigint':
|
|
145
|
-
return BigInt(obj.digits);
|
|
146
|
-
case 'undefined':
|
|
147
|
-
return undefined;
|
|
148
|
-
default:
|
|
149
|
-
return obj;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
return JSON.parse(body, reviver);
|
|
153
|
-
},
|
|
154
|
-
serialize: whole => {
|
|
155
|
-
const seen = new Map();
|
|
156
|
-
const slotIndex = v => {
|
|
157
|
-
if (seen.has(v)) {
|
|
158
|
-
return seen.get(v);
|
|
159
|
-
}
|
|
160
|
-
const index = seen.size;
|
|
161
|
-
seen.set(v, index);
|
|
162
|
-
return { index, iface: v.iface };
|
|
163
|
-
};
|
|
164
|
-
const recur = part => {
|
|
165
|
-
if (part === null) return null;
|
|
166
|
-
if (typeof part === 'bigint') {
|
|
167
|
-
return { '@qclass': 'bigint', digits: `${part}` };
|
|
168
|
-
}
|
|
169
|
-
if (Array.isArray(part)) {
|
|
170
|
-
return part.map(recur);
|
|
171
|
-
}
|
|
172
|
-
if (typeof part === 'object') {
|
|
173
|
-
if ('boardId' in part) {
|
|
174
|
-
return { '@qclass': 'slot', ...slotIndex(part.boardId) };
|
|
175
|
-
}
|
|
176
|
-
return Object.fromEntries(
|
|
177
|
-
Object.entries(part).map(([k, v]) => [k, recur(v)]),
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
return part;
|
|
181
|
-
};
|
|
182
|
-
const after = recur(whole);
|
|
183
|
-
return { body: JSON.stringify(after), slots: [...seen.keys()] };
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
export const makeFromBoard = (slotKey = 'boardId') => {
|
|
163
|
+
export const makeFromBoard = () => {
|
|
188
164
|
const cache = new Map();
|
|
189
|
-
const convertSlotToVal = (
|
|
190
|
-
if (cache.has(
|
|
191
|
-
return cache.get(
|
|
165
|
+
const convertSlotToVal = (boardId, iface) => {
|
|
166
|
+
if (cache.has(boardId)) {
|
|
167
|
+
return cache.get(boardId);
|
|
192
168
|
}
|
|
193
|
-
const val =
|
|
194
|
-
cache.set(
|
|
169
|
+
const val = makeBoardRemote({ boardId, iface });
|
|
170
|
+
cache.set(boardId, val);
|
|
195
171
|
return val;
|
|
196
172
|
};
|
|
197
173
|
return harden({ convertSlotToVal });
|
|
@@ -217,7 +193,7 @@ export const storageHelper = {
|
|
|
217
193
|
unserializeTxt: (txt, ctx) => {
|
|
218
194
|
const { capDatas } = storageHelper.parseCapData(txt);
|
|
219
195
|
return capDatas.map(capData =>
|
|
220
|
-
boardSlottingMarshaller(ctx.convertSlotToVal).
|
|
196
|
+
boardSlottingMarshaller(ctx.convertSlotToVal).fromCapData(capData),
|
|
221
197
|
);
|
|
222
198
|
},
|
|
223
199
|
/** @param {string[]} capDataStrings array of stringified capData */
|
|
@@ -239,18 +215,21 @@ harden(storageHelper);
|
|
|
239
215
|
/**
|
|
240
216
|
* @param {IdMap} ctx
|
|
241
217
|
* @param {VStorage} vstorage
|
|
242
|
-
* @returns {Promise<
|
|
218
|
+
* @returns {Promise<import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes>}
|
|
243
219
|
*/
|
|
244
220
|
export const makeAgoricNames = async (ctx, vstorage) => {
|
|
245
221
|
const reverse = {};
|
|
246
222
|
const entries = await Promise.all(
|
|
247
|
-
['brand', 'instance'].map(async kind => {
|
|
223
|
+
['brand', 'instance', 'vbankAsset'].map(async kind => {
|
|
248
224
|
const content = await vstorage.readLatest(
|
|
249
225
|
`published.agoricNames.${kind}`,
|
|
250
226
|
);
|
|
227
|
+
/** @type {Array<[string, import('@agoric/vats/tools/board-utils.js').BoardRemote]>} */
|
|
251
228
|
const parts = storageHelper.unserializeTxt(content, ctx).at(-1);
|
|
252
229
|
for (const [name, remote] of parts) {
|
|
253
|
-
|
|
230
|
+
if ('getBoardId' in remote) {
|
|
231
|
+
reverse[remote.getBoardId()] = name;
|
|
232
|
+
}
|
|
254
233
|
}
|
|
255
234
|
return [kind, Object.fromEntries(parts)];
|
|
256
235
|
}),
|
|
@@ -258,10 +237,36 @@ export const makeAgoricNames = async (ctx, vstorage) => {
|
|
|
258
237
|
return { ...Object.fromEntries(entries), reverse };
|
|
259
238
|
};
|
|
260
239
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
240
|
+
/**
|
|
241
|
+
* @param {{ fetch: typeof window.fetch }} io
|
|
242
|
+
* @param {MinimalNetworkConfig} config
|
|
243
|
+
*/
|
|
244
|
+
export const makeRpcUtils = async ({ fetch }, config = networkConfig) => {
|
|
245
|
+
try {
|
|
246
|
+
const vstorage = makeVStorage({ fetch }, config);
|
|
247
|
+
const fromBoard = makeFromBoard();
|
|
248
|
+
const agoricNames = await makeAgoricNames(fromBoard, vstorage);
|
|
249
|
+
|
|
250
|
+
const unserializer = boardSlottingMarshaller(fromBoard.convertSlotToVal);
|
|
265
251
|
|
|
266
|
-
|
|
252
|
+
/** @type {(txt: string) => unknown} */
|
|
253
|
+
const unserializeHead = txt =>
|
|
254
|
+
storageHelper.unserializeTxt(txt, fromBoard).at(-1);
|
|
255
|
+
|
|
256
|
+
/** @type {(path: string) => Promise<unknown>} */
|
|
257
|
+
const readLatestHead = path =>
|
|
258
|
+
vstorage.readLatest(path).then(unserializeHead);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
agoricNames,
|
|
262
|
+
fromBoard,
|
|
263
|
+
readLatestHead,
|
|
264
|
+
unserializeHead,
|
|
265
|
+
unserializer,
|
|
266
|
+
vstorage,
|
|
267
|
+
};
|
|
268
|
+
} catch (err) {
|
|
269
|
+
throw Error(`RPC failure (${config.rpcAddrs}): ${err.message}`);
|
|
270
|
+
}
|
|
267
271
|
};
|
|
272
|
+
/** @typedef {Awaited<ReturnType<typeof makeRpcUtils>>} RpcUtils */
|
package/src/lib/wallet.js
CHANGED
|
@@ -1,20 +1,315 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
/* global process */
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { iterateReverse } from '@agoric/casting';
|
|
5
|
+
import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js';
|
|
6
|
+
import { execSwingsetTransaction, pollBlocks, pollTx } from './chain.js';
|
|
7
|
+
import { boardSlottingMarshaller, makeRpcUtils } from './rpc.js';
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord} CurrentWalletRecord */
|
|
10
|
+
/** @typedef {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} AgoricNamesRemotes */
|
|
11
|
+
|
|
12
|
+
const { values } = Object;
|
|
13
|
+
const { Fail } = assert;
|
|
14
|
+
const marshaller = boardSlottingMarshaller();
|
|
15
|
+
|
|
16
|
+
/** @type {CurrentWalletRecord} */
|
|
17
|
+
const emptyCurrentRecord = {
|
|
18
|
+
purses: [],
|
|
19
|
+
offerToUsedInvitation: [],
|
|
20
|
+
offerToPublicSubscriberPaths: [],
|
|
21
|
+
liveOffers: [],
|
|
22
|
+
};
|
|
4
23
|
|
|
5
24
|
/**
|
|
6
25
|
* @param {string} addr
|
|
7
|
-
* @param {import('./rpc').
|
|
8
|
-
* @param {object} io
|
|
9
|
-
* @param {import('./rpc.js').VStorage} io.vstorage
|
|
26
|
+
* @param {Pick<import('./rpc.js').RpcUtils, 'readLatestHead'>} io
|
|
10
27
|
* @returns {Promise<import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord>}
|
|
11
28
|
*/
|
|
12
|
-
export const getCurrent = async (addr,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
export const getCurrent = async (addr, { readLatestHead }) => {
|
|
30
|
+
// Partial because older writes may not have had all properties
|
|
31
|
+
// NB: assumes changes are only additions
|
|
32
|
+
let current =
|
|
33
|
+
/** @type {Partial<import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord> | undefined} */ (
|
|
34
|
+
await readLatestHead(`published.wallet.${addr}.current`)
|
|
35
|
+
);
|
|
36
|
+
if (current === undefined) {
|
|
37
|
+
throw new Error(`undefined current node for ${addr}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Repair a type misunderstanding seen in the wild.
|
|
41
|
+
// See https://github.com/Agoric/agoric-sdk/pull/7139
|
|
42
|
+
let offerToUsedInvitation = current.offerToUsedInvitation;
|
|
43
|
+
if (
|
|
44
|
+
offerToUsedInvitation &&
|
|
45
|
+
typeof offerToUsedInvitation === 'object' &&
|
|
46
|
+
!Array.isArray(offerToUsedInvitation)
|
|
47
|
+
) {
|
|
48
|
+
offerToUsedInvitation = Object.entries(offerToUsedInvitation);
|
|
49
|
+
current = harden({
|
|
50
|
+
...current,
|
|
51
|
+
offerToUsedInvitation,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// override full empty record with defined values from published one
|
|
56
|
+
return { ...emptyCurrentRecord, ...current };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} addr
|
|
61
|
+
* @param {Pick<import('./rpc.js').RpcUtils, 'readLatestHead'>} io
|
|
62
|
+
* @returns {Promise<import('@agoric/smart-wallet/src/smartWallet').UpdateRecord>}
|
|
63
|
+
*/
|
|
64
|
+
export const getLastUpdate = (addr, { readLatestHead }) => {
|
|
65
|
+
// @ts-expect-error cast
|
|
66
|
+
return readLatestHead(`published.wallet.${addr}`);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('@agoric/smart-wallet/src/smartWallet').BridgeAction} bridgeAction
|
|
71
|
+
* @param {Pick<import('stream').Writable,'write'>} [stdout]
|
|
72
|
+
*/
|
|
73
|
+
export const outputAction = (bridgeAction, stdout = process.stdout) => {
|
|
74
|
+
const capData = marshaller.toCapData(harden(bridgeAction));
|
|
75
|
+
stdout.write(JSON.stringify(capData));
|
|
76
|
+
stdout.write('\n');
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const sendHint =
|
|
80
|
+
'Now use `agoric wallet send ...` to sign and broadcast the offer.\n';
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {import('@agoric/smart-wallet/src/smartWallet').BridgeAction} bridgeAction
|
|
84
|
+
* @param {{
|
|
85
|
+
* stdout: Pick<import('stream').Writable,'write'>,
|
|
86
|
+
* stderr: Pick<import('stream').Writable,'write'>,
|
|
87
|
+
* }} io
|
|
88
|
+
*/
|
|
89
|
+
export const outputActionAndHint = (bridgeAction, { stdout, stderr }) => {
|
|
90
|
+
outputAction(bridgeAction, stdout);
|
|
91
|
+
stderr.write(sendHint);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {import('@agoric/smart-wallet/src/offers.js').OfferSpec} offer
|
|
96
|
+
* @param {Pick<import('stream').Writable,'write'>} [stdout]
|
|
97
|
+
*/
|
|
98
|
+
export const outputExecuteOfferAction = (offer, stdout = process.stdout) => {
|
|
99
|
+
/** @type {import('@agoric/smart-wallet/src/smartWallet').BridgeAction} */
|
|
100
|
+
const spendAction = {
|
|
101
|
+
method: 'executeOffer',
|
|
102
|
+
offer,
|
|
103
|
+
};
|
|
104
|
+
outputAction(spendAction, stdout);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @deprecated use `.current` node for current state
|
|
109
|
+
* @param {import('@agoric/casting').Follower<import('@agoric/casting').ValueFollowerElement<import('@agoric/smart-wallet/src/smartWallet').UpdateRecord>>} follower
|
|
110
|
+
* @param {Brand<'set'>} [invitationBrand]
|
|
111
|
+
*/
|
|
112
|
+
export const coalesceWalletState = async (follower, invitationBrand) => {
|
|
113
|
+
// values with oldest last
|
|
114
|
+
const history = [];
|
|
115
|
+
for await (const followerElement of iterateReverse(follower)) {
|
|
116
|
+
history.push(followerElement.value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const coalescer = makeWalletStateCoalescer(invitationBrand);
|
|
120
|
+
// update with oldest first
|
|
121
|
+
for (const record of history.reverse()) {
|
|
122
|
+
coalescer.update(record);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return coalescer.state;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sign and broadcast a wallet-action.
|
|
130
|
+
*
|
|
131
|
+
* @throws { Error & { code: number } } if transaction fails
|
|
132
|
+
* @param {import('@agoric/smart-wallet/src/smartWallet').BridgeAction} bridgeAction
|
|
133
|
+
* @param {import('./rpc').MinimalNetworkConfig & {
|
|
134
|
+
* from: string,
|
|
135
|
+
* verbose?: boolean,
|
|
136
|
+
* keyring?: {home?: string, backend: string},
|
|
137
|
+
* stdout: Pick<import('stream').Writable, 'write'>,
|
|
138
|
+
* execFileSync: typeof import('child_process').execFileSync,
|
|
139
|
+
* delay: (ms: number) => Promise<void>,
|
|
140
|
+
* dryRun?: boolean,
|
|
141
|
+
* }} opts
|
|
142
|
+
*/
|
|
143
|
+
export const sendAction = async (bridgeAction, opts) => {
|
|
144
|
+
const offerBody = JSON.stringify(marshaller.toCapData(harden(bridgeAction)));
|
|
145
|
+
|
|
146
|
+
// tryExit should not require --allow-spend
|
|
147
|
+
// https://github.com/Agoric/agoric-sdk/issues/7291
|
|
148
|
+
const spendMethods = ['executeOffer', 'tryExitOffer'];
|
|
149
|
+
const spendArg = spendMethods.includes(bridgeAction.method)
|
|
150
|
+
? ['--allow-spend']
|
|
151
|
+
: [];
|
|
152
|
+
|
|
153
|
+
const act = ['wallet-action', ...spendArg, offerBody];
|
|
154
|
+
const out = execSwingsetTransaction([...act, '--output', 'json'], opts);
|
|
155
|
+
if (opts.dryRun) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
assert(out); // not dry run
|
|
160
|
+
const tx = JSON.parse(out);
|
|
161
|
+
if (tx.code !== 0) {
|
|
162
|
+
const err = Error(`failed to send action. code: ${tx.code}`);
|
|
163
|
+
// @ts-expect-error XXX how to add properties to an error?
|
|
164
|
+
err.code = tx.code;
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return pollTx(tx.txhash, opts);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {CurrentWalletRecord} current
|
|
173
|
+
* @param {AgoricNamesRemotes} agoricNames
|
|
174
|
+
*/
|
|
175
|
+
export const findContinuingIds = (current, agoricNames) => {
|
|
176
|
+
// XXX should runtime type-check
|
|
177
|
+
/** @type {{ offerToUsedInvitation: [string, Amount<'set'>][]}} */
|
|
178
|
+
const { offerToUsedInvitation: entries } = /** @type {any} */ (current);
|
|
179
|
+
|
|
180
|
+
Array.isArray(entries) || Fail`entries must be an array: ${entries}`;
|
|
16
181
|
|
|
17
|
-
const
|
|
182
|
+
const keyOf = (obj, val) => {
|
|
183
|
+
const found = Object.entries(obj).find(e => e[1] === val);
|
|
184
|
+
return found && found[0];
|
|
185
|
+
};
|
|
18
186
|
|
|
19
|
-
|
|
187
|
+
const found = [];
|
|
188
|
+
for (const [offerId, { value }] of entries) {
|
|
189
|
+
/** @type {{ description: string, instance: unknown }[]} */
|
|
190
|
+
const [{ description, instance }] = value;
|
|
191
|
+
if (
|
|
192
|
+
description === 'charter member invitation' ||
|
|
193
|
+
/Voter\d+/.test(description)
|
|
194
|
+
) {
|
|
195
|
+
const instanceName = keyOf(agoricNames.instance, instance);
|
|
196
|
+
found.push({ instance, instanceName, description, offerId });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return found;
|
|
20
200
|
};
|
|
201
|
+
|
|
202
|
+
export const makeWalletUtils = async (
|
|
203
|
+
{ fetch, execFileSync, delay },
|
|
204
|
+
networkConfig,
|
|
205
|
+
) => {
|
|
206
|
+
const { agoricNames, fromBoard, readLatestHead, vstorage } =
|
|
207
|
+
await makeRpcUtils({ fetch }, networkConfig);
|
|
208
|
+
/**
|
|
209
|
+
* @param {string} from
|
|
210
|
+
* @param {number|string} [minHeight]
|
|
211
|
+
*/
|
|
212
|
+
const storedWalletState = async (from, minHeight = undefined) => {
|
|
213
|
+
const m = boardSlottingMarshaller(fromBoard.convertSlotToVal);
|
|
214
|
+
|
|
215
|
+
const history = await vstorage.readFully(
|
|
216
|
+
`published.wallet.${from}`,
|
|
217
|
+
minHeight,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
/** @type {{ Invitation: Brand<'set'> }} */
|
|
221
|
+
// @ts-expect-error XXX how to narrow AssetKind to set?
|
|
222
|
+
const { Invitation } = agoricNames.brand;
|
|
223
|
+
const coalescer = makeWalletStateCoalescer(Invitation);
|
|
224
|
+
// update with oldest first
|
|
225
|
+
for (const txt of history.reverse()) {
|
|
226
|
+
const { body, slots } = JSON.parse(txt);
|
|
227
|
+
const record = m.fromCapData({ body, slots });
|
|
228
|
+
coalescer.update(record);
|
|
229
|
+
}
|
|
230
|
+
const coalesced = coalescer.state;
|
|
231
|
+
harden(coalesced);
|
|
232
|
+
return coalesced;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get OfferStatus by id, polling until available.
|
|
237
|
+
*
|
|
238
|
+
* @param {string} from
|
|
239
|
+
* @param {string|number} id
|
|
240
|
+
* @param {number|string} minHeight
|
|
241
|
+
* @param {boolean} [untilNumWantsSatisfied]
|
|
242
|
+
*/
|
|
243
|
+
const pollOffer = async (
|
|
244
|
+
from,
|
|
245
|
+
id,
|
|
246
|
+
minHeight,
|
|
247
|
+
untilNumWantsSatisfied = false,
|
|
248
|
+
) => {
|
|
249
|
+
const lookup = async () => {
|
|
250
|
+
// eslint-disable-next-line @jessie.js/no-nested-await, no-await-in-loop
|
|
251
|
+
const { offerStatuses } = await storedWalletState(from, minHeight);
|
|
252
|
+
const offerStatus = [...offerStatuses.values()].find(s => s.id === id);
|
|
253
|
+
if (!offerStatus) throw Error('retry');
|
|
254
|
+
harden(offerStatus);
|
|
255
|
+
if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) {
|
|
256
|
+
throw Error('retry (no numWantsSatisfied yet)');
|
|
257
|
+
}
|
|
258
|
+
return offerStatus;
|
|
259
|
+
};
|
|
260
|
+
const retryMessage = 'offer not in wallet at block';
|
|
261
|
+
const opts = { ...networkConfig, execFileSync, delay, retryMessage };
|
|
262
|
+
return pollBlocks(opts)(lookup);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
networkConfig,
|
|
267
|
+
agoricNames,
|
|
268
|
+
fromBoard,
|
|
269
|
+
vstorage,
|
|
270
|
+
readLatestHead,
|
|
271
|
+
storedWalletState,
|
|
272
|
+
pollOffer,
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {{
|
|
278
|
+
* brand: Record<string, Brand>,
|
|
279
|
+
* vbankAsset: Record<string, { brand: Brand, displayInfo: DisplayInfo }>,
|
|
280
|
+
* }} agoricNames
|
|
281
|
+
* @param {(msg: string) => Error} makeError error constructor
|
|
282
|
+
* @returns {(a: string) => Amount<'nat'>}
|
|
283
|
+
*/
|
|
284
|
+
export const makeParseAmount =
|
|
285
|
+
(agoricNames, makeError = msg => RangeError(msg)) =>
|
|
286
|
+
opt => {
|
|
287
|
+
assert.typeof(opt, 'string', 'parseAmount expected string');
|
|
288
|
+
const m = opt.match(/^(?<value>[\d_]+(\.[\d_]+)?)(?<brand>[A-Z]\w*?)$/);
|
|
289
|
+
if (!m || !m.groups) {
|
|
290
|
+
throw makeError(`invalid amount: ${opt}`);
|
|
291
|
+
}
|
|
292
|
+
const anyBrand = agoricNames.brand[m.groups.brand];
|
|
293
|
+
if (!anyBrand) {
|
|
294
|
+
throw makeError(`unknown brand: ${m.groups.brand}`);
|
|
295
|
+
}
|
|
296
|
+
const assetDesc = values(agoricNames.vbankAsset).find(
|
|
297
|
+
d => d.brand === anyBrand,
|
|
298
|
+
);
|
|
299
|
+
if (!assetDesc) {
|
|
300
|
+
throw makeError(`unknown brand: ${m.groups.brand}`);
|
|
301
|
+
}
|
|
302
|
+
const { displayInfo } = assetDesc;
|
|
303
|
+
if (!displayInfo.decimalPlaces || displayInfo.assetKind !== 'nat') {
|
|
304
|
+
throw makeError(`bad brand: ${displayInfo}`);
|
|
305
|
+
}
|
|
306
|
+
const value = BigInt(
|
|
307
|
+
Number(m.groups.value.replace(/_/g, '')) *
|
|
308
|
+
10 ** displayInfo.decimalPlaces,
|
|
309
|
+
);
|
|
310
|
+
/** @type {Brand<'nat'>} */
|
|
311
|
+
// @ts-expect-error dynamic cast
|
|
312
|
+
const natBrand = anyBrand;
|
|
313
|
+
const amt = { value, brand: natBrand };
|
|
314
|
+
return amt;
|
|
315
|
+
};
|
package/src/main-publish.js
CHANGED
|
@@ -15,12 +15,10 @@ const publishMain = async (progname, rawArgs, powers, opts) => {
|
|
|
15
15
|
const { node: rpcAddress, home: homeDirectory, chainID = 'agoric' } = opts;
|
|
16
16
|
|
|
17
17
|
if (typeof rpcAddress !== 'string') {
|
|
18
|
-
throw
|
|
19
|
-
`Required flag for agoric publish: -n, --node <rpcAddress>`,
|
|
20
|
-
);
|
|
18
|
+
throw Error(`Required flag for agoric publish: -n, --node <rpcAddress>`);
|
|
21
19
|
}
|
|
22
20
|
if (typeof homeDirectory !== 'string') {
|
|
23
|
-
throw
|
|
21
|
+
throw Error(
|
|
24
22
|
`Required flag for agoric publish: -h, --home <directory>, containing ag-solo-mnemonic`,
|
|
25
23
|
);
|
|
26
24
|
}
|
|
@@ -47,7 +45,7 @@ const publishMain = async (progname, rawArgs, powers, opts) => {
|
|
|
47
45
|
});
|
|
48
46
|
const publishBundle = makeBundlePublisher({
|
|
49
47
|
getDefaultConnection() {
|
|
50
|
-
throw
|
|
48
|
+
throw Error(
|
|
51
49
|
'Invariant: publishBundle will never call getDefaultConnection because we provide an explicit connectionSpec',
|
|
52
50
|
);
|
|
53
51
|
},
|