@agoric/fast-usdc 0.1.1-dev-9418efc.0 → 0.1.1-dev-6fbd20a.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/cli.js +5 -0
- package/src/cli/config.js +9 -0
- package/src/cli/transfer.js +42 -0
- package/src/exos/operator-kit.js +6 -6
- package/src/exos/transaction-feed.js +45 -23
- package/src/util/bank.js +12 -0
- package/src/util/cctp.js +1 -1
- package/src/utils/deploy-config.js +8 -0
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-6fbd20a.0+6fbd20a",
|
|
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-6fbd20a.0+6fbd20a",
|
|
26
|
+
"@agoric/vats": "0.15.2-dev-6fbd20a.0+6fbd20a",
|
|
27
|
+
"@agoric/zone": "0.2.3-dev-6fbd20a.0+6fbd20a",
|
|
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-6fbd20a.0+6fbd20a",
|
|
36
|
+
"@agoric/cosmic-proto": "0.4.1-dev-6fbd20a.0+6fbd20a",
|
|
37
|
+
"@agoric/ertp": "0.16.3-dev-6fbd20a.0+6fbd20a",
|
|
38
|
+
"@agoric/internal": "0.3.3-dev-6fbd20a.0+6fbd20a",
|
|
39
|
+
"@agoric/notifier": "0.6.3-dev-6fbd20a.0+6fbd20a",
|
|
40
|
+
"@agoric/orchestration": "0.1.1-dev-6fbd20a.0+6fbd20a",
|
|
41
|
+
"@agoric/store": "0.9.3-dev-6fbd20a.0+6fbd20a",
|
|
42
|
+
"@agoric/vat-data": "0.5.3-dev-6fbd20a.0+6fbd20a",
|
|
43
|
+
"@agoric/vow": "0.1.1-dev-6fbd20a.0+6fbd20a",
|
|
44
|
+
"@agoric/zoe": "0.26.3-dev-6fbd20a.0+6fbd20a",
|
|
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": "6fbd20a0d6b8342a0f607960da5b5b6550c29fa8"
|
|
85
85
|
}
|
package/src/cli/cli.js
CHANGED
|
@@ -91,7 +91,12 @@ export const initProgram = (
|
|
|
91
91
|
/** @type {string} */ amount,
|
|
92
92
|
/** @type {string} */ destination,
|
|
93
93
|
) => {
|
|
94
|
+
const start = now();
|
|
94
95
|
await transferHelpers.transfer(makeConfigFile(), amount, destination);
|
|
96
|
+
const duration = now() - start;
|
|
97
|
+
stdout.write(
|
|
98
|
+
`Transfer finished in ${(duration / 1000).toFixed(1)} seconds`,
|
|
99
|
+
);
|
|
95
100
|
},
|
|
96
101
|
);
|
|
97
102
|
|
package/src/cli/config.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as readline from 'node:readline/promises';
|
|
2
2
|
import { stdin as input, stdout as output } from 'node:process';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
@typedef {{
|
|
6
|
+
bech32Prefix: string,
|
|
7
|
+
api: string,
|
|
8
|
+
USDCDenom: string
|
|
9
|
+
}} DestinationChain
|
|
10
|
+
*/
|
|
11
|
+
|
|
4
12
|
/**
|
|
5
13
|
@typedef {{
|
|
6
14
|
nobleSeed: string,
|
|
@@ -11,6 +19,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
11
19
|
ethRpc: string,
|
|
12
20
|
tokenMessengerAddress: string,
|
|
13
21
|
tokenAddress: string
|
|
22
|
+
destinationChains?: DestinationChain[]
|
|
14
23
|
}} ConfigOpts
|
|
15
24
|
*/
|
|
16
25
|
|
package/src/cli/transfer.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
queryForwardingAccount,
|
|
15
15
|
registerFwdAccount,
|
|
16
16
|
} from '../util/noble.js';
|
|
17
|
+
import { queryUSDCBalance } from '../util/bank.js';
|
|
17
18
|
|
|
18
19
|
/** @import { File } from '../util/file' */
|
|
19
20
|
/** @import { VStorage } from '@agoric/client-utils' */
|
|
@@ -30,6 +31,7 @@ const transfer = async (
|
|
|
30
31
|
/** @type {{signer: SigningStargateClient, address: string} | undefined} */ nobleSigner,
|
|
31
32
|
/** @type {ethProvider | undefined} */ ethProvider,
|
|
32
33
|
env = process.env,
|
|
34
|
+
setTimeout = globalThis.setTimeout,
|
|
33
35
|
) => {
|
|
34
36
|
const execute = async (
|
|
35
37
|
/** @type {import('./config').ConfigOpts} */ config,
|
|
@@ -71,6 +73,18 @@ const transfer = async (
|
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
const destChain = config.destinationChains?.find(chain =>
|
|
77
|
+
EUD.startsWith(chain.bech32Prefix),
|
|
78
|
+
);
|
|
79
|
+
if (!destChain) {
|
|
80
|
+
out.error(
|
|
81
|
+
`No destination chain found in config with matching bech32 prefix for ${EUD}, cannot query destination address`,
|
|
82
|
+
);
|
|
83
|
+
throw new Error();
|
|
84
|
+
}
|
|
85
|
+
const { api, USDCDenom } = destChain;
|
|
86
|
+
const startingBalance = await queryUSDCBalance(EUD, api, USDCDenom, fetch);
|
|
87
|
+
|
|
74
88
|
ethProvider ||= makeProvider(config.ethRpc);
|
|
75
89
|
await depositForBurn(
|
|
76
90
|
ethProvider,
|
|
@@ -81,6 +95,34 @@ const transfer = async (
|
|
|
81
95
|
amount,
|
|
82
96
|
out,
|
|
83
97
|
);
|
|
98
|
+
|
|
99
|
+
const refreshDelayMS = 1200;
|
|
100
|
+
const completeP = /** @type {Promise<void>} */ (
|
|
101
|
+
new Promise((res, rej) => {
|
|
102
|
+
const refreshUSDCBalance = async () => {
|
|
103
|
+
out.log('polling usdc balance');
|
|
104
|
+
const currentBalance = await queryUSDCBalance(
|
|
105
|
+
EUD,
|
|
106
|
+
api,
|
|
107
|
+
USDCDenom,
|
|
108
|
+
fetch,
|
|
109
|
+
);
|
|
110
|
+
if (currentBalance !== startingBalance) {
|
|
111
|
+
res();
|
|
112
|
+
} else {
|
|
113
|
+
setTimeout(() => refreshUSDCBalance().catch(rej), refreshDelayMS);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
refreshUSDCBalance().catch(rej);
|
|
117
|
+
})
|
|
118
|
+
).catch(e => {
|
|
119
|
+
out.error(
|
|
120
|
+
'Error checking destination address balance, could not detect completion of transfer.',
|
|
121
|
+
);
|
|
122
|
+
out.error(e.message);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await completeP;
|
|
84
126
|
};
|
|
85
127
|
|
|
86
128
|
let config;
|
package/src/exos/operator-kit.js
CHANGED
|
@@ -12,7 +12,7 @@ const trace = makeTracer('TxOperator');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {object} OperatorPowers
|
|
15
|
-
* @property {(evidence: CctpTxEvidence,
|
|
15
|
+
* @property {(evidence: CctpTxEvidence, operatorId: string) => void} attest
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -35,7 +35,7 @@ const OperatorKitI = {
|
|
|
35
35
|
}),
|
|
36
36
|
|
|
37
37
|
operator: M.interface('Operator', {
|
|
38
|
-
submitEvidence: M.call(CctpTxEvidenceShape).returns(
|
|
38
|
+
submitEvidence: M.call(CctpTxEvidenceShape).returns(),
|
|
39
39
|
getStatus: M.call().returns(M.record()),
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
@@ -87,7 +87,7 @@ export const prepareOperatorKit = (zone, staticPowers) =>
|
|
|
87
87
|
const { operator } = this.facets;
|
|
88
88
|
// TODO(bootstrap integration): cause this call to throw and confirm that it
|
|
89
89
|
// shows up in the the smart-wallet UpdateRecord `error` property
|
|
90
|
-
|
|
90
|
+
operator.submitEvidence(evidence);
|
|
91
91
|
return staticPowers.makeInertInvitation(
|
|
92
92
|
'evidence was pushed in the invitation maker call',
|
|
93
93
|
);
|
|
@@ -98,12 +98,12 @@ export const prepareOperatorKit = (zone, staticPowers) =>
|
|
|
98
98
|
* submit evidence from this operator
|
|
99
99
|
*
|
|
100
100
|
* @param {CctpTxEvidence} evidence
|
|
101
|
+
* @returns {void}
|
|
101
102
|
*/
|
|
102
|
-
|
|
103
|
+
submitEvidence(evidence) {
|
|
103
104
|
const { state } = this;
|
|
104
105
|
!state.disabled || Fail`submitEvidence for disabled operator`;
|
|
105
|
-
|
|
106
|
-
return result;
|
|
106
|
+
state.powers.attest(evidence, state.operatorId);
|
|
107
107
|
},
|
|
108
108
|
/** @returns {OperatorStatus} */
|
|
109
109
|
getStatus() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { makeTracer } from '@agoric/internal';
|
|
2
2
|
import { prepareDurablePublishKit } from '@agoric/notifier';
|
|
3
|
-
import { M } from '@endo/patterns';
|
|
3
|
+
import { keyEQ, M } from '@endo/patterns';
|
|
4
|
+
import { Fail } from '@endo/errors';
|
|
4
5
|
import { CctpTxEvidenceShape } from '../type-guards.js';
|
|
5
6
|
import { defineInertInvitation } from '../utils/zoe.js';
|
|
6
7
|
import { prepareOperatorKit } from './operator-kit.js';
|
|
@@ -18,7 +19,7 @@ export const INVITATION_MAKERS_DESC = 'oracle operator invitation';
|
|
|
18
19
|
|
|
19
20
|
const TransactionFeedKitI = harden({
|
|
20
21
|
operatorPowers: M.interface('Transaction Feed Admin', {
|
|
21
|
-
|
|
22
|
+
attest: M.call(CctpTxEvidenceShape, M.string()).returns(),
|
|
22
23
|
}),
|
|
23
24
|
creator: M.interface('Transaction Feed Creator', {
|
|
24
25
|
// TODO narrow the return shape to OperatorKit
|
|
@@ -118,23 +119,16 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
|
|
|
118
119
|
/**
|
|
119
120
|
* Add evidence from an operator.
|
|
120
121
|
*
|
|
122
|
+
* NB: the operatorKit is responsible for
|
|
123
|
+
*
|
|
121
124
|
* @param {CctpTxEvidence} evidence
|
|
122
|
-
* @param {
|
|
125
|
+
* @param {string} operatorId
|
|
123
126
|
*/
|
|
124
|
-
|
|
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`;
|
|
127
|
+
attest(evidence, operatorId) {
|
|
128
|
+
const { operators, pending } = this.state;
|
|
129
|
+
trace('submitEvidence', operatorId, evidence);
|
|
137
130
|
|
|
131
|
+
// TODO https://github.com/Agoric/agoric-sdk/pull/10720
|
|
138
132
|
// TODO validate that it's a valid for Fast USDC before accepting
|
|
139
133
|
// E.g. that the `recipientAddress` is the FU settlement account and that
|
|
140
134
|
// the EUD is a chain supported by FU.
|
|
@@ -154,18 +148,46 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
|
|
|
154
148
|
const found = [...pending.values()].filter(store =>
|
|
155
149
|
store.has(txHash),
|
|
156
150
|
);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
const minAttestations = Math.ceil(operators.getSize() / 2);
|
|
152
|
+
trace(
|
|
153
|
+
'transaction',
|
|
154
|
+
txHash,
|
|
155
|
+
'has',
|
|
156
|
+
found.length,
|
|
157
|
+
'of',
|
|
158
|
+
minAttestations,
|
|
159
|
+
'necessary attestations',
|
|
160
|
+
);
|
|
161
|
+
if (found.length < minAttestations) {
|
|
160
162
|
return;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
let lastEvidence;
|
|
166
|
+
for (const store of found) {
|
|
167
|
+
const next = store.get(txHash);
|
|
168
|
+
if (lastEvidence) {
|
|
169
|
+
if (keyEQ(lastEvidence, next)) {
|
|
170
|
+
lastEvidence = next;
|
|
171
|
+
} else {
|
|
172
|
+
trace(
|
|
173
|
+
'🚨 conflicting evidence for',
|
|
174
|
+
txHash,
|
|
175
|
+
':',
|
|
176
|
+
lastEvidence,
|
|
177
|
+
'!=',
|
|
178
|
+
next,
|
|
179
|
+
);
|
|
180
|
+
Fail`conflicting evidence for ${txHash}`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
lastEvidence = next;
|
|
184
|
+
}
|
|
164
185
|
|
|
165
|
-
//
|
|
166
|
-
for (const
|
|
167
|
-
|
|
186
|
+
// sufficient agreement, so remove from pending and publish
|
|
187
|
+
for (const store of found) {
|
|
188
|
+
store.delete(txHash);
|
|
168
189
|
}
|
|
190
|
+
trace('publishing evidence', evidence);
|
|
169
191
|
publisher.publish(evidence);
|
|
170
192
|
},
|
|
171
193
|
},
|
package/src/util/bank.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const queryUSDCBalance = async (
|
|
2
|
+
/** @type {string} */ address,
|
|
3
|
+
/** @type {string} */ api,
|
|
4
|
+
/** @type {string} */ denom,
|
|
5
|
+
/** @type {typeof globalThis.fetch} */ fetch,
|
|
6
|
+
) => {
|
|
7
|
+
const query = `${api}/cosmos/bank/v1beta1/balances/${address}`;
|
|
8
|
+
const json = await fetch(query).then(res => res.json());
|
|
9
|
+
const amount = json.balances?.find(b => b.denom === denom)?.amount ?? '0';
|
|
10
|
+
|
|
11
|
+
return BigInt(amount);
|
|
12
|
+
};
|
package/src/util/cctp.js
CHANGED
|
@@ -67,5 +67,5 @@ export const depositForBurn = async (
|
|
|
67
67
|
|
|
68
68
|
out.log('Transaction confirmed in block', receipt.blockNumber);
|
|
69
69
|
out.log('Transaction hash:', receipt.hash);
|
|
70
|
-
out.log('USDC transfer initiated successfully
|
|
70
|
+
out.log('USDC transfer initiated successfully');
|
|
71
71
|
};
|
|
@@ -156,3 +156,11 @@ export const configurations = {
|
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
158
|
harden(configurations);
|
|
159
|
+
|
|
160
|
+
// Constraints on the configurations
|
|
161
|
+
const MAINNET_EXPECTED_ORACLES = 3;
|
|
162
|
+
assert(
|
|
163
|
+
new Set(Object.values(configurations.MAINNET.oracles)).size ===
|
|
164
|
+
MAINNET_EXPECTED_ORACLES,
|
|
165
|
+
`Mainnet must have exactly ${MAINNET_EXPECTED_ORACLES} oracles`,
|
|
166
|
+
);
|