1
+ import { AmountMath , AmountShape , BrandShape } from '@agoric/ertp' ;
1
2
import { assertAllDefined } from '@agoric/internal' ;
2
3
import { ChainAddressShape } from '@agoric/orchestration' ;
4
+ import { pickFacet } from '@agoric/vat-data' ;
3
5
import { VowShape } from '@agoric/vow' ;
4
6
import { makeError , q } from '@endo/errors' ;
5
7
import { E } from '@endo/far' ;
@@ -9,116 +11,219 @@ import { addressTools } from '../utils/address.js';
9
11
10
12
/**
11
13
* @import {HostInterface} from '@agoric/async-flow';
14
+ * @import {NatAmount} from '@agoric/ertp';
12
15
* @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration';
13
16
* @import {VowTools} from '@agoric/vow';
14
17
* @import {Zone} from '@agoric/zone';
15
18
* @import {CctpTxEvidence, LogFn} from '../types.js';
16
19
* @import {StatusManager} from './status-manager.js';
17
- * @import {TransactionFeedKit} from './transaction-feed.js';
18
20
*/
19
21
22
+ /**
23
+ * Expected interface from LiquidityPool
24
+ *
25
+ * @typedef {{
26
+ * lookupBalance(): NatAmount;
27
+ * borrowUnderlying(amount: Amount<"nat">): Promise<PaymentPKeywordRecord>;
28
+ * returnUnderlying(principalPayment: Payment<"nat">, feePayment: Payment<"nat">): Promise<void>
29
+ * }} AssetManagerFacet
30
+ */
31
+
32
+ /**
33
+ * @typedef {{
34
+ * chainHub: ChainHub;
35
+ * log: LogFn;
36
+ * statusManager: StatusManager;
37
+ * vowTools: VowTools;
38
+ * }} AdvancerKitCaps
39
+ */
40
+
41
+ /** type guards internal to the AdvancerKit */
42
+ const WatcherHandlersShape = {
43
+ depositHandler : M . interface ( 'DepositHandlerI' , {
44
+ onFulfilled : M . call ( AmountShape , ChainAddressShape ) . returns ( VowShape ) ,
45
+ } ) ,
46
+ transferHandler : M . interface ( 'TransferHandlerI' , {
47
+ // TODO confirm undefined, and not bigint (sequence)
48
+ onFulfilled : M . call ( M . undefined ( ) , {
49
+ amount : AmountShape ,
50
+ destination : ChainAddressShape ,
51
+ } ) . returns ( M . undefined ( ) ) ,
52
+ onRejected : M . call ( M . error ( ) , {
53
+ amount : AmountShape ,
54
+ destination : ChainAddressShape ,
55
+ } ) . returns ( M . undefined ( ) ) ,
56
+ } ) ,
57
+ } ;
58
+
20
59
/**
21
60
* @param {Zone } zone
22
- * @param {object } caps
23
- * @param {ChainHub } caps.chainHub
24
- * @param {LogFn } caps.log
25
- * @param {StatusManager } caps.statusManager
26
- * @param {VowTools } caps.vowTools
61
+ * @param {AdvancerKitCaps } caps
27
62
*/
28
- export const prepareAdvancer = (
63
+ export const prepareAdvancerKit = (
29
64
zone ,
30
- { chainHub, log, statusManager, vowTools : { watch } } ,
65
+ { chainHub, log, statusManager, vowTools : { watch, when } } ,
31
66
) => {
32
- assertAllDefined ( { statusManager, watch } ) ;
67
+ assertAllDefined ( {
68
+ chainHub,
69
+ statusManager,
70
+ watch,
71
+ when,
72
+ } ) ;
33
73
34
- const transferHandler = zone . exo (
35
- 'Fast USDC Advance Transfer Handler' ,
36
- M . interface ( 'TransferHandlerI' , {
37
- // TODO confirm undefined, and not bigint (sequence)
38
- onFulfilled : M . call ( M . undefined ( ) , {
39
- amount : M . bigint ( ) ,
40
- destination : ChainAddressShape ,
41
- } ) . returns ( M . undefined ( ) ) ,
42
- onRejected : M . call ( M . error ( ) , {
43
- amount : M . bigint ( ) ,
44
- destination : ChainAddressShape ,
45
- } ) . returns ( M . undefined ( ) ) ,
46
- } ) ,
74
+ return zone . exoClassKit (
75
+ 'Fast USDC Advancer' ,
47
76
{
48
- /**
49
- * @param {undefined } result TODO confirm this is not a bigint (sequence)
50
- * @param {{ destination: ChainAddress; amount: bigint; } } ctx
51
- */
52
- onFulfilled ( result , { destination, amount } ) {
53
- log (
54
- 'Advance transfer fulfilled' ,
55
- q ( { amount, destination, result } ) . toString ( ) ,
56
- ) ;
57
- } ,
58
- onRejected ( error ) {
59
- // XXX retry logic?
60
- // What do we do if we fail, should we keep a Status?
61
- log ( 'Advance transfer rejected' , q ( error ) . toString ( ) ) ;
62
- } ,
77
+ advancer : M . interface ( 'AdvancerI' , {
78
+ handleTransactionEvent : M . callWhen ( CctpTxEvidenceShape ) . returns (
79
+ M . or ( M . undefined ( ) , VowShape ) ,
80
+ ) ,
81
+ } ) ,
82
+ ...WatcherHandlersShape ,
63
83
} ,
64
- ) ;
65
-
66
- return zone . exoClass (
67
- 'Fast USDC Advancer' ,
68
- M . interface ( 'AdvancerI' , {
69
- handleTransactionEvent : M . call ( CctpTxEvidenceShape ) . returns ( VowShape ) ,
70
- } ) ,
71
84
/**
72
85
* @param {{
86
+ * assetManagerFacet: AssetManagerFacet;
73
87
* localDenom: Denom;
74
- * poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
88
+ * poolAccount: ERef<HostInterface<OrchestrationAccount<{chainId: 'agoric';}>>>
89
+ * usdcBrand: Brand<'nat'>;
75
90
* }} config
76
91
*/
77
92
config => harden ( config ) ,
78
93
{
79
- /** @param {CctpTxEvidence } evidence */
80
- handleTransactionEvent ( evidence ) {
81
- // TODO EventFeed will perform input validation checks.
82
- const { recipientAddress } = evidence . aux ;
83
- const { EUD } = addressTools . getQueryParams ( recipientAddress ) . params ;
84
- if ( ! EUD ) {
85
- statusManager . observe ( evidence ) ;
86
- throw makeError (
87
- `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
88
- ) ;
89
- }
94
+ advancer : {
95
+ /**
96
+ * Returns a Promise for a Vow in the happy path, or undefined
97
+ * when conditions are not met. Aims to perform a status update for
98
+ * every observed transaction.
99
+ *
100
+ * We do not expect any callers to depend on the settlement of
101
+ * `handleTransactionEvent` - errors caught are communicated to the
102
+ * `StatusManager` - so we don't need to concern ourselves with
103
+ * preserving the vow chain for callers.
104
+ *
105
+ * @param {CctpTxEvidence } evidence
106
+ */
107
+ async handleTransactionEvent ( evidence ) {
108
+ await null ;
109
+ try {
110
+ // TODO poolAccount might be a vow we need to unwrap
111
+ const { assetManagerFacet, poolAccount, usdcBrand } = this . state ;
112
+ const { recipientAddress } = evidence . aux ;
113
+ const { EUD } =
114
+ addressTools . getQueryParams ( recipientAddress ) . params ;
115
+ if ( ! EUD ) {
116
+ throw makeError (
117
+ `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
118
+ ) ;
119
+ }
90
120
91
- // TODO #10391 this can throw, and should make a status update in the catch
92
- const destination = chainHub . makeChainAddress ( EUD ) ;
121
+ // this will throw if the bech32 prefix is not found, but is handled by the catch
122
+ const destination = chainHub . makeChainAddress ( EUD ) ;
93
123
94
- /** @type {DenomAmount } */
95
- const requestedAmount = harden ( {
96
- denom : this . state . localDenom ,
97
- value : BigInt ( evidence . tx . amount ) ,
98
- } ) ;
124
+ const requestedValue = BigInt ( evidence . tx . amount ) ;
125
+ const requestedAmount = AmountMath . make ( usdcBrand , requestedValue ) ;
126
+ const poolBalance = assetManagerFacet . lookupBalance ( ) ;
99
127
100
- // TODO #10391 ensure there's enough funds in poolAccount
128
+ if ( ! AmountMath . isGTE ( poolBalance , requestedAmount ) ) {
129
+ log (
130
+ `Insufficient pool funds` ,
131
+ `Requested ${ q ( requestedAmount ) } but only have ${ q ( poolBalance ) } ` ,
132
+ ) ;
133
+ statusManager . observe ( evidence ) ;
134
+ return ;
135
+ }
101
136
102
- const transferV = E ( this . state . poolAccount ) . transfer (
103
- destination ,
104
- requestedAmount ,
105
- ) ;
137
+ try {
138
+ // mark as Advanced since `transferV` initiates the advance
139
+ // will throw if we've already .skipped or .advanced this evidence
140
+ statusManager . advance ( evidence ) ;
141
+ } catch ( e ) {
142
+ // only anticipated error is `assertNotSeen`, so
143
+ // intercept the catch so we don't call .skip which
144
+ // also performs this check
145
+ log ( 'Advancer error:' , q ( e ) . toString ( ) ) ;
146
+ return ;
147
+ }
106
148
107
- // mark as Advanced since `transferV` initiates the advance
108
- statusManager . advance ( evidence ) ;
149
+ try {
150
+ // should LiquidityPool return a vow here?
151
+ const { USDC : advancePmtP } =
152
+ await assetManagerFacet . borrowUnderlying ( requestedAmount ) ;
109
153
110
- return watch ( transferV , transferHandler , {
111
- destination,
112
- amount : requestedAmount . value ,
113
- } ) ;
154
+ // do we actually need to await here?
155
+ const advancePmt = await advancePmtP ;
156
+ const depositV = E ( poolAccount ) . deposit ( advancePmt ) ;
157
+ return watch ( depositV , this . facets . depositHandler , destination ) ;
158
+ } catch ( e ) {
159
+ // TODO how should we think about failure here?
160
+ log ( 'Ruh roh' , q ( e ) . toString ( ) ) ;
161
+ throw e ;
162
+ }
163
+ } catch ( e ) {
164
+ log ( 'Advancer error:' , q ( e ) . toString ( ) ) ;
165
+ statusManager . observe ( evidence ) ;
166
+ }
167
+ } ,
168
+ } ,
169
+ depositHandler : {
170
+ /**
171
+ * @param {NatAmount } amount amount returned from deposit
172
+ * @param {ChainAddress } destination
173
+ */
174
+ onFulfilled ( amount , destination ) {
175
+ const { localDenom, poolAccount } = this . state ;
176
+ const transferV = E ( poolAccount ) . transfer (
177
+ destination ,
178
+ /** @type {DenomAmount } */ ( {
179
+ denom : localDenom ,
180
+ value : amount . value ,
181
+ } ) ,
182
+ ) ;
183
+ return watch ( transferV , this . facets . transferHandler , {
184
+ destination,
185
+ amount,
186
+ } ) ;
187
+ } ,
188
+ // xxx return payment on rejected
189
+ } ,
190
+ transferHandler : {
191
+ /**
192
+ * @param {undefined } result TODO confirm this is not a bigint (sequence)
193
+ * @param {{ destination: ChainAddress; amount: NatAmount; } } ctx
194
+ */
195
+ onFulfilled ( result , { destination, amount } ) {
196
+ // TODO vstorage update?
197
+ log (
198
+ 'Advance transfer fulfilled' ,
199
+ q ( { amount, destination, result } ) . toString ( ) ,
200
+ ) ;
201
+ } ,
202
+ onRejected ( error ) {
203
+ // XXX retry logic?
204
+ // What do we do if we fail, should we keep a Status?
205
+ log ( 'Advance transfer rejected' , q ( error ) . toString ( ) ) ;
206
+ } ,
114
207
} ,
115
208
} ,
116
209
{
117
210
stateShape : harden ( {
211
+ assetManagerFacet : M . remotable ( ) ,
118
212
localDenom : M . string ( ) ,
119
- poolAccount : M . remotable ( ) ,
213
+ poolAccount : M . or ( VowShape , M . remotable ( ) ) ,
214
+ usdcBrand : BrandShape ,
120
215
} ) ,
121
216
} ,
122
217
) ;
123
218
} ;
219
+ harden ( prepareAdvancerKit ) ;
220
+
221
+ /**
222
+ * @param {Zone } zone
223
+ * @param {AdvancerKitCaps } caps
224
+ */
225
+ export const prepareAdvancer = ( zone , caps ) => {
226
+ const makeAdvancerKit = prepareAdvancerKit ( zone , caps ) ;
227
+ return pickFacet ( makeAdvancerKit , 'advancer' ) ;
228
+ } ;
124
229
harden ( prepareAdvancer ) ;
0 commit comments