@agoric/fast-usdc 0.2.0-upgrade-19-dev-0754752.0 → 0.2.0-upgrade-18a-dev-4ee0508.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.
@@ -1,22 +1,30 @@
1
- import { deeplyFulfilledObject, makeTracer } from '@agoric/internal';
1
+ import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal';
2
+ import {
3
+ CosmosChainInfoShape,
4
+ DenomDetailShape,
5
+ DenomShape,
6
+ } from '@agoric/orchestration';
7
+ import { Fail } from '@endo/errors';
2
8
  import { E } from '@endo/far';
3
- import { FastUSDCConfigShape } from './type-guards.js';
4
- import { fromExternalConfig } from './utils/config-marshal.js';
9
+ import { makeMarshal } from '@endo/marshal';
10
+ import { M } from '@endo/patterns';
5
11
  import {
6
- inviteOracles,
7
- publishDisplayInfo,
8
- publishFeedPolicy,
9
- } from './utils/core-eval.js';
12
+ FastUSDCTermsShape,
13
+ FeeConfigShape,
14
+ FeedPolicyShape,
15
+ } from './type-guards.js';
16
+ import { fromExternalConfig } from './utils/config-marshal.js';
10
17
 
11
18
  /**
12
- * @import {Brand, Issuer} from '@agoric/ertp';
19
+ * @import {DepositFacet} from '@agoric/ertp/src/types.js'
20
+ * @import {TypedPattern} from '@agoric/internal'
13
21
  * @import {Instance, StartParams} from '@agoric/zoe/src/zoeService/utils'
14
22
  * @import {Board} from '@agoric/vats'
15
23
  * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js'
16
24
  * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js'
17
25
  * @import {LegibleCapData} from './utils/config-marshal.js'
18
26
  * @import {FastUsdcSF} from './fast-usdc.contract.js'
19
- * @import {FastUSDCConfig} from './types.js'
27
+ * @import {FeedPolicy, FastUSDCConfig} from './types.js'
20
28
  */
21
29
 
22
30
  const ShareAssetInfo = /** @type {const} */ harden({
@@ -30,6 +38,16 @@ const trace = makeTracer('FUSD-Start', true);
30
38
 
31
39
  const contractName = 'fastUsdc';
32
40
 
41
+ /** @type {TypedPattern<FastUSDCConfig>} */
42
+ export const FastUSDCConfigShape = M.splitRecord({
43
+ terms: FastUSDCTermsShape,
44
+ oracles: M.recordOf(M.string(), M.string()),
45
+ feeConfig: FeeConfigShape,
46
+ feedPolicy: FeedPolicyShape,
47
+ chainInfo: M.recordOf(M.string(), CosmosChainInfoShape),
48
+ assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
49
+ });
50
+
33
51
  /**
34
52
  * XXX Shouldn't the bridge or board vat handle this?
35
53
  *
@@ -46,8 +64,38 @@ const makePublishingStorageKit = async (path, { chainStorage, board }) => {
46
64
  return { storageNode, marshaller };
47
65
  };
48
66
 
67
+ const BOARD_AUX = 'boardAux';
68
+ const marshalData = makeMarshal(_val => Fail`data only`);
69
+ /**
70
+ * @param {Brand} brand
71
+ * @param {Pick<BootstrapPowers['consume'], 'board' | 'chainStorage'>} powers
72
+ */
73
+ const publishDisplayInfo = async (brand, { board, chainStorage }) => {
74
+ // chainStorage type includes undefined, which doesn't apply here.
75
+ // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247
76
+ const boardAux = E(chainStorage).makeChildNode(BOARD_AUX);
77
+ const [id, displayInfo, allegedName] = await Promise.all([
78
+ E(board).getId(brand),
79
+ E(brand).getDisplayInfo(),
80
+ E(brand).getAllegedName(),
81
+ ]);
82
+ const node = E(boardAux).makeChildNode(id);
83
+ const aux = marshalData.toCapData(harden({ allegedName, displayInfo }));
84
+ await E(node).setValue(JSON.stringify(aux));
85
+ };
86
+
87
+ const FEED_POLICY = 'feedPolicy';
49
88
  const POOL_METRICS = 'poolMetrics';
50
89
 
90
+ /**
91
+ * @param {ERef<StorageNode>} node
92
+ * @param {FeedPolicy} policy
93
+ */
94
+ const publishFeedPolicy = async (node, policy) => {
95
+ const feedPolicy = E(node).makeChildNode(FEED_POLICY);
96
+ await E(feedPolicy).setValue(JSON.stringify(policy));
97
+ };
98
+
51
99
  /**
52
100
  * @typedef { PromiseSpaceOf<{
53
101
  * fastUsdcKit: FastUSDCKit
@@ -59,7 +107,6 @@ const POOL_METRICS = 'poolMetrics';
59
107
  * }} FastUSDCCorePowers
60
108
  *
61
109
  * @typedef {StartedInstanceKitWithLabel & {
62
- * creatorFacet: StartedInstanceKit<FastUsdcSF>['creatorFacet'];
63
110
  * privateArgs: StartParams<FastUsdcSF>['privateArgs'];
64
111
  * }} FastUSDCKit
65
112
  */
@@ -117,6 +164,18 @@ export const startFastUSDC = async (
117
164
  trace('using terms', terms);
118
165
  trace('using fee config', feeConfig);
119
166
 
167
+ trace('look up oracle deposit facets');
168
+ const oracleDepositFacets = await deeplyFulfilledObject(
169
+ objectMap(oracles, async address => {
170
+ /** @type {DepositFacet} */
171
+ const depositFacet = await E(namesByAddress).lookup(
172
+ address,
173
+ 'depositFacet',
174
+ );
175
+ return depositFacet;
176
+ }),
177
+ );
178
+
120
179
  const { storageNode, marshaller } = await makePublishingStorageKit(
121
180
  contractName,
122
181
  {
@@ -172,7 +231,16 @@ export const startFastUSDC = async (
172
231
  brand: shareBrand,
173
232
  });
174
233
 
175
- await inviteOracles({ creatorFacet, namesByAddress }, oracles);
234
+ await Promise.all(
235
+ Object.entries(oracleDepositFacets).map(async ([name, depositFacet]) => {
236
+ const address = oracles[name];
237
+ trace('making invitation for', name, address);
238
+ const toWatch = await E(creatorFacet).makeOperatorInvitation(address);
239
+
240
+ const amt = await E(depositFacet).receive(toWatch);
241
+ trace('sent', amt, 'to', name);
242
+ }),
243
+ );
176
244
 
177
245
  produceInstance.reset();
178
246
  produceInstance.resolve(instance);
@@ -225,10 +293,10 @@ export const getManifestForFastUSDC = (
225
293
  board: true,
226
294
  },
227
295
  issuer: {
228
- produce: { FastLP: true },
296
+ produce: { FastLP: true }, // UNTIL #10432
229
297
  },
230
298
  brand: {
231
- produce: { FastLP: true },
299
+ produce: { FastLP: true }, // UNTIL #10432
232
300
  },
233
301
  instance: {
234
302
  produce: { fastUsdc: true },
@@ -7,11 +7,9 @@ import {
7
7
  } from '@agoric/zoe/src/contractSupport/ratio.js';
8
8
  import { Fail, q } from '@endo/errors';
9
9
 
10
- const { keys } = Object;
11
- const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath;
10
+ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath;
12
11
 
13
12
  /**
14
- * @import {Amount, Brand, DepositFacet, NatValue, Payment} from '@agoric/ertp';
15
13
  * @import {PoolStats} from './types';
16
14
  * @import {RepayAmountKWR} from './exos/liquidity-pool';
17
15
  */
@@ -19,7 +17,8 @@ const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath;
19
17
  /**
20
18
  * Invariant: shareWorth is the pool balance divided by shares outstanding.
21
19
  *
22
- * Use `makeParity(USDC, PoolShares)` for an initial value.
20
+ * Use `makeParity(make(USDC, epsilon), PoolShares)` for an initial
21
+ * value, for some negligible `epsilon` such as 1n.
23
22
  *
24
23
  * @typedef {Ratio} ShareWorth
25
24
  */
@@ -27,26 +26,23 @@ const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath;
27
26
  /**
28
27
  * Make a 1-to-1 ratio between amounts of 2 brands.
29
28
  *
30
- * @param {Brand<'nat'>} numeratorBrand
29
+ * @param {Amount<'nat'>} numerator
31
30
  * @param {Brand<'nat'>} denominatorBrand
32
31
  */
33
- export const makeParity = (numeratorBrand, denominatorBrand) => {
34
- const dust = 1n;
35
- return makeRatio(dust, numeratorBrand, dust, denominatorBrand);
32
+ export const makeParity = (numerator, denominatorBrand) => {
33
+ const value = getValue(numerator.brand, numerator);
34
+ return makeRatio(value, numerator.brand, value, denominatorBrand);
36
35
  };
37
36
 
38
37
  /**
39
38
  * @typedef {{
40
39
  * deposit: {
41
40
  * give: { USDC: Amount<'nat'> },
42
- * want: { PoolShare: Amount<'nat'> }
41
+ * want?: { PoolShare: Amount<'nat'> }
43
42
  * },
44
43
  * withdraw: {
45
44
  * give: { PoolShare: Amount<'nat'> }
46
45
  * want: { USDC: Amount<'nat'> },
47
- * },
48
- * withdrawFees: {
49
- * want: { USDC: Amount<'nat'> }
50
46
  * }
51
47
  * }} USDCProposalShapes
52
48
  */
@@ -95,54 +91,14 @@ export const depositCalc = (shareWorth, { give, want }) => {
95
91
  });
96
92
  };
97
93
 
98
- /**
99
- * Verifies that the total pool balance (unencumbered + encumbered) matches the
100
- * shareWorth numerator. The total pool balance consists of:
101
- * 1. unencumbered balance - USDC available in the pool for borrowing
102
- * 2. encumbered balance - USDC currently lent out
103
- *
104
- * A negligible `dust` amount is used to initialize shareWorth with a non-zero
105
- * denominator. It must remain in the pool at all times.
106
- *
107
- * @param {Allocation} poolAlloc
108
- * @param {ShareWorth} shareWorth
109
- * @param {Amount<'nat'>} encumberedBalance
110
- */
111
- export const checkPoolBalance = (poolAlloc, shareWorth, encumberedBalance) => {
112
- const { brand: usdcBrand } = encumberedBalance;
113
- const unencumberedBalance = poolAlloc.USDC || makeEmpty(usdcBrand);
114
- const kwds = keys(poolAlloc);
115
- kwds.length === 0 ||
116
- (kwds.length === 1 && kwds[0] === 'USDC') ||
117
- Fail`unexpected pool allocations: ${poolAlloc}`;
118
- const dust = make(usdcBrand, 1n);
119
- const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance);
120
- isEqual(grossBalance, shareWorth.numerator) ||
121
- Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`;
122
- return harden({ unencumberedBalance, grossBalance });
123
- };
124
-
125
94
  /**
126
95
  * Compute payout from a withdraw proposal, along with updated shareWorth
127
96
  *
128
97
  * @param {ShareWorth} shareWorth
129
98
  * @param {USDCProposalShapes['withdraw']} proposal
130
- * @param {Allocation} poolAlloc
131
- * @param {Amount<'nat'>} [encumberedBalance]
132
99
  * @returns {{ shareWorth: ShareWorth, payouts: { USDC: Amount<'nat'> }}}
133
100
  */
134
- export const withdrawCalc = (
135
- shareWorth,
136
- { give, want },
137
- poolAlloc,
138
- encumberedBalance = makeEmpty(shareWorth.numerator.brand),
139
- ) => {
140
- const { unencumberedBalance } = checkPoolBalance(
141
- poolAlloc,
142
- shareWorth,
143
- encumberedBalance,
144
- );
145
-
101
+ export const withdrawCalc = (shareWorth, { give, want }) => {
146
102
  assert(!isEmpty(give.PoolShare));
147
103
  assert(!isEmpty(want.USDC));
148
104
 
@@ -152,8 +108,6 @@ export const withdrawCalc = (
152
108
  const { denominator: sharesOutstanding, numerator: poolBalance } = shareWorth;
153
109
  !isGTE(want.USDC, poolBalance) ||
154
110
  Fail`cannot withdraw ${q(want.USDC)}; only ${q(poolBalance)} in pool`;
155
- isGTE(unencumberedBalance, want.USDC) ||
156
- Fail`cannot withdraw ${q(want.USDC)}; ${q(encumberedBalance)} is in use; stand by for pool to return to ${q(poolBalance)}`;
157
111
  const balancePost = subtract(poolBalance, payout);
158
112
  // giving more shares than are outstanding is impossible,
159
113
  // so it's not worth a custom diagnostic. subtract will fail
@@ -1,18 +1,12 @@
1
1
  import { AmountShape, BrandShape, RatioShape } from '@agoric/ertp';
2
2
  import { M } from '@endo/patterns';
3
- import {
4
- CosmosChainInfoShape,
5
- DenomDetailShape,
6
- DenomShape,
7
- } from '@agoric/orchestration/src/typeGuards.js';
8
3
  import { PendingTxStatus } from './constants.js';
9
4
 
10
5
  /**
11
- * @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
12
6
  * @import {TypedPattern} from '@agoric/internal';
13
7
  * @import {FastUsdcTerms} from './fast-usdc.contract.js';
14
8
  * @import {USDCProposalShapes} from './pool-share-math.js';
15
- * @import {CctpTxEvidence, FastUSDCConfig, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook, EvmAddress, EvmHash, RiskAssessment, EvidenceWithRisk} from './types.js';
9
+ * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook, EvmAddress, EvmHash, RiskAssessment, EvidenceWithRisk} from './types.js';
16
10
  */
17
11
 
18
12
  /**
@@ -25,20 +19,16 @@ export const makeNatAmountShape = (brand, min) =>
25
19
  /** @param {Record<'PoolShares' | 'USDC', Brand<'nat'>>} brands */
26
20
  export const makeProposalShapes = ({ PoolShares, USDC }) => {
27
21
  /** @type {TypedPattern<USDCProposalShapes['deposit']>} */
28
- const deposit = M.splitRecord({
29
- give: { USDC: makeNatAmountShape(USDC, 1n) },
30
- want: { PoolShare: makeNatAmountShape(PoolShares) },
31
- });
22
+ const deposit = M.splitRecord(
23
+ { give: { USDC: makeNatAmountShape(USDC, 1n) } },
24
+ { want: M.splitRecord({}, { PoolShare: makeNatAmountShape(PoolShares) }) },
25
+ );
32
26
  /** @type {TypedPattern<USDCProposalShapes['withdraw']>} */
33
27
  const withdraw = M.splitRecord({
34
28
  give: { PoolShare: makeNatAmountShape(PoolShares, 1n) },
35
29
  want: { USDC: makeNatAmountShape(USDC, 1n) },
36
30
  });
37
- /** @type {TypedPattern<USDCProposalShapes['withdrawFees']>} */
38
- const withdrawFees = M.splitRecord({
39
- want: { USDC: makeNatAmountShape(USDC, 1n) },
40
- });
41
- return harden({ deposit, withdraw, withdrawFees });
31
+ return harden({ deposit, withdraw });
42
32
  };
43
33
 
44
34
  /** @type {TypedPattern<FastUsdcTerms>} */
@@ -76,7 +66,6 @@ export const CctpTxEvidenceShape = {
76
66
  },
77
67
  blockHash: EvmHashShape,
78
68
  blockNumber: M.nat(),
79
- blockTimestamp: M.nat(),
80
69
  chainId: M.number(),
81
70
  tx: {
82
71
  amount: M.nat(),
@@ -114,6 +103,7 @@ const NatAmountShape = { brand: BrandShape, value: M.nat() };
114
103
  export const FeeConfigShape = {
115
104
  flat: NatAmountShape,
116
105
  variableRate: RatioShape,
106
+ maxVariable: NatAmountShape,
117
107
  contractRate: RatioShape,
118
108
  };
119
109
  harden(FeeConfigShape);
@@ -131,19 +121,10 @@ harden(PoolMetricsShape);
131
121
 
132
122
  /** @type {TypedPattern<ChainPolicy>} */
133
123
  export const ChainPolicyShape = {
134
- attenuatedCttpBridgeAddresses: M.splitArray(
135
- [EvmHashShape],
136
- undefined,
137
- M.arrayOf(EvmHashShape),
138
- ),
124
+ attenuatedCttpBridgeAddress: EvmHashShape,
139
125
  cctpTokenMessengerAddress: EvmHashShape,
140
126
  confirmations: M.number(),
141
127
  chainId: M.number(),
142
- rateLimits: {
143
- tx: M.bigint(),
144
- blockWindow: M.bigint(),
145
- blockWindowSize: M.number(),
146
- },
147
128
  };
148
129
  harden(ChainPolicyShape);
149
130
 
@@ -162,13 +143,3 @@ export const FeedPolicyShape = M.splitRecord(
162
143
  { eventFilter: M.string() },
163
144
  );
164
145
  harden(FeedPolicyShape);
165
-
166
- /** @type {TypedPattern<FastUSDCConfig>} */
167
- export const FastUSDCConfigShape = M.splitRecord({
168
- terms: FastUSDCTermsShape,
169
- oracles: M.recordOf(M.string(), M.string()),
170
- feeConfig: FeeConfigShape,
171
- feedPolicy: FeedPolicyShape,
172
- chainInfo: M.recordOf(M.string(), CosmosChainInfoShape),
173
- assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
174
- });
package/src/types.ts CHANGED
@@ -27,21 +27,8 @@ export interface CctpTxEvidence {
27
27
  forwardingChannel: IBCChannelID;
28
28
  recipientAddress: ChainAddress['value'];
29
29
  };
30
- /** on the source chain (e.g. L1 Ethereum and L2s Arbitrum, Base) */
31
30
  blockHash: EvmHash;
32
- /** height of blockHash on the source chain */
33
31
  blockNumber: bigint;
34
- /**
35
- * Seconds since Unix epoch. Not all CCTP source chains update time the same
36
- * way but they all use Unix epoch and thus are approximately equal. (Within
37
- * minutes apart.)
38
- */
39
- blockTimestamp: bigint;
40
- //
41
- /**
42
- * [Domain of values](https://chainid.network/) per [EIP-155](https://eips.ethereum.org/EIPS/eip-155)
43
- * (We don't have [CCTP `domain`](https://developers.circle.com/stablecoins/supported-domains) )
44
- */
45
32
  chainId: number;
46
33
  /** data covered by signature (aka txHash) */
47
34
  tx: {
@@ -68,12 +55,6 @@ export interface TransactionRecord extends CopyRecord {
68
55
  status: TxStatus;
69
56
  }
70
57
 
71
- /** the record in vstorage at the path of the contract's node */
72
- export interface ContractRecord extends CopyRecord {
73
- poolAccount: ChainAddress['value'];
74
- settlementAccount: ChainAddress['value'];
75
- }
76
-
77
58
  export type LogFn = (...args: unknown[]) => void;
78
59
 
79
60
  export interface PendingTx extends CctpTxEvidence {
@@ -81,11 +62,9 @@ export interface PendingTx extends CctpTxEvidence {
81
62
  }
82
63
 
83
64
  export type FeeConfig = {
84
- /** flat fee charged for every advance */
85
65
  flat: Amount<'nat'>;
86
- /** proportion of advance kept as a fee */
87
66
  variableRate: Ratio;
88
- /** proportion of fees that goes to the contract (remaining goes to LPs) */
67
+ maxVariable: Amount<'nat'>;
89
68
  contractRate: Ratio;
90
69
  };
91
70
 
@@ -103,35 +82,27 @@ export interface PoolMetrics extends PoolStats {
103
82
 
104
83
  export interface ChainPolicy {
105
84
  /** `msg.sender` of DepositAndBurn to TokenMessenger must be an attenuated wrapper contract that does not contain `replaceDepositForBurn` */
106
- attenuatedCttpBridgeAddresses: EvmHash[];
85
+ attenuatedCttpBridgeAddress: EvmHash;
107
86
  /** @see {@link https://developers.circle.com/stablecoins/evm-smart-contracts} */
108
87
  cctpTokenMessengerAddress: EvmHash;
109
88
  /** e.g., `1` for ETH mainnet 42161 for Arbitrum One. @see {@link https://chainlist.org/} */
110
89
  chainId: EvmChainID;
111
90
  /** the number of block confirmations to observe before reporting */
112
91
  confirmations: number;
113
- rateLimits: {
114
- /** do not advance more than this amount for an individual transaction */
115
- tx: bigint;
116
- /** do not advance more than this amount per block window */
117
- blockWindow: bigint;
118
- /** the number of blocks to consider for `blockWindow` */
119
- blockWindowSize: number;
120
- };
121
92
  }
122
93
 
123
- export type FeedPolicy = {
94
+ export interface FeedPolicy {
124
95
  nobleDomainId: number;
125
96
  nobleAgoricChannelId: string;
126
97
  chainPolicies: Record<EvmChainName, ChainPolicy>;
127
98
  eventFilter?: string;
128
- } & CopyRecord;
99
+ }
129
100
 
130
101
  export type FastUSDCConfig = {
131
102
  terms: FastUsdcTerms;
132
103
  oracles: Record<string, string>;
133
104
  feeConfig: FeeConfig;
134
- feedPolicy: FeedPolicy;
105
+ feedPolicy: FeedPolicy & Passable;
135
106
  noNoble: boolean; // support a3p-integration, which has no noble chain
136
107
  chainInfo: Record<string, CosmosChainInfo & Passable>;
137
108
  assetInfo: [Denom, DenomDetail & { brandKey?: string }][];
@@ -1,38 +1,50 @@
1
1
  import { denomHash, withChainCapabilities } from '@agoric/orchestration';
2
2
  import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
3
- import { ChainPolicies } from './chain-policies.js';
4
3
 
5
4
  /**
6
- * @import {FastUSDCConfig} from '@agoric/fast-usdc';
5
+ * @import {FastUSDCConfig} from '@agoric/fast-usdc/src/types.js'
7
6
  * @import {Passable} from '@endo/marshal';
8
7
  * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
9
8
  */
10
9
 
11
- /** @type {[Denom, DenomDetail & { brandKey?: string}]} */
12
- const usdcOnAgoric = [
13
- `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`,
14
- {
15
- baseName: 'noble',
16
- chainName: 'agoric',
17
- baseDenom: 'uusdc',
18
- brandKey: 'USDC',
19
- },
20
- ];
21
-
22
10
  /** @type {[Denom, DenomDetail & { brandKey?: string}][]} */
23
- export const transferAssetInfo = [
24
- ['uusdc', { baseName: 'noble', chainName: 'noble', baseDenom: 'uusdc' }],
25
- usdcOnAgoric,
11
+ export const defaultAssetInfo = [
12
+ [
13
+ 'uusdc',
14
+ {
15
+ baseName: 'noble',
16
+ chainName: 'noble',
17
+ baseDenom: 'uusdc',
18
+ },
19
+ ],
20
+ [
21
+ `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`,
22
+ {
23
+ baseName: 'noble',
24
+ chainName: 'agoric',
25
+ baseDenom: 'uusdc',
26
+ brandKey: 'USDC',
27
+ },
28
+ ],
29
+ [
30
+ `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.osmosis.connections['noble-1'].transferChannel.channelId })}`,
31
+ {
32
+ baseName: 'noble',
33
+ chainName: 'osmosis',
34
+ baseDenom: 'uusdc',
35
+ },
36
+ ],
26
37
  ];
27
- harden(transferAssetInfo);
38
+ harden(defaultAssetInfo);
28
39
 
29
- /** ABI for DepositForBurn event in TokenMessenger contract */
30
- const DepositForBurnEvent =
31
- 'DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)';
40
+ const agoricAssetInfo = defaultAssetInfo.filter(
41
+ ([_d, i]) => i.chainName === 'agoric',
42
+ );
32
43
 
33
44
  /**
34
45
  * @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy' | 'chainInfo' | 'assetInfo' >>}
35
46
  *
47
+ * TODO: determine OCW operator addresses
36
48
  * meanwhile, use price oracle addresses (from updatePriceFeeds.js).
37
49
  */
38
50
  export const configurations = {
@@ -50,8 +62,16 @@ export const configurations = {
50
62
  feedPolicy: {
51
63
  nobleAgoricChannelId: 'channel-does-not-exist',
52
64
  nobleDomainId: 4,
53
- chainPolicies: ChainPolicies.TESTNET,
54
- eventFilter: DepositForBurnEvent,
65
+ chainPolicies: {
66
+ Arbitrum: {
67
+ attenuatedCttpBridgeAddress:
68
+ '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
69
+ cctpTokenMessengerAddress:
70
+ '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
71
+ chainId: 42161,
72
+ confirmations: 2,
73
+ },
74
+ },
55
75
  },
56
76
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
57
77
  withChainCapabilities({
@@ -60,25 +80,32 @@ export const configurations = {
60
80
  noble: fetchedChainInfo.noble,
61
81
  })
62
82
  ),
63
- assetInfo: [usdcOnAgoric],
83
+ assetInfo: agoricAssetInfo,
64
84
  },
65
85
  MAINNET: {
66
- // per JVC 12 Feb 2025
67
86
  oracles: {
68
- '01node': 'agoric1ym488t6j24x3ys3va3452ftx44lhs64rz8pu7h',
69
- SimplyStaking: 'agoric1s5yawjgj6xcw4ea5r2x4cjrnkprmd0fcun2tyk',
70
- DSRV: 'agoric17crpkfxarq658e9ddru2petrfr0fhjzvjfccq9',
87
+ '01node': 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8',
88
+ 'Simply Staking': 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr',
89
+ P2P: 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj',
71
90
  },
72
91
  feedPolicy: {
73
92
  nobleAgoricChannelId: 'channel-21',
74
93
  nobleDomainId: 4,
75
- chainPolicies: ChainPolicies.MAINNET,
76
- eventFilter: DepositForBurnEvent,
94
+ chainPolicies: {
95
+ Arbitrum: {
96
+ attenuatedCttpBridgeAddress:
97
+ '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
98
+ cctpTokenMessengerAddress:
99
+ '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
100
+ chainId: 42161,
101
+ confirmations: 2,
102
+ },
103
+ },
77
104
  },
78
105
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
79
106
  withChainCapabilities(fetchedChainInfo)
80
107
  ),
81
- assetInfo: transferAssetInfo,
108
+ assetInfo: defaultAssetInfo,
82
109
  },
83
110
  DEVNET: {
84
111
  oracles: {
@@ -91,13 +118,19 @@ export const configurations = {
91
118
  feedPolicy: {
92
119
  nobleAgoricChannelId: 'TODO',
93
120
  nobleDomainId: 4,
94
- chainPolicies: ChainPolicies.TESTNET,
95
- eventFilter: DepositForBurnEvent,
121
+ chainPolicies: {
122
+ Arbitrum: {
123
+ attenuatedCttpBridgeAddress: '0xTODO',
124
+ cctpTokenMessengerAddress: '0xTODO',
125
+ chainId: 421614,
126
+ confirmations: 2,
127
+ },
128
+ },
96
129
  },
97
130
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
98
131
  withChainCapabilities(fetchedChainInfo) // TODO: use devnet values
99
132
  ),
100
- assetInfo: transferAssetInfo,
133
+ assetInfo: defaultAssetInfo, // TODO: use emerynet values
101
134
  },
102
135
  EMERYNET: {
103
136
  oracles: {
@@ -107,13 +140,19 @@ export const configurations = {
107
140
  feedPolicy: {
108
141
  nobleAgoricChannelId: 'TODO',
109
142
  nobleDomainId: 4,
110
- chainPolicies: ChainPolicies.TESTNET,
111
- eventFilter: DepositForBurnEvent,
143
+ chainPolicies: {
144
+ Arbitrum: {
145
+ attenuatedCttpBridgeAddress: '0xTODO',
146
+ cctpTokenMessengerAddress: '0xTODO',
147
+ chainId: 421614,
148
+ confirmations: 2,
149
+ },
150
+ },
112
151
  },
113
152
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
114
153
  withChainCapabilities(fetchedChainInfo) // TODO: use emerynet values
115
154
  ),
116
- assetInfo: transferAssetInfo,
155
+ assetInfo: defaultAssetInfo, // TODO: use emerynet values
117
156
  },
118
157
  };
119
158
  harden(configurations);
package/src/utils/fees.js CHANGED
@@ -4,7 +4,7 @@ import { Fail } from '@endo/errors';
4
4
  import { mustMatch } from '@endo/patterns';
5
5
  import { FeeConfigShape } from '../type-guards.js';
6
6
 
7
- const { add, isGTE, subtract } = AmountMath;
7
+ const { add, isGTE, min, subtract } = AmountMath;
8
8
 
9
9
  /**
10
10
  * @import {Amount} from '@agoric/ertp';
@@ -15,7 +15,7 @@ const { add, isGTE, subtract } = AmountMath;
15
15
  /** @param {FeeConfig} feeConfig */
16
16
  export const makeFeeTools = feeConfig => {
17
17
  mustMatch(feeConfig, FeeConfigShape, 'Must provide feeConfig');
18
- const { flat, variableRate } = feeConfig;
18
+ const { flat, variableRate, maxVariable } = feeConfig;
19
19
  const feeTools = harden({
20
20
  /**
21
21
  * Calculate the net amount to advance after withholding fees.
@@ -34,7 +34,8 @@ export const makeFeeTools = feeConfig => {
34
34
  * @throws {Error} if requested does not exceed fees
35
35
  */
36
36
  calculateAdvanceFee(requested) {
37
- const fee = add(multiplyBy(requested, variableRate), flat);
37
+ const variable = min(multiplyBy(requested, variableRate), maxVariable);
38
+ const fee = add(variable, flat);
38
39
  !isGTE(fee, requested) || Fail`Request must exceed fees.`;
39
40
  return fee;
40
41
  },