@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.
@@ -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 */
@@ -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 */