1
+ import { AmountMath , BrandShape } from '@agoric/ertp' ;
1
2
import { assertAllDefined } from '@agoric/internal' ;
2
3
import { ChainAddressShape } from '@agoric/orchestration' ;
3
4
import { VowShape } from '@agoric/vow' ;
@@ -28,7 +29,7 @@ import { addressTools } from '../utils/address.js';
28
29
*/
29
30
export const prepareAdvancer = (
30
31
zone ,
31
- { chainHub, feed, log, statusManager, vowTools : { watch } } ,
32
+ { chainHub, feed, log, statusManager, vowTools : { watch, when } } ,
32
33
) => {
33
34
assertAllDefined ( { feed, statusManager, watch } ) ;
34
35
@@ -51,6 +52,7 @@ export const prepareAdvancer = (
51
52
* @param {{ destination: ChainAddress; amount: bigint; } } ctx
52
53
*/
53
54
onFulfilled ( result , { destination, amount } ) {
55
+ // TODO vstorage update?
54
56
log (
55
57
'Advance transfer fulfilled' ,
56
58
q ( { amount, destination, result } ) . toString ( ) ,
@@ -67,12 +69,15 @@ export const prepareAdvancer = (
67
69
return zone . exoClass (
68
70
'Fast USDC Advancer' ,
69
71
M . interface ( 'AdvancerI' , {
70
- handleTransactionEvent : M . call ( CctpTxEvidenceShape ) . returns ( VowShape ) ,
72
+ handleTransactionEvent : M . callWhen ( CctpTxEvidenceShape ) . returns (
73
+ M . or ( M . undefined ( ) , VowShape ) ,
74
+ ) ,
71
75
} ) ,
72
76
/**
73
77
* @param {{
74
78
* localDenom: Denom;
75
79
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
80
+ * usdcBrand: Brand<'nat'>;
76
81
* }} config
77
82
*/
78
83
config => harden ( config ) ,
@@ -81,56 +86,98 @@ export const prepareAdvancer = (
81
86
* XXX For now, assume this is invoked when a new entry is
82
87
* observed via the EventFeed.
83
88
*
89
+ * Returns a Promise for a Vow, since we we `await vt.when()`the
90
+ * `poolAccount.getBalance()` call. `getBalance()` interfaces with
91
+ * `BankManager` and `ChainHub`, so we can expect the calls to resolve
92
+ * promptly.
93
+ *
94
+ *
95
+ * This also might return `undefined` (unwrapped) if precondition checks
96
+ * fail.
97
+ *
98
+ * We don't expect any callers to depend on the settlement of
99
+ * `handleTransactionEvent` - errors caught are communicated to the
100
+ * `StatusManager` - so we don't need to concern ourselves with
101
+ * preserving the vow chain for callers.
102
+ *
84
103
* @param {CctpTxEvidence } evidence
85
104
*/
86
- handleTransactionEvent ( evidence ) {
87
- // XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
105
+ async handleTransactionEvent ( evidence ) {
106
+ await null ;
107
+ try {
108
+ const { recipientAddress } = evidence . aux ;
109
+ const { EUD } = addressTools . getQueryParams ( recipientAddress ) . params ;
110
+ // XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
111
+ if ( ! EUD ) {
112
+ throw makeError (
113
+ `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
114
+ ) ;
115
+ }
88
116
89
- const { recipientAddress } = evidence . aux ;
90
- const { EUD } = addressTools . getQueryParams ( recipientAddress ) . params ;
91
- if ( ! EUD ) {
92
- statusManager . skip ( evidence ) ;
93
- throw makeError (
94
- `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
95
- ) ;
96
- }
117
+ // this will throw if the bech32 prefix is not found
118
+ const { chainId } = chainHub . getChainInfoByAddress ( EUD ) ;
97
119
98
- // TODO #10391 this can throw, and should make a status update in the catch
99
- const { chainId } = chainHub . getChainInfoByAddress ( EUD ) ;
100
-
101
- /** @type {DenomAmount } */
102
- const requestedAmount = harden ( {
103
- denom : this . state . localDenom ,
104
- value : BigInt ( evidence . tx . amount ) ,
105
- } ) ;
120
+ /** @type {DenomAmount } */
121
+ const requestedAmount = harden ( {
122
+ denom : this . state . localDenom ,
123
+ value : BigInt ( evidence . tx . amount ) ,
124
+ } ) ;
125
+ // ensure there's enough funds in poolAccount
126
+ // XXX safe? `getBalance` calls out to `BankManager` and `ChainHub`
127
+ const poolBalance = await when (
128
+ E ( this . state . poolAccount ) . getBalance ( this . state . localDenom ) ,
129
+ ) ;
106
130
107
- // TODO #10391 ensure there's enough funds in poolAccount
131
+ if (
132
+ ! AmountMath . isGTE (
133
+ AmountMath . make ( this . state . usdcBrand , poolBalance . value ) ,
134
+ AmountMath . make ( this . state . usdcBrand , requestedAmount . value ) ,
135
+ )
136
+ ) {
137
+ log (
138
+ `Insufficient pool funds` ,
139
+ `Requested ${ q ( requestedAmount ) } but only have ${ q ( poolBalance ) } ` ,
140
+ ) ;
141
+ statusManager . skip ( evidence ) ;
142
+ return ;
143
+ }
108
144
109
- /** @type {ChainAddress } */
110
- const destination = harden ( {
111
- chainId,
112
- value : EUD ,
113
- encoding : /** @type {const } */ ( 'bech32' ) ,
114
- } ) ;
145
+ /** @type {ChainAddress } */
146
+ const destination = harden ( {
147
+ chainId,
148
+ value : EUD ,
149
+ encoding : /** @type {const } */ ( 'bech32' ) ,
150
+ } ) ;
115
151
116
- const transferV = E ( this . state . poolAccount ) . transfer (
117
- destination ,
118
- requestedAmount ,
119
- ) ;
152
+ // mark as Advanced since we're initiating the advance
153
+ try {
154
+ // will throw if we've already .skipped or .advanced this evidence
155
+ statusManager . advance ( evidence ) ;
156
+ } catch ( e ) {
157
+ log ( 'Advancer error:' , q ( e ) . toString ( ) ) ;
158
+ return ;
159
+ }
120
160
121
- // mark as Advanced since we're initiating the advance
122
- statusManager . advance ( evidence ) ;
161
+ const transferV = E ( this . state . poolAccount ) . transfer (
162
+ destination ,
163
+ requestedAmount ,
164
+ ) ;
123
165
124
- return watch ( transferV , transferHandler , {
125
- destination,
126
- amount : requestedAmount . value ,
127
- } ) ;
166
+ return watch ( transferV , transferHandler , {
167
+ destination,
168
+ amount : requestedAmount . value ,
169
+ } ) ;
170
+ } catch ( e ) {
171
+ log ( `Advancer error:` , q ( e ) . toString ( ) ) ;
172
+ statusManager . skip ( evidence ) ;
173
+ }
128
174
} ,
129
175
} ,
130
176
{
131
177
stateShape : harden ( {
132
178
localDenom : M . string ( ) ,
133
179
poolAccount : M . remotable ( ) ,
180
+ usdcBrand : BrandShape ,
134
181
} ) ,
135
182
} ,
136
183
) ;
0 commit comments