@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-other-dev-d15096d.0.d15096d
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 +162 -36
- package/package.json +39 -38
- package/src/cli/bridge-action.js +41 -0
- package/src/cli/cli.js +47 -154
- package/src/cli/config-commands.js +108 -0
- package/src/cli/config.js +15 -9
- package/src/cli/lp-commands.js +161 -0
- package/src/cli/operator-commands.js +143 -0
- package/src/cli/transfer.js +84 -23
- package/src/cli/util/agoric.js +11 -0
- package/src/cli/util/bank.js +12 -0
- package/src/{util → cli/util}/cctp.js +1 -1
- package/src/{util → cli/util}/file.js +1 -1
- package/src/clientSupport.js +101 -0
- package/src/constants.js +29 -6
- package/src/main.js +1 -0
- package/src/operator-kit-interface.js +29 -0
- package/src/pool-share-math.js +72 -34
- package/src/type-guards.js +122 -34
- package/src/types.ts +136 -15
- package/src/utils/fees.js +105 -20
- package/tools/cli-tools.ts +9 -0
- package/tools/mock-evidence.ts +205 -0
- package/tools/mock-io.ts +14 -0
- package/src/exos/README.md +0 -26
- package/src/exos/advancer.js +0 -255
- package/src/exos/liquidity-pool.js +0 -365
- package/src/exos/operator-kit.js +0 -120
- package/src/exos/settler.js +0 -97
- package/src/exos/status-manager.js +0 -176
- package/src/exos/transaction-feed.js +0 -180
- package/src/fast-usdc.contract.js +0 -235
- package/src/fast-usdc.flows.js +0 -13
- package/src/fast-usdc.start.js +0 -284
- package/src/util/agoric.js +0 -12
- package/src/utils/address.js +0 -71
- package/src/utils/config-marshal.js +0 -130
- package/src/utils/zoe.js +0 -28
- /package/src/{util → cli/util}/noble.js +0 -0
package/src/exos/operator-kit.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { makeTracer } from '@agoric/internal';
|
|
2
|
-
import { Fail } from '@endo/errors';
|
|
3
|
-
import { M } from '@endo/patterns';
|
|
4
|
-
import { CctpTxEvidenceShape } from '../type-guards.js';
|
|
5
|
-
|
|
6
|
-
const trace = makeTracer('TxOperator');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @import {Zone} from '@agoric/zone';
|
|
10
|
-
* @import {CctpTxEvidence} from '../types.js';
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {object} OperatorPowers
|
|
15
|
-
* @property {(evidence: CctpTxEvidence, operatorKit: OperatorKit) => void} submitEvidence
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {object} OperatorStatus
|
|
20
|
-
* @property {boolean} [disabled]
|
|
21
|
-
* @property {string} operatorId
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {Readonly<{ operatorId: string, powers: OperatorPowers }> & {disabled: boolean}} State
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const OperatorKitI = {
|
|
29
|
-
admin: M.interface('Admin', {
|
|
30
|
-
disable: M.call().returns(),
|
|
31
|
-
}),
|
|
32
|
-
|
|
33
|
-
invitationMakers: M.interface('InvitationMakers', {
|
|
34
|
-
SubmitEvidence: M.call(CctpTxEvidenceShape).returns(M.promise()),
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
|
-
operator: M.interface('Operator', {
|
|
38
|
-
submitEvidence: M.call(CctpTxEvidenceShape).returns(M.promise()),
|
|
39
|
-
getStatus: M.call().returns(M.record()),
|
|
40
|
-
}),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @param {Zone} zone
|
|
45
|
-
* @param {{ makeInertInvitation: Function }} staticPowers
|
|
46
|
-
*/
|
|
47
|
-
export const prepareOperatorKit = (zone, staticPowers) =>
|
|
48
|
-
zone.exoClassKit(
|
|
49
|
-
'Operator Kit',
|
|
50
|
-
OperatorKitI,
|
|
51
|
-
/**
|
|
52
|
-
* @param {string} operatorId
|
|
53
|
-
* @param {OperatorPowers} powers facet of the durable transaction feed
|
|
54
|
-
* @returns {State}
|
|
55
|
-
*/
|
|
56
|
-
(operatorId, powers) => {
|
|
57
|
-
return {
|
|
58
|
-
operatorId,
|
|
59
|
-
powers,
|
|
60
|
-
disabled: false,
|
|
61
|
-
};
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
admin: {
|
|
65
|
-
disable() {
|
|
66
|
-
trace(`operator ${this.state.operatorId} disabled`);
|
|
67
|
-
this.state.disabled = true;
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
/**
|
|
71
|
-
* NB: when this kit is an offer result, the smart-wallet will detect the `invitationMakers`
|
|
72
|
-
* key and save it for future offers.
|
|
73
|
-
*/
|
|
74
|
-
invitationMakers: {
|
|
75
|
-
/**
|
|
76
|
-
* Provide an API call in the form of an invitation maker, so that the
|
|
77
|
-
* capability is available in the smart-wallet bridge.
|
|
78
|
-
*
|
|
79
|
-
* NB: The `Invitation` object is evidence that the operation took
|
|
80
|
-
* place, rather than as a means of performing it as in the
|
|
81
|
-
* fluxAggregator contract used for price oracles.
|
|
82
|
-
*
|
|
83
|
-
* @param {CctpTxEvidence} evidence
|
|
84
|
-
* @returns {Promise<Invitation>}
|
|
85
|
-
*/
|
|
86
|
-
async SubmitEvidence(evidence) {
|
|
87
|
-
const { operator } = this.facets;
|
|
88
|
-
// TODO(bootstrap integration): cause this call to throw and confirm that it
|
|
89
|
-
// shows up in the the smart-wallet UpdateRecord `error` property
|
|
90
|
-
await operator.submitEvidence(evidence);
|
|
91
|
-
return staticPowers.makeInertInvitation(
|
|
92
|
-
'evidence was pushed in the invitation maker call',
|
|
93
|
-
);
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
operator: {
|
|
97
|
-
/**
|
|
98
|
-
* submit evidence from this operator
|
|
99
|
-
*
|
|
100
|
-
* @param {CctpTxEvidence} evidence
|
|
101
|
-
*/
|
|
102
|
-
async submitEvidence(evidence) {
|
|
103
|
-
const { state } = this;
|
|
104
|
-
!state.disabled || Fail`submitEvidence for disabled operator`;
|
|
105
|
-
const result = state.powers.submitEvidence(evidence, this.facets);
|
|
106
|
-
return result;
|
|
107
|
-
},
|
|
108
|
-
/** @returns {OperatorStatus} */
|
|
109
|
-
getStatus() {
|
|
110
|
-
const { state } = this;
|
|
111
|
-
return {
|
|
112
|
-
operatorId: state.operatorId,
|
|
113
|
-
disabled: state.disabled,
|
|
114
|
-
};
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
/** @typedef {ReturnType<ReturnType<typeof prepareOperatorKit>>} OperatorKit */
|
package/src/exos/settler.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { assertAllDefined } from '@agoric/internal';
|
|
2
|
-
import { atob } from '@endo/base64';
|
|
3
|
-
import { makeError, q } from '@endo/errors';
|
|
4
|
-
import { M } from '@endo/patterns';
|
|
5
|
-
|
|
6
|
-
import { addressTools } from '../utils/address.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
|
|
10
|
-
* @import {Denom} from '@agoric/orchestration';
|
|
11
|
-
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
|
|
12
|
-
* @import {Zone} from '@agoric/zone';
|
|
13
|
-
* @import {NobleAddress} from '../types.js';
|
|
14
|
-
* @import {StatusManager} from './status-manager.js';
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param {Zone} zone
|
|
19
|
-
* @param {object} caps
|
|
20
|
-
* @param {StatusManager} caps.statusManager
|
|
21
|
-
*/
|
|
22
|
-
export const prepareSettler = (zone, { statusManager }) => {
|
|
23
|
-
assertAllDefined({ statusManager });
|
|
24
|
-
return zone.exoClass(
|
|
25
|
-
'Fast USDC Settler',
|
|
26
|
-
M.interface('SettlerI', {
|
|
27
|
-
receiveUpcall: M.call(M.record()).returns(M.promise()),
|
|
28
|
-
}),
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
31
|
-
* @param {{
|
|
32
|
-
* sourceChannel: IBCChannelID;
|
|
33
|
-
* remoteDenom: Denom
|
|
34
|
-
* }} config
|
|
35
|
-
*/
|
|
36
|
-
config => harden(config),
|
|
37
|
-
{
|
|
38
|
-
/** @param {VTransferIBCEvent} event */
|
|
39
|
-
async receiveUpcall(event) {
|
|
40
|
-
if (event.packet.source_channel !== this.state.sourceChannel) {
|
|
41
|
-
// TODO #10390 log all early returns
|
|
42
|
-
// only interested in packets from the issuing chain
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const tx = /** @type {FungibleTokenPacketData} */ (
|
|
46
|
-
JSON.parse(atob(event.packet.data))
|
|
47
|
-
);
|
|
48
|
-
if (tx.denom !== this.state.remoteDenom) {
|
|
49
|
-
// only interested in uusdc
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!addressTools.hasQueryParams(tx.receiver)) {
|
|
54
|
-
// only interested in receivers with query params
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const { EUD } = addressTools.getQueryParams(tx.receiver);
|
|
59
|
-
if (!EUD) {
|
|
60
|
-
// only interested in receivers with EUD parameter
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// TODO discern between SETTLED and OBSERVED; each has different fees/destinations
|
|
65
|
-
const hasPendingSettlement = statusManager.hasPendingSettlement(
|
|
66
|
-
// given the sourceChannel check, we can be certain of this cast
|
|
67
|
-
/** @type {NobleAddress} */ (tx.sender),
|
|
68
|
-
BigInt(tx.amount),
|
|
69
|
-
);
|
|
70
|
-
if (!hasPendingSettlement) {
|
|
71
|
-
// TODO FAILURE PATH -> put money in recovery account or .transfer to receiver
|
|
72
|
-
// TODO should we have an ORPHANED TxStatus for this?
|
|
73
|
-
throw makeError(
|
|
74
|
-
`🚨 No pending settlement found for ${q(tx.sender)} ${q(tx.amount)}`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// TODO disperse funds
|
|
79
|
-
// ~1. fee to contractFeeAccount
|
|
80
|
-
// ~2. remainder in poolAccount
|
|
81
|
-
|
|
82
|
-
// update status manager, marking tx `SETTLED`
|
|
83
|
-
statusManager.settle(
|
|
84
|
-
/** @type {NobleAddress} */ (tx.sender),
|
|
85
|
-
BigInt(tx.amount),
|
|
86
|
-
);
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
stateShape: harden({
|
|
91
|
-
sourceChannel: M.string(),
|
|
92
|
-
remoteDenom: M.string(),
|
|
93
|
-
}),
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
harden(prepareSettler);
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { M } from '@endo/patterns';
|
|
2
|
-
import { makeError, q } from '@endo/errors';
|
|
3
|
-
|
|
4
|
-
import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
|
|
5
|
-
import { CctpTxEvidenceShape, PendingTxShape } from '../type-guards.js';
|
|
6
|
-
import { PendingTxStatus } from '../constants.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @import {MapStore, SetStore} from '@agoric/store';
|
|
10
|
-
* @import {Zone} from '@agoric/zone';
|
|
11
|
-
* @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx} from '../types.js';
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Create the key for the pendingTxs MapStore.
|
|
16
|
-
*
|
|
17
|
-
* The key is a composite of `txHash` and `chainId` and not meant to be
|
|
18
|
-
* parsable.
|
|
19
|
-
*
|
|
20
|
-
* @param {NobleAddress} addr
|
|
21
|
-
* @param {bigint} amount
|
|
22
|
-
* @returns {PendingTxKey}
|
|
23
|
-
*/
|
|
24
|
-
const makePendingTxKey = (addr, amount) =>
|
|
25
|
-
`pendingTx:${JSON.stringify([addr, String(amount)])}`;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get the key for the pendingTxs MapStore.
|
|
29
|
-
*
|
|
30
|
-
* @param {CctpTxEvidence} evidence
|
|
31
|
-
* @returns {PendingTxKey}
|
|
32
|
-
*/
|
|
33
|
-
const pendingTxKeyOf = evidence => {
|
|
34
|
-
const { amount, forwardingAddress } = evidence.tx;
|
|
35
|
-
return makePendingTxKey(forwardingAddress, amount);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get the key for the seenTxs SetStore.
|
|
40
|
-
*
|
|
41
|
-
* The key is a composite of `NobleAddress` and transaction `amount` and not
|
|
42
|
-
* meant to be parsable.
|
|
43
|
-
*
|
|
44
|
-
* @param {CctpTxEvidence} evidence
|
|
45
|
-
* @returns {SeenTxKey}
|
|
46
|
-
*/
|
|
47
|
-
const seenTxKeyOf = evidence => {
|
|
48
|
-
const { txHash, chainId } = evidence;
|
|
49
|
-
return `seenTx:${JSON.stringify([txHash, chainId])}`;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The `StatusManager` keeps track of Pending and Seen Transactions
|
|
54
|
-
* via {@link PendingTxStatus} states, aiding in coordination between the `Advancer`
|
|
55
|
-
* and `Settler`.
|
|
56
|
-
*
|
|
57
|
-
* XXX consider separate facets for `Advancing` and `Settling` capabilities.
|
|
58
|
-
*
|
|
59
|
-
* @param {Zone} zone
|
|
60
|
-
*/
|
|
61
|
-
export const prepareStatusManager = zone => {
|
|
62
|
-
/** @type {MapStore<PendingTxKey, PendingTx[]>} */
|
|
63
|
-
const pendingTxs = zone.mapStore('PendingTxs', {
|
|
64
|
-
keyShape: M.string(),
|
|
65
|
-
valueShape: M.arrayOf(PendingTxShape),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
/** @type {SetStore<SeenTxKey>} */
|
|
69
|
-
const seenTxs = zone.setStore('SeenTxs', {
|
|
70
|
-
keyShape: M.string(),
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Ensures that `txHash+chainId` has not been processed
|
|
75
|
-
* and adds entry to `seenTxs` set.
|
|
76
|
-
*
|
|
77
|
-
* Also records the CctpTxEvidence and status in `pendingTxs`.
|
|
78
|
-
*
|
|
79
|
-
* @param {CctpTxEvidence} evidence
|
|
80
|
-
* @param {PendingTxStatus} status
|
|
81
|
-
*/
|
|
82
|
-
const recordPendingTx = (evidence, status) => {
|
|
83
|
-
const seenKey = seenTxKeyOf(evidence);
|
|
84
|
-
if (seenTxs.has(seenKey)) {
|
|
85
|
-
throw makeError(`Transaction already seen: ${q(seenKey)}`);
|
|
86
|
-
}
|
|
87
|
-
seenTxs.add(seenKey);
|
|
88
|
-
|
|
89
|
-
appendToStoredArray(
|
|
90
|
-
pendingTxs,
|
|
91
|
-
pendingTxKeyOf(evidence),
|
|
92
|
-
harden({ ...evidence, status }),
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return zone.exo(
|
|
97
|
-
'Fast USDC Status Manager',
|
|
98
|
-
M.interface('StatusManagerI', {
|
|
99
|
-
advance: M.call(CctpTxEvidenceShape).returns(M.undefined()),
|
|
100
|
-
observe: M.call(CctpTxEvidenceShape).returns(M.undefined()),
|
|
101
|
-
hasPendingSettlement: M.call(M.string(), M.bigint()).returns(M.boolean()),
|
|
102
|
-
settle: M.call(M.string(), M.bigint()).returns(M.undefined()),
|
|
103
|
-
lookupPending: M.call(M.string(), M.bigint()).returns(
|
|
104
|
-
M.arrayOf(PendingTxShape),
|
|
105
|
-
),
|
|
106
|
-
}),
|
|
107
|
-
{
|
|
108
|
-
/**
|
|
109
|
-
* Add a new transaction with ADVANCED status
|
|
110
|
-
* @param {CctpTxEvidence} evidence
|
|
111
|
-
*/
|
|
112
|
-
advance(evidence) {
|
|
113
|
-
recordPendingTx(evidence, PendingTxStatus.Advanced);
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Add a new transaction with OBSERVED status
|
|
118
|
-
* @param {CctpTxEvidence} evidence
|
|
119
|
-
*/
|
|
120
|
-
observe(evidence) {
|
|
121
|
-
recordPendingTx(evidence, PendingTxStatus.Observed);
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Find an `ADVANCED` or `OBSERVED` tx waiting to be `SETTLED`
|
|
126
|
-
*
|
|
127
|
-
* @param {NobleAddress} address
|
|
128
|
-
* @param {bigint} amount
|
|
129
|
-
* @returns {boolean}
|
|
130
|
-
*/
|
|
131
|
-
hasPendingSettlement(address, amount) {
|
|
132
|
-
const key = makePendingTxKey(address, amount);
|
|
133
|
-
const pending = pendingTxs.get(key);
|
|
134
|
-
return !!pending.length;
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Mark an `ADVANCED` or `OBSERVED` transaction as `SETTLED` and remove it
|
|
139
|
-
*
|
|
140
|
-
* @param {NobleAddress} address
|
|
141
|
-
* @param {bigint} amount
|
|
142
|
-
*/
|
|
143
|
-
settle(address, amount) {
|
|
144
|
-
const key = makePendingTxKey(address, amount);
|
|
145
|
-
const pending = pendingTxs.get(key);
|
|
146
|
-
|
|
147
|
-
if (!pending.length) {
|
|
148
|
-
throw makeError(`No unsettled entry for ${q(key)}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const pendingCopy = [...pending];
|
|
152
|
-
pendingCopy.shift();
|
|
153
|
-
// TODO, vstorage update for `TxStatus.Settled`
|
|
154
|
-
pendingTxs.set(key, harden(pendingCopy));
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Lookup all pending entries for a given address and amount
|
|
159
|
-
*
|
|
160
|
-
* @param {NobleAddress} address
|
|
161
|
-
* @param {bigint} amount
|
|
162
|
-
* @returns {PendingTx[]}
|
|
163
|
-
*/
|
|
164
|
-
lookupPending(address, amount) {
|
|
165
|
-
const key = makePendingTxKey(address, amount);
|
|
166
|
-
if (!pendingTxs.has(key)) {
|
|
167
|
-
throw makeError(`Key ${q(key)} not yet observed`);
|
|
168
|
-
}
|
|
169
|
-
return pendingTxs.get(key);
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
harden(prepareStatusManager);
|
|
175
|
-
|
|
176
|
-
/** @typedef {ReturnType<typeof prepareStatusManager>} StatusManager */
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { makeTracer } from '@agoric/internal';
|
|
2
|
-
import { prepareDurablePublishKit } from '@agoric/notifier';
|
|
3
|
-
import { M } from '@endo/patterns';
|
|
4
|
-
import { CctpTxEvidenceShape } from '../type-guards.js';
|
|
5
|
-
import { defineInertInvitation } from '../utils/zoe.js';
|
|
6
|
-
import { prepareOperatorKit } from './operator-kit.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @import {Zone} from '@agoric/zone';
|
|
10
|
-
* @import {OperatorKit} from './operator-kit.js';
|
|
11
|
-
* @import {CctpTxEvidence} from '../types.js';
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const trace = makeTracer('TxFeed', true);
|
|
15
|
-
|
|
16
|
-
/** Name in the invitation purse (keyed also by this contract instance) */
|
|
17
|
-
export const INVITATION_MAKERS_DESC = 'oracle operator invitation';
|
|
18
|
-
|
|
19
|
-
const TransactionFeedKitI = harden({
|
|
20
|
-
operatorPowers: M.interface('Transaction Feed Admin', {
|
|
21
|
-
submitEvidence: M.call(CctpTxEvidenceShape, M.any()).returns(),
|
|
22
|
-
}),
|
|
23
|
-
creator: M.interface('Transaction Feed Creator', {
|
|
24
|
-
// TODO narrow the return shape to OperatorKit
|
|
25
|
-
initOperator: M.call(M.string()).returns(M.record()),
|
|
26
|
-
makeOperatorInvitation: M.call(M.string()).returns(M.promise()),
|
|
27
|
-
removeOperator: M.call(M.string()).returns(),
|
|
28
|
-
}),
|
|
29
|
-
public: M.interface('Transaction Feed Public', {
|
|
30
|
-
getEvidenceSubscriber: M.call().returns(M.remotable()),
|
|
31
|
-
}),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {Zone} zone
|
|
36
|
-
* @param {ZCF} zcf
|
|
37
|
-
*/
|
|
38
|
-
export const prepareTransactionFeedKit = (zone, zcf) => {
|
|
39
|
-
const kinds = zone.mapStore('Kinds');
|
|
40
|
-
const makeDurablePublishKit = prepareDurablePublishKit(
|
|
41
|
-
kinds,
|
|
42
|
-
'Transaction Feed',
|
|
43
|
-
);
|
|
44
|
-
/** @type {PublishKit<CctpTxEvidence>} */
|
|
45
|
-
const { publisher, subscriber } = makeDurablePublishKit();
|
|
46
|
-
|
|
47
|
-
const makeInertInvitation = defineInertInvitation(zcf, 'submitting evidence');
|
|
48
|
-
|
|
49
|
-
const makeOperatorKit = prepareOperatorKit(zone, {
|
|
50
|
-
makeInertInvitation,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return zone.exoClassKit(
|
|
54
|
-
'Fast USDC Feed',
|
|
55
|
-
TransactionFeedKitI,
|
|
56
|
-
() => {
|
|
57
|
-
/** @type {MapStore<string, OperatorKit>} */
|
|
58
|
-
const operators = zone.mapStore('operators', {
|
|
59
|
-
durable: true,
|
|
60
|
-
});
|
|
61
|
-
/** @type {MapStore<string, MapStore<string, CctpTxEvidence>>} */
|
|
62
|
-
const pending = zone.mapStore('pending', {
|
|
63
|
-
durable: true,
|
|
64
|
-
});
|
|
65
|
-
return { operators, pending };
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
creator: {
|
|
69
|
-
/**
|
|
70
|
-
* An "operator invitation" is an invitation to be an operator in the
|
|
71
|
-
* oracle network, with the able to submit data to submit evidence of
|
|
72
|
-
* CCTP transactions.
|
|
73
|
-
*
|
|
74
|
-
* @param {string} operatorId unique per contract instance
|
|
75
|
-
* @returns {Promise<Invitation<OperatorKit>>}
|
|
76
|
-
*/
|
|
77
|
-
makeOperatorInvitation(operatorId) {
|
|
78
|
-
const { creator } = this.facets;
|
|
79
|
-
trace('makeOperatorInvitation', operatorId);
|
|
80
|
-
|
|
81
|
-
return zcf.makeInvitation(
|
|
82
|
-
/** @type {OfferHandler<OperatorKit>} */
|
|
83
|
-
seat => {
|
|
84
|
-
seat.exit();
|
|
85
|
-
return creator.initOperator(operatorId);
|
|
86
|
-
},
|
|
87
|
-
INVITATION_MAKERS_DESC,
|
|
88
|
-
);
|
|
89
|
-
},
|
|
90
|
-
/** @param {string} operatorId */
|
|
91
|
-
initOperator(operatorId) {
|
|
92
|
-
const { operators, pending } = this.state;
|
|
93
|
-
trace('initOperator', operatorId);
|
|
94
|
-
|
|
95
|
-
const operatorKit = makeOperatorKit(
|
|
96
|
-
operatorId,
|
|
97
|
-
this.facets.operatorPowers,
|
|
98
|
-
);
|
|
99
|
-
operators.init(operatorId, operatorKit);
|
|
100
|
-
pending.init(
|
|
101
|
-
operatorId,
|
|
102
|
-
zone.detached().mapStore('pending evidence'),
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
return operatorKit;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/** @param {string} operatorId */
|
|
109
|
-
async removeOperator(operatorId) {
|
|
110
|
-
const { operators } = this.state;
|
|
111
|
-
trace('removeOperator', operatorId);
|
|
112
|
-
const operatorKit = operators.get(operatorId);
|
|
113
|
-
operatorKit.admin.disable();
|
|
114
|
-
operators.delete(operatorId);
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
operatorPowers: {
|
|
118
|
-
/**
|
|
119
|
-
* Add evidence from an operator.
|
|
120
|
-
*
|
|
121
|
-
* @param {CctpTxEvidence} evidence
|
|
122
|
-
* @param {OperatorKit} operatorKit
|
|
123
|
-
*/
|
|
124
|
-
submitEvidence(evidence, operatorKit) {
|
|
125
|
-
const { pending } = this.state;
|
|
126
|
-
trace(
|
|
127
|
-
'submitEvidence',
|
|
128
|
-
operatorKit.operator.getStatus().operatorId,
|
|
129
|
-
evidence,
|
|
130
|
-
);
|
|
131
|
-
const { operatorId } = operatorKit.operator.getStatus();
|
|
132
|
-
|
|
133
|
-
// TODO should this verify that the operator is one made by this exo?
|
|
134
|
-
// This doesn't work...
|
|
135
|
-
// operatorKit === operators.get(operatorId) ||
|
|
136
|
-
// Fail`operatorKit mismatch`;
|
|
137
|
-
|
|
138
|
-
// TODO validate that it's a valid for Fast USDC before accepting
|
|
139
|
-
// E.g. that the `recipientAddress` is the FU settlement account and that
|
|
140
|
-
// the EUD is a chain supported by FU.
|
|
141
|
-
const { txHash } = evidence;
|
|
142
|
-
|
|
143
|
-
// accept the evidence
|
|
144
|
-
{
|
|
145
|
-
const pendingStore = pending.get(operatorId);
|
|
146
|
-
if (pendingStore.has(txHash)) {
|
|
147
|
-
trace(`operator ${operatorId} already reported ${txHash}`);
|
|
148
|
-
} else {
|
|
149
|
-
pendingStore.init(txHash, evidence);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// check agreement
|
|
154
|
-
const found = [...pending.values()].filter(store =>
|
|
155
|
-
store.has(txHash),
|
|
156
|
-
);
|
|
157
|
-
// TODO determine the real policy for checking agreement
|
|
158
|
-
if (found.length < pending.getSize()) {
|
|
159
|
-
// not all have seen it
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// TODO verify that all found deep equal
|
|
164
|
-
|
|
165
|
-
// all agree, so remove from pending and publish
|
|
166
|
-
for (const pendingStore of pending.values()) {
|
|
167
|
-
pendingStore.delete(txHash);
|
|
168
|
-
}
|
|
169
|
-
publisher.publish(evidence);
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
public: {
|
|
173
|
-
getEvidenceSubscriber: () => subscriber,
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
);
|
|
177
|
-
};
|
|
178
|
-
harden(prepareTransactionFeedKit);
|
|
179
|
-
|
|
180
|
-
/** @typedef {ReturnType<ReturnType<typeof prepareTransactionFeedKit>>} TransactionFeedKit */
|