@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.2.0-u18.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.
@@ -6,7 +6,7 @@ import { PendingTxStatus } from './constants.js';
6
6
  * @import {TypedPattern} from '@agoric/internal';
7
7
  * @import {FastUsdcTerms} from './fast-usdc.contract.js';
8
8
  * @import {USDCProposalShapes} from './pool-share-math.js';
9
- * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy} from './types.js';
9
+ * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook, EvmAddress, EvmHash, RiskAssessment, EvidenceWithRisk} from './types.js';
10
10
  */
11
11
 
12
12
  /**
@@ -21,7 +21,7 @@ export const makeProposalShapes = ({ PoolShares, USDC }) => {
21
21
  /** @type {TypedPattern<USDCProposalShapes['deposit']>} */
22
22
  const deposit = M.splitRecord(
23
23
  { give: { USDC: makeNatAmountShape(USDC, 1n) } },
24
- { want: { PoolShare: makeNatAmountShape(PoolShares) } },
24
+ { want: M.splitRecord({}, { PoolShare: makeNatAmountShape(PoolShares) }) },
25
25
  );
26
26
  /** @type {TypedPattern<USDCProposalShapes['withdraw']>} */
27
27
  const withdraw = M.splitRecord({
@@ -36,12 +36,28 @@ export const FastUSDCTermsShape = harden({
36
36
  usdcDenom: M.string(),
37
37
  });
38
38
 
39
- /** @type {TypedPattern<string>} */
39
+ /** @type {TypedPattern<EvmAddress>} */
40
+ export const EvmAddressShape = M.string({
41
+ // 0x + 40 hex digits
42
+ stringLengthLimit: 42,
43
+ });
44
+ harden(EvmAddressShape);
45
+
46
+ /** @type {TypedPattern<EvmHash>} */
40
47
  export const EvmHashShape = M.string({
41
48
  stringLengthLimit: 66,
42
49
  });
43
50
  harden(EvmHashShape);
44
51
 
52
+ /** @type {TypedPattern<RiskAssessment>} */
53
+ export const RiskAssessmentShape = M.splitRecord(
54
+ {},
55
+ {
56
+ risksIdentified: M.arrayOf(M.string()),
57
+ },
58
+ );
59
+ harden(RiskAssessmentShape);
60
+
45
61
  /** @type {TypedPattern<CctpTxEvidence>} */
46
62
  export const CctpTxEvidenceShape = {
47
63
  aux: {
@@ -49,17 +65,24 @@ export const CctpTxEvidenceShape = {
49
65
  recipientAddress: M.string(),
50
66
  },
51
67
  blockHash: EvmHashShape,
52
- blockNumber: M.bigint(),
53
- blockTimestamp: M.bigint(),
68
+ blockNumber: M.nat(),
54
69
  chainId: M.number(),
55
70
  tx: {
56
- amount: M.bigint(),
71
+ amount: M.nat(),
57
72
  forwardingAddress: M.string(),
73
+ sender: EvmAddressShape,
58
74
  },
59
75
  txHash: EvmHashShape,
60
76
  };
61
77
  harden(CctpTxEvidenceShape);
62
78
 
79
+ /** @type {TypedPattern<EvidenceWithRisk>} */
80
+ export const EvidenceWithRiskShape = {
81
+ evidence: CctpTxEvidenceShape,
82
+ risk: RiskAssessmentShape,
83
+ };
84
+ harden(EvidenceWithRiskShape);
85
+
63
86
  /** @type {TypedPattern<PendingTx>} */
64
87
  // @ts-expect-error TypedPattern not recognized as record
65
88
  export const PendingTxShape = {
@@ -68,10 +91,12 @@ export const PendingTxShape = {
68
91
  };
69
92
  harden(PendingTxShape);
70
93
 
71
- export const EudParamShape = {
72
- EUD: M.string(),
94
+ /** @type {TypedPattern<AddressHook>} */
95
+ export const AddressHookShape = {
96
+ baseAddress: M.string(),
97
+ query: { EUD: M.string() },
73
98
  };
74
- harden(EudParamShape);
99
+ harden(AddressHookShape);
75
100
 
76
101
  const NatAmountShape = { brand: BrandShape, value: M.nat() };
77
102
  /** @type {TypedPattern<FeeConfig>} */
@@ -95,16 +120,13 @@ export const PoolMetricsShape = {
95
120
  harden(PoolMetricsShape);
96
121
 
97
122
  /** @type {TypedPattern<ChainPolicy>} */
98
- export const ChainPoliciesShape = M.splitRecord(
99
- {
100
- nobleContractAddress: EvmHashShape,
101
- cctpTokenMessengerAddress: EvmHashShape,
102
- confirmations: M.number(),
103
- chainId: M.number(),
104
- },
105
- { chainType: M.number() },
106
- );
107
- harden(ChainPoliciesShape);
123
+ export const ChainPolicyShape = {
124
+ attenuatedCttpBridgeAddress: EvmHashShape,
125
+ cctpTokenMessengerAddress: EvmHashShape,
126
+ confirmations: M.number(),
127
+ chainId: M.number(),
128
+ };
129
+ harden(ChainPolicyShape);
108
130
 
109
131
  /**
110
132
  * @type {TypedPattern<FeedPolicy>}
@@ -116,7 +138,7 @@ export const FeedPolicyShape = M.splitRecord(
116
138
  {
117
139
  nobleDomainId: M.number(),
118
140
  nobleAgoricChannelId: M.string(),
119
- chainPolicies: M.recordOf(M.string(), ChainPoliciesShape),
141
+ chainPolicies: M.recordOf(M.string(), ChainPolicyShape),
120
142
  },
121
143
  { eventFilter: M.string() },
122
144
  );
package/src/types.ts CHANGED
@@ -1,13 +1,26 @@
1
- import type { ChainAddress } from '@agoric/orchestration';
1
+ import type {
2
+ ChainAddress,
3
+ CosmosChainInfo,
4
+ Denom,
5
+ DenomDetail,
6
+ } from '@agoric/orchestration';
2
7
  import type { IBCChannelID } from '@agoric/vats';
3
8
  import type { Amount } from '@agoric/ertp';
4
- import type { PendingTxStatus } from './constants.js';
9
+ import type { CopyRecord, Passable } from '@endo/pass-style';
10
+ import type { PendingTxStatus, TxStatus } from './constants.js';
11
+ import type { FastUsdcTerms } from './fast-usdc.contract.js';
12
+ import type { RepayAmountKWR } from './exos/liquidity-pool.js';
5
13
 
6
14
  export type EvmHash = `0x${string}`;
15
+ export type EvmAddress = `0x${string & { length: 40 }}`;
7
16
  export type NobleAddress = `noble1${string}`;
8
17
  export type EvmChainID = number;
9
18
  export type EvmChainName = string;
10
19
 
20
+ export interface RiskAssessment {
21
+ risksIdentified?: string[];
22
+ }
23
+
11
24
  export interface CctpTxEvidence {
12
25
  /** from Noble RPC */
13
26
  aux: {
@@ -16,28 +29,38 @@ export interface CctpTxEvidence {
16
29
  };
17
30
  blockHash: EvmHash;
18
31
  blockNumber: bigint;
19
- blockTimestamp: bigint;
20
32
  chainId: number;
21
33
  /** data covered by signature (aka txHash) */
22
34
  tx: {
23
35
  amount: bigint;
24
36
  forwardingAddress: NobleAddress;
37
+ sender: EvmAddress;
25
38
  };
26
39
  txHash: EvmHash;
27
40
  }
28
41
 
42
+ export interface EvidenceWithRisk {
43
+ evidence: CctpTxEvidence;
44
+ risk: RiskAssessment;
45
+ }
46
+
47
+ /**
48
+ * 'evidence' only available when it's first observed and not in subsequent
49
+ * updates.
50
+ */
51
+ export interface TransactionRecord extends CopyRecord {
52
+ evidence?: CctpTxEvidence;
53
+ split?: RepayAmountKWR;
54
+ risksIdentified?: string[];
55
+ status: TxStatus;
56
+ }
57
+
29
58
  export type LogFn = (...args: unknown[]) => void;
30
59
 
31
60
  export interface PendingTx extends CctpTxEvidence {
32
61
  status: PendingTxStatus;
33
62
  }
34
63
 
35
- /** internal key for `StatusManager` exo */
36
- export type PendingTxKey = `pendingTx:${string}`;
37
-
38
- /** internal key for `StatusManager` exo */
39
- export type SeenTxKey = `seenTx:${string}`;
40
-
41
64
  export type FeeConfig = {
42
65
  flat: Amount<'nat'>;
43
66
  variableRate: Ratio;
@@ -58,11 +81,14 @@ export interface PoolMetrics extends PoolStats {
58
81
  }
59
82
 
60
83
  export interface ChainPolicy {
61
- nobleContractAddress: EvmHash;
84
+ /** `msg.sender` of DepositAndBurn to TokenMessenger must be an attenuated wrapper contract that does not contain `replaceDepositForBurn` */
85
+ attenuatedCttpBridgeAddress: EvmHash;
86
+ /** @see {@link https://developers.circle.com/stablecoins/evm-smart-contracts} */
62
87
  cctpTokenMessengerAddress: EvmHash;
63
- confirmations: number;
88
+ /** e.g., `1` for ETH mainnet 42161 for Arbitrum One. @see {@link https://chainlist.org/} */
64
89
  chainId: EvmChainID;
65
- chainType?: number;
90
+ /** the number of block confirmations to observe before reporting */
91
+ confirmations: number;
66
92
  }
67
93
 
68
94
  export interface FeedPolicy {
@@ -72,4 +98,24 @@ export interface FeedPolicy {
72
98
  eventFilter?: string;
73
99
  }
74
100
 
101
+ export type FastUSDCConfig = {
102
+ terms: FastUsdcTerms;
103
+ oracles: Record<string, string>;
104
+ feeConfig: FeeConfig;
105
+ feedPolicy: FeedPolicy & Passable;
106
+ noNoble: boolean; // support a3p-integration, which has no noble chain
107
+ chainInfo: Record<string, CosmosChainInfo & Passable>;
108
+ assetInfo: [Denom, DenomDetail & { brandKey?: string }][];
109
+ } & CopyRecord;
110
+
111
+ /** decoded address hook parameters */
112
+ export type AddressHook = {
113
+ baseAddress: string;
114
+ query: {
115
+ /** end user destination address */
116
+ EUD: string;
117
+ };
118
+ };
119
+
75
120
  export type * from './constants.js';
121
+ export type { LiquidityPoolKit } from './exos/liquidity-pool.js';
@@ -5,7 +5,7 @@ export const queryFastUSDCLocalChainAccount = async (
5
5
  out = console,
6
6
  ) => {
7
7
  const agoricAddr = await vstorage.readLatest(
8
- 'published.fastUSDC.settlementAccount',
8
+ 'published.fastUsdc.settlementAccount',
9
9
  );
10
10
  out.log(`Got Fast USDC Local Chain Account ${agoricAddr}`);
11
11
  return agoricAddr;
@@ -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, our work here is done.');
70
+ out.log('USDC transfer initiated successfully');
71
71
  };
package/src/util/file.js CHANGED
@@ -27,4 +27,4 @@ export const makeFile = (
27
27
  return { read, write, exists, path };
28
28
  };
29
29
 
30
- /** @typedef {ReturnType<typeof makeFile>} file */
30
+ /** @typedef {ReturnType<typeof makeFile>} File */
@@ -0,0 +1,166 @@
1
+ import { denomHash, withChainCapabilities } from '@agoric/orchestration';
2
+ import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
3
+
4
+ /**
5
+ * @import {FastUSDCConfig} from '@agoric/fast-usdc/src/types.js'
6
+ * @import {Passable} from '@endo/marshal';
7
+ * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
8
+ */
9
+
10
+ /** @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
+ ],
37
+ ];
38
+ harden(defaultAssetInfo);
39
+
40
+ const agoricAssetInfo = defaultAssetInfo.filter(
41
+ ([_d, i]) => i.chainName === 'agoric',
42
+ );
43
+
44
+ /**
45
+ * @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy' | 'chainInfo' | 'assetInfo' >>}
46
+ *
47
+ * TODO: determine OCW operator addresses
48
+ * meanwhile, use price oracle addresses (from updatePriceFeeds.js).
49
+ */
50
+ export const configurations = {
51
+ /**
52
+ * NOTE: The a3p-integration runtime does _not_ include
53
+ * a noble chain; this limits functionality to advancing
54
+ * to the Agoric chain.
55
+ */
56
+ A3P_INTEGRATION: {
57
+ oracles: {
58
+ gov1: 'agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q',
59
+ gov2: 'agoric1wrfh296eu2z34p6pah7q04jjuyj3mxu9v98277',
60
+ gov3: 'agoric1ydzxwh6f893jvpaslmaz6l8j2ulup9a7x8qvvq',
61
+ },
62
+ feedPolicy: {
63
+ nobleAgoricChannelId: 'channel-does-not-exist',
64
+ nobleDomainId: 4,
65
+ chainPolicies: {
66
+ Arbitrum: {
67
+ attenuatedCttpBridgeAddress:
68
+ '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
69
+ cctpTokenMessengerAddress:
70
+ '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
71
+ chainId: 42161,
72
+ confirmations: 2,
73
+ },
74
+ },
75
+ },
76
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
77
+ withChainCapabilities({
78
+ agoric: fetchedChainInfo.agoric,
79
+ // registering USDC-on-agoric requires registering the noble chain
80
+ noble: fetchedChainInfo.noble,
81
+ })
82
+ ),
83
+ assetInfo: agoricAssetInfo,
84
+ },
85
+ MAINNET: {
86
+ oracles: {
87
+ '01node': 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8',
88
+ 'Simply Staking': 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr',
89
+ P2P: 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj',
90
+ },
91
+ feedPolicy: {
92
+ nobleAgoricChannelId: 'channel-21',
93
+ nobleDomainId: 4,
94
+ chainPolicies: {
95
+ Arbitrum: {
96
+ attenuatedCttpBridgeAddress:
97
+ '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347',
98
+ cctpTokenMessengerAddress:
99
+ '0x19330d10D9Cc8751218eaf51E8885D058642E08A',
100
+ chainId: 42161,
101
+ confirmations: 2,
102
+ },
103
+ },
104
+ },
105
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
106
+ withChainCapabilities(fetchedChainInfo)
107
+ ),
108
+ assetInfo: defaultAssetInfo,
109
+ },
110
+ DEVNET: {
111
+ oracles: {
112
+ DSRV: 'agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft',
113
+ Stakin: 'agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82',
114
+ '01node': 'agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4',
115
+ 'Simply Staking': 'agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf',
116
+ P2P: 'agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk',
117
+ },
118
+ feedPolicy: {
119
+ nobleAgoricChannelId: 'TODO',
120
+ nobleDomainId: 4,
121
+ chainPolicies: {
122
+ Arbitrum: {
123
+ attenuatedCttpBridgeAddress: '0xTODO',
124
+ cctpTokenMessengerAddress: '0xTODO',
125
+ chainId: 421614,
126
+ confirmations: 2,
127
+ },
128
+ },
129
+ },
130
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
131
+ withChainCapabilities(fetchedChainInfo) // TODO: use devnet values
132
+ ),
133
+ assetInfo: defaultAssetInfo, // TODO: use emerynet values
134
+ },
135
+ EMERYNET: {
136
+ oracles: {
137
+ gov1: 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce',
138
+ gov2: 'agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang',
139
+ },
140
+ feedPolicy: {
141
+ nobleAgoricChannelId: 'TODO',
142
+ nobleDomainId: 4,
143
+ chainPolicies: {
144
+ Arbitrum: {
145
+ attenuatedCttpBridgeAddress: '0xTODO',
146
+ cctpTokenMessengerAddress: '0xTODO',
147
+ chainId: 421614,
148
+ confirmations: 2,
149
+ },
150
+ },
151
+ },
152
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
153
+ withChainCapabilities(fetchedChainInfo) // TODO: use emerynet values
154
+ ),
155
+ assetInfo: defaultAssetInfo, // TODO: use emerynet values
156
+ },
157
+ };
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
+ );
@@ -0,0 +1,9 @@
1
+ export const flags = (
2
+ record: Record<string, string | number | bigint | undefined>,
3
+ ): string[] => {
4
+ // @ts-expect-error undefined is filtered out
5
+ const skipUndef: [string, string][] = Object.entries(record).filter(
6
+ ([_k, v]) => v !== undefined,
7
+ );
8
+ return skipUndef.map(([k, v]) => [`--${k}`, `${v}`]).flat();
9
+ };
@@ -0,0 +1,14 @@
1
+ import type { Writable } from 'node:stream';
2
+
3
+ /**
4
+ * mock stdout / stderr, for example
5
+ *
6
+ * @param buf - caller-provided buffer for written data
7
+ */
8
+ export const mockStream = <T extends Writable>(buf: string[]): T =>
9
+ ({
10
+ write: txt => {
11
+ buf.push(txt);
12
+ return true;
13
+ },
14
+ }) as T;
@@ -1,26 +0,0 @@
1
- ## **StatusManager** state diagram, showing different transitions
2
-
3
-
4
- ### Contract state diagram
5
-
6
- *Transactions are qualified by the OCW and EventFeed before arriving to the Advancer.*
7
-
8
- ```mermaid
9
- stateDiagram-v2
10
- [*] --> Advanced: Advancer .advance()
11
- Advanced --> Settled: Settler .settle() after fees
12
- [*] --> Observed: Advancer .observed()
13
- Observed --> Settled: Settler .settle() sans fees
14
- Settled --> [*]
15
- ```
16
-
17
- ### Complete state diagram (starting from OCW)
18
-
19
- ```mermaid
20
- stateDiagram-v2
21
- Observed --> Qualified
22
- Observed --> Unqualified
23
- Qualified --> Advanced
24
- Advanced --> Settled
25
- Qualified --> Settled
26
- ```
@@ -1,71 +0,0 @@
1
- import { makeError, q } from '@endo/errors';
2
- import { M, mustMatch } from '@endo/patterns';
3
-
4
- /**
5
- * @import {Pattern} from '@endo/patterns';
6
- */
7
-
8
- /**
9
- * Default pattern matcher for `getQueryParams`.
10
- * Does not assert keys exist, but ensures existing keys are strings.
11
- */
12
- const QueryParamsShape = M.splitRecord(
13
- {},
14
- {},
15
- M.recordOf(M.string(), M.string()),
16
- );
17
-
18
- /**
19
- * Very minimal 'URL query string'-like parser that handles:
20
- * - Query string delimiter (?)
21
- * - Key-value separator (=)
22
- * - Query parameter separator (&)
23
- *
24
- * Does not handle:
25
- * - Subpaths (`agoric1bech32addr/opt/account?k=v`)
26
- * - URI encoding/decoding (`%20` -> ` `)
27
- * - note: `decodeURIComponent` seems to be available in XS
28
- * - Multiple question marks (foo?bar=1?baz=2)
29
- * - Empty parameters (foo=)
30
- * - Array parameters (`foo?k=v1&k=v2` -> k: [v1, v2])
31
- * - Parameters without values (foo&bar=2)
32
- */
33
- export const addressTools = {
34
- /**
35
- * @param {string} address
36
- * @returns {boolean}
37
- */
38
- hasQueryParams: address => {
39
- try {
40
- const params = addressTools.getQueryParams(address);
41
- return Object.keys(params).length > 0;
42
- } catch {
43
- return false;
44
- }
45
- },
46
- /**
47
- * @param {string} address
48
- * @param {Pattern} [shape]
49
- * @returns {Record<string, string>}
50
- * @throws {Error} if the address cannot be parsed or params do not match `shape`
51
- */
52
- getQueryParams: (address, shape = QueryParamsShape) => {
53
- const parts = address.split('?');
54
- if (parts.length !== 2) {
55
- throw makeError(`Unable to parse query params: ${q(address)}`);
56
- }
57
- /** @type {Record<string, string>} */
58
- const result = {};
59
- const paramPairs = parts[1].split('&');
60
- for (const pair of paramPairs) {
61
- const [key, value] = pair.split('=');
62
- if (!key || !value) {
63
- throw makeError(`Invalid parameter format in pair: ${q(pair)}`);
64
- }
65
- result[key] = value;
66
- }
67
- harden(result);
68
- mustMatch(result, shape);
69
- return result;
70
- },
71
- };