@agoric/fast-usdc 0.1.1-dev-9e124c3.0 → 0.1.1-dev-a2813f6.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/package.json +15 -15
- package/src/cli/operator-commands.js +3 -1
- package/src/constants.js +6 -1
- package/src/exos/settler.js +17 -17
- package/src/exos/status-manager.js +103 -53
- package/src/fast-usdc.contract.js +5 -4
- package/src/type-guards.js +10 -2
- package/src/types.ts +12 -1
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-a2813f6.0+a2813f6",
|
|
4
4
|
"description": "CLI and library for Fast USDC product",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"lint:eslint": "eslint ."
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
26
|
-
"@agoric/vats": "0.15.2-dev-
|
|
27
|
-
"@agoric/zone": "0.2.3-dev-
|
|
25
|
+
"@agoric/swingset-liveslots": "0.10.3-dev-a2813f6.0+a2813f6",
|
|
26
|
+
"@agoric/vats": "0.15.2-dev-a2813f6.0+a2813f6",
|
|
27
|
+
"@agoric/zone": "0.2.3-dev-a2813f6.0+a2813f6",
|
|
28
28
|
"@fast-check/ava": "^2.0.1",
|
|
29
29
|
"ava": "^5.3.0",
|
|
30
30
|
"c8": "^10.1.2",
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
"ts-blank-space": "^0.4.4"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@agoric/client-utils": "0.1.1-dev-
|
|
36
|
-
"@agoric/cosmic-proto": "0.4.1-dev-
|
|
37
|
-
"@agoric/ertp": "0.16.3-dev-
|
|
38
|
-
"@agoric/internal": "0.3.3-dev-
|
|
39
|
-
"@agoric/notifier": "0.6.3-dev-
|
|
40
|
-
"@agoric/orchestration": "0.1.1-dev-
|
|
41
|
-
"@agoric/store": "0.9.3-dev-
|
|
42
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
43
|
-
"@agoric/vow": "0.1.1-dev-
|
|
44
|
-
"@agoric/zoe": "0.26.3-dev-
|
|
35
|
+
"@agoric/client-utils": "0.1.1-dev-a2813f6.0+a2813f6",
|
|
36
|
+
"@agoric/cosmic-proto": "0.4.1-dev-a2813f6.0+a2813f6",
|
|
37
|
+
"@agoric/ertp": "0.16.3-dev-a2813f6.0+a2813f6",
|
|
38
|
+
"@agoric/internal": "0.3.3-dev-a2813f6.0+a2813f6",
|
|
39
|
+
"@agoric/notifier": "0.6.3-dev-a2813f6.0+a2813f6",
|
|
40
|
+
"@agoric/orchestration": "0.1.1-dev-a2813f6.0+a2813f6",
|
|
41
|
+
"@agoric/store": "0.9.3-dev-a2813f6.0+a2813f6",
|
|
42
|
+
"@agoric/vat-data": "0.5.3-dev-a2813f6.0+a2813f6",
|
|
43
|
+
"@agoric/vow": "0.1.1-dev-a2813f6.0+a2813f6",
|
|
44
|
+
"@agoric/zoe": "0.26.3-dev-a2813f6.0+a2813f6",
|
|
45
45
|
"@cosmjs/proto-signing": "^0.32.4",
|
|
46
46
|
"@cosmjs/stargate": "^0.32.4",
|
|
47
47
|
"@endo/base64": "^1.0.9",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"publishConfig": {
|
|
82
82
|
"access": "public"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "a2813f626adc1be43c16a23ab5c447f54ab21449"
|
|
85
85
|
}
|
|
@@ -99,6 +99,7 @@ export const addOperatorCommands = (
|
|
|
99
99
|
.requiredOption('--chainId <string>', 'chain id', Number)
|
|
100
100
|
.requiredOption('--amount <number>', 'number', parseNat)
|
|
101
101
|
.requiredOption('--forwardingAddress <string>', 'bech32 address', String)
|
|
102
|
+
.requiredOption('--sender <string>', 'Ethereum address initiating', String)
|
|
102
103
|
.requiredOption('--txHash <0xhexo>', 'hex hash', parseHex)
|
|
103
104
|
.option('--offerId <string>', 'Offer id', String, `operatorAttest-${now()}`)
|
|
104
105
|
.action(async opts => {
|
|
@@ -109,12 +110,13 @@ export const addOperatorCommands = (
|
|
|
109
110
|
recipientAddress,
|
|
110
111
|
amount,
|
|
111
112
|
forwardingAddress,
|
|
113
|
+
sender,
|
|
112
114
|
...flat
|
|
113
115
|
} = opts;
|
|
114
116
|
|
|
115
117
|
const evidence = harden({
|
|
116
118
|
aux: { forwardingChannel, recipientAddress },
|
|
117
|
-
tx: { amount, forwardingAddress },
|
|
119
|
+
tx: { amount, forwardingAddress, sender },
|
|
118
120
|
...flat,
|
|
119
121
|
});
|
|
120
122
|
mustMatch(evidence, CctpTxEvidenceShape);
|
package/src/constants.js
CHANGED
|
@@ -21,7 +21,12 @@ export const TxStatus = /** @type {const} */ ({
|
|
|
21
21
|
});
|
|
22
22
|
harden(TxStatus);
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// According to the state diagram
|
|
25
|
+
export const TerminalTxStatus = {
|
|
26
|
+
[TxStatus.Forwarded]: true,
|
|
27
|
+
[TxStatus.ForwardFailed]: true,
|
|
28
|
+
[TxStatus.Disbursed]: true,
|
|
29
|
+
};
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
32
|
* Status values for the StatusManager.
|
package/src/exos/settler.js
CHANGED
|
@@ -143,7 +143,7 @@ export const prepareSettler = (
|
|
|
143
143
|
);
|
|
144
144
|
|
|
145
145
|
// given the sourceChannel check, we can be certain of this cast
|
|
146
|
-
const
|
|
146
|
+
const nfa = /** @type {NobleAddress} */ (tx.sender);
|
|
147
147
|
|
|
148
148
|
if (tx.denom !== remoteDenom) {
|
|
149
149
|
const { denom: actual } = tx;
|
|
@@ -170,23 +170,23 @@ export const prepareSettler = (
|
|
|
170
170
|
const amount = BigInt(tx.amount); // TODO: what if this throws?
|
|
171
171
|
|
|
172
172
|
const { self } = this.facets;
|
|
173
|
-
const found = statusManager.dequeueStatus(
|
|
174
|
-
log('dequeued', found, 'for',
|
|
173
|
+
const found = statusManager.dequeueStatus(nfa, amount);
|
|
174
|
+
log('dequeued', found, 'for', nfa, amount);
|
|
175
175
|
switch (found?.status) {
|
|
176
176
|
case PendingTxStatus.Advanced:
|
|
177
|
-
return self.disburse(found.txHash,
|
|
177
|
+
return self.disburse(found.txHash, nfa, amount);
|
|
178
178
|
|
|
179
179
|
case PendingTxStatus.Advancing:
|
|
180
|
-
this.state.mintedEarly.add(makeMintedEarlyKey(
|
|
180
|
+
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
|
|
181
181
|
return;
|
|
182
182
|
|
|
183
183
|
case PendingTxStatus.Observed:
|
|
184
184
|
case PendingTxStatus.AdvanceFailed:
|
|
185
|
-
return self.forward(found.txHash,
|
|
185
|
+
return self.forward(found.txHash, nfa, amount, EUD);
|
|
186
186
|
|
|
187
187
|
case undefined:
|
|
188
188
|
default:
|
|
189
|
-
log('⚠️ tap: no status for ',
|
|
189
|
+
log('⚠️ tap: no status for ', nfa, amount);
|
|
190
190
|
}
|
|
191
191
|
},
|
|
192
192
|
},
|
|
@@ -231,10 +231,10 @@ export const prepareSettler = (
|
|
|
231
231
|
self: {
|
|
232
232
|
/**
|
|
233
233
|
* @param {EvmHash} txHash
|
|
234
|
-
* @param {NobleAddress}
|
|
234
|
+
* @param {NobleAddress} nfa
|
|
235
235
|
* @param {NatValue} fullValue
|
|
236
236
|
*/
|
|
237
|
-
async disburse(txHash,
|
|
237
|
+
async disburse(txHash, nfa, fullValue) {
|
|
238
238
|
const { repayer, settlementAccount } = this.state;
|
|
239
239
|
const received = AmountMath.make(USDC, fullValue);
|
|
240
240
|
const { zcfSeat: settlingSeat } = zcf.makeEmptySeatKit();
|
|
@@ -264,11 +264,11 @@ export const prepareSettler = (
|
|
|
264
264
|
},
|
|
265
265
|
/**
|
|
266
266
|
* @param {EvmHash} txHash
|
|
267
|
-
* @param {NobleAddress}
|
|
267
|
+
* @param {NobleAddress} nfa
|
|
268
268
|
* @param {NatValue} fullValue
|
|
269
269
|
* @param {string} EUD
|
|
270
270
|
*/
|
|
271
|
-
forward(txHash,
|
|
271
|
+
forward(txHash, nfa, fullValue, EUD) {
|
|
272
272
|
const { settlementAccount, intermediateRecipient } = this.state;
|
|
273
273
|
|
|
274
274
|
const dest = chainHub.makeChainAddress(EUD);
|
|
@@ -281,7 +281,7 @@ export const prepareSettler = (
|
|
|
281
281
|
);
|
|
282
282
|
void vowTools.watch(txfrV, this.facets.transferHandler, {
|
|
283
283
|
txHash,
|
|
284
|
-
|
|
284
|
+
nfa,
|
|
285
285
|
fullValue,
|
|
286
286
|
});
|
|
287
287
|
},
|
|
@@ -293,13 +293,13 @@ export const prepareSettler = (
|
|
|
293
293
|
*
|
|
294
294
|
* @typedef {{
|
|
295
295
|
* txHash: EvmHash;
|
|
296
|
-
*
|
|
296
|
+
* nfa: NobleAddress;
|
|
297
297
|
* fullValue: NatValue;
|
|
298
298
|
* }} SettlerTransferCtx
|
|
299
299
|
*/
|
|
300
300
|
onFulfilled(_result, ctx) {
|
|
301
|
-
const { txHash,
|
|
302
|
-
statusManager.forwarded(txHash,
|
|
301
|
+
const { txHash, nfa, fullValue } = ctx;
|
|
302
|
+
statusManager.forwarded(txHash, nfa, fullValue);
|
|
303
303
|
},
|
|
304
304
|
/**
|
|
305
305
|
* @param {unknown} reason
|
|
@@ -307,8 +307,8 @@ export const prepareSettler = (
|
|
|
307
307
|
*/
|
|
308
308
|
onRejected(reason, ctx) {
|
|
309
309
|
log('⚠️ transfer rejected!', reason, ctx);
|
|
310
|
-
// const { txHash,
|
|
311
|
-
// TODO(#10510): statusManager.forwardFailed(txHash,
|
|
310
|
+
// const { txHash, nfa, amount } = ctx;
|
|
311
|
+
// TODO(#10510): statusManager.forwardFailed(txHash, nfa, amount);
|
|
312
312
|
},
|
|
313
313
|
},
|
|
314
314
|
},
|
|
@@ -2,26 +2,23 @@ import { M } from '@endo/patterns';
|
|
|
2
2
|
import { Fail, makeError, q } from '@endo/errors';
|
|
3
3
|
import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
|
|
4
4
|
import { E } from '@endo/eventual-send';
|
|
5
|
-
import { makeTracer } from '@agoric/internal';
|
|
5
|
+
import { makeTracer, pureDataMarshaller } from '@agoric/internal';
|
|
6
6
|
import {
|
|
7
7
|
CctpTxEvidenceShape,
|
|
8
8
|
EvmHashShape,
|
|
9
9
|
PendingTxShape,
|
|
10
10
|
} from '../type-guards.js';
|
|
11
|
-
import { PendingTxStatus, TxStatus } from '../constants.js';
|
|
11
|
+
import { PendingTxStatus, TerminalTxStatus, TxStatus } from '../constants.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @import {MapStore, SetStore} from '@agoric/store';
|
|
15
15
|
* @import {Zone} from '@agoric/zone';
|
|
16
|
-
* @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn} from '../types.js';
|
|
16
|
+
* @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn, TransactionRecord} from '../types.js';
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @typedef {`pendingTx:${bigint}:${NobleAddress}`} PendingTxKey
|
|
21
21
|
* The string template is for developer visibility but not meant to ever be parsed.
|
|
22
|
-
*
|
|
23
|
-
* @typedef {`seenTx:${string}:${EvmHash}`} SeenTxKey
|
|
24
|
-
* The string template is for developer visibility but not meant to ever be parsed.
|
|
25
22
|
*/
|
|
26
23
|
|
|
27
24
|
/**
|
|
@@ -29,13 +26,13 @@ import { PendingTxStatus, TxStatus } from '../constants.js';
|
|
|
29
26
|
*
|
|
30
27
|
* The key is a composite but not meant to be parsable.
|
|
31
28
|
*
|
|
32
|
-
* @param {NobleAddress}
|
|
29
|
+
* @param {NobleAddress} nfa Noble Forwarding Account (implies EUD)
|
|
33
30
|
* @param {bigint} amount
|
|
34
31
|
* @returns {PendingTxKey}
|
|
35
32
|
*/
|
|
36
|
-
const makePendingTxKey = (
|
|
33
|
+
const makePendingTxKey = (nfa, amount) =>
|
|
37
34
|
// amount can't contain colon
|
|
38
|
-
`pendingTx:${amount}:${
|
|
35
|
+
`pendingTx:${amount}:${nfa}`;
|
|
39
36
|
|
|
40
37
|
/**
|
|
41
38
|
* Get the key for the pendingTxs MapStore.
|
|
@@ -48,20 +45,6 @@ const pendingTxKeyOf = evidence => {
|
|
|
48
45
|
return makePendingTxKey(forwardingAddress, amount);
|
|
49
46
|
};
|
|
50
47
|
|
|
51
|
-
/**
|
|
52
|
-
* Get the key for the seenTxs SetStore.
|
|
53
|
-
*
|
|
54
|
-
* The key is a composite but not meant to be parsable.
|
|
55
|
-
*
|
|
56
|
-
* @param {CctpTxEvidence} evidence
|
|
57
|
-
* @returns {SeenTxKey}
|
|
58
|
-
*/
|
|
59
|
-
const seenTxKeyOf = evidence => {
|
|
60
|
-
const { txHash, chainId } = evidence;
|
|
61
|
-
// chainId can't contain colon
|
|
62
|
-
return `seenTx:${chainId}:${txHash}`;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
48
|
/**
|
|
66
49
|
* @typedef {{
|
|
67
50
|
* log?: LogFn;
|
|
@@ -76,35 +59,85 @@ const seenTxKeyOf = evidence => {
|
|
|
76
59
|
* XXX consider separate facets for `Advancing` and `Settling` capabilities.
|
|
77
60
|
*
|
|
78
61
|
* @param {Zone} zone
|
|
79
|
-
* @param {ERef<StorageNode>}
|
|
62
|
+
* @param {ERef<StorageNode>} txnsNode
|
|
80
63
|
* @param {StatusManagerPowers} caps
|
|
81
64
|
*/
|
|
82
65
|
export const prepareStatusManager = (
|
|
83
66
|
zone,
|
|
84
|
-
|
|
67
|
+
txnsNode,
|
|
85
68
|
{
|
|
86
69
|
log = makeTracer('Advancer', true),
|
|
87
70
|
} = /** @type {StatusManagerPowers} */ ({}),
|
|
88
71
|
) => {
|
|
89
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Keyed by a tuple of the Noble Forwarding Account and amount.
|
|
74
|
+
* @type {MapStore<PendingTxKey, PendingTx[]>}
|
|
75
|
+
*/
|
|
90
76
|
const pendingTxs = zone.mapStore('PendingTxs', {
|
|
91
77
|
keyShape: M.string(),
|
|
92
78
|
valueShape: M.arrayOf(PendingTxShape),
|
|
93
79
|
});
|
|
94
80
|
|
|
95
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Transactions seen *ever* by the contract.
|
|
83
|
+
*
|
|
84
|
+
* Note that like all durable stores, this SetStore is stored in IAVL. It
|
|
85
|
+
* grows without bound (though the amount of growth per incoming message to
|
|
86
|
+
* the contract is bounded). At some point in the future we may want to prune.
|
|
87
|
+
* @type {SetStore<EvmHash>}
|
|
88
|
+
*/
|
|
96
89
|
const seenTxs = zone.setStore('SeenTxs', {
|
|
97
90
|
keyShape: M.string(),
|
|
98
91
|
});
|
|
99
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Transactions that have completed, but are still in vstorage.
|
|
95
|
+
*
|
|
96
|
+
* @type {SetStore<EvmHash>}
|
|
97
|
+
*/
|
|
98
|
+
const storedCompletedTxs = zone.setStore('StoredCompletedTxs', {
|
|
99
|
+
keyShape: M.string(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {EvmHash} txId
|
|
104
|
+
* @param {TransactionRecord} record
|
|
105
|
+
*/
|
|
106
|
+
const publishTxnRecord = (txId, record) => {
|
|
107
|
+
const txNode = E(txnsNode).makeChildNode(txId, {
|
|
108
|
+
sequence: true, // avoid overwriting other output in the block
|
|
109
|
+
});
|
|
110
|
+
void E(txNode).setValue(
|
|
111
|
+
JSON.stringify(pureDataMarshaller.toCapData(record)),
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {CctpTxEvidence['txHash']} hash
|
|
117
|
+
* @param {CctpTxEvidence} evidence
|
|
118
|
+
*/
|
|
119
|
+
const publishEvidence = (hash, evidence) => {
|
|
120
|
+
// Don't await, just writing to vstorage.
|
|
121
|
+
void publishTxnRecord(
|
|
122
|
+
hash,
|
|
123
|
+
harden({ evidence, status: TxStatus.Observed }),
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
100
127
|
/**
|
|
101
128
|
* @param {CctpTxEvidence['txHash']} hash
|
|
102
129
|
* @param {TxStatus} status
|
|
103
130
|
*/
|
|
104
131
|
const publishStatus = (hash, status) => {
|
|
105
|
-
const txnNodeP = E(transactionsNode).makeChildNode(hash);
|
|
106
132
|
// Don't await, just writing to vstorage.
|
|
107
|
-
void
|
|
133
|
+
void publishTxnRecord(hash, harden({ status }));
|
|
134
|
+
if (TerminalTxStatus[status]) {
|
|
135
|
+
// UNTIL https://github.com/Agoric/agoric-sdk/issues/7405
|
|
136
|
+
// Queue it for deletion later because if we deleted it now the earlier
|
|
137
|
+
// writes in this block would be wiped. For now we keep track of what to
|
|
138
|
+
// delete when we know it'll be another block.
|
|
139
|
+
storedCompletedTxs.add(hash);
|
|
140
|
+
}
|
|
108
141
|
};
|
|
109
142
|
|
|
110
143
|
/**
|
|
@@ -117,32 +150,36 @@ export const prepareStatusManager = (
|
|
|
117
150
|
* @param {PendingTxStatus} status
|
|
118
151
|
*/
|
|
119
152
|
const initPendingTx = (evidence, status) => {
|
|
120
|
-
const
|
|
121
|
-
if (seenTxs.has(
|
|
122
|
-
throw makeError(`Transaction already seen: ${q(
|
|
153
|
+
const { txHash } = evidence;
|
|
154
|
+
if (seenTxs.has(txHash)) {
|
|
155
|
+
throw makeError(`Transaction already seen: ${q(txHash)}`);
|
|
123
156
|
}
|
|
124
|
-
seenTxs.add(
|
|
157
|
+
seenTxs.add(txHash);
|
|
125
158
|
|
|
126
159
|
appendToStoredArray(
|
|
127
160
|
pendingTxs,
|
|
128
161
|
pendingTxKeyOf(evidence),
|
|
129
162
|
harden({ ...evidence, status }),
|
|
130
163
|
);
|
|
131
|
-
|
|
164
|
+
publishEvidence(txHash, evidence);
|
|
165
|
+
if (status !== PendingTxStatus.Observed) {
|
|
166
|
+
// publishEvidence publishes Observed
|
|
167
|
+
publishStatus(txHash, status);
|
|
168
|
+
}
|
|
132
169
|
};
|
|
133
170
|
|
|
134
171
|
/**
|
|
135
172
|
* Update the pending transaction status.
|
|
136
173
|
*
|
|
137
|
-
* @param {{
|
|
174
|
+
* @param {{nfa: NobleAddress, amount: bigint}} keyParts
|
|
138
175
|
* @param {PendingTxStatus} status
|
|
139
176
|
*/
|
|
140
|
-
function setPendingTxStatus({
|
|
141
|
-
const key = makePendingTxKey(
|
|
142
|
-
pendingTxs.has(key) || Fail`no advancing tx with ${{
|
|
177
|
+
function setPendingTxStatus({ nfa, amount }, status) {
|
|
178
|
+
const key = makePendingTxKey(nfa, amount);
|
|
179
|
+
pendingTxs.has(key) || Fail`no advancing tx with ${{ nfa, amount }}`;
|
|
143
180
|
const pending = pendingTxs.get(key);
|
|
144
181
|
const ix = pending.findIndex(tx => tx.status === PendingTxStatus.Advancing);
|
|
145
|
-
ix >= 0 || Fail`no advancing tx with ${{
|
|
182
|
+
ix >= 0 || Fail`no advancing tx with ${{ nfa, amount }}`;
|
|
146
183
|
const [prefix, tx, suffix] = [
|
|
147
184
|
pending.slice(0, ix),
|
|
148
185
|
pending[ix],
|
|
@@ -161,6 +198,7 @@ export const prepareStatusManager = (
|
|
|
161
198
|
advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
|
|
162
199
|
observe: M.call(CctpTxEvidenceShape).returns(M.undefined()),
|
|
163
200
|
hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
|
|
201
|
+
deleteCompletedTxs: M.call().returns(M.undefined()),
|
|
164
202
|
dequeueStatus: M.call(M.string(), M.bigint()).returns(
|
|
165
203
|
M.or(
|
|
166
204
|
{
|
|
@@ -197,14 +235,14 @@ export const prepareStatusManager = (
|
|
|
197
235
|
/**
|
|
198
236
|
* Record result of ADVANCING
|
|
199
237
|
*
|
|
200
|
-
* @param {NobleAddress}
|
|
238
|
+
* @param {NobleAddress} nfa Noble Forwarding Account
|
|
201
239
|
* @param {import('@agoric/ertp').NatValue} amount
|
|
202
240
|
* @param {boolean} success - Advanced vs. AdvanceFailed
|
|
203
241
|
* @throws {Error} if nothing to advance
|
|
204
242
|
*/
|
|
205
|
-
advanceOutcome(
|
|
243
|
+
advanceOutcome(nfa, amount, success) {
|
|
206
244
|
setPendingTxStatus(
|
|
207
|
-
{
|
|
245
|
+
{ nfa, amount },
|
|
208
246
|
success ? PendingTxStatus.Advanced : PendingTxStatus.AdvanceFailed,
|
|
209
247
|
);
|
|
210
248
|
},
|
|
@@ -223,20 +261,32 @@ export const prepareStatusManager = (
|
|
|
223
261
|
* @param {CctpTxEvidence} evidence
|
|
224
262
|
*/
|
|
225
263
|
hasBeenObserved(evidence) {
|
|
226
|
-
|
|
227
|
-
|
|
264
|
+
return seenTxs.has(evidence.txHash);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// UNTIL https://github.com/Agoric/agoric-sdk/issues/7405
|
|
268
|
+
deleteCompletedTxs() {
|
|
269
|
+
for (const txHash of storedCompletedTxs.values()) {
|
|
270
|
+
// As of now, setValue('') on a non-sequence node will delete it
|
|
271
|
+
const txNode = E(txnsNode).makeChildNode(txHash, {
|
|
272
|
+
sequence: false,
|
|
273
|
+
});
|
|
274
|
+
void E(txNode)
|
|
275
|
+
.setValue('')
|
|
276
|
+
.then(() => storedCompletedTxs.delete(txHash));
|
|
277
|
+
}
|
|
228
278
|
},
|
|
229
279
|
|
|
230
280
|
/**
|
|
231
281
|
* Remove and return an `ADVANCED` or `OBSERVED` tx waiting to be `SETTLED`.
|
|
232
282
|
*
|
|
233
|
-
* @param {NobleAddress}
|
|
283
|
+
* @param {NobleAddress} nfa
|
|
234
284
|
* @param {bigint} amount
|
|
235
285
|
* @returns {Pick<PendingTx, 'status' | 'txHash'> | undefined} undefined if nothing
|
|
236
286
|
* with this address and amount has been marked pending.
|
|
237
287
|
*/
|
|
238
|
-
dequeueStatus(
|
|
239
|
-
const key = makePendingTxKey(
|
|
288
|
+
dequeueStatus(nfa, amount) {
|
|
289
|
+
const key = makePendingTxKey(nfa, amount);
|
|
240
290
|
if (!pendingTxs.has(key)) return undefined;
|
|
241
291
|
const pending = pendingTxs.get(key);
|
|
242
292
|
|
|
@@ -272,16 +322,16 @@ export const prepareStatusManager = (
|
|
|
272
322
|
* Mark a transaction as `FORWARDED`
|
|
273
323
|
*
|
|
274
324
|
* @param {EvmHash | undefined} txHash - undefined in case mint before observed
|
|
275
|
-
* @param {NobleAddress}
|
|
325
|
+
* @param {NobleAddress} nfa
|
|
276
326
|
* @param {bigint} amount
|
|
277
327
|
*/
|
|
278
|
-
forwarded(txHash,
|
|
328
|
+
forwarded(txHash, nfa, amount) {
|
|
279
329
|
if (txHash) {
|
|
280
330
|
publishStatus(txHash, TxStatus.Forwarded);
|
|
281
331
|
} else {
|
|
282
332
|
// TODO store (early) `Minted` transactions to check against incoming evidence
|
|
283
333
|
log(
|
|
284
|
-
`⚠️ Forwarded minted amount ${amount} from account ${
|
|
334
|
+
`⚠️ Forwarded minted amount ${amount} from account ${nfa} before it was observed.`,
|
|
285
335
|
);
|
|
286
336
|
}
|
|
287
337
|
},
|
|
@@ -291,12 +341,12 @@ export const prepareStatusManager = (
|
|
|
291
341
|
*
|
|
292
342
|
* XXX only used in tests. should we remove?
|
|
293
343
|
*
|
|
294
|
-
* @param {NobleAddress}
|
|
344
|
+
* @param {NobleAddress} nfa
|
|
295
345
|
* @param {bigint} amount
|
|
296
346
|
* @returns {PendingTx[]}
|
|
297
347
|
*/
|
|
298
|
-
lookupPending(
|
|
299
|
-
const key = makePendingTxKey(
|
|
348
|
+
lookupPending(nfa, amount) {
|
|
349
|
+
const key = makePendingTxKey(nfa, amount);
|
|
300
350
|
if (!pendingTxs.has(key)) {
|
|
301
351
|
return harden([]);
|
|
302
352
|
}
|
|
@@ -26,7 +26,7 @@ import { defineInertInvitation } from './utils/zoe.js';
|
|
|
26
26
|
|
|
27
27
|
const trace = makeTracer('FastUsdc');
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const TXNS_NODE = 'txns';
|
|
30
30
|
const FEE_NODE = 'feeConfig';
|
|
31
31
|
const ADDRESSES_BAGGAGE_KEY = 'addresses';
|
|
32
32
|
|
|
@@ -39,7 +39,6 @@ const ADDRESSES_BAGGAGE_KEY = 'addresses';
|
|
|
39
39
|
* @import {Zone} from '@agoric/zone';
|
|
40
40
|
* @import {OperatorKit} from './exos/operator-kit.js';
|
|
41
41
|
* @import {CctpTxEvidence, FeeConfig} from './types.js';
|
|
42
|
-
* @import {RepayAmountKWR, RepayPaymentKWR} from './exos/liquidity-pool.js';
|
|
43
42
|
*/
|
|
44
43
|
|
|
45
44
|
/**
|
|
@@ -110,8 +109,10 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
|
|
|
110
109
|
marshaller,
|
|
111
110
|
);
|
|
112
111
|
|
|
113
|
-
const
|
|
114
|
-
|
|
112
|
+
const statusManager = prepareStatusManager(
|
|
113
|
+
zone,
|
|
114
|
+
E(storageNode).makeChildNode(TXNS_NODE),
|
|
115
|
+
);
|
|
115
116
|
|
|
116
117
|
const { USDC } = terms.brands;
|
|
117
118
|
const { withdrawToSeat } = tools.zoeTools;
|
package/src/type-guards.js
CHANGED
|
@@ -6,7 +6,7 @@ import { PendingTxStatus } from './constants.js';
|
|
|
6
6
|
* @import {TypedPattern} from '@agoric/internal';
|
|
7
7
|
* @import {FastUsdcTerms} from './fast-usdc.contract.js';
|
|
8
8
|
* @import {USDCProposalShapes} from './pool-share-math.js';
|
|
9
|
-
* @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook} from './types.js';
|
|
9
|
+
* @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook, EvmAddress, EvmHash} from './types.js';
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -36,7 +36,14 @@ export const FastUSDCTermsShape = harden({
|
|
|
36
36
|
usdcDenom: M.string(),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
/** @type {TypedPattern<
|
|
39
|
+
/** @type {TypedPattern<EvmAddress>} */
|
|
40
|
+
export const EvmAddressShape = M.string({
|
|
41
|
+
// 0x + 40 hex digits
|
|
42
|
+
stringLengthLimit: 42,
|
|
43
|
+
});
|
|
44
|
+
harden(EvmAddressShape);
|
|
45
|
+
|
|
46
|
+
/** @type {TypedPattern<EvmHash>} */
|
|
40
47
|
export const EvmHashShape = M.string({
|
|
41
48
|
stringLengthLimit: 66,
|
|
42
49
|
});
|
|
@@ -54,6 +61,7 @@ export const CctpTxEvidenceShape = {
|
|
|
54
61
|
tx: {
|
|
55
62
|
amount: M.nat(),
|
|
56
63
|
forwardingAddress: M.string(),
|
|
64
|
+
sender: EvmAddressShape,
|
|
57
65
|
},
|
|
58
66
|
txHash: EvmHashShape,
|
|
59
67
|
};
|
package/src/types.ts
CHANGED
|
@@ -7,10 +7,11 @@ import type {
|
|
|
7
7
|
import type { IBCChannelID } from '@agoric/vats';
|
|
8
8
|
import type { Amount } from '@agoric/ertp';
|
|
9
9
|
import type { CopyRecord, Passable } from '@endo/pass-style';
|
|
10
|
-
import type { PendingTxStatus } from './constants.js';
|
|
10
|
+
import type { PendingTxStatus, TxStatus } from './constants.js';
|
|
11
11
|
import type { FastUsdcTerms } from './fast-usdc.contract.js';
|
|
12
12
|
|
|
13
13
|
export type EvmHash = `0x${string}`;
|
|
14
|
+
export type EvmAddress = `0x${string & { length: 40 }}`;
|
|
14
15
|
export type NobleAddress = `noble1${string}`;
|
|
15
16
|
export type EvmChainID = number;
|
|
16
17
|
export type EvmChainName = string;
|
|
@@ -28,10 +29,20 @@ export interface CctpTxEvidence {
|
|
|
28
29
|
tx: {
|
|
29
30
|
amount: bigint;
|
|
30
31
|
forwardingAddress: NobleAddress;
|
|
32
|
+
sender: EvmAddress;
|
|
31
33
|
};
|
|
32
34
|
txHash: EvmHash;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 'evidence' only available when it's first observed and not in subsequent
|
|
39
|
+
* updates.
|
|
40
|
+
*/
|
|
41
|
+
export interface TransactionRecord extends CopyRecord {
|
|
42
|
+
evidence?: CctpTxEvidence;
|
|
43
|
+
status: TxStatus;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
export type LogFn = (...args: unknown[]) => void;
|
|
36
47
|
|
|
37
48
|
export interface PendingTx extends CctpTxEvidence {
|