@agoric/fast-usdc 0.1.1-dev-a035a67.0 → 0.1.1-dev-decd4ff.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/fast-usdc",
3
- "version": "0.1.1-dev-a035a67.0+a035a67",
3
+ "version": "0.1.1-dev-decd4ff.0+decd4ff",
4
4
  "description": "CLI and library for Fast USDC product",
5
5
  "type": "module",
6
6
  "files": [
@@ -29,13 +29,13 @@
29
29
  "ts-blank-space": "^0.4.4"
30
30
  },
31
31
  "dependencies": {
32
- "@agoric/client-utils": "0.1.1-dev-a035a67.0+a035a67",
33
- "@agoric/cosmic-proto": "0.4.1-dev-a035a67.0+a035a67",
34
- "@agoric/ertp": "0.16.3-dev-a035a67.0+a035a67",
35
- "@agoric/internal": "0.3.3-dev-a035a67.0+a035a67",
36
- "@agoric/notifier": "0.6.3-dev-a035a67.0+a035a67",
37
- "@agoric/orchestration": "0.1.1-dev-a035a67.0+a035a67",
38
- "@agoric/zoe": "0.26.3-dev-a035a67.0+a035a67",
32
+ "@agoric/client-utils": "0.1.1-dev-decd4ff.0+decd4ff",
33
+ "@agoric/cosmic-proto": "0.4.1-dev-decd4ff.0+decd4ff",
34
+ "@agoric/ertp": "0.16.3-dev-decd4ff.0+decd4ff",
35
+ "@agoric/internal": "0.3.3-dev-decd4ff.0+decd4ff",
36
+ "@agoric/notifier": "0.6.3-dev-decd4ff.0+decd4ff",
37
+ "@agoric/orchestration": "0.1.1-dev-decd4ff.0+decd4ff",
38
+ "@agoric/zoe": "0.26.3-dev-decd4ff.0+decd4ff",
39
39
  "@cosmjs/proto-signing": "^0.33.0",
40
40
  "@cosmjs/stargate": "^0.33.0",
41
41
  "@endo/base64": "^1.0.9",
@@ -75,5 +75,5 @@
75
75
  "publishConfig": {
76
76
  "access": "public"
77
77
  },
78
- "gitHead": "a035a67d66f974ed4b58ed1024bb5aa665e580d4"
78
+ "gitHead": "decd4fff2577c63c48facbadb8f9ea45080a5e11"
79
79
  }
@@ -218,7 +218,10 @@ export const repayCalc = (shareWorth, split, encumberedBalance, poolStats) => {
218
218
  ...poolStats,
219
219
  totalRepays: add(poolStats.totalRepays, split.Principal),
220
220
  totalPoolFees: add(poolStats.totalPoolFees, split.PoolFee),
221
- totalContractFees: add(poolStats.totalContractFees, split.ContractFee),
221
+ totalContractFees: add(
222
+ add(poolStats.totalContractFees, split.ContractFee),
223
+ split.RelayFee,
224
+ ),
222
225
  },
223
226
  });
224
227
  };
@@ -1,7 +1,6 @@
1
1
  import { AmountShape, BrandShape, RatioShape } from '@agoric/ertp';
2
2
  import { M } from '@endo/patterns';
3
3
  import {
4
- CosmosChainInfoShape,
5
4
  DenomDetailShape,
6
5
  DenomShape,
7
6
  } from '@agoric/orchestration/src/typeGuards.js';
@@ -10,7 +9,7 @@ import { PendingTxStatus } from './constants.js';
10
9
  /**
11
10
  * @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
12
11
  * @import {TypedPattern} from '@agoric/internal';
13
- * @import {CosmosChainInfo, Denom, DenomDetail, OrchestrationAccount, IBCConnectionInfo} from '@agoric/orchestration';
12
+ * @import {CosmosChainInfo, Denom, DenomDetail, OrchestrationAccount, IBCConnectionInfo, CaipChainId} from '@agoric/orchestration';
14
13
  * @import {USDCProposalShapes} from './pool-share-math.js';
15
14
  * @import {CctpTxEvidence, FastUSDCConfig, FastUsdcTerms, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook, EvmAddress, EvmHash, RiskAssessment, EvidenceWithRisk} from './types.js';
16
15
  */
@@ -110,13 +109,33 @@ export const AddressHookShape = {
110
109
  harden(AddressHookShape);
111
110
 
112
111
  const NatAmountShape = { brand: BrandShape, value: M.nat() };
112
+
113
+ export const DestinationOverridesShape = M.recordOf(
114
+ M.string(),
115
+ M.splitRecord(
116
+ {},
117
+ {
118
+ flat: NatAmountShape,
119
+ variableRate: RatioShape,
120
+ contractRate: RatioShape,
121
+ relay: NatAmountShape,
122
+ },
123
+ ),
124
+ );
125
+
113
126
  /** @type {TypedPattern<FeeConfig>} */
114
- export const FeeConfigShape = {
115
- flat: NatAmountShape,
116
- variableRate: RatioShape,
117
- contractRate: RatioShape,
118
- };
119
- harden(FeeConfigShape);
127
+ export const FeeConfigShape = M.splitRecord(
128
+ {
129
+ flat: NatAmountShape,
130
+ variableRate: RatioShape,
131
+ contractRate: RatioShape,
132
+ },
133
+ {
134
+ relay: NatAmountShape,
135
+ destinationOverrides: DestinationOverridesShape,
136
+ },
137
+ {},
138
+ );
120
139
 
121
140
  /** @type {TypedPattern<PoolMetrics>} */
122
141
  export const PoolMetricsShape = {
@@ -162,16 +181,6 @@ export const FeedPolicyShape = M.splitRecord(
162
181
  );
163
182
  harden(FeedPolicyShape);
164
183
 
165
- /** @type {TypedPattern<FastUSDCConfig>} */
166
- export const FastUSDCConfigShape = M.splitRecord({
167
- terms: FastUSDCTermsShape,
168
- oracles: M.recordOf(M.string(), M.string()),
169
- feeConfig: FeeConfigShape,
170
- feedPolicy: FeedPolicyShape,
171
- chainInfo: M.recordOf(M.string(), CosmosChainInfoShape),
172
- assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
173
- });
174
-
175
184
  /**
176
185
  * The version of CosmosChainInfoShape that matches the `valueShape` used in FUSDC's ChainHub's `chainInfos` mapStore.
177
186
  * @type {TypedPattern<CosmosChainInfo>}
@@ -189,3 +198,13 @@ export const CosmosChainInfoShapeV1 = M.splitRecord(
189
198
  pfmEnabled: M.boolean(),
190
199
  },
191
200
  );
201
+
202
+ /** @type {TypedPattern<FastUSDCConfig>} */
203
+ export const FastUSDCConfigShape = M.splitRecord({
204
+ terms: FastUSDCTermsShape,
205
+ oracles: M.recordOf(M.string(), M.string()),
206
+ feeConfig: FeeConfigShape,
207
+ feedPolicy: FeedPolicyShape,
208
+ chainInfo: M.recordOf(M.string(), CosmosChainInfoShapeV1),
209
+ assetInfo: M.arrayOf([DenomShape, DenomDetailShape]),
210
+ });
package/src/types.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  DenomDetail,
8
8
  BaseChainInfo,
9
9
  KnownNamespace,
10
+ CaipChainId,
10
11
  } from '@agoric/orchestration';
11
12
  import type { IBCChannelID } from '@agoric/vats';
12
13
  import type { Amount } from '@agoric/ertp';
@@ -42,7 +43,7 @@ export interface CctpTxEvidence {
42
43
  /** from Noble RPC */
43
44
  aux: {
44
45
  forwardingChannel: IBCChannelID;
45
- recipientAddress: CosmosChainAddress['value'];
46
+ recipientAddress: CosmosChainAddress['value'] | AccountId;
46
47
  };
47
48
  /** on the source chain (e.g. L1 Ethereum and L2s Arbitrum, Base) */
48
49
  blockHash: EvmHash;
@@ -98,12 +99,22 @@ export interface PendingTx extends CctpTxEvidence {
98
99
  }
99
100
 
100
101
  export type FeeConfig = {
101
- /** flat fee charged for every advance */
102
+ /** flat fee charged for every advance, eligible for LP disbursement */
102
103
  flat: Amount<'nat'>;
103
- /** proportion of advance kept as a fee */
104
+ /** proportion of advance kept as a fee, eligible for LP disbursement */
104
105
  variableRate: Ratio;
105
- /** proportion of fees that goes to the contract (remaining goes to LPs) */
106
+ /** proportion of `flat` and `variableRate` fees that goes to the contract (remaining goes to LPs) */
106
107
  contractRate: Ratio;
108
+ /**
109
+ * if present, a fee required to relay (e.g. CCTP to EVM/Solana). Not
110
+ * considered for LP disbursement - goes to `contractSeat`.
111
+ */
112
+ relay?: Amount<'nat'>;
113
+ /** Optional destination-specific overrides. If present, must supersede base values. */
114
+ destinationOverrides?: Record<
115
+ CaipChainId,
116
+ Partial<Omit<FeeConfig, 'destinationOverrides'>>
117
+ >;
107
118
  };
108
119
 
109
120
  export interface PoolStats {
package/src/utils/fees.js CHANGED
@@ -2,12 +2,14 @@ import { AmountMath } from '@agoric/ertp';
2
2
  import { multiplyBy } from '@agoric/ertp/src/ratio.js';
3
3
  import { Fail } from '@endo/errors';
4
4
  import { mustMatch } from '@endo/patterns';
5
+ import { chainOfAccount } from '@agoric/orchestration/src/utils/address.js';
5
6
  import { FeeConfigShape } from '../type-guards.js';
6
7
 
7
- const { add, isGTE, subtract } = AmountMath;
8
+ const { add, isGTE, subtract, makeEmpty } = AmountMath;
8
9
 
9
10
  /**
10
11
  * @import {Amount} from '@agoric/ertp';
12
+ * @import {AccountId} from '@agoric/orchestration';
11
13
  * @import {FeeConfig} from '../types.js';
12
14
  */
13
15
 
@@ -16,6 +18,7 @@ const { add, isGTE, subtract } = AmountMath;
16
18
  * Principal: Amount<'nat'>;
17
19
  * PoolFee: Amount<'nat'>;
18
20
  * ContractFee: Amount<'nat'>;
21
+ * RelayFee: Amount<'nat'>;
19
22
  * }} RepayAmountKWR
20
23
  */
21
24
 
@@ -24,48 +27,116 @@ const { add, isGTE, subtract } = AmountMath;
24
27
  * Principal: Payment<'nat'>;
25
28
  * PoolFee: Payment<'nat'>;
26
29
  * ContractFee: Payment<'nat'>;
30
+ * RelayFee: Payment<'nat'>;
27
31
  * }} RepayPaymentKWR
28
32
  */
29
33
 
30
- /** @param {FeeConfig} feeConfig */
34
+ /**
35
+ * @template {keyof Omit<FeeConfig, 'destinationOverrides'>} K
36
+ * @param {FeeConfig} feeConfig
37
+ * @param {K} key
38
+ * @param {AccountId} destination
39
+ * @returns {FeeConfig[K]}
40
+ */
41
+ const getConfigValue = (feeConfig, key, destination) => {
42
+ const chainId = chainOfAccount(destination);
43
+ if (
44
+ feeConfig.destinationOverrides?.[chainId] &&
45
+ feeConfig.destinationOverrides[chainId][key] !== undefined
46
+ ) {
47
+ return feeConfig.destinationOverrides[chainId][key];
48
+ }
49
+ return feeConfig[key];
50
+ };
51
+
52
+ /**
53
+ * @param {FeeConfig} feeConfig
54
+ */
31
55
  export const makeFeeTools = feeConfig => {
32
56
  mustMatch(feeConfig, FeeConfigShape, 'Must provide feeConfig');
33
- const { flat, variableRate } = feeConfig;
57
+ const emptyAmount = makeEmpty(feeConfig.flat.brand);
58
+
34
59
  const feeTools = harden({
35
60
  /**
36
- * Calculate the net amount to advance after withholding fees.
61
+ * Calculate the base fee to charge for the advance (variable + flat).
62
+ * Will be shared between the pool and the contract based on
63
+ * {@link FeeConfig.contractRate}.
37
64
  *
38
65
  * @param {Amount<'nat'>} requested
39
- * @throws {Error} if requested does not exceed fees
66
+ * @param {AccountId} destination
67
+ * @returns {Amount<'nat'>}
40
68
  */
41
- calculateAdvance(requested) {
42
- const fee = feeTools.calculateAdvanceFee(requested);
43
- return subtract(requested, fee);
69
+ calculateBaseFee(requested, destination) {
70
+ const flat = getConfigValue(feeConfig, 'flat', destination);
71
+ const variableRate = getConfigValue(
72
+ feeConfig,
73
+ 'variableRate',
74
+ destination,
75
+ );
76
+ return add(multiplyBy(requested, variableRate), flat);
77
+ },
78
+ /**
79
+ * Calculate the optional relay fee charged for certain destinations.
80
+ * Only disbursed to contract seat.
81
+ *
82
+ * @param {AccountId} destination
83
+ * @returns {Amount<'nat'>}
84
+ */
85
+ calculateRelayFee(destination) {
86
+ const relay = getConfigValue(feeConfig, 'relay', destination);
87
+ return relay || emptyAmount;
44
88
  },
45
89
  /**
46
90
  * Calculate the total fee to charge for the advance.
47
91
  *
48
92
  * @param {Amount<'nat'>} requested
93
+ * @param {AccountId} destination
49
94
  * @throws {Error} if requested does not exceed fees
50
95
  */
51
- calculateAdvanceFee(requested) {
52
- const fee = add(multiplyBy(requested, variableRate), flat);
96
+ calculateAdvanceFee(requested, destination) {
97
+ const baseFee = feeTools.calculateBaseFee(requested, destination);
98
+ const relayFee = feeTools.calculateRelayFee(destination);
99
+ const fee = add(baseFee, relayFee);
53
100
  !isGTE(fee, requested) || Fail`Request must exceed fees.`;
54
101
  return fee;
55
102
  },
103
+ /**
104
+ * Calculate the net amount to advance after withholding fees.
105
+ *
106
+ * @param {Amount<'nat'>} requested
107
+ * @param {AccountId} destination
108
+ * @throws {Error} if requested does not exceed fees
109
+ */
110
+ calculateAdvance(requested, destination) {
111
+ const fee = feeTools.calculateAdvanceFee(requested, destination);
112
+ return subtract(requested, fee);
113
+ },
56
114
  /**
57
115
  * Calculate the split of fees between pool and contract.
58
116
  *
117
+ * The `ContractFee` includes base fees plus the relay fee.
118
+ *
59
119
  * @param {Amount<'nat'>} requested
120
+ * @param {AccountId} destination
60
121
  * @returns {RepayAmountKWR} an {@link AmountKeywordRecord}
61
122
  * @throws {Error} if requested does not exceed fees
62
123
  */
63
- calculateSplit(requested) {
64
- const fee = feeTools.calculateAdvanceFee(requested);
65
- const Principal = subtract(requested, fee);
66
- const ContractFee = multiplyBy(fee, feeConfig.contractRate);
67
- const PoolFee = subtract(fee, ContractFee);
68
- return harden({ Principal, PoolFee, ContractFee });
124
+ calculateSplit(requested, destination) {
125
+ const baseFee = feeTools.calculateBaseFee(requested, destination);
126
+ const relayFee = feeTools.calculateRelayFee(destination);
127
+ const totalFee = add(baseFee, relayFee);
128
+ !isGTE(totalFee, requested) || Fail`Request must exceed fees.`;
129
+
130
+ const contractRate = getConfigValue(
131
+ feeConfig,
132
+ 'contractRate',
133
+ destination,
134
+ );
135
+ const Principal = subtract(requested, totalFee);
136
+ const ContractFee = multiplyBy(baseFee, contractRate);
137
+ const PoolFee = subtract(baseFee, ContractFee);
138
+
139
+ return harden({ Principal, PoolFee, ContractFee, RelayFee: relayFee });
69
140
  },
70
141
  });
71
142
  return feeTools;