@agoric/orchestration 0.1.1-dev-286302a.0 → 0.1.1-dev-24f7f32.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/index.js +1 -2
- package/package.json +14 -14
- package/src/examples/stakeAtom.contract.js +15 -5
- package/src/exos/chainAccountKit.js +27 -38
- package/src/exos/icqConnectionKit.js +125 -0
- package/src/exos/stakingAccountKit.js +71 -30
- package/src/proposals/orchestration-proposal.js +7 -8
- package/src/proposals/start-stakeAtom.js +7 -2
- package/src/service.js +75 -14
- package/src/typeGuards.js +20 -0
- package/src/types.d.ts +11 -0
- package/src/utils/address.js +23 -6
- package/src/utils/packet.js +110 -0
- package/src/vat-orchestration.js +3 -3
- package/src/utils/tx.js +0 -48
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/orchestration",
|
|
3
|
-
"version": "0.1.1-dev-
|
|
3
|
+
"version": "0.1.1-dev-24f7f32.0+24f7f32",
|
|
4
4
|
"description": "Chain abstraction for Agoric's orchestration clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,18 +29,18 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/Agoric/agoric-sdk#readme",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@agoric/assert": "0.6.1-dev-
|
|
33
|
-
"@agoric/cosmic-proto": "0.4.1-dev-
|
|
34
|
-
"@agoric/ertp": "0.16.3-dev-
|
|
35
|
-
"@agoric/internal": "0.3.3-dev-
|
|
36
|
-
"@agoric/network": "0.1.1-dev-
|
|
37
|
-
"@agoric/notifier": "0.6.3-dev-
|
|
38
|
-
"@agoric/store": "0.9.3-dev-
|
|
39
|
-
"@agoric/time": "0.3.3-dev-
|
|
40
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
41
|
-
"@agoric/vats": "0.15.2-dev-
|
|
42
|
-
"@agoric/zoe": "0.26.3-dev-
|
|
43
|
-
"@agoric/zone": "0.2.3-dev-
|
|
32
|
+
"@agoric/assert": "0.6.1-dev-24f7f32.0+24f7f32",
|
|
33
|
+
"@agoric/cosmic-proto": "0.4.1-dev-24f7f32.0+24f7f32",
|
|
34
|
+
"@agoric/ertp": "0.16.3-dev-24f7f32.0+24f7f32",
|
|
35
|
+
"@agoric/internal": "0.3.3-dev-24f7f32.0+24f7f32",
|
|
36
|
+
"@agoric/network": "0.1.1-dev-24f7f32.0+24f7f32",
|
|
37
|
+
"@agoric/notifier": "0.6.3-dev-24f7f32.0+24f7f32",
|
|
38
|
+
"@agoric/store": "0.9.3-dev-24f7f32.0+24f7f32",
|
|
39
|
+
"@agoric/time": "0.3.3-dev-24f7f32.0+24f7f32",
|
|
40
|
+
"@agoric/vat-data": "0.5.3-dev-24f7f32.0+24f7f32",
|
|
41
|
+
"@agoric/vats": "0.15.2-dev-24f7f32.0+24f7f32",
|
|
42
|
+
"@agoric/zoe": "0.26.3-dev-24f7f32.0+24f7f32",
|
|
43
|
+
"@agoric/zone": "0.2.3-dev-24f7f32.0+24f7f32",
|
|
44
44
|
"@endo/base64": "^1.0.4",
|
|
45
45
|
"@endo/far": "^1.1.1",
|
|
46
46
|
"@endo/marshal": "^1.4.1",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"typeCoverage": {
|
|
83
83
|
"atLeast": 96.39
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "24f7f32495a6eda13fd75d18620ab9f7989b5d2e"
|
|
86
86
|
}
|
|
@@ -11,15 +11,16 @@ import { prepareStakingAccountKit } from '../exos/stakingAccountKit.js';
|
|
|
11
11
|
|
|
12
12
|
const trace = makeTracer('StakeAtom');
|
|
13
13
|
/**
|
|
14
|
-
* @import { OrchestrationService } from '../service.js'
|
|
15
14
|
* @import { Baggage } from '@agoric/vat-data';
|
|
16
15
|
* @import { IBCConnectionID } from '@agoric/vats';
|
|
16
|
+
* @import { ICQConnection, OrchestrationService } from '../types.js';
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @typedef {{
|
|
21
21
|
* hostConnectionId: IBCConnectionID;
|
|
22
22
|
* controllerConnectionId: IBCConnectionID;
|
|
23
|
+
* bondDenom: string;
|
|
23
24
|
* }} StakeAtomTerms
|
|
24
25
|
*/
|
|
25
26
|
|
|
@@ -34,7 +35,9 @@ const trace = makeTracer('StakeAtom');
|
|
|
34
35
|
* @param {Baggage} baggage
|
|
35
36
|
*/
|
|
36
37
|
export const start = async (zcf, privateArgs, baggage) => {
|
|
37
|
-
|
|
38
|
+
// TODO #9063 this roughly matches what we'll get from Chain<C>.getChainInfo()
|
|
39
|
+
const { hostConnectionId, controllerConnectionId, bondDenom } =
|
|
40
|
+
zcf.getTerms();
|
|
38
41
|
const { orchestration, marshaller, storageNode } = privateArgs;
|
|
39
42
|
|
|
40
43
|
const zone = makeDurableZone(baggage);
|
|
@@ -52,12 +55,19 @@ export const start = async (zcf, privateArgs, baggage) => {
|
|
|
52
55
|
hostConnectionId,
|
|
53
56
|
controllerConnectionId,
|
|
54
57
|
);
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
// #9212 TODO do not fail if host does not have `async-icq` module;
|
|
59
|
+
// communicate to OrchestrationAccount that it can't send queries
|
|
60
|
+
const icqConnection = await E(orchestration).provideICQConnection(
|
|
61
|
+
controllerConnectionId,
|
|
62
|
+
);
|
|
63
|
+
const accountAddress = await E(account).getAddress();
|
|
64
|
+
trace('account address', accountAddress);
|
|
57
65
|
const { holder, invitationMakers } = makeStakingAccountKit(
|
|
58
66
|
account,
|
|
59
67
|
storageNode,
|
|
60
|
-
|
|
68
|
+
accountAddress,
|
|
69
|
+
icqConnection,
|
|
70
|
+
bondDenom,
|
|
61
71
|
);
|
|
62
72
|
return {
|
|
63
73
|
publicSubscribers: holder.getPublicTopics(),
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
/** @file
|
|
3
|
-
import { NonNullish } from '@agoric/assert';
|
|
4
|
-
import { makeTracer } from '@agoric/internal';
|
|
2
|
+
/** @file ChainAccount exo */
|
|
5
3
|
|
|
6
4
|
// XXX ambient types runtime imports until https://github.com/Agoric/agoric-sdk/issues/6512
|
|
7
5
|
import '@agoric/network/exported.js';
|
|
8
6
|
|
|
7
|
+
import { NonNullish } from '@agoric/assert';
|
|
8
|
+
import { makeTracer } from '@agoric/internal';
|
|
9
9
|
import { V as E } from '@agoric/vat-data/vow.js';
|
|
10
10
|
import { M } from '@endo/patterns';
|
|
11
11
|
import { PaymentShape, PurseShape } from '@agoric/ertp';
|
|
12
12
|
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { findAddressField } from '../utils/address.js';
|
|
14
|
+
import {
|
|
15
|
+
ConnectionHandlerI,
|
|
16
|
+
ChainAddressShape,
|
|
17
|
+
Proto3Shape,
|
|
18
|
+
} from '../typeGuards.js';
|
|
19
|
+
import { makeTxPacket, parseTxPacket } from '../utils/packet.js';
|
|
15
20
|
|
|
16
21
|
/**
|
|
22
|
+
* @import { Zone } from '@agoric/base-zone';
|
|
17
23
|
* @import { Connection, Port } from '@agoric/network';
|
|
18
24
|
* @import { Remote } from '@agoric/vow';
|
|
19
|
-
* @import { Zone } from '@agoric/base-zone';
|
|
20
25
|
* @import { AnyJson } from '@agoric/cosmic-proto';
|
|
21
26
|
* @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
|
|
22
27
|
* @import { LocalIbcAddress, RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js';
|
|
@@ -24,18 +29,7 @@ import { makeTxPacket, parsePacketAck } from '../utils/tx.js';
|
|
|
24
29
|
*/
|
|
25
30
|
|
|
26
31
|
const { Fail } = assert;
|
|
27
|
-
const trace = makeTracer('
|
|
28
|
-
|
|
29
|
-
export const Proto3Shape = {
|
|
30
|
-
typeUrl: M.string(),
|
|
31
|
-
value: M.string(),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const ChainAddressShape = {
|
|
35
|
-
address: M.string(),
|
|
36
|
-
chainId: M.string(),
|
|
37
|
-
addressEncoding: M.string(),
|
|
38
|
-
};
|
|
32
|
+
const trace = makeTracer('ChainAccountKit');
|
|
39
33
|
|
|
40
34
|
/** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */
|
|
41
35
|
const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS';
|
|
@@ -55,32 +49,28 @@ export const ChainAccountI = M.interface('ChainAccount', {
|
|
|
55
49
|
prepareTransfer: M.callWhen().returns(InvitationShape),
|
|
56
50
|
});
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {{
|
|
54
|
+
* port: Port;
|
|
55
|
+
* connection: Remote<Connection> | undefined;
|
|
56
|
+
* localAddress: LocalIbcAddress | undefined;
|
|
57
|
+
* requestedRemoteAddress: string;
|
|
58
|
+
* remoteAddress: RemoteIbcAddress | undefined;
|
|
59
|
+
* chainAddress: ChainAddress | undefined;
|
|
60
|
+
* }} State
|
|
61
|
+
*/
|
|
63
62
|
|
|
64
63
|
/** @param {Zone} zone */
|
|
65
64
|
export const prepareChainAccountKit = zone =>
|
|
66
65
|
zone.exoClassKit(
|
|
67
|
-
'
|
|
66
|
+
'ChainAccountKit',
|
|
68
67
|
{ account: ChainAccountI, connectionHandler: ConnectionHandlerI },
|
|
69
68
|
/**
|
|
70
69
|
* @param {Port} port
|
|
71
70
|
* @param {string} requestedRemoteAddress
|
|
72
71
|
*/
|
|
73
72
|
(port, requestedRemoteAddress) =>
|
|
74
|
-
/**
|
|
75
|
-
* @type {{
|
|
76
|
-
* port: Port;
|
|
77
|
-
* connection: Remote<Connection> | undefined;
|
|
78
|
-
* localAddress: LocalIbcAddress | undefined;
|
|
79
|
-
* requestedRemoteAddress: string;
|
|
80
|
-
* remoteAddress: RemoteIbcAddress | undefined;
|
|
81
|
-
* chainAddress: ChainAddress | undefined;
|
|
82
|
-
* }}
|
|
83
|
-
*/ (
|
|
73
|
+
/** @type {State} */ (
|
|
84
74
|
harden({
|
|
85
75
|
port,
|
|
86
76
|
connection: undefined,
|
|
@@ -131,8 +121,8 @@ export const prepareChainAccountKit = zone =>
|
|
|
131
121
|
if (!connection) throw Fail`connection not available`;
|
|
132
122
|
return E.when(
|
|
133
123
|
E(connection).send(makeTxPacket(msgs, opts)),
|
|
134
|
-
// if
|
|
135
|
-
ack =>
|
|
124
|
+
// if parseTxPacket cannot find a `result` key, it throws
|
|
125
|
+
ack => parseTxPacket(ack),
|
|
136
126
|
);
|
|
137
127
|
},
|
|
138
128
|
/**
|
|
@@ -175,9 +165,8 @@ export const prepareChainAccountKit = zone =>
|
|
|
175
165
|
this.state.connection = connection;
|
|
176
166
|
this.state.remoteAddress = remoteAddr;
|
|
177
167
|
this.state.localAddress = localAddr;
|
|
178
|
-
// XXX parseAddress currently throws, should it return '' instead?
|
|
179
168
|
this.state.chainAddress = harden({
|
|
180
|
-
address:
|
|
169
|
+
address: findAddressField(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS,
|
|
181
170
|
// TODO get this from `Chain` object #9063
|
|
182
171
|
// XXX how do we get a chainId for an unknown chain? seems it may need to be a user supplied arg
|
|
183
172
|
chainId: 'FIXME',
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @file ICQConnection Exo */
|
|
3
|
+
import { NonNullish } from '@agoric/assert';
|
|
4
|
+
import { makeTracer } from '@agoric/internal';
|
|
5
|
+
import { V as E } from '@agoric/vat-data/vow.js';
|
|
6
|
+
import { M } from '@endo/patterns';
|
|
7
|
+
import { makeQueryPacket, parseQueryPacket } from '../utils/packet.js';
|
|
8
|
+
import { ConnectionHandlerI } from '../typeGuards.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @import {Zone} from '@agoric/base-zone';
|
|
12
|
+
* @import {Connection, Port} from '@agoric/network';
|
|
13
|
+
* @import {Remote} from '@agoric/vow';
|
|
14
|
+
* @import {Base64Any, RequestQueryJson} from '@agoric/cosmic-proto';
|
|
15
|
+
* @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
|
|
16
|
+
* @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js';
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { Fail } = assert;
|
|
20
|
+
const trace = makeTracer('Orchestration:ICQConnection');
|
|
21
|
+
|
|
22
|
+
export const ICQMsgShape = M.splitRecord(
|
|
23
|
+
{ path: M.string(), data: M.string() },
|
|
24
|
+
{ height: M.string(), prove: M.boolean() },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const ICQConnectionI = M.interface('ICQConnection', {
|
|
28
|
+
getLocalAddress: M.call().returns(M.string()),
|
|
29
|
+
getRemoteAddress: M.call().returns(M.string()),
|
|
30
|
+
query: M.call(M.arrayOf(ICQMsgShape)).returns(M.promise()),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {{
|
|
35
|
+
* port: Port;
|
|
36
|
+
* connection: Remote<Connection> | undefined;
|
|
37
|
+
* localAddress: LocalIbcAddress | undefined;
|
|
38
|
+
* remoteAddress: RemoteIbcAddress | undefined;
|
|
39
|
+
* }} ICQConnectionKitState
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Prepares an ICQ Connection Kit based on the {@link https://github.com/cosmos/ibc-apps/blob/e9b46e4bf0ad0a66cf6bc53b5e5496f6e2b4b02b/modules/async-icq/README.md | `icq/v1` IBC application protocol}.
|
|
44
|
+
*
|
|
45
|
+
* `icq/v1`, also referred to as `async-icq`, is a protocol for asynchronous queries
|
|
46
|
+
* between IBC-enabled chains. It allows a chain to send queries to another chain
|
|
47
|
+
* and receive responses asynchronously.
|
|
48
|
+
*
|
|
49
|
+
* The ICQ connection kit provides the necessary functionality to establish and manage
|
|
50
|
+
* an ICQ connection between two chains. It includes methods for retrieving the local
|
|
51
|
+
* and remote addresses of the connection, as well as sending queries and handling
|
|
52
|
+
* connection events.
|
|
53
|
+
*
|
|
54
|
+
* @param {Zone} zone
|
|
55
|
+
*/
|
|
56
|
+
export const prepareICQConnectionKit = zone =>
|
|
57
|
+
zone.exoClassKit(
|
|
58
|
+
'ICQConnectionKit',
|
|
59
|
+
{ connection: ICQConnectionI, connectionHandler: ConnectionHandlerI },
|
|
60
|
+
/**
|
|
61
|
+
* @param {Port} port
|
|
62
|
+
*/
|
|
63
|
+
port =>
|
|
64
|
+
/** @type {ICQConnectionKitState} */ (
|
|
65
|
+
harden({
|
|
66
|
+
port,
|
|
67
|
+
connection: undefined,
|
|
68
|
+
remoteAddress: undefined,
|
|
69
|
+
localAddress: undefined,
|
|
70
|
+
})
|
|
71
|
+
),
|
|
72
|
+
{
|
|
73
|
+
connection: {
|
|
74
|
+
getLocalAddress() {
|
|
75
|
+
return NonNullish(
|
|
76
|
+
this.state.localAddress,
|
|
77
|
+
'local address not available',
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
getRemoteAddress() {
|
|
81
|
+
return NonNullish(
|
|
82
|
+
this.state.remoteAddress,
|
|
83
|
+
'remote address not available',
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* @param {RequestQueryJson[]} msgs
|
|
88
|
+
* @returns {Promise<Base64Any<ResponseQuery>[]>}
|
|
89
|
+
* @throws {Error} if packet fails to send or an error is returned
|
|
90
|
+
*/
|
|
91
|
+
query(msgs) {
|
|
92
|
+
const { connection } = this.state;
|
|
93
|
+
if (!connection) throw Fail`connection not available`;
|
|
94
|
+
return E.when(
|
|
95
|
+
E(connection).send(makeQueryPacket(msgs)),
|
|
96
|
+
// if parseTxPacket cannot find a `result` key, it throws
|
|
97
|
+
ack => parseQueryPacket(ack),
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
connectionHandler: {
|
|
102
|
+
/**
|
|
103
|
+
* @param {Remote<Connection>} connection
|
|
104
|
+
* @param {LocalIbcAddress} localAddr
|
|
105
|
+
* @param {RemoteIbcAddress} remoteAddr
|
|
106
|
+
*/
|
|
107
|
+
async onOpen(connection, localAddr, remoteAddr) {
|
|
108
|
+
trace(`ICQ Channel Opened for ${localAddr} at ${remoteAddr}`);
|
|
109
|
+
this.state.connection = connection;
|
|
110
|
+
this.state.remoteAddress = remoteAddr;
|
|
111
|
+
this.state.localAddress = localAddr;
|
|
112
|
+
},
|
|
113
|
+
async onClose(_connection, reason) {
|
|
114
|
+
trace(`ICQ Channel closed. Reason: ${reason}`);
|
|
115
|
+
},
|
|
116
|
+
async onReceive(connection, bytes) {
|
|
117
|
+
trace(`ICQ Channel onReceive`, connection, bytes);
|
|
118
|
+
return '';
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/** @typedef {ReturnType<ReturnType<typeof prepareICQConnectionKit>>} ICQConnectionKit */
|
|
125
|
+
/** @typedef {ICQConnectionKit['connection']} ICQConnection */
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
MsgDelegate,
|
|
9
9
|
MsgDelegateResponse,
|
|
10
10
|
} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js';
|
|
11
|
+
import {
|
|
12
|
+
QueryBalanceRequest,
|
|
13
|
+
QueryBalanceResponse,
|
|
14
|
+
} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js';
|
|
11
15
|
import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js';
|
|
12
16
|
import { AmountShape } from '@agoric/ertp';
|
|
13
17
|
import { makeTracer } from '@agoric/internal';
|
|
@@ -16,11 +20,13 @@ import { M, prepareExoClassKit } from '@agoric/vat-data';
|
|
|
16
20
|
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js';
|
|
17
21
|
import { decodeBase64 } from '@endo/base64';
|
|
18
22
|
import { E } from '@endo/far';
|
|
23
|
+
import { toRequestQueryJson } from '@agoric/cosmic-proto';
|
|
24
|
+
import { ChainAddressShape, CoinShape } from '../typeGuards.js';
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
|
-
* @import {
|
|
22
|
-
* @import {
|
|
23
|
-
* @import {
|
|
27
|
+
* @import {ChainAccount, ChainAddress, ChainAmount, CosmosValidatorAddress, ICQConnection} from '../types.js';
|
|
28
|
+
* @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js';
|
|
29
|
+
* @import {Baggage} from '@agoric/swingset-liveslots';
|
|
24
30
|
* @import {AnyJson} from '@agoric/cosmic-proto';
|
|
25
31
|
*/
|
|
26
32
|
|
|
@@ -37,13 +43,17 @@ const { Fail } = assert;
|
|
|
37
43
|
* topicKit: RecorderKit<StakingAccountNotification>;
|
|
38
44
|
* account: ChainAccount;
|
|
39
45
|
* chainAddress: ChainAddress;
|
|
46
|
+
* icqConnection: ICQConnection;
|
|
47
|
+
* bondDenom: string;
|
|
40
48
|
* }} State
|
|
41
49
|
*/
|
|
42
50
|
|
|
43
|
-
const
|
|
51
|
+
export const ChainAccountHolderI = M.interface('ChainAccountHolder', {
|
|
44
52
|
getPublicTopics: M.call().returns(TopicsRecordShape),
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
getAddress: M.call().returns(ChainAddressShape),
|
|
54
|
+
getBalance: M.callWhen().optional(M.string()).returns(CoinShape),
|
|
55
|
+
delegate: M.callWhen(ChainAddressShape, AmountShape).returns(M.record()),
|
|
56
|
+
withdrawReward: M.callWhen(ChainAddressShape).returns(M.arrayOf(CoinShape)),
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
/** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */
|
|
@@ -89,10 +99,10 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
89
99
|
'Staking Account Holder',
|
|
90
100
|
{
|
|
91
101
|
helper: UnguardedHelperI,
|
|
92
|
-
holder:
|
|
102
|
+
holder: ChainAccountHolderI,
|
|
93
103
|
invitationMakers: M.interface('invitationMakers', {
|
|
94
|
-
Delegate: M.call(
|
|
95
|
-
WithdrawReward: M.call(
|
|
104
|
+
Delegate: M.call(ChainAddressShape, AmountShape).returns(M.promise()),
|
|
105
|
+
WithdrawReward: M.call(ChainAddressShape).returns(M.promise()),
|
|
96
106
|
CloseAccount: M.call().returns(M.promise()),
|
|
97
107
|
TransferAccount: M.call().returns(M.promise()),
|
|
98
108
|
}),
|
|
@@ -101,13 +111,15 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
101
111
|
* @param {ChainAccount} account
|
|
102
112
|
* @param {StorageNode} storageNode
|
|
103
113
|
* @param {ChainAddress} chainAddress
|
|
114
|
+
* @param {ICQConnection} icqConnection
|
|
115
|
+
* @param {string} bondDenom e.g. 'uatom'
|
|
104
116
|
* @returns {State}
|
|
105
117
|
*/
|
|
106
|
-
(account, storageNode, chainAddress) => {
|
|
118
|
+
(account, storageNode, chainAddress, icqConnection, bondDenom) => {
|
|
107
119
|
// must be the fully synchronous maker because the kit is held in durable state
|
|
108
120
|
const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]);
|
|
109
121
|
|
|
110
|
-
return { account, chainAddress, topicKit };
|
|
122
|
+
return { account, chainAddress, topicKit, icqConnection, bondDenom };
|
|
111
123
|
},
|
|
112
124
|
{
|
|
113
125
|
helper: {
|
|
@@ -126,24 +138,24 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
126
138
|
invitationMakers: {
|
|
127
139
|
/**
|
|
128
140
|
*
|
|
129
|
-
* @param {
|
|
141
|
+
* @param {CosmosValidatorAddress} validator
|
|
130
142
|
* @param {Amount<'nat'>} amount
|
|
131
143
|
*/
|
|
132
|
-
Delegate(
|
|
133
|
-
trace('Delegate',
|
|
144
|
+
Delegate(validator, amount) {
|
|
145
|
+
trace('Delegate', validator, amount);
|
|
134
146
|
|
|
135
147
|
return zcf.makeInvitation(async seat => {
|
|
136
148
|
seat.exit();
|
|
137
|
-
return this.facets.holder.delegate(
|
|
149
|
+
return this.facets.holder.delegate(validator, amount);
|
|
138
150
|
}, 'Delegate');
|
|
139
151
|
},
|
|
140
|
-
/** @param {
|
|
141
|
-
WithdrawReward(
|
|
142
|
-
trace('WithdrawReward',
|
|
152
|
+
/** @param {CosmosValidatorAddress} validator */
|
|
153
|
+
WithdrawReward(validator) {
|
|
154
|
+
trace('WithdrawReward', validator);
|
|
143
155
|
|
|
144
156
|
return zcf.makeInvitation(async seat => {
|
|
145
157
|
seat.exit();
|
|
146
|
-
return this.facets.holder.withdrawReward(
|
|
158
|
+
return this.facets.holder.withdrawReward(validator);
|
|
147
159
|
}, 'WithdrawReward');
|
|
148
160
|
},
|
|
149
161
|
CloseAccount() {
|
|
@@ -170,20 +182,23 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
170
182
|
},
|
|
171
183
|
// TODO move this beneath the Orchestration abstraction,
|
|
172
184
|
// to the OrchestrationAccount provided by makeAccount()
|
|
185
|
+
/** @returns {ChainAddress} */
|
|
186
|
+
getAddress() {
|
|
187
|
+
return this.state.chainAddress;
|
|
188
|
+
},
|
|
173
189
|
/**
|
|
174
190
|
* _Assumes users has already sent funds to their ICA, until #9193
|
|
175
|
-
* @param {
|
|
191
|
+
* @param {CosmosValidatorAddress} validator
|
|
176
192
|
* @param {Amount<'nat'>} ertpAmount
|
|
177
193
|
*/
|
|
178
|
-
async delegate(
|
|
179
|
-
trace('delegate',
|
|
194
|
+
async delegate(validator, ertpAmount) {
|
|
195
|
+
trace('delegate', validator, ertpAmount);
|
|
180
196
|
|
|
181
|
-
// FIXME
|
|
182
|
-
// FIXME brand handling and amount scaling
|
|
197
|
+
// FIXME brand handling and amount scaling #9211
|
|
183
198
|
trace('TODO: handle brand', ertpAmount);
|
|
184
199
|
const amount = {
|
|
185
200
|
amount: String(ertpAmount.value),
|
|
186
|
-
denom:
|
|
201
|
+
denom: this.state.bondDenom,
|
|
187
202
|
};
|
|
188
203
|
|
|
189
204
|
const account = this.facets.helper.owned();
|
|
@@ -193,7 +208,7 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
193
208
|
toAnyJSON(
|
|
194
209
|
MsgDelegate.toProtoMsg({
|
|
195
210
|
delegatorAddress,
|
|
196
|
-
validatorAddress,
|
|
211
|
+
validatorAddress: validator.address,
|
|
197
212
|
amount,
|
|
198
213
|
}),
|
|
199
214
|
),
|
|
@@ -204,15 +219,15 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
204
219
|
},
|
|
205
220
|
|
|
206
221
|
/**
|
|
207
|
-
* @param {
|
|
222
|
+
* @param {CosmosValidatorAddress} validator
|
|
208
223
|
* @returns {Promise<ChainAmount[]>}
|
|
209
224
|
*/
|
|
210
|
-
async withdrawReward(
|
|
225
|
+
async withdrawReward(validator) {
|
|
211
226
|
const { chainAddress } = this.state;
|
|
212
|
-
assert.typeof(
|
|
227
|
+
assert.typeof(validator.address, 'string');
|
|
213
228
|
const msg = MsgWithdrawDelegatorReward.toProtoMsg({
|
|
214
229
|
delegatorAddress: chainAddress.address,
|
|
215
|
-
validatorAddress,
|
|
230
|
+
validatorAddress: validator.address,
|
|
216
231
|
});
|
|
217
232
|
const account = this.facets.helper.owned();
|
|
218
233
|
const result = await E(account).executeEncodedTx([toAnyJSON(msg)]);
|
|
@@ -222,9 +237,35 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => {
|
|
|
222
237
|
);
|
|
223
238
|
return harden(coins.map(toChainAmount));
|
|
224
239
|
},
|
|
240
|
+
/**
|
|
241
|
+
* @param {ChainAmount['denom']} [denom] - defaults to bondDenom
|
|
242
|
+
* @returns {Promise<ChainAmount>}
|
|
243
|
+
*/
|
|
244
|
+
async getBalance(denom) {
|
|
245
|
+
const { chainAddress, icqConnection, bondDenom } = this.state;
|
|
246
|
+
denom ||= bondDenom;
|
|
247
|
+
assert.typeof(denom, 'string');
|
|
248
|
+
|
|
249
|
+
const [result] = await E(icqConnection).query([
|
|
250
|
+
toRequestQueryJson(
|
|
251
|
+
QueryBalanceRequest.toProtoMsg({
|
|
252
|
+
address: chainAddress.address,
|
|
253
|
+
denom,
|
|
254
|
+
}),
|
|
255
|
+
),
|
|
256
|
+
]);
|
|
257
|
+
if (!result?.key) throw Fail`Error parsing result ${result}`;
|
|
258
|
+
const { balance } = QueryBalanceResponse.decode(
|
|
259
|
+
decodeBase64(result.key),
|
|
260
|
+
);
|
|
261
|
+
if (!balance) throw Fail`Result lacked balance key: ${result}`;
|
|
262
|
+
return harden(toChainAmount(balance));
|
|
263
|
+
},
|
|
225
264
|
},
|
|
226
265
|
},
|
|
227
266
|
);
|
|
228
267
|
return makeStakingAccountKit;
|
|
229
268
|
};
|
|
269
|
+
|
|
230
270
|
/** @typedef {ReturnType<ReturnType<typeof prepareStakingAccountKit>>} StakingAccountKit */
|
|
271
|
+
/** @typedef {StakingAccountKit['holder']} StakingAccounHolder */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { V as E } from '@agoric/vat-data/vow.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @import {
|
|
6
|
-
* @import {
|
|
7
|
-
* @import {
|
|
5
|
+
* @import {PortAllocator} from '@agoric/network';
|
|
6
|
+
* @import {OrchestrationService} from '../service.js'
|
|
7
|
+
* @import {OrchestrationVat} from '../vat-orchestration.js'
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -19,8 +19,7 @@ import { V as E } from '@agoric/vat-data/vow.js';
|
|
|
19
19
|
* orchestrationVat: Producer<any>;
|
|
20
20
|
* };
|
|
21
21
|
* }} powers
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {{ orchestrationRef: VatSourceRef }} options.options
|
|
22
|
+
* @param {{ options: { orchestrationRef: VatSourceRef }}} options
|
|
24
23
|
*
|
|
25
24
|
* @typedef {{
|
|
26
25
|
* orchestration: ERef<OrchestrationVat>;
|
|
@@ -50,7 +49,7 @@ export const setupOrchestrationVat = async (
|
|
|
50
49
|
|
|
51
50
|
const portAllocator = await portAllocatorP;
|
|
52
51
|
|
|
53
|
-
const newOrchestrationKit = await E(vats.orchestration).
|
|
52
|
+
const newOrchestrationKit = await E(vats.orchestration).makeOrchestrationKit({
|
|
54
53
|
portAllocator,
|
|
55
54
|
});
|
|
56
55
|
|
|
@@ -84,8 +83,8 @@ export const getManifestForOrchestration = (_powers, { orchestrationRef }) => ({
|
|
|
84
83
|
},
|
|
85
84
|
produce: {
|
|
86
85
|
orchestration: 'orchestration',
|
|
87
|
-
orchestrationKit: '
|
|
88
|
-
orchestrationVat: '
|
|
86
|
+
orchestrationKit: 'orchestrationKit',
|
|
87
|
+
orchestrationVat: 'orchestrationVat',
|
|
89
88
|
},
|
|
90
89
|
},
|
|
91
90
|
},
|
|
@@ -27,10 +27,14 @@ export const startStakeAtom = async (
|
|
|
27
27
|
produce: { stakeAtom: produceInstance },
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
|
-
{ options: { hostConnectionId, controllerConnectionId } },
|
|
30
|
+
{ options: { hostConnectionId, controllerConnectionId, bondDenom } },
|
|
31
31
|
) => {
|
|
32
32
|
const VSTORAGE_PATH = 'stakeAtom';
|
|
33
|
-
trace('startStakeAtom', {
|
|
33
|
+
trace('startStakeAtom', {
|
|
34
|
+
hostConnectionId,
|
|
35
|
+
controllerConnectionId,
|
|
36
|
+
bondDenom,
|
|
37
|
+
});
|
|
34
38
|
await null;
|
|
35
39
|
|
|
36
40
|
const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH);
|
|
@@ -46,6 +50,7 @@ export const startStakeAtom = async (
|
|
|
46
50
|
terms: {
|
|
47
51
|
hostConnectionId,
|
|
48
52
|
controllerConnectionId,
|
|
53
|
+
bondDenom,
|
|
49
54
|
},
|
|
50
55
|
privateArgs: {
|
|
51
56
|
orchestration: await orchestration,
|
package/src/service.js
CHANGED
|
@@ -6,14 +6,19 @@ import '@agoric/network/exported.js';
|
|
|
6
6
|
|
|
7
7
|
import { V as E } from '@agoric/vat-data/vow.js';
|
|
8
8
|
import { M } from '@endo/patterns';
|
|
9
|
+
import { Shape as NetworkShape } from '@agoric/network';
|
|
9
10
|
import { prepareChainAccountKit } from './exos/chainAccountKit.js';
|
|
10
|
-
import {
|
|
11
|
+
import { prepareICQConnectionKit } from './exos/icqConnectionKit.js';
|
|
12
|
+
import {
|
|
13
|
+
makeICAChannelAddress,
|
|
14
|
+
makeICQChannelAddress,
|
|
15
|
+
} from './utils/address.js';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
|
-
* @import { PortAllocator} from '@agoric/network';
|
|
14
|
-
* @import { IBCConnectionID } from '@agoric/vats';
|
|
15
18
|
* @import { Zone } from '@agoric/base-zone';
|
|
16
|
-
* @import {
|
|
19
|
+
* @import { Port, PortAllocator } from '@agoric/network';
|
|
20
|
+
* @import { IBCConnectionID } from '@agoric/vats';
|
|
21
|
+
* @import { ICQConnection, ChainAccount, ICQConnectionKit } from './types.js';
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
24
|
const { Fail, bare } = assert;
|
|
@@ -34,6 +39,10 @@ const { Fail, bare } = assert;
|
|
|
34
39
|
* >} PowerStore
|
|
35
40
|
*/
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {MapStore<IBCConnectionID,ICQConnectionKit>} ICQConnectionStore
|
|
44
|
+
*/
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* @template {keyof OrchestrationPowers} K
|
|
39
48
|
* @param {PowerStore} powers
|
|
@@ -48,18 +57,33 @@ export const OrchestrationI = M.interface('Orchestration', {
|
|
|
48
57
|
makeAccount: M.callWhen(M.string(), M.string()).returns(
|
|
49
58
|
M.remotable('ChainAccount'),
|
|
50
59
|
),
|
|
60
|
+
provideICQConnection: M.callWhen(M.string()).returns(
|
|
61
|
+
M.remotable('Connection'),
|
|
62
|
+
),
|
|
51
63
|
});
|
|
52
64
|
|
|
65
|
+
/** @typedef {{ powers: PowerStore; icqConnections: ICQConnectionStore } } OrchestrationState */
|
|
66
|
+
|
|
53
67
|
/**
|
|
54
68
|
* @param {Zone} zone
|
|
55
69
|
* @param {ReturnType<typeof prepareChainAccountKit>} makeChainAccountKit
|
|
70
|
+
* @param {ReturnType<typeof prepareICQConnectionKit>} makeICQConnectionKit
|
|
56
71
|
*/
|
|
57
|
-
const
|
|
72
|
+
const prepareOrchestrationKit = (
|
|
73
|
+
zone,
|
|
74
|
+
makeChainAccountKit,
|
|
75
|
+
makeICQConnectionKit,
|
|
76
|
+
) =>
|
|
58
77
|
zone.exoClassKit(
|
|
59
78
|
'Orchestration',
|
|
60
79
|
{
|
|
61
80
|
self: M.interface('OrchestrationSelf', {
|
|
62
|
-
|
|
81
|
+
allocateICAControllerPort: M.callWhen().returns(
|
|
82
|
+
NetworkShape.Vow$(NetworkShape.Port),
|
|
83
|
+
),
|
|
84
|
+
allocateICQControllerPort: M.callWhen().returns(
|
|
85
|
+
NetworkShape.Vow$(NetworkShape.Port),
|
|
86
|
+
),
|
|
63
87
|
}),
|
|
64
88
|
public: OrchestrationI,
|
|
65
89
|
},
|
|
@@ -72,11 +96,16 @@ const prepareOrchestration = (zone, makeChainAccountKit) =>
|
|
|
72
96
|
powers.init(/** @type {keyof OrchestrationPowers} */ (name), power);
|
|
73
97
|
}
|
|
74
98
|
}
|
|
75
|
-
|
|
99
|
+
const icqConnections = zone.detached().mapStore('ICQConnections');
|
|
100
|
+
return /** @type {OrchestrationState} */ ({ powers, icqConnections });
|
|
76
101
|
},
|
|
77
102
|
{
|
|
78
103
|
self: {
|
|
79
|
-
async
|
|
104
|
+
async allocateICAControllerPort() {
|
|
105
|
+
const portAllocator = getPower(this.state.powers, 'portAllocator');
|
|
106
|
+
return E(portAllocator).allocateICAControllerPort();
|
|
107
|
+
},
|
|
108
|
+
async allocateICQControllerPort() {
|
|
80
109
|
const portAllocator = getPower(this.state.powers, 'portAllocator');
|
|
81
110
|
return E(portAllocator).allocateICAControllerPort();
|
|
82
111
|
},
|
|
@@ -90,9 +119,9 @@ const prepareOrchestration = (zone, makeChainAccountKit) =>
|
|
|
90
119
|
* @returns {Promise<ChainAccount>}
|
|
91
120
|
*/
|
|
92
121
|
async makeAccount(hostConnectionId, controllerConnectionId) {
|
|
93
|
-
const port = await this.facets.self.
|
|
122
|
+
const port = await this.facets.self.allocateICAControllerPort();
|
|
94
123
|
|
|
95
|
-
const remoteConnAddr =
|
|
124
|
+
const remoteConnAddr = makeICAChannelAddress(
|
|
96
125
|
hostConnectionId,
|
|
97
126
|
controllerConnectionId,
|
|
98
127
|
);
|
|
@@ -104,9 +133,36 @@ const prepareOrchestration = (zone, makeChainAccountKit) =>
|
|
|
104
133
|
chainAccountKit.connectionHandler,
|
|
105
134
|
);
|
|
106
135
|
// XXX if we fail, should we close the port (if it was created in this flow)?
|
|
107
|
-
|
|
108
136
|
return chainAccountKit.account;
|
|
109
137
|
},
|
|
138
|
+
/**
|
|
139
|
+
* @param {IBCConnectionID} controllerConnectionId
|
|
140
|
+
* @returns {Promise<ICQConnection>}
|
|
141
|
+
*/
|
|
142
|
+
async provideICQConnection(controllerConnectionId) {
|
|
143
|
+
if (this.state.icqConnections.has(controllerConnectionId)) {
|
|
144
|
+
return this.state.icqConnections.get(controllerConnectionId)
|
|
145
|
+
.connection;
|
|
146
|
+
}
|
|
147
|
+
// allocate a new Port for every Connection
|
|
148
|
+
// TODO #9317 optimize ICQ port allocation
|
|
149
|
+
const port = await this.facets.self.allocateICQControllerPort();
|
|
150
|
+
const remoteConnAddr = makeICQChannelAddress(controllerConnectionId);
|
|
151
|
+
const icqConnectionKit = makeICQConnectionKit(port);
|
|
152
|
+
|
|
153
|
+
// await so we do not return/save a ICQConnection before it successfully instantiates
|
|
154
|
+
await E(port).connect(
|
|
155
|
+
remoteConnAddr,
|
|
156
|
+
icqConnectionKit.connectionHandler,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
this.state.icqConnections.init(
|
|
160
|
+
controllerConnectionId,
|
|
161
|
+
icqConnectionKit,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return icqConnectionKit.connection;
|
|
165
|
+
},
|
|
110
166
|
},
|
|
111
167
|
},
|
|
112
168
|
);
|
|
@@ -114,12 +170,17 @@ const prepareOrchestration = (zone, makeChainAccountKit) =>
|
|
|
114
170
|
/** @param {Zone} zone */
|
|
115
171
|
export const prepareOrchestrationTools = zone => {
|
|
116
172
|
const makeChainAccountKit = prepareChainAccountKit(zone);
|
|
117
|
-
const
|
|
173
|
+
const makeICQConnectionKit = prepareICQConnectionKit(zone);
|
|
174
|
+
const makeOrchestrationKit = prepareOrchestrationKit(
|
|
175
|
+
zone,
|
|
176
|
+
makeChainAccountKit,
|
|
177
|
+
makeICQConnectionKit,
|
|
178
|
+
);
|
|
118
179
|
|
|
119
|
-
return harden({
|
|
180
|
+
return harden({ makeOrchestrationKit });
|
|
120
181
|
};
|
|
121
182
|
harden(prepareOrchestrationTools);
|
|
122
183
|
|
|
123
184
|
/** @typedef {ReturnType<typeof prepareOrchestrationTools>} OrchestrationTools */
|
|
124
|
-
/** @typedef {ReturnType<OrchestrationTools['
|
|
185
|
+
/** @typedef {ReturnType<OrchestrationTools['makeOrchestrationKit']>} OrchestrationKit */
|
|
125
186
|
/** @typedef {OrchestrationKit['public']} OrchestrationService */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { M } from '@endo/patterns';
|
|
2
|
+
|
|
3
|
+
export const ConnectionHandlerI = M.interface('ConnectionHandler', {
|
|
4
|
+
onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()),
|
|
5
|
+
onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()),
|
|
6
|
+
onReceive: M.callWhen(M.any(), M.string()).returns(M.any()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const ChainAddressShape = {
|
|
10
|
+
address: M.string(),
|
|
11
|
+
chainId: M.string(),
|
|
12
|
+
addressEncoding: M.string(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const Proto3Shape = {
|
|
16
|
+
typeUrl: M.string(),
|
|
17
|
+
value: M.string(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const CoinShape = { value: M.bigint(), denom: M.string() };
|
package/src/types.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ import type {
|
|
|
19
19
|
} from '@agoric/vats/tools/ibc-utils.js';
|
|
20
20
|
import type { Port } from '@agoric/network';
|
|
21
21
|
import { MsgTransferResponse } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js';
|
|
22
|
+
import type { IBCConnectionID } from '@agoric/vats';
|
|
23
|
+
import type { ICQConnection } from './exos/icqConnectionKit.js';
|
|
24
|
+
|
|
25
|
+
export type * from './service.js';
|
|
26
|
+
export type * from './vat-orchestration.js';
|
|
27
|
+
export type * from './exos/chainAccountKit.js';
|
|
28
|
+
export type * from './exos/icqConnectionKit.js';
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* static declaration of known chain types will allow type support for
|
|
@@ -114,6 +121,10 @@ export interface Orchestrator {
|
|
|
114
121
|
getChain: <C extends keyof KnownChains>(chainName: C) => Promise<Chain<C>>;
|
|
115
122
|
|
|
116
123
|
makeLocalAccount: () => Promise<LocalChainAccount>;
|
|
124
|
+
/** Send queries to ibc chains unknown to KnownChains */
|
|
125
|
+
provideICQConnection: (
|
|
126
|
+
controllerConnectionId: IBCConnectionID,
|
|
127
|
+
) => ICQConnection;
|
|
117
128
|
|
|
118
129
|
/**
|
|
119
130
|
* For a denom, return information about a denom including the equivalent
|
package/src/utils/address.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Fail } from '@agoric/assert';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @import { IBCConnectionID } from '@agoric/vats';
|
|
6
|
-
* @import { ChainAddress
|
|
6
|
+
* @import { ChainAddress } from '../types.js';
|
|
7
|
+
* @import { RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js';
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -14,8 +15,9 @@ import { Fail } from '@agoric/assert';
|
|
|
14
15
|
* @param {'ordered' | 'unordered'} [opts.ordering] - channel ordering. currently only `ordered` is supported for ics27-1
|
|
15
16
|
* @param {string} [opts.txType] - default is `sdk_multi_msg`
|
|
16
17
|
* @param {string} [opts.version] - default is `ics27-1`
|
|
18
|
+
* @returns {RemoteIbcAddress}
|
|
17
19
|
*/
|
|
18
|
-
export const
|
|
20
|
+
export const makeICAChannelAddress = (
|
|
19
21
|
hostConnectionId,
|
|
20
22
|
controllerConnectionId,
|
|
21
23
|
{
|
|
@@ -37,15 +39,30 @@ export const makeICAConnectionAddress = (
|
|
|
37
39
|
});
|
|
38
40
|
return `/ibc-hop/${controllerConnectionId}/ibc-port/icahost/${ordering}/${connString}`;
|
|
39
41
|
};
|
|
42
|
+
harden(makeICAChannelAddress);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {IBCConnectionID} controllerConnectionId
|
|
46
|
+
* @param {{ version?: string }} [opts]
|
|
47
|
+
* @returns {RemoteIbcAddress}
|
|
48
|
+
*/
|
|
49
|
+
export const makeICQChannelAddress = (
|
|
50
|
+
controllerConnectionId,
|
|
51
|
+
{ version = 'icq-1' } = {},
|
|
52
|
+
) => {
|
|
53
|
+
controllerConnectionId || Fail`controllerConnectionId is required`;
|
|
54
|
+
return `/ibc-hop/${controllerConnectionId}/ibc-port/icqhost/unordered/${version}`;
|
|
55
|
+
};
|
|
56
|
+
harden(makeICQChannelAddress);
|
|
40
57
|
|
|
41
58
|
/**
|
|
42
59
|
* Parse a chain address from a remote address string.
|
|
43
60
|
* Assumes the address string is in a JSON format and contains an "address" field.
|
|
44
61
|
* This function is designed to be safe against malformed inputs and unexpected data types, and will return `undefined` in those cases.
|
|
45
|
-
* @param {
|
|
46
|
-
* @returns {
|
|
62
|
+
* @param {RemoteIbcAddress} remoteAddressString - remote address string, including version
|
|
63
|
+
* @returns {ChainAddress['address'] | undefined} returns undefined on error
|
|
47
64
|
*/
|
|
48
|
-
export const
|
|
65
|
+
export const findAddressField = remoteAddressString => {
|
|
49
66
|
try {
|
|
50
67
|
// Extract JSON version string assuming it's always surrounded by {}
|
|
51
68
|
const jsonStr = remoteAddressString?.match(/{.*?}/)?.[0];
|
|
@@ -55,4 +72,4 @@ export const parseAddress = remoteAddressString => {
|
|
|
55
72
|
return undefined;
|
|
56
73
|
}
|
|
57
74
|
};
|
|
58
|
-
harden(
|
|
75
|
+
harden(findAddressField);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
|
|
3
|
+
import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js';
|
|
4
|
+
import { RequestQuery } from '@agoric/cosmic-proto/tendermint/abci/types.js';
|
|
5
|
+
import { atob, decodeBase64, encodeBase64 } from '@endo/base64';
|
|
6
|
+
import {
|
|
7
|
+
CosmosQuery,
|
|
8
|
+
CosmosResponse,
|
|
9
|
+
} from '@agoric/cosmic-proto/icq/v1/packet.js';
|
|
10
|
+
import { Type as PacketType } from '@agoric/cosmic-proto/ibc/applications/interchain_accounts/v1/packet.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @import {AnyJson, RequestQueryJson, Base64Any} from '@agoric/cosmic-proto';
|
|
14
|
+
* @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js';
|
|
15
|
+
* @import {InterchainAccountPacketData} from '@agoric/cosmic-proto/ibc/applications/interchain_accounts/v1/packet.js';
|
|
16
|
+
* @import {InterchainQueryPacketData} from '@agoric/cosmic-proto/icq/v1/packet.js';
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Makes an IBC transaction packet from an array of messages. Expects the `value` of each message
|
|
21
|
+
* to be base64 encoded bytes.
|
|
22
|
+
* Skips checks for malformed messages in favor of interface guards.
|
|
23
|
+
* @param {AnyJson[]} msgs
|
|
24
|
+
* @param {Partial<Omit<TxBody, 'messages'>>} [opts]
|
|
25
|
+
* @returns {string} stringified InterchainAccountPacketData
|
|
26
|
+
* @throws {Error} if malformed messages are provided
|
|
27
|
+
*/
|
|
28
|
+
export function makeTxPacket(msgs, opts) {
|
|
29
|
+
const messages = msgs.map(Any.fromJSON);
|
|
30
|
+
const bytes = TxBody.encode(
|
|
31
|
+
TxBody.fromPartial({
|
|
32
|
+
messages,
|
|
33
|
+
...opts,
|
|
34
|
+
}),
|
|
35
|
+
).finish();
|
|
36
|
+
|
|
37
|
+
return JSON.stringify(
|
|
38
|
+
/** @type {Base64Any<InterchainAccountPacketData>} */ ({
|
|
39
|
+
type: PacketType.TYPE_EXECUTE_TX,
|
|
40
|
+
data: encodeBase64(bytes),
|
|
41
|
+
memo: '',
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
harden(makeTxPacket);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Makes an IBC query packet from an array of query messages. Expects the `data` of each message
|
|
49
|
+
* to be base64 encoded bytes.
|
|
50
|
+
* Skips checks for malformed messages in favor of interface guards.
|
|
51
|
+
* @param {RequestQueryJson[]} msgs
|
|
52
|
+
* @returns {string} stringified InterchainQueryPacketData
|
|
53
|
+
* @throws {Error} if malformed messages are provided
|
|
54
|
+
*/
|
|
55
|
+
export function makeQueryPacket(msgs) {
|
|
56
|
+
const bytes = CosmosQuery.encode(
|
|
57
|
+
CosmosQuery.fromPartial({
|
|
58
|
+
requests: msgs.map(RequestQuery.fromJSON),
|
|
59
|
+
}),
|
|
60
|
+
).finish();
|
|
61
|
+
|
|
62
|
+
return JSON.stringify(
|
|
63
|
+
/** @type {Base64Any<InterchainQueryPacketData>} */ ({
|
|
64
|
+
data: encodeBase64(bytes),
|
|
65
|
+
memo: '',
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
harden(makeQueryPacket);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Looks for a result or error key in the response string, and returns
|
|
73
|
+
* a Base64Bytes string. This string can be decoded using the corresponding
|
|
74
|
+
* Msg*Response object.
|
|
75
|
+
* Error strings seem to be plain text and do not need decoding.
|
|
76
|
+
* @param {string} response
|
|
77
|
+
* @returns {string} - base64 encoded bytes string
|
|
78
|
+
* @throws {Error} if error key is detected in response string, or result key is not found
|
|
79
|
+
*/
|
|
80
|
+
export function parseTxPacket(response) {
|
|
81
|
+
const { result, error } = JSON.parse(response);
|
|
82
|
+
if (result) return result;
|
|
83
|
+
else if (error) throw Error(error);
|
|
84
|
+
else throw Error(response);
|
|
85
|
+
}
|
|
86
|
+
harden(parseTxPacket);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Looks for a result or error key in the response string. If a result is found,
|
|
90
|
+
* `responses` is decoded via `CosmosResponse`. The `key` and `value` fields on the
|
|
91
|
+
* resulting entries are base64 encoded for inter-vat communication. These can be
|
|
92
|
+
* decoded using the corresponding Query*Response objects.
|
|
93
|
+
* Error strings seem to be plain text and do not need decoding.
|
|
94
|
+
* @param {string} response
|
|
95
|
+
* @returns {Base64Any<ResponseQuery>[]}
|
|
96
|
+
* @throws {Error} if error key is detected in response string, or result key is not found
|
|
97
|
+
*/
|
|
98
|
+
export function parseQueryPacket(response) {
|
|
99
|
+
const result = parseTxPacket(response);
|
|
100
|
+
const { data } = JSON.parse(atob(result));
|
|
101
|
+
const { responses = [] } = CosmosResponse.decode(decodeBase64(data));
|
|
102
|
+
return harden(
|
|
103
|
+
responses.map(resp => ({
|
|
104
|
+
...resp,
|
|
105
|
+
key: encodeBase64(resp.key),
|
|
106
|
+
value: encodeBase64(resp.value),
|
|
107
|
+
})),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
harden(parseQueryPacket);
|
package/src/vat-orchestration.js
CHANGED
|
@@ -7,14 +7,14 @@ import { prepareOrchestrationTools } from './service.js';
|
|
|
7
7
|
|
|
8
8
|
export const buildRootObject = (_vatPowers, _args, baggage) => {
|
|
9
9
|
const zone = makeDurableZone(baggage);
|
|
10
|
-
const {
|
|
10
|
+
const { makeOrchestrationKit } = prepareOrchestrationTools(
|
|
11
11
|
zone.subZone('orchestration'),
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
return Far('OrchestrationVat', {
|
|
15
15
|
/** @param {Partial<OrchestrationPowers>} [initialPowers] */
|
|
16
|
-
|
|
17
|
-
return
|
|
16
|
+
makeOrchestrationKit(initialPowers = {}) {
|
|
17
|
+
return makeOrchestrationKit(initialPowers);
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
20
|
};
|
package/src/utils/tx.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
|
|
3
|
-
import { encodeBase64 } from '@endo/base64';
|
|
4
|
-
import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Makes an IBC packet from an array of messages. Expects the `value` of each message
|
|
8
|
-
* to be base64 encoded bytes.
|
|
9
|
-
* Skips checks for malformed messages in favor of interface guards.
|
|
10
|
-
* @param {import('@agoric/cosmic-proto').AnyJson[]} msgs
|
|
11
|
-
* // XXX intellisense does not seem to infer well here
|
|
12
|
-
* @param {Omit<TxBody, 'messages'>} [opts]
|
|
13
|
-
* @returns {string} - IBC TX packet
|
|
14
|
-
* @throws {Error} if malformed messages are provided
|
|
15
|
-
*/
|
|
16
|
-
export function makeTxPacket(msgs, opts) {
|
|
17
|
-
const messages = msgs.map(Any.fromJSON);
|
|
18
|
-
const bytes = TxBody.encode(
|
|
19
|
-
TxBody.fromPartial({
|
|
20
|
-
messages,
|
|
21
|
-
...opts,
|
|
22
|
-
}),
|
|
23
|
-
).finish();
|
|
24
|
-
|
|
25
|
-
return JSON.stringify({
|
|
26
|
-
type: 1,
|
|
27
|
-
data: encodeBase64(bytes),
|
|
28
|
-
memo: '',
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
harden(makeTxPacket);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Looks for a result or error key in the response string, and returns
|
|
35
|
-
* a Base64Bytes string. This string can be decoded using the corresponding
|
|
36
|
-
* Msg*Response object.
|
|
37
|
-
* Error strings seem to be plain text and do not need decoding.
|
|
38
|
-
* @param {string} response
|
|
39
|
-
* @returns {string} - base64 encoded bytes string
|
|
40
|
-
* @throws {Error} if error key is detected in response string, or result key is not found
|
|
41
|
-
*/
|
|
42
|
-
export function parsePacketAck(response) {
|
|
43
|
-
const { result, error } = JSON.parse(response);
|
|
44
|
-
if (result) return result;
|
|
45
|
-
else if (error) throw Error(error);
|
|
46
|
-
else throw Error(response);
|
|
47
|
-
}
|
|
48
|
-
harden(parsePacketAck);
|