@agoric/fast-usdc 0.2.0-u18.0 → 0.2.0-u19.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/src/types.ts CHANGED
@@ -27,8 +27,21 @@ 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) */
30
31
  blockHash: EvmHash;
32
+ /** height of blockHash on the source chain */
31
33
  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
+ */
32
45
  chainId: number;
33
46
  /** data covered by signature (aka txHash) */
34
47
  tx: {
@@ -55,6 +68,12 @@ export interface TransactionRecord extends CopyRecord {
55
68
  status: TxStatus;
56
69
  }
57
70
 
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
+
58
77
  export type LogFn = (...args: unknown[]) => void;
59
78
 
60
79
  export interface PendingTx extends CctpTxEvidence {
@@ -62,9 +81,11 @@ export interface PendingTx extends CctpTxEvidence {
62
81
  }
63
82
 
64
83
  export type FeeConfig = {
84
+ /** flat fee charged for every advance */
65
85
  flat: Amount<'nat'>;
86
+ /** proportion of advance kept as a fee */
66
87
  variableRate: Ratio;
67
- maxVariable: Amount<'nat'>;
88
+ /** proportion of fees that goes to the contract (remaining goes to LPs) */
68
89
  contractRate: Ratio;
69
90
  };
70
91
 
@@ -82,27 +103,35 @@ export interface PoolMetrics extends PoolStats {
82
103
 
83
104
  export interface ChainPolicy {
84
105
  /** `msg.sender` of DepositAndBurn to TokenMessenger must be an attenuated wrapper contract that does not contain `replaceDepositForBurn` */
85
- attenuatedCttpBridgeAddress: EvmHash;
106
+ attenuatedCttpBridgeAddresses: EvmHash[];
86
107
  /** @see {@link https://developers.circle.com/stablecoins/evm-smart-contracts} */
87
108
  cctpTokenMessengerAddress: EvmHash;
88
109
  /** e.g., `1` for ETH mainnet 42161 for Arbitrum One. @see {@link https://chainlist.org/} */
89
110
  chainId: EvmChainID;
90
111
  /** the number of block confirmations to observe before reporting */
91
112
  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
+ };
92
121
  }
93
122
 
94
- export interface FeedPolicy {
123
+ export type FeedPolicy = {
95
124
  nobleDomainId: number;
96
125
  nobleAgoricChannelId: string;
97
126
  chainPolicies: Record<EvmChainName, ChainPolicy>;
98
127
  eventFilter?: string;
99
- }
128
+ } & CopyRecord;
100
129
 
101
130
  export type FastUSDCConfig = {
102
131
  terms: FastUsdcTerms;
103
132
  oracles: Record<string, string>;
104
133
  feeConfig: FeeConfig;
105
- feedPolicy: FeedPolicy & Passable;
134
+ feedPolicy: FeedPolicy;
106
135
  noNoble: boolean; // support a3p-integration, which has no noble chain
107
136
  chainInfo: Record<string, CosmosChainInfo & Passable>;
108
137
  assetInfo: [Denom, DenomDetail & { brandKey?: string }][];
@@ -0,0 +1,140 @@
1
+ /** @import {ChainPolicy} from '../types.js'; */
2
+
3
+ /** @satisfies {Record<'MAINNET'| 'TESTNET', Record<string, ChainPolicy>>} */
4
+ export const ChainPolicies = /** @type {const} */ ({
5
+ MAINNET: {
6
+ Arbitrum: {
7
+ attenuatedCttpBridgeAddresses: [
8
+ '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
9
+ ],
10
+ cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
11
+ chainId: 42161,
12
+ // TODO confirm confirmations and rateLimits
13
+ confirmations: 2,
14
+ rateLimits: {
15
+ blockWindow: 20_000_000_000n,
16
+ blockWindowSize: 10,
17
+ tx: 10_000_000_000n,
18
+ },
19
+ },
20
+ Base: {
21
+ attenuatedCttpBridgeAddresses: [
22
+ '0xB6615B2662b35fc3533F8479002e62D0523341De',
23
+ ],
24
+ cctpTokenMessengerAddress: '0x1682Ae6375C4E4A97e4B583BC394c861A46D8962',
25
+ chainId: 8453,
26
+ // TODO confirm confirmations and rateLimits
27
+ confirmations: 2,
28
+ rateLimits: {
29
+ blockWindow: 20_000_000_000n,
30
+ blockWindowSize: 10,
31
+ tx: 10_000_000_000n,
32
+ },
33
+ },
34
+ Ethereum: {
35
+ attenuatedCttpBridgeAddresses: [
36
+ '0xBC8552339dA68EB65C8b88B414B5854E0E366cFc',
37
+ ],
38
+ cctpTokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155',
39
+ chainId: 1,
40
+ // TODO confirm confirmations and rateLimits
41
+ confirmations: 2,
42
+ rateLimits: {
43
+ blockWindow: 20_000_000_000n,
44
+ blockWindowSize: 10,
45
+ tx: 10_000_000_000n,
46
+ },
47
+ },
48
+ Optimism: {
49
+ attenuatedCttpBridgeAddresses: [
50
+ '0x48C5417ED570928eC85D5e3AD4e7E0EeD7dB1E2A',
51
+ ],
52
+ cctpTokenMessengerAddress: '0x2B4069517957735bE00ceE0fadAE88a26365528f',
53
+ chainId: 10,
54
+ // TODO confirm confirmations and rateLimits
55
+ confirmations: 2,
56
+ rateLimits: {
57
+ blockWindow: 20_000_000_000n,
58
+ blockWindowSize: 10,
59
+ tx: 10_000_000_000n,
60
+ },
61
+ },
62
+ Polygon: {
63
+ attenuatedCttpBridgeAddresses: [
64
+ '0x32cb9574650AFF312c80edc4B4343Ff5500767cA',
65
+ ],
66
+ cctpTokenMessengerAddress: '0x9daF8c91AEFAE50b9c0E69629D3F6Ca40cA3B3FE',
67
+ chainId: 137,
68
+ // TODO confirm confirmations and rateLimits
69
+ confirmations: 2,
70
+ rateLimits: {
71
+ blockWindow: 20_000_000_000n,
72
+ blockWindowSize: 10,
73
+ tx: 10_000_000_000n,
74
+ },
75
+ },
76
+ },
77
+ TESTNET: {
78
+ // Arbitrum Sepolia
79
+ Arbitrum: {
80
+ attenuatedCttpBridgeAddresses: ['0xTODO'],
81
+ cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
82
+ chainId: 421614,
83
+ confirmations: 2,
84
+ rateLimits: {
85
+ blockWindow: 20_000_000_000n,
86
+ blockWindowSize: 10,
87
+ tx: 10_000_000_000n,
88
+ },
89
+ },
90
+ // Base Sepolia
91
+ Base: {
92
+ attenuatedCttpBridgeAddresses: ['0xTODO'],
93
+ cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
94
+ chainId: 84532,
95
+ confirmations: 2,
96
+ rateLimits: {
97
+ blockWindow: 20_000_000_000n,
98
+ blockWindowSize: 10,
99
+ tx: 10_000_000_000n,
100
+ },
101
+ },
102
+ // Ethereum Sepolia
103
+ Ethereum: {
104
+ attenuatedCttpBridgeAddresses: ['0xTODO'],
105
+ cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
106
+ chainId: 11155111,
107
+ confirmations: 2,
108
+ rateLimits: {
109
+ blockWindow: 20_000_000_000n,
110
+ blockWindowSize: 10,
111
+ tx: 10_000_000_000n,
112
+ },
113
+ },
114
+ // OP Sepolia
115
+ Optimism: {
116
+ attenuatedCttpBridgeAddresses: ['0xTODO'],
117
+ cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
118
+ chainId: 11155420,
119
+ confirmations: 2,
120
+ rateLimits: {
121
+ blockWindow: 20_000_000_000n,
122
+ blockWindowSize: 10,
123
+ tx: 10_000_000_000n,
124
+ },
125
+ },
126
+ // Polygon PoS Amoy
127
+ Polygon: {
128
+ attenuatedCttpBridgeAddresses: ['0xTODO'],
129
+ cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
130
+ chainId: 80002,
131
+ confirmations: 2,
132
+ rateLimits: {
133
+ blockWindow: 20_000_000_000n,
134
+ blockWindowSize: 10,
135
+ tx: 10_000_000_000n,
136
+ },
137
+ },
138
+ },
139
+ });
140
+ harden(ChainPolicies);
@@ -0,0 +1,73 @@
1
+ import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal';
2
+ import { Fail } from '@endo/errors';
3
+ import { E } from '@endo/eventual-send';
4
+ import { makeMarshal } from '@endo/marshal';
5
+
6
+ const trace = makeTracer('FUCoreEval');
7
+
8
+ /**
9
+ * @import {Brand, DepositFacet} from '@agoric/ertp';
10
+ * @import {FastUSDCKit} from '../start-fast-usdc.core.js'
11
+ * @import {FeedPolicy} from '../types.js'
12
+ */
13
+
14
+ export const FEED_POLICY = 'feedPolicy';
15
+ export const marshalData = makeMarshal(_val => Fail`data only`);
16
+
17
+ /**
18
+ * @param {ERef<StorageNode>} node
19
+ * @param {FeedPolicy} policy
20
+ */
21
+ export const publishFeedPolicy = async (node, policy) => {
22
+ const feedPolicy = E(node).makeChildNode(FEED_POLICY);
23
+ const value = marshalData.toCapData(policy);
24
+ await E(feedPolicy).setValue(JSON.stringify(value));
25
+ };
26
+
27
+ /**
28
+ * @param {object} powers
29
+ * @param {FastUSDCKit['creatorFacet']} powers.creatorFacet
30
+ * @param {BootstrapPowers['consume']['namesByAddress']} powers.namesByAddress
31
+ * @param {Record<string, string>} oracles
32
+ */
33
+ export const inviteOracles = async (
34
+ { creatorFacet, namesByAddress },
35
+ oracles,
36
+ ) => {
37
+ const oracleDepositFacets = await deeplyFulfilledObject(
38
+ objectMap(
39
+ oracles,
40
+ /** @type {(address: string) => Promise<DepositFacet>} */
41
+ address => E(namesByAddress).lookup(address, 'depositFacet'),
42
+ ),
43
+ );
44
+ await Promise.all(
45
+ Object.entries(oracleDepositFacets).map(async ([name, depositFacet]) => {
46
+ const address = oracles[name];
47
+ trace('making invitation for', name, address);
48
+ const toWatch = await E(creatorFacet).makeOperatorInvitation(address);
49
+
50
+ const amt = await E(depositFacet).receive(toWatch);
51
+ trace('sent', amt, 'to', name);
52
+ }),
53
+ );
54
+ };
55
+
56
+ const BOARD_AUX = 'boardAux';
57
+ /**
58
+ * @param {Brand} brand
59
+ * @param {Pick<BootstrapPowers['consume'], 'board' | 'chainStorage'>} powers
60
+ */
61
+ export 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
+ };
@@ -1,50 +1,38 @@
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';
3
4
 
4
5
  /**
5
- * @import {FastUSDCConfig} from '@agoric/fast-usdc/src/types.js'
6
+ * @import {FastUSDCConfig} from '@agoric/fast-usdc';
6
7
  * @import {Passable} from '@endo/marshal';
7
8
  * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
8
9
  */
9
10
 
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
+
10
22
  /** @type {[Denom, DenomDetail & { brandKey?: string}][]} */
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
- ],
23
+ export const transferAssetInfo = [
24
+ ['uusdc', { baseName: 'noble', chainName: 'noble', baseDenom: 'uusdc' }],
25
+ usdcOnAgoric,
37
26
  ];
38
- harden(defaultAssetInfo);
27
+ harden(transferAssetInfo);
39
28
 
40
- const agoricAssetInfo = defaultAssetInfo.filter(
41
- ([_d, i]) => i.chainName === 'agoric',
42
- );
29
+ /** ABI for DepositForBurn event in TokenMessenger contract */
30
+ const DepositForBurnEvent =
31
+ 'DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)';
43
32
 
44
33
  /**
45
34
  * @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy' | 'chainInfo' | 'assetInfo' >>}
46
35
  *
47
- * TODO: determine OCW operator addresses
48
36
  * meanwhile, use price oracle addresses (from updatePriceFeeds.js).
49
37
  */
50
38
  export const configurations = {
@@ -62,16 +50,8 @@ export const configurations = {
62
50
  feedPolicy: {
63
51
  nobleAgoricChannelId: 'channel-does-not-exist',
64
52
  nobleDomainId: 4,
65
- chainPolicies: {
66
- Arbitrum: {
67
- attenuatedCttpBridgeAddress:
68
- '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
69
- cctpTokenMessengerAddress:
70
- '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
71
- chainId: 42161,
72
- confirmations: 2,
73
- },
74
- },
53
+ chainPolicies: ChainPolicies.TESTNET,
54
+ eventFilter: DepositForBurnEvent,
75
55
  },
76
56
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
77
57
  withChainCapabilities({
@@ -80,32 +60,25 @@ export const configurations = {
80
60
  noble: fetchedChainInfo.noble,
81
61
  })
82
62
  ),
83
- assetInfo: agoricAssetInfo,
63
+ assetInfo: [usdcOnAgoric],
84
64
  },
85
65
  MAINNET: {
66
+ // per JVC 12 Feb 2025
86
67
  oracles: {
87
- '01node': 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8',
88
- 'Simply Staking': 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr',
89
- P2P: 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj',
68
+ '01node': 'agoric1ym488t6j24x3ys3va3452ftx44lhs64rz8pu7h',
69
+ SimplyStaking: 'agoric1s5yawjgj6xcw4ea5r2x4cjrnkprmd0fcun2tyk',
70
+ DSRV: 'agoric17crpkfxarq658e9ddru2petrfr0fhjzvjfccq9',
90
71
  },
91
72
  feedPolicy: {
92
73
  nobleAgoricChannelId: 'channel-21',
93
74
  nobleDomainId: 4,
94
- chainPolicies: {
95
- Arbitrum: {
96
- attenuatedCttpBridgeAddress:
97
- '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
98
- cctpTokenMessengerAddress:
99
- '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
100
- chainId: 42161,
101
- confirmations: 2,
102
- },
103
- },
75
+ chainPolicies: ChainPolicies.MAINNET,
76
+ eventFilter: DepositForBurnEvent,
104
77
  },
105
78
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
106
79
  withChainCapabilities(fetchedChainInfo)
107
80
  ),
108
- assetInfo: defaultAssetInfo,
81
+ assetInfo: transferAssetInfo,
109
82
  },
110
83
  DEVNET: {
111
84
  oracles: {
@@ -118,19 +91,13 @@ export const configurations = {
118
91
  feedPolicy: {
119
92
  nobleAgoricChannelId: 'TODO',
120
93
  nobleDomainId: 4,
121
- chainPolicies: {
122
- Arbitrum: {
123
- attenuatedCttpBridgeAddress: '0xTODO',
124
- cctpTokenMessengerAddress: '0xTODO',
125
- chainId: 421614,
126
- confirmations: 2,
127
- },
128
- },
94
+ chainPolicies: ChainPolicies.TESTNET,
95
+ eventFilter: DepositForBurnEvent,
129
96
  },
130
97
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
131
98
  withChainCapabilities(fetchedChainInfo) // TODO: use devnet values
132
99
  ),
133
- assetInfo: defaultAssetInfo, // TODO: use emerynet values
100
+ assetInfo: transferAssetInfo,
134
101
  },
135
102
  EMERYNET: {
136
103
  oracles: {
@@ -140,19 +107,13 @@ export const configurations = {
140
107
  feedPolicy: {
141
108
  nobleAgoricChannelId: 'TODO',
142
109
  nobleDomainId: 4,
143
- chainPolicies: {
144
- Arbitrum: {
145
- attenuatedCttpBridgeAddress: '0xTODO',
146
- cctpTokenMessengerAddress: '0xTODO',
147
- chainId: 421614,
148
- confirmations: 2,
149
- },
150
- },
110
+ chainPolicies: ChainPolicies.TESTNET,
111
+ eventFilter: DepositForBurnEvent,
151
112
  },
152
113
  chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
153
114
  withChainCapabilities(fetchedChainInfo) // TODO: use emerynet values
154
115
  ),
155
- assetInfo: defaultAssetInfo, // TODO: use emerynet values
116
+ assetInfo: transferAssetInfo,
156
117
  },
157
118
  };
158
119
  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, min, subtract } = AmountMath;
7
+ const { add, isGTE, subtract } = AmountMath;
8
8
 
9
9
  /**
10
10
  * @import {Amount} from '@agoric/ertp';
@@ -15,7 +15,7 @@ const { add, isGTE, min, 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, maxVariable } = feeConfig;
18
+ const { flat, variableRate } = feeConfig;
19
19
  const feeTools = harden({
20
20
  /**
21
21
  * Calculate the net amount to advance after withholding fees.
@@ -34,8 +34,7 @@ export const makeFeeTools = feeConfig => {
34
34
  * @throws {Error} if requested does not exceed fees
35
35
  */
36
36
  calculateAdvanceFee(requested) {
37
- const variable = min(multiplyBy(requested, variableRate), maxVariable);
38
- const fee = add(variable, flat);
37
+ const fee = add(multiplyBy(requested, variableRate), flat);
39
38
  !isGTE(fee, requested) || Fail`Request must exceed fees.`;
40
39
  return fee;
41
40
  },
File without changes
File without changes
File without changes
File without changes
File without changes