@agoric/fast-usdc 0.1.1-dev-02967de.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.
@@ -0,0 +1,264 @@
1
+ import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal';
2
+ import { Fail } from '@endo/errors';
3
+ import { E } from '@endo/far';
4
+ import { makeMarshal } from '@endo/marshal';
5
+ import { M } from '@endo/patterns';
6
+ import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
7
+ import { fromExternalConfig } from './utils/config-marshal.js';
8
+
9
+ /**
10
+ * @import {DepositFacet} from '@agoric/ertp/src/types.js'
11
+ * @import {TypedPattern} from '@agoric/internal'
12
+ * @import {Instance, StartParams} from '@agoric/zoe/src/zoeService/utils'
13
+ * @import {Board} from '@agoric/vats'
14
+ * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js'
15
+ * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js'
16
+ * @import {LegibleCapData} from './utils/config-marshal.js'
17
+ * @import {FastUsdcSF, FastUsdcTerms} from './fast-usdc.contract.js'
18
+ * @import {FeeConfig} from './types.js'
19
+ */
20
+
21
+ const trace = makeTracer('FUSD-Start', true);
22
+
23
+ const contractName = 'fastUsdc';
24
+
25
+ /**
26
+ * @typedef {{
27
+ * terms: FastUsdcTerms;
28
+ * oracles: Record<string, string>;
29
+ * feeConfig: FeeConfig;
30
+ * }} FastUSDCConfig
31
+ */
32
+ /** @type {TypedPattern<FastUSDCConfig>} */
33
+ export const FastUSDCConfigShape = M.splitRecord({
34
+ terms: FastUSDCTermsShape,
35
+ oracles: M.recordOf(M.string(), M.string()),
36
+ feeConfig: FeeConfigShape,
37
+ });
38
+
39
+ /**
40
+ * XXX Shouldn't the bridge or board vat handle this?
41
+ *
42
+ * @param {string} path
43
+ * @param {{
44
+ * chainStorage: ERef<StorageNode>;
45
+ * board: ERef<Board>;
46
+ * }} io
47
+ */
48
+ const makePublishingStorageKit = async (path, { chainStorage, board }) => {
49
+ const storageNode = await E(chainStorage).makeChildNode(path);
50
+
51
+ const marshaller = await E(board).getPublishingMarshaller();
52
+ return { storageNode, marshaller };
53
+ };
54
+
55
+ const BOARD_AUX = 'boardAux';
56
+ const marshalData = makeMarshal(_val => Fail`data only`);
57
+ /**
58
+ * @param {Brand} brand
59
+ * @param {Pick<BootstrapPowers['consume'], 'board' | 'chainStorage'>} powers
60
+ */
61
+ const publishDisplayInfo = async (brand, { board, chainStorage }) => {
62
+ // chainStorage type includes undefined, which doesn't apply here.
63
+ // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247
64
+ const boardAux = E(chainStorage).makeChildNode(BOARD_AUX);
65
+ const [id, displayInfo, allegedName] = await Promise.all([
66
+ E(board).getId(brand),
67
+ E(brand).getDisplayInfo(),
68
+ E(brand).getAllegedName(),
69
+ ]);
70
+ const node = E(boardAux).makeChildNode(id);
71
+ const aux = marshalData.toCapData(harden({ allegedName, displayInfo }));
72
+ await E(node).setValue(JSON.stringify(aux));
73
+ };
74
+
75
+ /**
76
+ * @typedef { PromiseSpaceOf<{
77
+ * fastUsdcKit: FastUSDCKit
78
+ * }> & {
79
+ * installation: PromiseSpaceOf<{ fastUsdc: Installation<FastUsdcSF> }>;
80
+ * instance: PromiseSpaceOf<{ fastUsdc: Instance<FastUsdcSF> }>;
81
+ * issuer: PromiseSpaceOf<{ FastLP: Issuer }>;
82
+ * brand: PromiseSpaceOf<{ FastLP: Brand }>;
83
+ * }} FastUSDCCorePowers
84
+ *
85
+ * @typedef {StartedInstanceKitWithLabel & {
86
+ * privateArgs: StartParams<FastUsdcSF>['privateArgs'];
87
+ * }} FastUSDCKit
88
+ */
89
+
90
+ /**
91
+ * @throws if oracle smart wallets are not yet provisioned
92
+ *
93
+ * @param {BootstrapPowers & FastUSDCCorePowers } powers
94
+ * @param {{ options: LegibleCapData<FastUSDCConfig> }} config
95
+ */
96
+ export const startFastUSDC = async (
97
+ {
98
+ produce: { fastUsdcKit },
99
+ consume: {
100
+ agoricNames,
101
+ namesByAddress,
102
+ board,
103
+ chainStorage,
104
+ chainTimerService: timerService,
105
+ localchain,
106
+ cosmosInterchainService,
107
+ startUpgradable,
108
+ zoe,
109
+ },
110
+ issuer: {
111
+ produce: { FastLP: produceShareIssuer },
112
+ },
113
+ brand: {
114
+ produce: { FastLP: produceShareBrand },
115
+ },
116
+ installation: {
117
+ consume: { fastUsdc },
118
+ },
119
+ instance: {
120
+ produce: { fastUsdc: produceInstance },
121
+ },
122
+ },
123
+ config,
124
+ ) => {
125
+ trace('startFastUSDC');
126
+
127
+ await null;
128
+ /** @type {Issuer<'nat'>} */
129
+ const USDCissuer = await E(agoricNames).lookup('issuer', 'USDC');
130
+ const brands = harden({
131
+ USDC: await E(USDCissuer).getBrand(),
132
+ });
133
+
134
+ const { terms, oracles, feeConfig } = fromExternalConfig(
135
+ config?.options, // just in case config is missing somehow
136
+ brands,
137
+ FastUSDCConfigShape,
138
+ );
139
+ trace('using terms', terms);
140
+ trace('using fee config', feeConfig);
141
+
142
+ trace('look up oracle deposit facets');
143
+ const oracleDepositFacets = await deeplyFulfilledObject(
144
+ objectMap(oracles, async address => {
145
+ /** @type {DepositFacet} */
146
+ const depositFacet = await E(namesByAddress).lookup(
147
+ address,
148
+ 'depositFacet',
149
+ );
150
+ return depositFacet;
151
+ }),
152
+ );
153
+
154
+ const { storageNode, marshaller } = await makePublishingStorageKit(
155
+ contractName,
156
+ {
157
+ board,
158
+ // @ts-expect-error Promise<null> case is vestigial
159
+ chainStorage,
160
+ },
161
+ );
162
+
163
+ const privateArgs = await deeplyFulfilledObject(
164
+ harden({
165
+ agoricNames,
166
+ feeConfig,
167
+ localchain,
168
+ orchestrationService: cosmosInterchainService,
169
+ storageNode,
170
+ timerService,
171
+ marshaller,
172
+ }),
173
+ );
174
+
175
+ const kit = await E(startUpgradable)({
176
+ label: contractName,
177
+ installation: fastUsdc,
178
+ issuerKeywordRecord: harden({ USDC: USDCissuer }),
179
+ terms,
180
+ privateArgs,
181
+ });
182
+ fastUsdcKit.resolve(harden({ ...kit, privateArgs }));
183
+ const { instance, creatorFacet } = kit;
184
+
185
+ const {
186
+ issuers: { PoolShares: shareIssuer },
187
+ brands: { PoolShares: shareBrand },
188
+ } = await E(zoe).getTerms(instance);
189
+ produceShareIssuer.resolve(shareIssuer);
190
+ produceShareBrand.resolve(shareBrand);
191
+ await publishDisplayInfo(shareBrand, { board, chainStorage });
192
+
193
+ await Promise.all(
194
+ Object.entries(oracleDepositFacets).map(async ([name, depositFacet]) => {
195
+ const address = oracles[name];
196
+ trace('making invitation for', name, address);
197
+ const toWatch = await E(creatorFacet).makeOperatorInvitation(address);
198
+
199
+ const amt = await E(depositFacet).receive(toWatch);
200
+ trace('sent', amt, 'to', name);
201
+ }),
202
+ );
203
+
204
+ produceInstance.reset();
205
+ produceInstance.resolve(instance);
206
+ trace('startFastUSDC done', instance);
207
+ };
208
+ harden(startFastUSDC);
209
+
210
+ /**
211
+ * @param {{
212
+ * restoreRef: (b: ERef<ManifestBundleRef>) => Promise<Installation>;
213
+ * }} utils
214
+ * @param {{
215
+ * installKeys: { fastUsdc: ERef<ManifestBundleRef> };
216
+ * options: LegibleCapData<FastUSDCConfig>;
217
+ * }} param1
218
+ */
219
+ export const getManifestForFastUSDC = (
220
+ { restoreRef },
221
+ { installKeys, options },
222
+ ) => {
223
+ return {
224
+ /** @type {BootstrapManifest} */
225
+ manifest: {
226
+ [startFastUSDC.name]: {
227
+ produce: {
228
+ fastUsdcKit: true,
229
+ },
230
+ consume: {
231
+ chainStorage: true,
232
+ chainTimerService: true,
233
+ localchain: true,
234
+ cosmosInterchainService: true,
235
+
236
+ // limited distribution durin MN2: contract installation
237
+ startUpgradable: true,
238
+ zoe: true, // only getTerms() is needed. XXX should be split?
239
+
240
+ // widely shared: name services
241
+ agoricNames: true,
242
+ namesByAddress: true,
243
+ board: true,
244
+ },
245
+ issuer: {
246
+ produce: { FastLP: true }, // UNTIL #10432
247
+ },
248
+ brand: {
249
+ produce: { FastLP: true }, // UNTIL #10432
250
+ },
251
+ instance: {
252
+ produce: { fastUsdc: true },
253
+ },
254
+ installation: {
255
+ consume: { fastUsdc: true },
256
+ },
257
+ },
258
+ },
259
+ installations: {
260
+ fastUsdc: restoreRef(installKeys.fastUsdc),
261
+ },
262
+ options,
263
+ };
264
+ };
@@ -0,0 +1,189 @@
1
+ import { AmountMath } from '@agoric/ertp/src/amountMath.js';
2
+ import {
3
+ divideBy,
4
+ makeRatio,
5
+ makeRatioFromAmounts,
6
+ multiplyBy,
7
+ } from '@agoric/zoe/src/contractSupport/ratio.js';
8
+ import { Fail, q } from '@endo/errors';
9
+
10
+ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath;
11
+
12
+ /**
13
+ * @import {PoolStats} from './types';
14
+ * @import {RepayAmountKWR} from './exos/liquidity-pool';
15
+ */
16
+
17
+ /**
18
+ * Invariant: shareWorth is the pool balance divided by shares outstanding.
19
+ *
20
+ * Use `makeParity(make(USDC, epsilon), PoolShares)` for an initial
21
+ * value, for some negligible `epsilon` such as 1n.
22
+ *
23
+ * @typedef {Ratio} ShareWorth
24
+ */
25
+
26
+ /**
27
+ * Make a 1-to-1 ratio between amounts of 2 brands.
28
+ *
29
+ * @param {Amount<'nat'>} numerator
30
+ * @param {Brand<'nat'>} denominatorBrand
31
+ */
32
+ export const makeParity = (numerator, denominatorBrand) => {
33
+ const value = getValue(numerator.brand, numerator);
34
+ return makeRatio(value, numerator.brand, value, denominatorBrand);
35
+ };
36
+
37
+ /**
38
+ * @typedef {{
39
+ * deposit: {
40
+ * give: { USDC: Amount<'nat'> },
41
+ * want?: { PoolShare: Amount<'nat'> }
42
+ * },
43
+ * withdraw: {
44
+ * give: { PoolShare: Amount<'nat'> }
45
+ * want: { USDC: Amount<'nat'> },
46
+ * }
47
+ * }} USDCProposalShapes
48
+ */
49
+
50
+ /**
51
+ * Compute Shares payout from a deposit proposal, as well as updated shareWorth.
52
+ *
53
+ * Clearly:
54
+ *
55
+ * sharesOutstanding' = sharesOutstanding + Shares
56
+ * poolBalance' = poolBalance + ToPool
57
+ * shareWorth' = poolBalance' / sharesOutstanding'
58
+ *
59
+ * In order to maintain the ShareWorth invariant, we need:
60
+ *
61
+ * Shares = ToPool / shareWorth'
62
+ *
63
+ * Solving for Shares gives:
64
+ *
65
+ * Shares = ToPool * sharesOutstanding / poolBalance
66
+ *
67
+ * that is:
68
+ *
69
+ * Shares = ToPool / shareWorth
70
+ *
71
+ * @param {ShareWorth} shareWorth previous to the deposit
72
+ * @param {USDCProposalShapes['deposit']} proposal
73
+ * @returns {{ payouts: { PoolShare: Amount<'nat'> }; shareWorth: ShareWorth }}
74
+ */
75
+ export const depositCalc = (shareWorth, { give, want }) => {
76
+ assert(!isEmpty(give.USDC)); // nice diagnostic provided by proposalShape
77
+
78
+ const { denominator: sharesOutstanding, numerator: poolBalance } = shareWorth;
79
+
80
+ const fairPoolShare = divideBy(give.USDC, shareWorth);
81
+ if (want?.PoolShare) {
82
+ isGTE(fairPoolShare, want.PoolShare) ||
83
+ Fail`deposit cannot pay out ${q(want.PoolShare)}; ${q(give.USDC)} only gets ${q(fairPoolShare)}`;
84
+ }
85
+ const outstandingPost = add(sharesOutstanding, fairPoolShare);
86
+ const balancePost = add(poolBalance, give.USDC);
87
+ const worthPost = makeRatioFromAmounts(balancePost, outstandingPost);
88
+ return harden({
89
+ payouts: { PoolShare: fairPoolShare },
90
+ shareWorth: worthPost,
91
+ });
92
+ };
93
+
94
+ /**
95
+ * Compute payout from a withdraw proposal, along with updated shareWorth
96
+ *
97
+ * @param {ShareWorth} shareWorth
98
+ * @param {USDCProposalShapes['withdraw']} proposal
99
+ * @returns {{ shareWorth: ShareWorth, payouts: { USDC: Amount<'nat'> }}}
100
+ */
101
+ export const withdrawCalc = (shareWorth, { give, want }) => {
102
+ assert(!isEmpty(give.PoolShare));
103
+ assert(!isEmpty(want.USDC));
104
+
105
+ const payout = multiplyBy(give.PoolShare, shareWorth);
106
+ isGTE(payout, want.USDC) ||
107
+ Fail`cannot withdraw ${q(want.USDC)}; ${q(give.PoolShare)} only worth ${q(payout)}`;
108
+ const { denominator: sharesOutstanding, numerator: poolBalance } = shareWorth;
109
+ !isGTE(want.USDC, poolBalance) ||
110
+ Fail`cannot withdraw ${q(want.USDC)}; only ${q(poolBalance)} in pool`;
111
+ const balancePost = subtract(poolBalance, payout);
112
+ // giving more shares than are outstanding is impossible,
113
+ // so it's not worth a custom diagnostic. subtract will fail
114
+ const outstandingPost = subtract(sharesOutstanding, give.PoolShare);
115
+
116
+ const worthPost = makeRatioFromAmounts(balancePost, outstandingPost);
117
+ return harden({ shareWorth: worthPost, payouts: { USDC: payout } });
118
+ };
119
+
120
+ /**
121
+ * @param {ShareWorth} shareWorth
122
+ * @param {Amount<'nat'>} fees
123
+ */
124
+ export const withFees = (shareWorth, fees) => {
125
+ const balancePost = add(shareWorth.numerator, fees);
126
+ return makeRatioFromAmounts(balancePost, shareWorth.denominator);
127
+ };
128
+
129
+ /**
130
+ *
131
+ * @param {Amount<'nat'>} requested
132
+ * @param {Amount<'nat'>} poolSeatAllocation
133
+ * @param {Amount<'nat'>} encumberedBalance
134
+ * @param {PoolStats} poolStats
135
+ * @throws {Error} if requested is not less than poolSeatAllocation
136
+ */
137
+ export const borrowCalc = (
138
+ requested,
139
+ poolSeatAllocation,
140
+ encumberedBalance,
141
+ poolStats,
142
+ ) => {
143
+ // pool must never go empty
144
+ !isGTE(requested, poolSeatAllocation) ||
145
+ Fail`Cannot borrow. Requested ${q(requested)} must be less than pool balance ${q(poolSeatAllocation)}.`;
146
+
147
+ return harden({
148
+ encumberedBalance: add(encumberedBalance, requested),
149
+ poolStats: {
150
+ ...poolStats,
151
+ totalBorrows: add(poolStats.totalBorrows, requested),
152
+ },
153
+ });
154
+ };
155
+
156
+ /**
157
+ * @param {ShareWorth} shareWorth
158
+ * @param {Allocation} fromSeatAllocation
159
+ * @param {RepayAmountKWR} amounts
160
+ * @param {Amount<'nat'>} encumberedBalance aka 'outstanding borrows'
161
+ * @param {PoolStats} poolStats
162
+ * @throws {Error} if allocations do not match amounts or Principal exceeds encumberedBalance
163
+ */
164
+ export const repayCalc = (
165
+ shareWorth,
166
+ fromSeatAllocation,
167
+ amounts,
168
+ encumberedBalance,
169
+ poolStats,
170
+ ) => {
171
+ (isEqual(fromSeatAllocation.Principal, amounts.Principal) &&
172
+ isEqual(fromSeatAllocation.PoolFee, amounts.PoolFee) &&
173
+ isEqual(fromSeatAllocation.ContractFee, amounts.ContractFee)) ||
174
+ Fail`Cannot repay. From seat allocation ${q(fromSeatAllocation)} does not equal amounts ${q(amounts)}.`;
175
+
176
+ isGTE(encumberedBalance, amounts.Principal) ||
177
+ Fail`Cannot repay. Principal ${q(amounts.Principal)} exceeds encumberedBalance ${q(encumberedBalance)}.`;
178
+
179
+ return harden({
180
+ shareWorth: withFees(shareWorth, amounts.PoolFee),
181
+ encumberedBalance: subtract(encumberedBalance, amounts.Principal),
182
+ poolStats: {
183
+ ...poolStats,
184
+ totalRepays: add(poolStats.totalRepays, amounts.Principal),
185
+ totalPoolFees: add(poolStats.totalPoolFees, amounts.PoolFee),
186
+ totalContractFees: add(poolStats.totalContractFees, amounts.ContractFee),
187
+ },
188
+ });
189
+ };
@@ -0,0 +1,95 @@
1
+ import { AmountShape, BrandShape, RatioShape } from '@agoric/ertp';
2
+ import { M } from '@endo/patterns';
3
+ import { PendingTxStatus } from './constants.js';
4
+
5
+ /**
6
+ * @import {TypedPattern} from '@agoric/internal';
7
+ * @import {FastUsdcTerms} from './fast-usdc.contract.js';
8
+ * @import {USDCProposalShapes} from './pool-share-math.js';
9
+ * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics} from './types.js';
10
+ */
11
+
12
+ /**
13
+ * @param {Brand} brand must be a 'nat' brand, not checked
14
+ * @param {NatValue} [min]
15
+ */
16
+ export const makeNatAmountShape = (brand, min) =>
17
+ harden({ brand, value: min ? M.gte(min) : M.nat() });
18
+
19
+ /** @param {Record<'PoolShares' | 'USDC', Brand<'nat'>>} brands */
20
+ export const makeProposalShapes = ({ PoolShares, USDC }) => {
21
+ /** @type {TypedPattern<USDCProposalShapes['deposit']>} */
22
+ const deposit = M.splitRecord(
23
+ { give: { USDC: makeNatAmountShape(USDC, 1n) } },
24
+ { want: { PoolShare: makeNatAmountShape(PoolShares) } },
25
+ );
26
+ /** @type {TypedPattern<USDCProposalShapes['withdraw']>} */
27
+ const withdraw = M.splitRecord({
28
+ give: { PoolShare: makeNatAmountShape(PoolShares, 1n) },
29
+ want: { USDC: makeNatAmountShape(USDC, 1n) },
30
+ });
31
+ return harden({ deposit, withdraw });
32
+ };
33
+
34
+ /** @type {TypedPattern<FastUsdcTerms>} */
35
+ export const FastUSDCTermsShape = harden({
36
+ usdcDenom: M.string(),
37
+ });
38
+
39
+ /** @type {TypedPattern<string>} */
40
+ export const EvmHashShape = M.string({
41
+ stringLengthLimit: 66,
42
+ });
43
+ harden(EvmHashShape);
44
+
45
+ /** @type {TypedPattern<CctpTxEvidence>} */
46
+ export const CctpTxEvidenceShape = {
47
+ aux: {
48
+ forwardingChannel: M.string(),
49
+ recipientAddress: M.string(),
50
+ },
51
+ blockHash: EvmHashShape,
52
+ blockNumber: M.bigint(),
53
+ blockTimestamp: M.bigint(),
54
+ chainId: M.number(),
55
+ tx: {
56
+ amount: M.bigint(),
57
+ forwardingAddress: M.string(),
58
+ },
59
+ txHash: EvmHashShape,
60
+ };
61
+ harden(CctpTxEvidenceShape);
62
+
63
+ /** @type {TypedPattern<PendingTx>} */
64
+ // @ts-expect-error TypedPattern not recognized as record
65
+ export const PendingTxShape = {
66
+ ...CctpTxEvidenceShape,
67
+ status: M.or(...Object.values(PendingTxStatus)),
68
+ };
69
+ harden(PendingTxShape);
70
+
71
+ export const EudParamShape = {
72
+ EUD: M.string(),
73
+ };
74
+ harden(EudParamShape);
75
+
76
+ const NatAmountShape = { brand: BrandShape, value: M.nat() };
77
+ /** @type {TypedPattern<FeeConfig>} */
78
+ export const FeeConfigShape = {
79
+ flat: NatAmountShape,
80
+ variableRate: RatioShape,
81
+ maxVariable: NatAmountShape,
82
+ contractRate: RatioShape,
83
+ };
84
+ harden(FeeConfigShape);
85
+
86
+ /** @type {TypedPattern<PoolMetrics>} */
87
+ export const PoolMetricsShape = {
88
+ encumberedBalance: AmountShape,
89
+ shareWorth: RatioShape,
90
+ totalContractFees: AmountShape,
91
+ totalPoolFees: AmountShape,
92
+ totalBorrows: AmountShape,
93
+ totalRepays: AmountShape,
94
+ };
95
+ harden(PoolMetricsShape);
@@ -0,0 +1 @@
1
+ export type * from './types.js';
@@ -0,0 +1 @@
1
+ export {};
package/src/types.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { ChainAddress } from '@agoric/orchestration';
2
+ import type { IBCChannelID } from '@agoric/vats';
3
+ import type { Amount } from '@agoric/ertp';
4
+ import type { PendingTxStatus } from './constants.js';
5
+
6
+ export type EvmHash = `0x${string}`;
7
+ export type NobleAddress = `noble1${string}`;
8
+
9
+ export interface CctpTxEvidence {
10
+ /** from Noble RPC */
11
+ aux: {
12
+ forwardingChannel: IBCChannelID;
13
+ recipientAddress: ChainAddress['value'];
14
+ };
15
+ blockHash: EvmHash;
16
+ blockNumber: bigint;
17
+ blockTimestamp: bigint;
18
+ chainId: number;
19
+ /** data covered by signature (aka txHash) */
20
+ tx: {
21
+ amount: bigint;
22
+ forwardingAddress: NobleAddress;
23
+ };
24
+ txHash: EvmHash;
25
+ }
26
+
27
+ export type LogFn = (...args: unknown[]) => void;
28
+
29
+ export interface PendingTx extends CctpTxEvidence {
30
+ status: PendingTxStatus;
31
+ }
32
+
33
+ /** internal key for `StatusManager` exo */
34
+ export type PendingTxKey = `pendingTx:${string}`;
35
+
36
+ /** internal key for `StatusManager` exo */
37
+ export type SeenTxKey = `seenTx:${string}`;
38
+
39
+ export type FeeConfig = {
40
+ flat: Amount<'nat'>;
41
+ variableRate: Ratio;
42
+ maxVariable: Amount<'nat'>;
43
+ contractRate: Ratio;
44
+ };
45
+
46
+ export interface PoolStats {
47
+ totalBorrows: Amount<'nat'>;
48
+ totalContractFees: Amount<'nat'>;
49
+ totalPoolFees: Amount<'nat'>;
50
+ totalRepays: Amount<'nat'>;
51
+ }
52
+
53
+ export interface PoolMetrics extends PoolStats {
54
+ encumberedBalance: Amount<'nat'>;
55
+ shareWorth: Ratio;
56
+ }
57
+
58
+ export type * from './constants.js';
@@ -0,0 +1,12 @@
1
+ /** @import { VStorage } from '@agoric/client-utils' */
2
+
3
+ export const queryFastUSDCLocalChainAccount = async (
4
+ /** @type {VStorage} */ vstorage,
5
+ out = console,
6
+ ) => {
7
+ const agoricAddr = await vstorage.readLatest(
8
+ 'published.fastUSDC.settlementAccount',
9
+ );
10
+ out.log(`Got Fast USDC Local Chain Account ${agoricAddr}`);
11
+ return agoricAddr;
12
+ };
@@ -0,0 +1,71 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { bech32 } from 'bech32';
3
+ import { ethers } from 'ethers';
4
+
5
+ /**
6
+ * Adapted from https://docs.noble.xyz/cctp/mint#encoding
7
+ *
8
+ * @param {string} address
9
+ * @returns {string}
10
+ */
11
+ export const encodeBech32Address = address => {
12
+ const decoded = bech32.decode(address);
13
+ const rawBytes = Buffer.from(bech32.fromWords(decoded.words));
14
+
15
+ const padded = Buffer.alloc(32);
16
+ rawBytes.copy(padded, 32 - rawBytes.length);
17
+
18
+ return `0x${padded.toString('hex')}`;
19
+ };
20
+
21
+ const tokenAbi = ['function approve(address spender, uint256 value) external'];
22
+
23
+ const contractAbi = [
24
+ 'function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external',
25
+ ];
26
+
27
+ export const makeProvider = (/** @type {string} */ rpc) =>
28
+ new ethers.JsonRpcProvider(rpc);
29
+
30
+ const USDC_DECIMALS = 6;
31
+ // For CCTP, noble's domain is universally "4"
32
+ const NOBLE_DOMAIN = 4;
33
+
34
+ export const depositForBurn = async (
35
+ /** @type {ethers.JsonRpcProvider} */ provider,
36
+ /** @type {string} */ ethSeed,
37
+ /** @type {string} */ tokenMessengerAddress,
38
+ /** @type {string} */ tokenAddress,
39
+ /** @type {string} */ destination,
40
+ /** @type {string} */ amount,
41
+ out = console,
42
+ ) => {
43
+ const privateKey = ethSeed;
44
+ const wallet = new ethers.Wallet(privateKey, provider);
45
+ const contractAddress = tokenMessengerAddress;
46
+ const token = new ethers.Contract(tokenAddress, tokenAbi, wallet);
47
+ const contract = new ethers.Contract(contractAddress, contractAbi, wallet);
48
+ const parsedAmount = ethers.parseUnits(amount, USDC_DECIMALS);
49
+ out.log('approving');
50
+ const approveTx = await token.approve(contractAddress, parsedAmount);
51
+ out.log('Transaction sent, waiting for confirmation...');
52
+ const approveReceipt = await approveTx.wait();
53
+ out.log('Transaction confirmed in block', approveReceipt.blockNumber);
54
+ out.log('Transaction hash:', approveReceipt.hash);
55
+
56
+ const mintRecipient = encodeBech32Address(destination);
57
+ out.log('depositing for burn', parsedAmount, 4, mintRecipient, tokenAddress);
58
+ const tx = await contract.depositForBurn(
59
+ parsedAmount,
60
+ NOBLE_DOMAIN,
61
+ mintRecipient,
62
+ tokenAddress,
63
+ );
64
+
65
+ out.log('Transaction sent, waiting for confirmation...');
66
+ const receipt = await tx.wait();
67
+
68
+ out.log('Transaction confirmed in block', receipt.blockNumber);
69
+ out.log('Transaction hash:', receipt.hash);
70
+ out.log('USDC transfer initiated successfully, our work here is done.');
71
+ };