@agoric/fast-usdc 0.1.1-dev-8fd731c.0 → 0.1.1-dev-aa11d5c.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/package.json +14 -14
- package/src/constants.js +16 -2
- package/src/exos/advancer.js +72 -47
- package/src/exos/liquidity-pool.js +1 -6
- package/src/exos/settler.js +251 -57
- package/src/exos/status-manager.js +111 -24
- package/src/fast-usdc.contract.js +36 -11
- package/src/types.ts +1 -0
- package/src/exos/README.md +0 -26
package/README.md
CHANGED
|
@@ -56,3 +56,44 @@ sequenceDiagram
|
|
|
56
56
|
|
|
57
57
|
A->>TF: notify(evidence)
|
|
58
58
|
```
|
|
59
|
+
|
|
60
|
+
# Status Manager
|
|
61
|
+
|
|
62
|
+
### Pending Advance State Diagram
|
|
63
|
+
|
|
64
|
+
*Transactions are qualified by the OCW and EventFeed before arriving to the Advancer.*
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
stateDiagram-v2
|
|
68
|
+
[*] --> Observed: observe()
|
|
69
|
+
[*] --> Advancing: advancing()
|
|
70
|
+
|
|
71
|
+
Advancing --> Advanced: advanceOutcome(...true)
|
|
72
|
+
Advancing --> AdvanceFailed: advanceOutcome(...false)
|
|
73
|
+
|
|
74
|
+
Observed --> [*]: dequeueStatus()
|
|
75
|
+
Advanced --> [*]: dequeueStatus()
|
|
76
|
+
AdvanceFailed --> [*]: dequeueStatus()
|
|
77
|
+
|
|
78
|
+
note right of [*]
|
|
79
|
+
After dequeueStatus():
|
|
80
|
+
Transaction is removed
|
|
81
|
+
from pendingTxs store.
|
|
82
|
+
Settler will .disburse()
|
|
83
|
+
or .forward()
|
|
84
|
+
end note
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Complete state diagram (starting from Transaction Feed into Advancer)
|
|
88
|
+
|
|
89
|
+
```mermaid
|
|
90
|
+
stateDiagram-v2
|
|
91
|
+
Observed --> Advancing
|
|
92
|
+
Observed --> Forwarding:Minted
|
|
93
|
+
Forwarding --> Forwarded
|
|
94
|
+
Advancing --> Advanced
|
|
95
|
+
Advanced --> Disbursed
|
|
96
|
+
AdvanceFailed --> Forwarding
|
|
97
|
+
Advancing --> AdvanceFailed
|
|
98
|
+
Forwarding --> ForwardFailed
|
|
99
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/fast-usdc",
|
|
3
|
-
"version": "0.1.1-dev-
|
|
3
|
+
"version": "0.1.1-dev-aa11d5c.0+aa11d5c",
|
|
4
4
|
"description": "CLI and library for Fast USDC product",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"lint:eslint": "eslint ."
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
25
|
-
"@agoric/vats": "0.15.2-dev-
|
|
26
|
-
"@agoric/zone": "0.2.3-dev-
|
|
24
|
+
"@agoric/swingset-liveslots": "0.10.3-dev-aa11d5c.0+aa11d5c",
|
|
25
|
+
"@agoric/vats": "0.15.2-dev-aa11d5c.0+aa11d5c",
|
|
26
|
+
"@agoric/zone": "0.2.3-dev-aa11d5c.0+aa11d5c",
|
|
27
27
|
"@fast-check/ava": "^2.0.1",
|
|
28
28
|
"ava": "^5.3.0",
|
|
29
29
|
"c8": "^10.1.2",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"ts-blank-space": "^0.4.1"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@agoric/client-utils": "0.1.1-dev-
|
|
35
|
-
"@agoric/ertp": "0.16.3-dev-
|
|
36
|
-
"@agoric/internal": "0.3.3-dev-
|
|
37
|
-
"@agoric/notifier": "0.6.3-dev-
|
|
38
|
-
"@agoric/orchestration": "0.1.1-dev-
|
|
39
|
-
"@agoric/store": "0.9.3-dev-
|
|
40
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
41
|
-
"@agoric/vow": "0.1.1-dev-
|
|
42
|
-
"@agoric/zoe": "0.26.3-dev-
|
|
34
|
+
"@agoric/client-utils": "0.1.1-dev-aa11d5c.0+aa11d5c",
|
|
35
|
+
"@agoric/ertp": "0.16.3-dev-aa11d5c.0+aa11d5c",
|
|
36
|
+
"@agoric/internal": "0.3.3-dev-aa11d5c.0+aa11d5c",
|
|
37
|
+
"@agoric/notifier": "0.6.3-dev-aa11d5c.0+aa11d5c",
|
|
38
|
+
"@agoric/orchestration": "0.1.1-dev-aa11d5c.0+aa11d5c",
|
|
39
|
+
"@agoric/store": "0.9.3-dev-aa11d5c.0+aa11d5c",
|
|
40
|
+
"@agoric/vat-data": "0.5.3-dev-aa11d5c.0+aa11d5c",
|
|
41
|
+
"@agoric/vow": "0.1.1-dev-aa11d5c.0+aa11d5c",
|
|
42
|
+
"@agoric/zoe": "0.26.3-dev-aa11d5c.0+aa11d5c",
|
|
43
43
|
"@cosmjs/proto-signing": "^0.32.4",
|
|
44
44
|
"@cosmjs/stargate": "^0.32.4",
|
|
45
45
|
"@endo/base64": "^1.0.9",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"publishConfig": {
|
|
79
79
|
"access": "public"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "aa11d5c2482ac47f1a6ab8ac5e67a9678e2946f7"
|
|
82
82
|
}
|
package/src/constants.js
CHANGED
|
@@ -7,12 +7,22 @@ export const TxStatus = /** @type {const} */ ({
|
|
|
7
7
|
/** tx was observed but not advanced */
|
|
8
8
|
Observed: 'OBSERVED',
|
|
9
9
|
/** IBC transfer is initiated */
|
|
10
|
+
Advancing: 'ADVANCING',
|
|
11
|
+
/** IBC transfer is complete */
|
|
10
12
|
Advanced: 'ADVANCED',
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
+
/** IBC transfer failed (timed out) */
|
|
14
|
+
AdvanceFailed: 'ADVANCE_FAILED',
|
|
15
|
+
/** settlement for matching advance received and funds disbursed */
|
|
16
|
+
Disbursed: 'DISBURSED',
|
|
17
|
+
/** fallback: do not collect fees */
|
|
18
|
+
Forwarded: 'FORWARDED',
|
|
19
|
+
/** failed to forward to EUD */
|
|
20
|
+
ForwardFailed: 'FORWARD_FAILED',
|
|
13
21
|
});
|
|
14
22
|
harden(TxStatus);
|
|
15
23
|
|
|
24
|
+
// TODO: define valid state transitions
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
27
|
* Status values for the StatusManager.
|
|
18
28
|
*
|
|
@@ -22,6 +32,10 @@ export const PendingTxStatus = /** @type {const} */ ({
|
|
|
22
32
|
/** tx was observed but not advanced */
|
|
23
33
|
Observed: 'OBSERVED',
|
|
24
34
|
/** IBC transfer is initiated */
|
|
35
|
+
Advancing: 'ADVANCING',
|
|
36
|
+
/** IBC transfer failed (timed out) */
|
|
37
|
+
AdvanceFailed: 'ADVANCE_FAILED',
|
|
38
|
+
/** IBC transfer is complete */
|
|
25
39
|
Advanced: 'ADVANCED',
|
|
26
40
|
});
|
|
27
41
|
harden(PendingTxStatus);
|
package/src/exos/advancer.js
CHANGED
|
@@ -6,12 +6,14 @@ import { VowShape } from '@agoric/vow';
|
|
|
6
6
|
import { q } from '@endo/errors';
|
|
7
7
|
import { E } from '@endo/far';
|
|
8
8
|
import { M } from '@endo/patterns';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CctpTxEvidenceShape,
|
|
11
|
+
EudParamShape,
|
|
12
|
+
EvmHashShape,
|
|
13
|
+
} from '../type-guards.js';
|
|
10
14
|
import { addressTools } from '../utils/address.js';
|
|
11
15
|
import { makeFeeTools } from '../utils/fees.js';
|
|
12
16
|
|
|
13
|
-
const { isGTE } = AmountMath;
|
|
14
|
-
|
|
15
17
|
/**
|
|
16
18
|
* @import {HostInterface} from '@agoric/async-flow';
|
|
17
19
|
* @import {NatAmount} from '@agoric/ertp';
|
|
@@ -19,7 +21,7 @@ const { isGTE } = AmountMath;
|
|
|
19
21
|
* @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
|
|
20
22
|
* @import {VowTools} from '@agoric/vow';
|
|
21
23
|
* @import {Zone} from '@agoric/zone';
|
|
22
|
-
* @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js';
|
|
24
|
+
* @import {CctpTxEvidence, EvmHash, FeeConfig, LogFn, NobleAddress} from '../types.js';
|
|
23
25
|
* @import {StatusManager} from './status-manager.js';
|
|
24
26
|
* @import {LiquidityPoolKit} from './liquidity-pool.js';
|
|
25
27
|
*/
|
|
@@ -46,12 +48,16 @@ const AdvancerKitI = harden({
|
|
|
46
48
|
onFulfilled: M.call(M.undefined(), {
|
|
47
49
|
amount: AmountShape,
|
|
48
50
|
destination: ChainAddressShape,
|
|
51
|
+
forwardingAddress: M.string(),
|
|
49
52
|
tmpSeat: M.remotable(),
|
|
53
|
+
txHash: EvmHashShape,
|
|
50
54
|
}).returns(VowShape),
|
|
51
55
|
onRejected: M.call(M.error(), {
|
|
52
56
|
amount: AmountShape,
|
|
53
57
|
destination: ChainAddressShape,
|
|
58
|
+
forwardingAddress: M.string(),
|
|
54
59
|
tmpSeat: M.remotable(),
|
|
60
|
+
txHash: EvmHashShape,
|
|
55
61
|
}).returns(),
|
|
56
62
|
}),
|
|
57
63
|
transferHandler: M.interface('TransferHandlerI', {
|
|
@@ -59,14 +65,27 @@ const AdvancerKitI = harden({
|
|
|
59
65
|
onFulfilled: M.call(M.undefined(), {
|
|
60
66
|
amount: AmountShape,
|
|
61
67
|
destination: ChainAddressShape,
|
|
68
|
+
forwardingAddress: M.string(),
|
|
69
|
+
txHash: EvmHashShape,
|
|
62
70
|
}).returns(M.undefined()),
|
|
63
71
|
onRejected: M.call(M.error(), {
|
|
64
72
|
amount: AmountShape,
|
|
65
73
|
destination: ChainAddressShape,
|
|
74
|
+
forwardingAddress: M.string(),
|
|
75
|
+
txHash: EvmHashShape,
|
|
66
76
|
}).returns(M.undefined()),
|
|
67
77
|
}),
|
|
68
78
|
});
|
|
69
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {{
|
|
82
|
+
* amount: NatAmount;
|
|
83
|
+
* destination: ChainAddress;
|
|
84
|
+
* forwardingAddress: NobleAddress;
|
|
85
|
+
* txHash: EvmHash;
|
|
86
|
+
* }} AdvancerVowCtx
|
|
87
|
+
*/
|
|
88
|
+
|
|
70
89
|
/**
|
|
71
90
|
* @param {Zone} zone
|
|
72
91
|
* @param {AdvancerKitPowers} caps
|
|
@@ -100,6 +119,7 @@ export const prepareAdvancerKit = (
|
|
|
100
119
|
AdvancerKitI,
|
|
101
120
|
/**
|
|
102
121
|
* @param {{
|
|
122
|
+
* notifyFacet: import('./settler.js').SettlerKit['notify'];
|
|
103
123
|
* borrowerFacet: LiquidityPoolKit['borrower'];
|
|
104
124
|
* poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
|
|
105
125
|
* }} config
|
|
@@ -120,51 +140,32 @@ export const prepareAdvancerKit = (
|
|
|
120
140
|
async handleTransactionEvent(evidence) {
|
|
121
141
|
await null;
|
|
122
142
|
try {
|
|
143
|
+
if (statusManager.hasBeenObserved(evidence)) {
|
|
144
|
+
log('txHash already seen:', evidence.txHash);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
123
148
|
const { borrowerFacet, poolAccount } = this.state;
|
|
124
149
|
const { recipientAddress } = evidence.aux;
|
|
150
|
+
// throws if EUD is not found
|
|
125
151
|
const { EUD } = addressTools.getQueryParams(
|
|
126
152
|
recipientAddress,
|
|
127
153
|
EudParamShape,
|
|
128
154
|
);
|
|
129
|
-
|
|
130
|
-
// this will throw if the bech32 prefix is not found, but is handled by the catch
|
|
155
|
+
// throws if the bech32 prefix is not found
|
|
131
156
|
const destination = chainHub.makeChainAddress(EUD);
|
|
157
|
+
|
|
132
158
|
const requestedAmount = toAmount(evidence.tx.amount);
|
|
159
|
+
// throws if requested does not exceed fees
|
|
133
160
|
const advanceAmount = feeTools.calculateAdvance(requestedAmount);
|
|
134
161
|
|
|
135
|
-
// TODO: consider skipping and using `borrow()`s internal balance check
|
|
136
|
-
const poolBalance = borrowerFacet.getBalance();
|
|
137
|
-
if (!isGTE(poolBalance, requestedAmount)) {
|
|
138
|
-
log(
|
|
139
|
-
`Insufficient pool funds`,
|
|
140
|
-
`Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`,
|
|
141
|
-
);
|
|
142
|
-
statusManager.observe(evidence);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
// Mark as Advanced since `transferV` initiates the advance.
|
|
148
|
-
// Will throw if we've already .skipped or .advanced this evidence.
|
|
149
|
-
statusManager.advance(evidence);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
// Only anticipated error is `assertNotSeen`, so intercept the
|
|
152
|
-
// catch so we don't call .skip which also performs this check
|
|
153
|
-
log('Advancer error:', q(e).toString());
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
162
|
const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
|
|
158
163
|
const amountKWR = harden({ USDC: advanceAmount });
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// We catch to report outside of the normal error flow since this is
|
|
165
|
-
// not expected.
|
|
166
|
-
log('🚨 advance borrow failed', q(e).toString());
|
|
167
|
-
}
|
|
164
|
+
// throws if the pool has insufficient funds
|
|
165
|
+
borrowerFacet.borrow(tmpSeat, amountKWR);
|
|
166
|
+
|
|
167
|
+
// this cannot throw since `.isSeen()` is called in the same turn
|
|
168
|
+
statusManager.advance(evidence);
|
|
168
169
|
|
|
169
170
|
const depositV = localTransfer(
|
|
170
171
|
tmpSeat,
|
|
@@ -175,7 +176,9 @@ export const prepareAdvancerKit = (
|
|
|
175
176
|
void watch(depositV, this.facets.depositHandler, {
|
|
176
177
|
amount: advanceAmount,
|
|
177
178
|
destination,
|
|
179
|
+
forwardingAddress: evidence.tx.forwardingAddress,
|
|
178
180
|
tmpSeat,
|
|
181
|
+
txHash: evidence.txHash,
|
|
179
182
|
});
|
|
180
183
|
} catch (e) {
|
|
181
184
|
log('Advancer error:', q(e).toString());
|
|
@@ -186,10 +189,11 @@ export const prepareAdvancerKit = (
|
|
|
186
189
|
depositHandler: {
|
|
187
190
|
/**
|
|
188
191
|
* @param {undefined} result
|
|
189
|
-
* @param {
|
|
192
|
+
* @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
|
|
190
193
|
*/
|
|
191
|
-
onFulfilled(result,
|
|
194
|
+
onFulfilled(result, ctx) {
|
|
192
195
|
const { poolAccount } = this.state;
|
|
196
|
+
const { amount, destination, forwardingAddress, txHash } = ctx;
|
|
193
197
|
const transferV = E(poolAccount).transfer(destination, {
|
|
194
198
|
denom: usdc.denom,
|
|
195
199
|
value: amount.value,
|
|
@@ -197,11 +201,13 @@ export const prepareAdvancerKit = (
|
|
|
197
201
|
return watch(transferV, this.facets.transferHandler, {
|
|
198
202
|
destination,
|
|
199
203
|
amount,
|
|
204
|
+
forwardingAddress,
|
|
205
|
+
txHash,
|
|
200
206
|
});
|
|
201
207
|
},
|
|
202
208
|
/**
|
|
203
209
|
* @param {Error} error
|
|
204
|
-
* @param {
|
|
210
|
+
* @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
|
|
205
211
|
*/
|
|
206
212
|
onRejected(error, { tmpSeat }) {
|
|
207
213
|
// TODO return seat allocation from ctx to LP?
|
|
@@ -217,25 +223,44 @@ export const prepareAdvancerKit = (
|
|
|
217
223
|
transferHandler: {
|
|
218
224
|
/**
|
|
219
225
|
* @param {undefined} result TODO confirm this is not a bigint (sequence)
|
|
220
|
-
* @param {
|
|
226
|
+
* @param {AdvancerVowCtx} ctx
|
|
221
227
|
*/
|
|
222
|
-
onFulfilled(result,
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
onFulfilled(result, ctx) {
|
|
229
|
+
const { notifyFacet } = this.state;
|
|
230
|
+
const { amount, destination, forwardingAddress, txHash } = ctx;
|
|
225
231
|
log(
|
|
226
232
|
'Advance transfer fulfilled',
|
|
227
233
|
q({ amount, destination, result }).toString(),
|
|
228
234
|
);
|
|
235
|
+
notifyFacet.notifyAdvancingResult(
|
|
236
|
+
txHash,
|
|
237
|
+
forwardingAddress,
|
|
238
|
+
amount.value,
|
|
239
|
+
destination.value,
|
|
240
|
+
true,
|
|
241
|
+
);
|
|
229
242
|
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
/**
|
|
244
|
+
* @param {Error} error
|
|
245
|
+
* @param {AdvancerVowCtx} ctx
|
|
246
|
+
*/
|
|
247
|
+
onRejected(error, ctx) {
|
|
248
|
+
const { notifyFacet } = this.state;
|
|
249
|
+
const { amount, destination, forwardingAddress, txHash } = ctx;
|
|
233
250
|
log('Advance transfer rejected', q(error).toString());
|
|
251
|
+
notifyFacet.notifyAdvancingResult(
|
|
252
|
+
txHash,
|
|
253
|
+
forwardingAddress,
|
|
254
|
+
amount.value,
|
|
255
|
+
destination.value,
|
|
256
|
+
false,
|
|
257
|
+
);
|
|
234
258
|
},
|
|
235
259
|
},
|
|
236
260
|
},
|
|
237
261
|
{
|
|
238
262
|
stateShape: harden({
|
|
263
|
+
notifyFacet: M.remotable(),
|
|
239
264
|
borrowerFacet: M.remotable(),
|
|
240
265
|
poolAccount: M.remotable(),
|
|
241
266
|
}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AmountMath
|
|
1
|
+
import { AmountMath } from '@agoric/ertp';
|
|
2
2
|
import {
|
|
3
3
|
makeRecorderTopic,
|
|
4
4
|
TopicsRecordShape,
|
|
@@ -84,7 +84,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
|
|
|
84
84
|
'Liquidity Pool',
|
|
85
85
|
{
|
|
86
86
|
borrower: M.interface('borrower', {
|
|
87
|
-
getBalance: M.call().returns(AmountShape),
|
|
88
87
|
borrow: M.call(
|
|
89
88
|
SeatShape,
|
|
90
89
|
harden({ USDC: makeNatAmountShape(USDC, 1n) }),
|
|
@@ -152,10 +151,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
|
|
|
152
151
|
},
|
|
153
152
|
{
|
|
154
153
|
borrower: {
|
|
155
|
-
getBalance() {
|
|
156
|
-
const { poolSeat } = this.state;
|
|
157
|
-
return poolSeat.getAmountAllocated('USDC', USDC);
|
|
158
|
-
},
|
|
159
154
|
/**
|
|
160
155
|
* @param {ZCFSeat} toSeat
|
|
161
156
|
* @param {{ USDC: Amount<'nat'>}} amountKWR
|
package/src/exos/settler.js
CHANGED
|
@@ -1,97 +1,291 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AmountMath } from '@agoric/ertp';
|
|
2
|
+
import { assertAllDefined, makeTracer } from '@agoric/internal';
|
|
2
3
|
import { atob } from '@endo/base64';
|
|
3
|
-
import {
|
|
4
|
+
import { E } from '@endo/far';
|
|
4
5
|
import { M } from '@endo/patterns';
|
|
5
6
|
|
|
7
|
+
import { PendingTxStatus } from '../constants.js';
|
|
6
8
|
import { addressTools } from '../utils/address.js';
|
|
9
|
+
import { makeFeeTools } from '../utils/fees.js';
|
|
10
|
+
import { EvmHashShape } from '../type-guards.js';
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
|
|
10
|
-
* @import {Denom} from '@agoric/orchestration';
|
|
14
|
+
* @import {Denom, OrchestrationAccount, ChainHub} from '@agoric/orchestration';
|
|
15
|
+
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
|
|
11
16
|
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
|
|
12
17
|
* @import {Zone} from '@agoric/zone';
|
|
13
|
-
* @import {
|
|
18
|
+
* @import {HostOf, HostInterface} from '@agoric/async-flow';
|
|
19
|
+
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
|
|
20
|
+
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash} from '../types.js';
|
|
14
21
|
* @import {StatusManager} from './status-manager.js';
|
|
15
22
|
*/
|
|
16
23
|
|
|
24
|
+
const trace = makeTracer('Settler');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* NOTE: not meant to be parsable.
|
|
28
|
+
*
|
|
29
|
+
* @param {NobleAddress} addr
|
|
30
|
+
* @param {bigint} amount
|
|
31
|
+
*/
|
|
32
|
+
const makeMintedEarlyKey = (addr, amount) =>
|
|
33
|
+
`pendingTx:${JSON.stringify([addr, String(amount)])}`;
|
|
34
|
+
|
|
17
35
|
/**
|
|
18
36
|
* @param {Zone} zone
|
|
19
37
|
* @param {object} caps
|
|
20
38
|
* @param {StatusManager} caps.statusManager
|
|
39
|
+
* @param {Brand<'nat'>} caps.USDC
|
|
40
|
+
* @param {Pick<ZCF, 'makeEmptySeatKit' | 'atomicRearrange'>} caps.zcf
|
|
41
|
+
* @param {FeeConfig} caps.feeConfig
|
|
42
|
+
* @param {HostOf<WithdrawToSeat>} caps.withdrawToSeat
|
|
43
|
+
* @param {import('@agoric/vow').VowTools} caps.vowTools
|
|
44
|
+
* @param {ChainHub} caps.chainHub
|
|
21
45
|
*/
|
|
22
|
-
export const prepareSettler = (
|
|
46
|
+
export const prepareSettler = (
|
|
47
|
+
zone,
|
|
48
|
+
{ statusManager, USDC, zcf, feeConfig, withdrawToSeat, vowTools, chainHub },
|
|
49
|
+
) => {
|
|
23
50
|
assertAllDefined({ statusManager });
|
|
24
|
-
return zone.
|
|
51
|
+
return zone.exoClassKit(
|
|
25
52
|
'Fast USDC Settler',
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
{
|
|
54
|
+
creator: M.interface('SettlerCreatorI', {
|
|
55
|
+
monitorMintingDeposits: M.callWhen().returns(M.any()),
|
|
56
|
+
}),
|
|
57
|
+
tap: M.interface('SettlerTapI', {
|
|
58
|
+
receiveUpcall: M.call(M.record()).returns(M.promise()),
|
|
59
|
+
}),
|
|
60
|
+
notify: M.interface('SettlerNotifyI', {
|
|
61
|
+
notifyAdvancingResult: M.call(
|
|
62
|
+
M.string(),
|
|
63
|
+
M.nat(),
|
|
64
|
+
M.boolean(),
|
|
65
|
+
).returns(),
|
|
66
|
+
}),
|
|
67
|
+
self: M.interface('SettlerSelfI', {
|
|
68
|
+
disburse: M.call(EvmHashShape, M.string(), M.nat()).returns(
|
|
69
|
+
M.promise(),
|
|
70
|
+
),
|
|
71
|
+
forward: M.call(
|
|
72
|
+
M.opt(EvmHashShape),
|
|
73
|
+
M.string(),
|
|
74
|
+
M.nat(),
|
|
75
|
+
M.string(),
|
|
76
|
+
).returns(),
|
|
77
|
+
}),
|
|
78
|
+
transferHandler: M.interface('SettlerTransferI', {
|
|
79
|
+
onFulfilled: M.call(M.any(), M.record()).returns(),
|
|
80
|
+
onRejected: M.call(M.any(), M.record()).returns(),
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
29
83
|
/**
|
|
30
|
-
*
|
|
31
84
|
* @param {{
|
|
32
85
|
* sourceChannel: IBCChannelID;
|
|
33
|
-
* remoteDenom: Denom
|
|
86
|
+
* remoteDenom: Denom;
|
|
87
|
+
* repayer: LiquidityPoolKit['repayer'];
|
|
88
|
+
* settlementAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>
|
|
34
89
|
* }} config
|
|
35
90
|
*/
|
|
36
|
-
config =>
|
|
91
|
+
config => {
|
|
92
|
+
return {
|
|
93
|
+
...config,
|
|
94
|
+
/** @type {HostInterface<TargetRegistration>|undefined} */
|
|
95
|
+
registration: undefined,
|
|
96
|
+
/** @type {SetStore<ReturnType<typeof makeMintedEarlyKey>>} */
|
|
97
|
+
mintedEarly: zone.detached().setStore('mintedEarly'),
|
|
98
|
+
};
|
|
99
|
+
},
|
|
37
100
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// TODO discern between SETTLED and OBSERVED; each has different fees/destinations
|
|
65
|
-
const hasPendingSettlement = statusManager.hasPendingSettlement(
|
|
101
|
+
creator: {
|
|
102
|
+
async monitorMintingDeposits() {
|
|
103
|
+
const { settlementAccount } = this.state;
|
|
104
|
+
const registration = await vowTools.when(
|
|
105
|
+
settlementAccount.monitorTransfers(this.facets.tap),
|
|
106
|
+
);
|
|
107
|
+
assert.typeof(registration, 'object');
|
|
108
|
+
this.state.registration = registration;
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
tap: {
|
|
112
|
+
/** @param {VTransferIBCEvent} event */
|
|
113
|
+
async receiveUpcall(event) {
|
|
114
|
+
const { sourceChannel, remoteDenom } = this.state;
|
|
115
|
+
const { packet } = event;
|
|
116
|
+
if (packet.source_channel !== sourceChannel) {
|
|
117
|
+
const { source_channel: actual } = packet;
|
|
118
|
+
trace('unexpected channel', { actual, expected: sourceChannel });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// TODO: why is it safe to cast this without a runtime check?
|
|
123
|
+
const tx = /** @type {FungibleTokenPacketData} */ (
|
|
124
|
+
JSON.parse(atob(packet.data))
|
|
125
|
+
);
|
|
126
|
+
|
|
66
127
|
// given the sourceChannel check, we can be certain of this cast
|
|
67
|
-
/** @type {NobleAddress} */ (tx.sender)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
128
|
+
const sender = /** @type {NobleAddress} */ (tx.sender);
|
|
129
|
+
|
|
130
|
+
if (tx.denom !== remoteDenom) {
|
|
131
|
+
const { denom: actual } = tx;
|
|
132
|
+
trace('unexpected denom', { actual, expected: remoteDenom });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!addressTools.hasQueryParams(tx.receiver)) {
|
|
137
|
+
console.log('not query params', tx.receiver);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { EUD } = addressTools.getQueryParams(tx.receiver);
|
|
142
|
+
if (!EUD) {
|
|
143
|
+
console.log('no EUD parameter', tx.receiver);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const amount = BigInt(tx.amount); // TODO: what if this throws?
|
|
148
|
+
|
|
149
|
+
const { self } = this.facets;
|
|
150
|
+
const found = statusManager.dequeueStatus(sender, amount);
|
|
151
|
+
trace('dequeued', found, 'for', sender, amount);
|
|
152
|
+
switch (found?.status) {
|
|
153
|
+
case PendingTxStatus.Advanced:
|
|
154
|
+
return self.disburse(found.txHash, sender, amount);
|
|
155
|
+
|
|
156
|
+
case PendingTxStatus.Advancing:
|
|
157
|
+
this.state.mintedEarly.add(makeMintedEarlyKey(sender, amount));
|
|
158
|
+
return;
|
|
159
|
+
|
|
160
|
+
case undefined:
|
|
161
|
+
case PendingTxStatus.Observed:
|
|
162
|
+
case PendingTxStatus.AdvanceFailed:
|
|
163
|
+
default:
|
|
164
|
+
return self.forward(found?.txHash, sender, amount, EUD);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
notify: {
|
|
169
|
+
/**
|
|
170
|
+
* @param {EvmHash} txHash
|
|
171
|
+
* @param {NobleAddress} sender
|
|
172
|
+
* @param {NatValue} amount
|
|
173
|
+
* @param {string} EUD
|
|
174
|
+
* @param {boolean} success
|
|
175
|
+
* @returns {void}
|
|
176
|
+
*/
|
|
177
|
+
notifyAdvancingResult(txHash, sender, amount, EUD, success) {
|
|
178
|
+
const { mintedEarly } = this.state;
|
|
179
|
+
const key = makeMintedEarlyKey(sender, amount);
|
|
180
|
+
if (mintedEarly.has(key)) {
|
|
181
|
+
mintedEarly.delete(key);
|
|
182
|
+
if (success) {
|
|
183
|
+
void this.facets.self.disburse(txHash, sender, amount);
|
|
184
|
+
} else {
|
|
185
|
+
void this.facets.self.forward(txHash, sender, amount, EUD);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
statusManager.advanceOutcome(sender, amount, success);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
self: {
|
|
193
|
+
/**
|
|
194
|
+
* @param {EvmHash} txHash
|
|
195
|
+
* @param {NobleAddress} sender
|
|
196
|
+
* @param {NatValue} amount
|
|
197
|
+
*/
|
|
198
|
+
async disburse(txHash, sender, amount) {
|
|
199
|
+
const { repayer, settlementAccount } = this.state;
|
|
200
|
+
const received = AmountMath.make(USDC, amount);
|
|
201
|
+
const { zcfSeat: settlingSeat } = zcf.makeEmptySeatKit();
|
|
202
|
+
const { calculateSplit } = makeFeeTools(feeConfig);
|
|
203
|
+
const split = calculateSplit(received);
|
|
204
|
+
trace('disbursing', split);
|
|
205
|
+
|
|
206
|
+
// TODO: what if this throws?
|
|
207
|
+
// arguably, it cannot. Even if deposits
|
|
208
|
+
// and notifications get out of order,
|
|
209
|
+
// we don't ever withdraw more than has been deposited.
|
|
210
|
+
await vowTools.when(
|
|
211
|
+
withdrawToSeat(
|
|
212
|
+
// @ts-expect-error Vow vs. Promise stuff. TODO: is this OK???
|
|
213
|
+
settlementAccount,
|
|
214
|
+
settlingSeat,
|
|
215
|
+
harden({ In: received }),
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
zcf.atomicRearrange(
|
|
219
|
+
harden([[settlingSeat, settlingSeat, { In: received }, split]]),
|
|
75
220
|
);
|
|
76
|
-
|
|
221
|
+
repayer.repay(settlingSeat, split);
|
|
77
222
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
223
|
+
// update status manager, marking tx `SETTLED`
|
|
224
|
+
statusManager.disbursed(txHash, sender, amount);
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* @param {EvmHash | undefined} txHash
|
|
228
|
+
* @param {NobleAddress} sender
|
|
229
|
+
* @param {NatValue} amount
|
|
230
|
+
* @param {string} EUD
|
|
231
|
+
*/
|
|
232
|
+
forward(txHash, sender, amount, EUD) {
|
|
233
|
+
const { settlementAccount } = this.state;
|
|
81
234
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
235
|
+
const dest = chainHub.makeChainAddress(EUD);
|
|
236
|
+
|
|
237
|
+
// TODO? statusManager.forwarding(txHash, sender, amount);
|
|
238
|
+
const txfrV = E(settlementAccount).transfer(
|
|
239
|
+
dest,
|
|
240
|
+
AmountMath.make(USDC, amount),
|
|
241
|
+
);
|
|
242
|
+
void vowTools.watch(txfrV, this.facets.transferHandler, {
|
|
243
|
+
txHash,
|
|
244
|
+
sender,
|
|
245
|
+
amount,
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
transferHandler: {
|
|
250
|
+
/**
|
|
251
|
+
* @param {unknown} result
|
|
252
|
+
* @param {SettlerTransferCtx} ctx
|
|
253
|
+
*
|
|
254
|
+
* @typedef {{
|
|
255
|
+
* txHash: EvmHash;
|
|
256
|
+
* sender: NobleAddress;
|
|
257
|
+
* amount: NatValue;
|
|
258
|
+
* }} SettlerTransferCtx
|
|
259
|
+
*/
|
|
260
|
+
onFulfilled(result, ctx) {
|
|
261
|
+
const { txHash, sender, amount } = ctx;
|
|
262
|
+
statusManager.forwarded(txHash, sender, amount);
|
|
263
|
+
},
|
|
264
|
+
/**
|
|
265
|
+
* @param {unknown} _result
|
|
266
|
+
* @param {SettlerTransferCtx} _ctx
|
|
267
|
+
*/
|
|
268
|
+
onRejected(_result, _ctx) {
|
|
269
|
+
// const { txHash, sender, amount } = ctx;
|
|
270
|
+
// TODO: statusManager.forwardFailed(txHash, sender, amount);
|
|
271
|
+
},
|
|
87
272
|
},
|
|
88
273
|
},
|
|
89
274
|
{
|
|
90
275
|
stateShape: harden({
|
|
276
|
+
repayer: M.remotable('Repayer'),
|
|
277
|
+
settlementAccount: M.remotable('Account'),
|
|
278
|
+
registration: M.or(M.undefined(), M.remotable('Registration')),
|
|
91
279
|
sourceChannel: M.string(),
|
|
92
280
|
remoteDenom: M.string(),
|
|
281
|
+
mintedEarly: M.remotable('mintedEarly'),
|
|
93
282
|
}),
|
|
94
283
|
},
|
|
95
284
|
);
|
|
96
285
|
};
|
|
97
286
|
harden(prepareSettler);
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* XXX consider using pickFacet (do we have pickFacets?)
|
|
290
|
+
* @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit
|
|
291
|
+
*/
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { M } from '@endo/patterns';
|
|
2
|
-
import { makeError, q } from '@endo/errors';
|
|
2
|
+
import { Fail, makeError, q } from '@endo/errors';
|
|
3
3
|
|
|
4
4
|
import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CctpTxEvidenceShape,
|
|
7
|
+
EvmHashShape,
|
|
8
|
+
PendingTxShape,
|
|
9
|
+
} from '../type-guards.js';
|
|
6
10
|
import { PendingTxStatus } from '../constants.js';
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* @import {MapStore, SetStore} from '@agoric/store';
|
|
10
14
|
* @import {Zone} from '@agoric/zone';
|
|
11
|
-
* @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx} from '../types.js';
|
|
15
|
+
* @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx, EvmHash} from '../types.js';
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -96,21 +100,69 @@ export const prepareStatusManager = zone => {
|
|
|
96
100
|
return zone.exo(
|
|
97
101
|
'Fast USDC Status Manager',
|
|
98
102
|
M.interface('StatusManagerI', {
|
|
103
|
+
// TODO: naming scheme for transition events
|
|
99
104
|
advance: M.call(CctpTxEvidenceShape).returns(M.undefined()),
|
|
105
|
+
advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
|
|
100
106
|
observe: M.call(CctpTxEvidenceShape).returns(M.undefined()),
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
|
|
108
|
+
dequeueStatus: M.call(M.string(), M.bigint()).returns(
|
|
109
|
+
M.or(
|
|
110
|
+
{
|
|
111
|
+
txHash: EvmHashShape,
|
|
112
|
+
status: M.or(
|
|
113
|
+
PendingTxStatus.Advanced,
|
|
114
|
+
PendingTxStatus.AdvanceFailed,
|
|
115
|
+
PendingTxStatus.Observed,
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
M.undefined(),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
disbursed: M.call(EvmHashShape, M.string(), M.nat()).returns(
|
|
122
|
+
M.undefined(),
|
|
123
|
+
),
|
|
124
|
+
forwarded: M.call(M.opt(EvmHashShape), M.string(), M.nat()).returns(
|
|
125
|
+
M.undefined(),
|
|
126
|
+
),
|
|
103
127
|
lookupPending: M.call(M.string(), M.bigint()).returns(
|
|
104
128
|
M.arrayOf(PendingTxShape),
|
|
105
129
|
),
|
|
106
130
|
}),
|
|
107
131
|
{
|
|
108
132
|
/**
|
|
109
|
-
* Add a new transaction with
|
|
133
|
+
* Add a new transaction with ADVANCING status
|
|
110
134
|
* @param {CctpTxEvidence} evidence
|
|
111
135
|
*/
|
|
112
136
|
advance(evidence) {
|
|
113
|
-
recordPendingTx(evidence, PendingTxStatus.
|
|
137
|
+
recordPendingTx(evidence, PendingTxStatus.Advancing);
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Record result of ADVANCING
|
|
142
|
+
*
|
|
143
|
+
* @param {NobleAddress} sender
|
|
144
|
+
* @param {import('@agoric/ertp').NatValue} amount
|
|
145
|
+
* @param {boolean} success - Advanced vs. AdvanceFailed
|
|
146
|
+
* @throws {Error} if nothing to advance
|
|
147
|
+
*/
|
|
148
|
+
advanceOutcome(sender, amount, success) {
|
|
149
|
+
const key = makePendingTxKey(sender, amount);
|
|
150
|
+
pendingTxs.has(key) || Fail`no advancing tx with ${{ sender, amount }}`;
|
|
151
|
+
const pending = pendingTxs.get(key);
|
|
152
|
+
const ix = pending.findIndex(
|
|
153
|
+
tx => tx.status === PendingTxStatus.Advancing,
|
|
154
|
+
);
|
|
155
|
+
ix >= 0 || Fail`no advancing tx with ${{ sender, amount }}`;
|
|
156
|
+
const [prefix, tx, suffix] = [
|
|
157
|
+
pending.slice(0, ix),
|
|
158
|
+
pending[ix],
|
|
159
|
+
pending.slice(ix + 1),
|
|
160
|
+
];
|
|
161
|
+
const status = success
|
|
162
|
+
? PendingTxStatus.Advanced
|
|
163
|
+
: PendingTxStatus.AdvanceFailed;
|
|
164
|
+
const txpost = { ...tx, status };
|
|
165
|
+
pendingTxs.set(key, harden([...prefix, txpost, ...suffix]));
|
|
114
166
|
},
|
|
115
167
|
|
|
116
168
|
/**
|
|
@@ -122,41 +174,76 @@ export const prepareStatusManager = zone => {
|
|
|
122
174
|
},
|
|
123
175
|
|
|
124
176
|
/**
|
|
125
|
-
*
|
|
177
|
+
* Note: ADVANCING state implies tx has been OBSERVED
|
|
126
178
|
*
|
|
127
|
-
* @param {
|
|
128
|
-
* @param {bigint} amount
|
|
129
|
-
* @returns {boolean}
|
|
179
|
+
* @param {CctpTxEvidence} evidence
|
|
130
180
|
*/
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
return !!pending.length;
|
|
181
|
+
hasBeenObserved(evidence) {
|
|
182
|
+
const seenKey = seenTxKeyOf(evidence);
|
|
183
|
+
return seenTxs.has(seenKey);
|
|
135
184
|
},
|
|
136
185
|
|
|
137
186
|
/**
|
|
138
|
-
*
|
|
187
|
+
* Remove and return an `ADVANCED` or `OBSERVED` tx waiting to be `SETTLED`.
|
|
139
188
|
*
|
|
140
189
|
* @param {NobleAddress} address
|
|
141
190
|
* @param {bigint} amount
|
|
191
|
+
* @returns {Pick<PendingTx, 'status' | 'txHash'> | undefined} undefined if nothing
|
|
192
|
+
* with this address and amount has been marked pending.
|
|
142
193
|
*/
|
|
143
|
-
|
|
194
|
+
dequeueStatus(address, amount) {
|
|
144
195
|
const key = makePendingTxKey(address, amount);
|
|
196
|
+
if (!pendingTxs.has(key)) return undefined;
|
|
145
197
|
const pending = pendingTxs.get(key);
|
|
146
198
|
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
const dequeueIdx = pending.findIndex(
|
|
200
|
+
x => x.status !== PendingTxStatus.Advancing,
|
|
201
|
+
);
|
|
202
|
+
if (dequeueIdx < 0) return undefined;
|
|
203
|
+
|
|
204
|
+
if (pending.length > 1) {
|
|
205
|
+
const pendingCopy = [...pending];
|
|
206
|
+
pendingCopy.splice(dequeueIdx, 1);
|
|
207
|
+
pendingTxs.set(key, harden(pendingCopy));
|
|
208
|
+
} else {
|
|
209
|
+
pendingTxs.delete(key);
|
|
149
210
|
}
|
|
150
211
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
212
|
+
const { status, txHash } = pending[dequeueIdx];
|
|
213
|
+
// TODO: store txHash -> evidence for txs pending settlement?
|
|
214
|
+
// If necessary for vstorage writes in `forwarded` and `settled`
|
|
215
|
+
return harden({ status, txHash });
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Mark a transaction as `DISBURSED`
|
|
220
|
+
*
|
|
221
|
+
* @param {EvmHash} txHash
|
|
222
|
+
* @param {NobleAddress} address
|
|
223
|
+
* @param {bigint} amount
|
|
224
|
+
*/
|
|
225
|
+
disbursed(txHash, address, amount) {
|
|
226
|
+
// TODO: store txHash -> evidence for txs pending settlement?
|
|
227
|
+
console.log('TODO: vstorage update', { txHash, address, amount });
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Mark a transaction as `FORWARDED`
|
|
232
|
+
*
|
|
233
|
+
* @param {EvmHash | undefined} txHash - undefined in case mint before observed
|
|
234
|
+
* @param {NobleAddress} address
|
|
235
|
+
* @param {bigint} amount
|
|
236
|
+
*/
|
|
237
|
+
forwarded(txHash, address, amount) {
|
|
238
|
+
// TODO: store txHash -> evidence for txs pending settlement?
|
|
239
|
+
console.log('TODO: vstorage update', { txHash, address, amount });
|
|
155
240
|
},
|
|
156
241
|
|
|
157
242
|
/**
|
|
158
243
|
* Lookup all pending entries for a given address and amount
|
|
159
244
|
*
|
|
245
|
+
* XXX only used in tests. should we remove?
|
|
246
|
+
*
|
|
160
247
|
* @param {NobleAddress} address
|
|
161
248
|
* @param {bigint} amount
|
|
162
249
|
* @returns {PendingTx[]}
|
|
@@ -164,7 +251,7 @@ export const prepareStatusManager = zone => {
|
|
|
164
251
|
lookupPending(address, amount) {
|
|
165
252
|
const key = makePendingTxKey(address, amount);
|
|
166
253
|
if (!pendingTxs.has(key)) {
|
|
167
|
-
|
|
254
|
+
return harden([]);
|
|
168
255
|
}
|
|
169
256
|
return pendingTxs.get(key);
|
|
170
257
|
},
|
|
@@ -71,14 +71,28 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
71
71
|
const terms = zcf.getTerms();
|
|
72
72
|
assert('USDC' in terms.brands, 'no USDC brand');
|
|
73
73
|
assert('usdcDenom' in terms, 'no usdcDenom');
|
|
74
|
+
|
|
74
75
|
const { feeConfig, marshaller } = privateArgs;
|
|
75
76
|
const { makeRecorderKit } = prepareRecorderKitMakers(
|
|
76
77
|
zone.mapStore('vstorage'),
|
|
77
78
|
marshaller,
|
|
78
79
|
);
|
|
80
|
+
|
|
79
81
|
const statusManager = prepareStatusManager(zone);
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
const { USDC } = terms.brands;
|
|
84
|
+
const { withdrawToSeat } = tools.zoeTools;
|
|
81
85
|
const { chainHub, orchestrateAll, vowTools } = tools;
|
|
86
|
+
const makeSettler = prepareSettler(zone, {
|
|
87
|
+
statusManager,
|
|
88
|
+
USDC,
|
|
89
|
+
withdrawToSeat,
|
|
90
|
+
feeConfig,
|
|
91
|
+
vowTools: tools.vowTools,
|
|
92
|
+
zcf,
|
|
93
|
+
chainHub,
|
|
94
|
+
});
|
|
95
|
+
|
|
82
96
|
const { localTransfer } = makeZoeTools(zcf, vowTools);
|
|
83
97
|
const makeAdvancer = prepareAdvancer(zone, {
|
|
84
98
|
chainHub,
|
|
@@ -92,8 +106,10 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
92
106
|
vowTools,
|
|
93
107
|
zcf,
|
|
94
108
|
});
|
|
109
|
+
|
|
95
110
|
const makeFeedKit = prepareTransactionFeedKit(zone, zcf);
|
|
96
111
|
assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager });
|
|
112
|
+
|
|
97
113
|
const makeLiquidityPoolKit = prepareLiquidityPoolKit(
|
|
98
114
|
zone,
|
|
99
115
|
zcf,
|
|
@@ -111,7 +127,6 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
111
127
|
const creatorFacet = zone.exo('Fast USDC Creator', undefined, {
|
|
112
128
|
/** @type {(operatorId: string) => Promise<Invitation<OperatorKit>>} */
|
|
113
129
|
async makeOperatorInvitation(operatorId) {
|
|
114
|
-
// eslint-disable-next-line no-use-before-define
|
|
115
130
|
return feedKit.creator.makeOperatorInvitation(operatorId);
|
|
116
131
|
},
|
|
117
132
|
/**
|
|
@@ -157,7 +172,6 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
157
172
|
* @param {CctpTxEvidence} evidence
|
|
158
173
|
*/
|
|
159
174
|
makeTestPushInvitation(evidence) {
|
|
160
|
-
// eslint-disable-next-line no-use-before-define
|
|
161
175
|
void advancer.handleTransactionEvent(evidence);
|
|
162
176
|
return makeTestInvitation();
|
|
163
177
|
},
|
|
@@ -200,18 +214,27 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
200
214
|
|
|
201
215
|
const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit());
|
|
202
216
|
|
|
203
|
-
const poolAccountV =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
const poolAccountV = zone.makeOnce('PoolAccount', () => makeLocalAccount());
|
|
218
|
+
const settleAccountV = zone.makeOnce('SettleAccount', () =>
|
|
219
|
+
makeLocalAccount(),
|
|
220
|
+
);
|
|
221
|
+
// when() is OK here since this clearly resolves promptly.
|
|
222
|
+
/** @type {HostInterface<OrchestrationAccount<{chainId: 'agoric';}>>[]} */
|
|
223
|
+
const [poolAccount, settlementAccount] = await vowTools.when(
|
|
224
|
+
vowTools.all([poolAccountV, settleAccountV]),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const settlerKit = makeSettler({
|
|
228
|
+
repayer: poolKit.repayer,
|
|
229
|
+
sourceChannel: 'channel-1234', // TODO: fix this as soon as testing needs it',
|
|
230
|
+
remoteDenom: 'uusdc',
|
|
231
|
+
settlementAccount,
|
|
232
|
+
});
|
|
211
233
|
|
|
212
234
|
const advancer = zone.makeOnce('Advancer', () =>
|
|
213
235
|
makeAdvancer({
|
|
214
236
|
borrowerFacet: poolKit.borrower,
|
|
237
|
+
notifyFacet: settlerKit.notify,
|
|
215
238
|
poolAccount,
|
|
216
239
|
}),
|
|
217
240
|
);
|
|
@@ -226,6 +249,8 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
226
249
|
},
|
|
227
250
|
});
|
|
228
251
|
|
|
252
|
+
await settlerKit.creator.monitorMintingDeposits();
|
|
253
|
+
|
|
229
254
|
return harden({ creatorFacet, publicFacet });
|
|
230
255
|
};
|
|
231
256
|
harden(contract);
|
package/src/types.ts
CHANGED
package/src/exos/README.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
## **StatusManager** state diagram, showing different transitions
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
### Contract state diagram
|
|
5
|
-
|
|
6
|
-
*Transactions are qualified by the OCW and EventFeed before arriving to the Advancer.*
|
|
7
|
-
|
|
8
|
-
```mermaid
|
|
9
|
-
stateDiagram-v2
|
|
10
|
-
[*] --> Advanced: Advancer .advance()
|
|
11
|
-
Advanced --> Settled: Settler .settle() after fees
|
|
12
|
-
[*] --> Observed: Advancer .observed()
|
|
13
|
-
Observed --> Settled: Settler .settle() sans fees
|
|
14
|
-
Settled --> [*]
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Complete state diagram (starting from OCW)
|
|
18
|
-
|
|
19
|
-
```mermaid
|
|
20
|
-
stateDiagram-v2
|
|
21
|
-
Observed --> Qualified
|
|
22
|
-
Observed --> Unqualified
|
|
23
|
-
Qualified --> Advanced
|
|
24
|
-
Advanced --> Settled
|
|
25
|
-
Qualified --> Settled
|
|
26
|
-
```
|