@agoric/orchestration 0.1.1-dev-81a66f2.0 → 0.1.1-dev-e468caa.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 -0
- package/package.json +14 -12
- package/src/contracts/localchainAccountHolder.js +4 -4
- package/src/contracts/stakeAtom.contract.js +57 -11
- package/src/contracts/stakingAccountHolder.js +192 -0
- package/src/orchestration.js +40 -21
- package/src/proposals/start-stakeAtom.js +24 -3
- package/src/types.d.ts +1 -2
- package/src/utils/address.js +3 -3
- package/src/utils/tx.js +65 -0
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-e468caa.0+e468caa",
|
|
4
4
|
"description": "Chain abstraction for Agoric's orchestration clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,16 +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/
|
|
34
|
-
"@agoric/
|
|
35
|
-
"@agoric/
|
|
36
|
-
"@agoric/
|
|
37
|
-
"@agoric/
|
|
38
|
-
"@agoric/
|
|
39
|
-
"@agoric/
|
|
40
|
-
"@agoric/
|
|
41
|
-
"@agoric/
|
|
32
|
+
"@agoric/assert": "0.6.1-dev-e468caa.0+e468caa",
|
|
33
|
+
"@agoric/cosmic-proto": "0.4.1-dev-e468caa.0+e468caa",
|
|
34
|
+
"@agoric/ertp": "0.16.3-dev-e468caa.0+e468caa",
|
|
35
|
+
"@agoric/internal": "0.3.3-dev-e468caa.0+e468caa",
|
|
36
|
+
"@agoric/network": "0.1.1-dev-e468caa.0+e468caa",
|
|
37
|
+
"@agoric/notifier": "0.6.3-dev-e468caa.0+e468caa",
|
|
38
|
+
"@agoric/store": "0.9.3-dev-e468caa.0+e468caa",
|
|
39
|
+
"@agoric/vat-data": "0.5.3-dev-e468caa.0+e468caa",
|
|
40
|
+
"@agoric/vats": "0.15.2-dev-e468caa.0+e468caa",
|
|
41
|
+
"@agoric/zoe": "0.26.3-dev-e468caa.0+e468caa",
|
|
42
|
+
"@agoric/zone": "0.2.3-dev-e468caa.0+e468caa",
|
|
43
|
+
"@endo/base64": "^1.0.4",
|
|
42
44
|
"@endo/far": "^1.1.1",
|
|
43
45
|
"@endo/marshal": "^1.4.1",
|
|
44
46
|
"@endo/patterns": "^1.3.1"
|
|
@@ -80,5 +82,5 @@
|
|
|
80
82
|
"typeCoverage": {
|
|
81
83
|
"atLeast": 96.96
|
|
82
84
|
},
|
|
83
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "e468caafe7b1bb0a4b105d2acadb30874c5c30d5"
|
|
84
86
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/** @file Use-object for the owner of a localchain account */
|
|
3
|
+
import { typedJson } from '@agoric/cosmic-proto/vatsafe';
|
|
3
4
|
import { AmountShape } from '@agoric/ertp';
|
|
4
5
|
import { makeTracer } from '@agoric/internal';
|
|
5
6
|
import { UnguardedHelperI } from '@agoric/internal/src/typeGuards.js';
|
|
@@ -114,7 +115,7 @@ export const prepareAccountHolder = (baggage, makeRecorderKit, zcf) => {
|
|
|
114
115
|
// FIXME get values from proposal or args
|
|
115
116
|
// FIXME brand handling and amount scaling
|
|
116
117
|
const amount = {
|
|
117
|
-
amount: ertpAmount.value,
|
|
118
|
+
amount: String(ertpAmount.value),
|
|
118
119
|
denom: 'ubld',
|
|
119
120
|
};
|
|
120
121
|
|
|
@@ -126,12 +127,11 @@ export const prepareAccountHolder = (baggage, makeRecorderKit, zcf) => {
|
|
|
126
127
|
const delegatorAddress = await E(lca).getAddress();
|
|
127
128
|
trace('delegatorAddress', delegatorAddress);
|
|
128
129
|
const result = await E(lca).executeTx([
|
|
129
|
-
{
|
|
130
|
-
'@type': '/cosmos.staking.v1beta1.MsgDelegate',
|
|
130
|
+
typedJson('/cosmos.staking.v1beta1.MsgDelegate', {
|
|
131
131
|
amount,
|
|
132
132
|
validatorAddress,
|
|
133
133
|
delegatorAddress,
|
|
134
|
-
},
|
|
134
|
+
}),
|
|
135
135
|
]);
|
|
136
136
|
trace('got result', result);
|
|
137
137
|
return result;
|
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @file Example contract that uses orchestration
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
import { makeTracer } from '@agoric/internal';
|
|
6
6
|
import { makeDurableZone } from '@agoric/zone/durable.js';
|
|
7
7
|
import { V as E } from '@agoric/vat-data/vow.js';
|
|
8
8
|
import { M } from '@endo/patterns';
|
|
9
|
+
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport';
|
|
10
|
+
import { prepareStakingAccountHolder } from './stakingAccountHolder.js';
|
|
9
11
|
|
|
12
|
+
const trace = makeTracer('StakeAtom');
|
|
10
13
|
/**
|
|
11
|
-
* @import
|
|
12
|
-
* @import
|
|
14
|
+
* @import { Orchestration } from '../types.js';
|
|
15
|
+
* @import { Baggage } from '@agoric/vat-data';
|
|
16
|
+
* @import { IBCConnectionID } from '@agoric/vats';
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* @typedef {{
|
|
17
|
-
* hostConnectionId:
|
|
18
|
-
* controllerConnectionId:
|
|
21
|
+
* hostConnectionId: IBCConnectionID;
|
|
22
|
+
* controllerConnectionId: IBCConnectionID;
|
|
19
23
|
* }} StakeAtomTerms
|
|
20
24
|
*/
|
|
21
25
|
|
|
@@ -23,26 +27,66 @@ import { M } from '@endo/patterns';
|
|
|
23
27
|
*
|
|
24
28
|
* @param {ZCF<StakeAtomTerms>} zcf
|
|
25
29
|
* @param {{
|
|
26
|
-
*
|
|
30
|
+
* orchestration: Orchestration;
|
|
31
|
+
* storageNode: StorageNode;
|
|
32
|
+
* marshaller: Marshaller;
|
|
27
33
|
* }} privateArgs
|
|
28
|
-
* @param {
|
|
34
|
+
* @param {Baggage} baggage
|
|
29
35
|
*/
|
|
30
36
|
export const start = async (zcf, privateArgs, baggage) => {
|
|
31
37
|
const { hostConnectionId, controllerConnectionId } = zcf.getTerms();
|
|
32
|
-
const { orchestration } = privateArgs;
|
|
38
|
+
const { orchestration, marshaller, storageNode } = privateArgs;
|
|
33
39
|
|
|
34
40
|
const zone = makeDurableZone(baggage);
|
|
35
41
|
|
|
42
|
+
const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller);
|
|
43
|
+
|
|
44
|
+
const makeStakingAccountHolder = prepareStakingAccountHolder(
|
|
45
|
+
baggage,
|
|
46
|
+
makeRecorderKit,
|
|
47
|
+
zcf,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
async function createAccount() {
|
|
51
|
+
const account = await E(orchestration).createAccount(
|
|
52
|
+
hostConnectionId,
|
|
53
|
+
controllerConnectionId,
|
|
54
|
+
);
|
|
55
|
+
const accountAddress = await E(account).getAccountAddress();
|
|
56
|
+
trace('account address', accountAddress);
|
|
57
|
+
const { holder, invitationMakers } = makeStakingAccountHolder(
|
|
58
|
+
account,
|
|
59
|
+
storageNode,
|
|
60
|
+
accountAddress,
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
publicSubscribers: holder.getPublicTopics(),
|
|
64
|
+
invitationMakers,
|
|
65
|
+
account: holder,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
36
69
|
const publicFacet = zone.exo(
|
|
37
70
|
'StakeAtom',
|
|
38
71
|
M.interface('StakeAtomI', {
|
|
39
72
|
createAccount: M.callWhen().returns(M.remotable('ChainAccount')),
|
|
73
|
+
makeCreateAccountInvitation: M.call().returns(M.promise()),
|
|
40
74
|
}),
|
|
41
75
|
{
|
|
42
76
|
async createAccount() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
trace('createAccount');
|
|
78
|
+
return createAccount().then(({ account }) => account);
|
|
79
|
+
},
|
|
80
|
+
makeCreateAccountInvitation() {
|
|
81
|
+
trace('makeCreateAccountInvitation');
|
|
82
|
+
return zcf.makeInvitation(
|
|
83
|
+
async seat => {
|
|
84
|
+
seat.exit();
|
|
85
|
+
return createAccount();
|
|
86
|
+
},
|
|
87
|
+
'wantStakingAccount',
|
|
88
|
+
undefined,
|
|
89
|
+
undefined,
|
|
46
90
|
);
|
|
47
91
|
},
|
|
48
92
|
},
|
|
@@ -50,3 +94,5 @@ export const start = async (zcf, privateArgs, baggage) => {
|
|
|
50
94
|
|
|
51
95
|
return { publicFacet };
|
|
52
96
|
};
|
|
97
|
+
|
|
98
|
+
/** @typedef {typeof start} StakeAtomSF */
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @file Use-object for the owner of a staking account */
|
|
3
|
+
import {
|
|
4
|
+
MsgDelegate,
|
|
5
|
+
MsgDelegateResponse,
|
|
6
|
+
} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js';
|
|
7
|
+
import { AmountShape } from '@agoric/ertp';
|
|
8
|
+
import { makeTracer } from '@agoric/internal';
|
|
9
|
+
import { UnguardedHelperI } from '@agoric/internal/src/typeGuards.js';
|
|
10
|
+
import { M, prepareExoClassKit } from '@agoric/vat-data';
|
|
11
|
+
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js';
|
|
12
|
+
import { decodeBase64 } from '@endo/base64';
|
|
13
|
+
import { E } from '@endo/far';
|
|
14
|
+
import { txToBase64 } from '../utils/tx.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @import { ChainAccount, ChainAddress } from '../types.js';
|
|
18
|
+
* @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js';
|
|
19
|
+
* @import { Baggage } from '@agoric/swingset-liveslots';
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const trace = makeTracer('StakingAccountHolder');
|
|
23
|
+
|
|
24
|
+
const { Fail } = assert;
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} StakingAccountNotification
|
|
27
|
+
* @property {string} address
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {{
|
|
32
|
+
* topicKit: RecorderKit<StakingAccountNotification>;
|
|
33
|
+
* account: ChainAccount;
|
|
34
|
+
* chainAddress: ChainAddress;
|
|
35
|
+
* }} State
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const HolderI = M.interface('holder', {
|
|
39
|
+
getPublicTopics: M.call().returns(TopicsRecordShape),
|
|
40
|
+
makeDelegateInvitation: M.call(M.string(), AmountShape).returns(M.promise()),
|
|
41
|
+
makeCloseAccountInvitation: M.call().returns(M.promise()),
|
|
42
|
+
makeTransferAccountInvitation: M.call().returns(M.promise()),
|
|
43
|
+
delegate: M.callWhen(M.string(), AmountShape).returns(M.string()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */
|
|
47
|
+
const PUBLIC_TOPICS = {
|
|
48
|
+
account: ['Staking Account holder status', M.any()],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Baggage} baggage
|
|
53
|
+
* @param {MakeRecorderKit} makeRecorderKit
|
|
54
|
+
* @param {ZCF} zcf
|
|
55
|
+
*/
|
|
56
|
+
export const prepareStakingAccountHolder = (baggage, makeRecorderKit, zcf) => {
|
|
57
|
+
const makeAccountHolderKit = prepareExoClassKit(
|
|
58
|
+
baggage,
|
|
59
|
+
'Staking Account Holder',
|
|
60
|
+
{
|
|
61
|
+
helper: UnguardedHelperI,
|
|
62
|
+
holder: HolderI,
|
|
63
|
+
invitationMakers: M.interface('invitationMakers', {
|
|
64
|
+
Delegate: HolderI.payload.methodGuards.makeDelegateInvitation,
|
|
65
|
+
CloseAccount: HolderI.payload.methodGuards.makeCloseAccountInvitation,
|
|
66
|
+
TransferAccount:
|
|
67
|
+
HolderI.payload.methodGuards.makeTransferAccountInvitation,
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* @param {ChainAccount} account
|
|
72
|
+
* @param {StorageNode} storageNode
|
|
73
|
+
* @param {ChainAddress} chainAddress
|
|
74
|
+
* @returns {State}
|
|
75
|
+
*/
|
|
76
|
+
(account, storageNode, chainAddress) => {
|
|
77
|
+
// must be the fully synchronous maker because the kit is held in durable state
|
|
78
|
+
const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]);
|
|
79
|
+
|
|
80
|
+
return { account, chainAddress, topicKit };
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
helper: {
|
|
84
|
+
/** @throws if this holder no longer owns the account */
|
|
85
|
+
owned() {
|
|
86
|
+
const { account } = this.state;
|
|
87
|
+
if (!account) {
|
|
88
|
+
throw Fail`Using account holder after transfer`;
|
|
89
|
+
}
|
|
90
|
+
return account;
|
|
91
|
+
},
|
|
92
|
+
getUpdater() {
|
|
93
|
+
return this.state.topicKit.recorder;
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* _Assumes users has already sent funds to their ICA, until #9193
|
|
97
|
+
* @param {string} validatorAddress
|
|
98
|
+
* @param {Amount<'nat'>} ertpAmount
|
|
99
|
+
*/
|
|
100
|
+
async delegate(validatorAddress, ertpAmount) {
|
|
101
|
+
// FIXME get values from proposal or args
|
|
102
|
+
// FIXME brand handling and amount scaling
|
|
103
|
+
const amount = {
|
|
104
|
+
amount: String(ertpAmount.value),
|
|
105
|
+
denom: 'uatom',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const account = this.facets.helper.owned();
|
|
109
|
+
const delegatorAddress = this.state.chainAddress;
|
|
110
|
+
|
|
111
|
+
const result = await E(account).executeEncodedTx([
|
|
112
|
+
txToBase64(
|
|
113
|
+
MsgDelegate.toProtoMsg({
|
|
114
|
+
delegatorAddress,
|
|
115
|
+
validatorAddress,
|
|
116
|
+
amount,
|
|
117
|
+
}),
|
|
118
|
+
),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
if (!result) throw Fail`Failed to delegate.`;
|
|
122
|
+
try {
|
|
123
|
+
const decoded = MsgDelegateResponse.decode(decodeBase64(result));
|
|
124
|
+
if (JSON.stringify(decoded) === '{}') return 'Success';
|
|
125
|
+
throw Fail`Unexpected response: ${result}`;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
throw Fail`Unable to decode result: ${result}`;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
invitationMakers: {
|
|
132
|
+
Delegate(validatorAddress, amount) {
|
|
133
|
+
return this.facets.holder.makeDelegateInvitation(
|
|
134
|
+
validatorAddress,
|
|
135
|
+
amount,
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
CloseAccount() {
|
|
139
|
+
return this.facets.holder.makeCloseAccountInvitation();
|
|
140
|
+
},
|
|
141
|
+
TransferAccount() {
|
|
142
|
+
return this.facets.holder.makeTransferAccountInvitation();
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
holder: {
|
|
146
|
+
getPublicTopics() {
|
|
147
|
+
const { topicKit } = this.state;
|
|
148
|
+
return harden({
|
|
149
|
+
account: {
|
|
150
|
+
description: PUBLIC_TOPICS.account[0],
|
|
151
|
+
subscriber: topicKit.subscriber,
|
|
152
|
+
storagePath: topicKit.recorder.getStoragePath(),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
*
|
|
158
|
+
* @param {string} validatorAddress
|
|
159
|
+
* @param {Amount<'nat'>} ertpAmount
|
|
160
|
+
*/
|
|
161
|
+
async delegate(validatorAddress, ertpAmount) {
|
|
162
|
+
trace('delegate', validatorAddress, ertpAmount);
|
|
163
|
+
return this.facets.helper.delegate(validatorAddress, ertpAmount);
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
*
|
|
167
|
+
* @param {string} validatorAddress
|
|
168
|
+
* @param {Amount<'nat'>} ertpAmount
|
|
169
|
+
*/
|
|
170
|
+
makeDelegateInvitation(validatorAddress, ertpAmount) {
|
|
171
|
+
trace('makeDelegateInvitation', validatorAddress, ertpAmount);
|
|
172
|
+
|
|
173
|
+
return zcf.makeInvitation(async seat => {
|
|
174
|
+
seat.exit();
|
|
175
|
+
return this.facets.helper.delegate(validatorAddress, ertpAmount);
|
|
176
|
+
}, 'Delegate');
|
|
177
|
+
},
|
|
178
|
+
makeCloseAccountInvitation() {
|
|
179
|
+
throw Error('not yet implemented');
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Starting a transfer revokes the account holder. The associated updater
|
|
183
|
+
* will get a special notification that the account is being transferred.
|
|
184
|
+
*/
|
|
185
|
+
makeTransferAccountInvitation() {
|
|
186
|
+
throw Error('not yet implemented');
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
return makeAccountHolderKit;
|
|
192
|
+
};
|
package/src/orchestration.js
CHANGED
|
@@ -5,24 +5,27 @@ import { makeTracer } from '@agoric/internal';
|
|
|
5
5
|
import { V as E } from '@agoric/vat-data/vow.js';
|
|
6
6
|
import { M } from '@endo/patterns';
|
|
7
7
|
import { makeICAConnectionAddress, parseAddress } from './utils/address.js';
|
|
8
|
+
import { makeTxPacket, parsePacketAck } from './utils/tx.js';
|
|
8
9
|
import '@agoric/network/exported.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* @import {
|
|
12
|
+
* @import { AttenuatedNetwork } from './types.js';
|
|
13
|
+
* @import { IBCConnectionID } from '@agoric/vats';
|
|
12
14
|
* @import { Zone } from '@agoric/base-zone';
|
|
15
|
+
* @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
const { Fail, bare } = assert;
|
|
16
19
|
const trace = makeTracer('Orchestration');
|
|
17
20
|
|
|
21
|
+
/** @import {Proto3Msg} from './utils/tx.js'; */
|
|
22
|
+
|
|
18
23
|
// TODO improve me
|
|
19
24
|
/** @typedef {string} ChainAddress */
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* @typedef {object} OrchestrationPowers
|
|
23
|
-
* @property {ERef<
|
|
24
|
-
* import('@agoric/orchestration/src/types').AttenuatedNetwork
|
|
25
|
-
* >} network
|
|
28
|
+
* @property {ERef<AttenuatedNetwork>} network
|
|
26
29
|
*/
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -46,11 +49,19 @@ const getPower = (powers, name) => {
|
|
|
46
49
|
return /** @type {OrchestrationPowers[K]} */ (powers.get(name));
|
|
47
50
|
};
|
|
48
51
|
|
|
52
|
+
export const Proto3Shape = {
|
|
53
|
+
typeUrl: M.string(),
|
|
54
|
+
value: M.string(),
|
|
55
|
+
};
|
|
56
|
+
|
|
49
57
|
export const ChainAccountI = M.interface('ChainAccount', {
|
|
50
58
|
getAccountAddress: M.call().returns(M.string()),
|
|
51
59
|
getLocalAddress: M.call().returns(M.string()),
|
|
52
60
|
getRemoteAddress: M.call().returns(M.string()),
|
|
53
61
|
getPort: M.call().returns(M.remotable('Port')),
|
|
62
|
+
executeEncodedTx: M.call(M.arrayOf(Proto3Shape))
|
|
63
|
+
.optional(M.record())
|
|
64
|
+
.returns(M.promise()),
|
|
54
65
|
close: M.callWhen().returns(M.string()),
|
|
55
66
|
});
|
|
56
67
|
|
|
@@ -73,7 +84,7 @@ const prepareChainAccount = zone =>
|
|
|
73
84
|
/**
|
|
74
85
|
* @type {{
|
|
75
86
|
* port: Port;
|
|
76
|
-
* connection: Connection | undefined;
|
|
87
|
+
* connection: Remote<Connection> | undefined;
|
|
77
88
|
* localAddress: string | undefined;
|
|
78
89
|
* requestedRemoteAddress: string;
|
|
79
90
|
* remoteAddress: string | undefined;
|
|
@@ -112,24 +123,34 @@ const prepareChainAccount = zone =>
|
|
|
112
123
|
getPort() {
|
|
113
124
|
return this.state.port;
|
|
114
125
|
},
|
|
126
|
+
/**
|
|
127
|
+
* @param {Proto3Msg[]} msgs
|
|
128
|
+
* @param {Omit<TxBody, 'messages'>} [opts]
|
|
129
|
+
* @returns {Promise<string>} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object.
|
|
130
|
+
* @throws {Error} if packet fails to send or an error is returned
|
|
131
|
+
*/
|
|
132
|
+
executeEncodedTx(msgs, opts) {
|
|
133
|
+
const { connection } = this.state;
|
|
134
|
+
if (!connection) throw Fail`connection not available`;
|
|
135
|
+
return E.when(
|
|
136
|
+
E(connection).send(makeTxPacket(msgs, opts)),
|
|
137
|
+
// if parsePacketAck cannot find a `result` key, it throws
|
|
138
|
+
ack => parsePacketAck(ack),
|
|
139
|
+
);
|
|
140
|
+
},
|
|
115
141
|
async close() {
|
|
116
142
|
/// XXX what should the behavior be here? and `onClose`?
|
|
117
143
|
// - retrieve assets?
|
|
118
144
|
// - revoke the port?
|
|
119
145
|
const { connection } = this.state;
|
|
120
146
|
if (!connection) throw Fail`connection not available`;
|
|
121
|
-
await
|
|
122
|
-
try {
|
|
123
|
-
await E(connection).close();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
throw Fail`Failed to close connection: ${e}`;
|
|
126
|
-
}
|
|
147
|
+
await E(connection).close();
|
|
127
148
|
return 'Connection closed';
|
|
128
149
|
},
|
|
129
150
|
},
|
|
130
151
|
connectionHandler: {
|
|
131
152
|
/**
|
|
132
|
-
* @param {Connection} connection
|
|
153
|
+
* @param {Remote<Connection>} connection
|
|
133
154
|
* @param {string} localAddr
|
|
134
155
|
* @param {string} remoteAddr
|
|
135
156
|
*/
|
|
@@ -188,18 +209,18 @@ const prepareOrchestration = (zone, createChainAccount) =>
|
|
|
188
209
|
self: {
|
|
189
210
|
async bindPort() {
|
|
190
211
|
const network = getPower(this.state.powers, 'network');
|
|
191
|
-
const port = await E(network)
|
|
192
|
-
|
|
193
|
-
|
|
212
|
+
const port = await E(network).bind(
|
|
213
|
+
`/ibc-port/icacontroller-${this.state.icaControllerNonce}`,
|
|
214
|
+
);
|
|
194
215
|
this.state.icaControllerNonce += 1;
|
|
195
216
|
return port;
|
|
196
217
|
},
|
|
197
218
|
},
|
|
198
219
|
public: {
|
|
199
220
|
/**
|
|
200
|
-
* @param {
|
|
221
|
+
* @param {IBCConnectionID} hostConnectionId
|
|
201
222
|
* the counterparty connection_id
|
|
202
|
-
* @param {
|
|
223
|
+
* @param {IBCConnectionID} controllerConnectionId
|
|
203
224
|
* self connection_id
|
|
204
225
|
* @returns {Promise<ChainAccount>}
|
|
205
226
|
*/
|
|
@@ -213,10 +234,8 @@ const prepareOrchestration = (zone, createChainAccount) =>
|
|
|
213
234
|
const chainAccount = createChainAccount(port, remoteConnAddr);
|
|
214
235
|
|
|
215
236
|
// await so we do not return a ChainAccount before it successfully instantiates
|
|
216
|
-
await E(port)
|
|
217
|
-
|
|
218
|
-
// XXX if we fail, should we close the port (if it was created in this flow)?
|
|
219
|
-
.catch(e => Fail`Failed to create ICA connection: ${bare(e)}`);
|
|
237
|
+
await E(port).connect(remoteConnAddr, chainAccount.connectionHandler);
|
|
238
|
+
// XXX if we fail, should we close the port (if it was created in this flow)?
|
|
220
239
|
|
|
221
240
|
return chainAccount.account;
|
|
222
241
|
},
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { makeTracer } from '@agoric/internal';
|
|
3
|
+
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
|
|
3
4
|
import { E } from '@endo/far';
|
|
4
5
|
|
|
6
|
+
/** @import { StakeAtomSF, StakeAtomTerms} from '../contracts/stakeAtom.contract' */
|
|
7
|
+
|
|
5
8
|
const trace = makeTracer('StartStakeAtom', true);
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* @param {BootstrapPowers & { installation: {consume: {stakeAtom: Installation<import('../contracts/stakeAtom.contract.js').start>}}}} powers
|
|
9
|
-
* @param {{options:
|
|
12
|
+
* @param {{options: StakeAtomTerms }} options
|
|
10
13
|
*/
|
|
11
14
|
export const startStakeAtom = async (
|
|
12
15
|
{
|
|
13
|
-
consume: {
|
|
16
|
+
consume: {
|
|
17
|
+
agoricNames,
|
|
18
|
+
board,
|
|
19
|
+
chainStorage,
|
|
20
|
+
orchestration,
|
|
21
|
+
startUpgradable,
|
|
22
|
+
},
|
|
14
23
|
installation: {
|
|
15
24
|
consume: { stakeAtom },
|
|
16
25
|
},
|
|
@@ -20,19 +29,28 @@ export const startStakeAtom = async (
|
|
|
20
29
|
},
|
|
21
30
|
{ options: { hostConnectionId, controllerConnectionId } },
|
|
22
31
|
) => {
|
|
32
|
+
const VSTORAGE_PATH = 'stakeAtom';
|
|
23
33
|
trace('startStakeAtom', { hostConnectionId, controllerConnectionId });
|
|
24
34
|
await null;
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH);
|
|
37
|
+
const marshaller = await E(board).getPublishingMarshaller();
|
|
38
|
+
const atomIssuer = await E(agoricNames).lookup('issuer', 'ATOM');
|
|
39
|
+
trace('ATOM Issuer', atomIssuer);
|
|
40
|
+
|
|
41
|
+
/** @type {StartUpgradableOpts<StakeAtomSF>} */
|
|
27
42
|
const startOpts = {
|
|
28
43
|
label: 'stakeAtom',
|
|
29
44
|
installation: stakeAtom,
|
|
45
|
+
issuerKeywordRecord: harden({ ATOM: atomIssuer }),
|
|
30
46
|
terms: {
|
|
31
47
|
hostConnectionId,
|
|
32
48
|
controllerConnectionId,
|
|
33
49
|
},
|
|
34
50
|
privateArgs: {
|
|
35
51
|
orchestration: await orchestration,
|
|
52
|
+
storageNode,
|
|
53
|
+
marshaller,
|
|
36
54
|
},
|
|
37
55
|
};
|
|
38
56
|
|
|
@@ -49,6 +67,9 @@ export const getManifestForStakeAtom = (
|
|
|
49
67
|
manifest: {
|
|
50
68
|
[startStakeAtom.name]: {
|
|
51
69
|
consume: {
|
|
70
|
+
agoricNames: true,
|
|
71
|
+
board: true,
|
|
72
|
+
chainStorage: true,
|
|
52
73
|
orchestration: true,
|
|
53
74
|
startUpgradable: true,
|
|
54
75
|
},
|
package/src/types.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { RouterProtocol } from '@agoric/network/src/router';
|
|
2
2
|
|
|
3
|
-
export type ConnectionId = `connection-${number}`;
|
|
4
|
-
|
|
5
3
|
export type AttenuatedNetwork = Pick<RouterProtocol, 'bind'>;
|
|
6
4
|
|
|
7
5
|
export type * from './orchestration.js';
|
|
8
6
|
export type * from './vat-orchestration.js';
|
|
7
|
+
export type * from './utils/tx.js';
|
package/src/utils/address.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Fail } from '@agoric/assert';
|
|
3
3
|
|
|
4
|
-
/** @import {
|
|
4
|
+
/** @import { IBCConnectionID } from '@agoric/vats'; */
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {
|
|
7
|
+
* @param {IBCConnectionID} hostConnectionId Counterpart Connection ID
|
|
8
|
+
* @param {IBCConnectionID} controllerConnectionId Self Connection ID
|
|
9
9
|
* @param {object} [opts]
|
|
10
10
|
* @param {string} [opts.encoding] - message encoding format for the channel. default is `proto3`
|
|
11
11
|
* @param {'ordered' | 'unordered'} [opts.ordering] - channel ordering. currently only `ordered` is supported for ics27-1
|
package/src/utils/tx.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js';
|
|
3
|
+
import { decodeBase64, encodeBase64 } from '@endo/base64';
|
|
4
|
+
|
|
5
|
+
/** @typedef {{ typeUrl: string; value: string; }} Proto3Msg */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Makes an IBC packet from an array of messages. Expects the `value` of each message
|
|
9
|
+
* to be base64 encoded bytes.
|
|
10
|
+
* Skips checks for malformed messages in favor of interface guards.
|
|
11
|
+
* @param {Proto3Msg[]} msgs
|
|
12
|
+
* // XXX intellisense does not seem to infer well here
|
|
13
|
+
* @param {Omit<TxBody, 'messages'>} [opts]
|
|
14
|
+
* @returns {string} - IBC TX packet
|
|
15
|
+
* @throws {Error} if malformed messages are provided
|
|
16
|
+
*/
|
|
17
|
+
export function makeTxPacket(msgs, opts) {
|
|
18
|
+
const messages = msgs.map(msg => ({
|
|
19
|
+
typeUrl: msg.typeUrl,
|
|
20
|
+
value: decodeBase64(msg.value),
|
|
21
|
+
}));
|
|
22
|
+
const bytes = TxBody.encode(
|
|
23
|
+
TxBody.fromPartial({
|
|
24
|
+
messages,
|
|
25
|
+
...opts,
|
|
26
|
+
}),
|
|
27
|
+
).finish();
|
|
28
|
+
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
type: 1,
|
|
31
|
+
data: encodeBase64(bytes),
|
|
32
|
+
memo: '',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
harden(makeTxPacket);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* base64 encode an EncodeObject for cross-vat communication
|
|
39
|
+
* @param {{ typeUrl: string, value: Uint8Array }} tx
|
|
40
|
+
* @returns {Proto3Msg}
|
|
41
|
+
*/
|
|
42
|
+
export function txToBase64(tx) {
|
|
43
|
+
return {
|
|
44
|
+
typeUrl: tx.typeUrl,
|
|
45
|
+
value: encodeBase64(tx.value),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
harden(txToBase64);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Looks for a result or error key in the response string, and returns
|
|
52
|
+
* a Base64Bytes string. This string can be decoded using the corresponding
|
|
53
|
+
* Msg*Response object.
|
|
54
|
+
* Error strings seem to be plain text and do not need decoding.
|
|
55
|
+
* @param {string} response
|
|
56
|
+
* @returns {string} - base64 encoded bytes string
|
|
57
|
+
* @throws {Error} if error key is detected in response string, or result key is not found
|
|
58
|
+
*/
|
|
59
|
+
export function parsePacketAck(response) {
|
|
60
|
+
const { result, error } = JSON.parse(response);
|
|
61
|
+
if (result) return result;
|
|
62
|
+
else if (error) throw Error(error);
|
|
63
|
+
else throw Error(response);
|
|
64
|
+
}
|
|
65
|
+
harden(parsePacketAck);
|