@gauntlet-xyz/sdk 2.0.0 → 2.5.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.
Files changed (129) hide show
  1. package/README.md +134 -1
  2. package/dist/api/activity.d.ts +70 -0
  3. package/dist/api/activity.d.ts.map +1 -0
  4. package/dist/api/activity.js +154 -0
  5. package/dist/api/activity.js.map +1 -0
  6. package/dist/api/caip.d.ts +29 -0
  7. package/dist/api/caip.d.ts.map +1 -0
  8. package/dist/api/caip.js +38 -0
  9. package/dist/api/caip.js.map +1 -0
  10. package/dist/api/client.d.ts +135 -0
  11. package/dist/api/client.d.ts.map +1 -0
  12. package/dist/api/client.js +119 -0
  13. package/dist/api/client.js.map +1 -0
  14. package/dist/api/decimal.d.ts +21 -0
  15. package/dist/api/decimal.d.ts.map +1 -0
  16. package/dist/api/decimal.js +44 -0
  17. package/dist/api/decimal.js.map +1 -0
  18. package/dist/api/errors.d.ts +32 -0
  19. package/dist/api/errors.d.ts.map +1 -0
  20. package/dist/api/errors.js +41 -0
  21. package/dist/api/errors.js.map +1 -0
  22. package/dist/api/generated.d.ts +1505 -0
  23. package/dist/api/generated.d.ts.map +1 -0
  24. package/dist/api/generated.js +6 -0
  25. package/dist/api/generated.js.map +1 -0
  26. package/dist/api/http.d.ts +16 -0
  27. package/dist/api/http.d.ts.map +1 -0
  28. package/dist/api/http.js +39 -0
  29. package/dist/api/http.js.map +1 -0
  30. package/dist/api/index.d.ts +14 -0
  31. package/dist/api/index.d.ts.map +1 -0
  32. package/dist/api/index.js +8 -0
  33. package/dist/api/index.js.map +1 -0
  34. package/dist/api/positionHistory.d.ts +44 -0
  35. package/dist/api/positionHistory.d.ts.map +1 -0
  36. package/dist/api/positionHistory.js +126 -0
  37. package/dist/api/positionHistory.js.map +1 -0
  38. package/dist/client.d.ts +6 -0
  39. package/dist/client.d.ts.map +1 -1
  40. package/dist/client.js +2 -0
  41. package/dist/client.js.map +1 -1
  42. package/dist/errors.d.ts +11 -0
  43. package/dist/errors.d.ts.map +1 -1
  44. package/dist/errors.js +21 -2
  45. package/dist/errors.js.map +1 -1
  46. package/dist/evm/abis/priceAndFeeCalculatorV2.d.ts +44 -0
  47. package/dist/evm/abis/priceAndFeeCalculatorV2.d.ts.map +1 -1
  48. package/dist/evm/abis/priceAndFeeCalculatorV2.js +22 -0
  49. package/dist/evm/abis/priceAndFeeCalculatorV2.js.map +1 -1
  50. package/dist/evm/abis/provisionerV2.d.ts +280 -0
  51. package/dist/evm/abis/provisionerV2.d.ts.map +1 -1
  52. package/dist/evm/abis/provisionerV2.js +92 -0
  53. package/dist/evm/abis/provisionerV2.js.map +1 -1
  54. package/dist/evm/adapters/aera.d.ts.map +1 -1
  55. package/dist/evm/adapters/aera.js +41 -10
  56. package/dist/evm/adapters/aera.js.map +1 -1
  57. package/dist/evm/adapters/types.d.ts +2 -0
  58. package/dist/evm/adapters/types.d.ts.map +1 -1
  59. package/dist/evm/aeraContracts/priceAndFeeCalculator.d.ts +10 -5
  60. package/dist/evm/aeraContracts/priceAndFeeCalculator.d.ts.map +1 -1
  61. package/dist/evm/aeraContracts/priceAndFeeCalculator.js +39 -5
  62. package/dist/evm/aeraContracts/priceAndFeeCalculator.js.map +1 -1
  63. package/dist/evm/aeraContracts/readOptions.d.ts +8 -0
  64. package/dist/evm/aeraContracts/readOptions.d.ts.map +1 -0
  65. package/dist/evm/aeraContracts/readOptions.js +4 -0
  66. package/dist/evm/aeraContracts/readOptions.js.map +1 -0
  67. package/dist/evm/aeraContracts/v2.d.ts +1932 -217
  68. package/dist/evm/aeraContracts/v2.d.ts.map +1 -1
  69. package/dist/evm/aeraContracts/v2.js +84 -28
  70. package/dist/evm/aeraContracts/v2.js.map +1 -1
  71. package/dist/evm/aeraContracts.d.ts +9 -3
  72. package/dist/evm/aeraContracts.d.ts.map +1 -1
  73. package/dist/evm/aeraContracts.js +69 -8
  74. package/dist/evm/aeraContracts.js.map +1 -1
  75. package/dist/evm/deposit.d.ts.map +1 -1
  76. package/dist/evm/deposit.js +1 -1
  77. package/dist/evm/deposit.js.map +1 -1
  78. package/dist/evm/index.d.ts +8 -0
  79. package/dist/evm/index.d.ts.map +1 -1
  80. package/dist/evm/index.js +3 -0
  81. package/dist/evm/index.js.map +1 -1
  82. package/dist/evm/tokenModes.d.ts +22 -0
  83. package/dist/evm/tokenModes.d.ts.map +1 -0
  84. package/dist/evm/tokenModes.js +34 -0
  85. package/dist/evm/tokenModes.js.map +1 -0
  86. package/dist/evm/withdraw.d.ts +8 -0
  87. package/dist/evm/withdraw.d.ts.map +1 -1
  88. package/dist/evm/withdraw.js +85 -5
  89. package/dist/evm/withdraw.js.map +1 -1
  90. package/dist/evm/withdrawQuote.d.ts +127 -0
  91. package/dist/evm/withdrawQuote.d.ts.map +1 -0
  92. package/dist/evm/withdrawQuote.js +164 -0
  93. package/dist/evm/withdrawQuote.js.map +1 -0
  94. package/dist/index.d.ts +8 -1
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +4 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/privy/index.d.ts +52 -0
  99. package/dist/privy/index.d.ts.map +1 -0
  100. package/dist/privy/index.js +51 -0
  101. package/dist/privy/index.js.map +1 -0
  102. package/manifest/vaults.json +255 -3
  103. package/package.json +2 -2
  104. package/src/api/activity.ts +219 -0
  105. package/src/api/caip.ts +61 -0
  106. package/src/api/client.ts +234 -0
  107. package/src/api/decimal.ts +49 -0
  108. package/src/api/errors.ts +64 -0
  109. package/src/api/generated.ts +1505 -0
  110. package/src/api/http.ts +62 -0
  111. package/src/api/index.ts +70 -0
  112. package/src/api/positionHistory.ts +167 -0
  113. package/src/client.ts +7 -1
  114. package/src/errors.ts +32 -2
  115. package/src/evm/abis/priceAndFeeCalculatorV2.ts +22 -0
  116. package/src/evm/abis/provisionerV2.ts +92 -0
  117. package/src/evm/adapters/aera.ts +50 -23
  118. package/src/evm/adapters/types.ts +2 -0
  119. package/src/evm/aeraContracts/priceAndFeeCalculator.ts +72 -5
  120. package/src/evm/aeraContracts/readOptions.ts +3 -0
  121. package/src/evm/aeraContracts/v2.ts +158 -34
  122. package/src/evm/aeraContracts.ts +81 -8
  123. package/src/evm/deposit.ts +2 -1
  124. package/src/evm/index.ts +32 -0
  125. package/src/evm/tokenModes.ts +54 -0
  126. package/src/evm/withdraw.ts +131 -7
  127. package/src/evm/withdrawQuote.ts +360 -0
  128. package/src/index.ts +18 -0
  129. package/src/privy/index.ts +95 -0
@@ -1,5 +1,6 @@
1
1
  import type { Abi, Address, Hex, PublicClient } from 'viem';
2
2
  import type { AeraRuntimeContracts } from '../aeraContracts';
3
+ import type { SyncWithdrawQuoteBounds } from '../withdrawQuote';
3
4
  import type { TokenInfo, VaultDeployment } from '../types';
4
5
 
5
6
  export interface EvmTxStep<TAbi extends Abi = Abi, TFunctionName extends string = string> {
@@ -53,6 +54,7 @@ export type AdapterWithdrawParams = {
53
54
  /** Maximum price age passed to async Aera provisioner requests. Defaults to 10 days. */
54
55
  maxPriceAge?: bigint;
55
56
  aeraRuntime?: AeraRuntimeContracts;
57
+ syncWithdrawQuote?: SyncWithdrawQuoteBounds;
56
58
  } & (
57
59
  | { shares: bigint; amount?: never; entireAmount?: never }
58
60
  | { amount: bigint; shares?: never; entireAmount?: never }
@@ -3,6 +3,7 @@ import { UnsupportedFeatureError } from '../../errors';
3
3
  import { priceAndFeeCalculatorAbi } from '../abis/priceAndFeeCalculator';
4
4
  import { priceAndFeeCalculatorV2Abi } from '../abis/priceAndFeeCalculatorV2';
5
5
  import { ContractVersion } from '../types';
6
+ import { readAtBlock } from './readOptions';
6
7
 
7
8
  const ROUNDING_FLOOR = 0;
8
9
  const ROUNDING_CEIL = 1;
@@ -13,6 +14,10 @@ export interface NormalizedVaultPriceState {
13
14
  timestamp: number;
14
15
  }
15
16
 
17
+ type AeraPriceReadOptions = {
18
+ blockNumber?: bigint;
19
+ };
20
+
16
21
  function toNumber(value: bigint | number): number {
17
22
  return typeof value === 'bigint' ? Number(value) : value;
18
23
  }
@@ -54,7 +59,8 @@ export async function convertTokenToUnitsIfActive(
54
59
  vault: Address,
55
60
  token: Address,
56
61
  tokenAmount: bigint,
57
- rounding: typeof ROUNDING_FLOOR | typeof ROUNDING_CEIL
62
+ rounding: typeof ROUNDING_FLOOR | typeof ROUNDING_CEIL,
63
+ options: AeraPriceReadOptions = {}
58
64
  ): Promise<bigint> {
59
65
  if (feeCalculatorVersion === ContractVersion.V2) {
60
66
  return client.readContract({
@@ -62,6 +68,7 @@ export async function convertTokenToUnitsIfActive(
62
68
  abi: priceAndFeeCalculatorV2Abi,
63
69
  functionName: 'convertTokenToUnitsIfActive',
64
70
  args: [vault, token, tokenAmount, rounding],
71
+ ...readAtBlock(options),
65
72
  });
66
73
  }
67
74
 
@@ -70,6 +77,7 @@ export async function convertTokenToUnitsIfActive(
70
77
  abi: priceAndFeeCalculatorAbi,
71
78
  functionName: 'convertTokenToUnitsIfActive',
72
79
  args: [vault, token, tokenAmount, rounding],
80
+ ...readAtBlock(options),
73
81
  });
74
82
  }
75
83
 
@@ -80,7 +88,8 @@ export async function convertUnitsToTokenIfActive(
80
88
  vault: Address,
81
89
  token: Address,
82
90
  unitsAmount: bigint,
83
- rounding: typeof ROUNDING_FLOOR | typeof ROUNDING_CEIL
91
+ rounding: typeof ROUNDING_FLOOR | typeof ROUNDING_CEIL,
92
+ options: AeraPriceReadOptions = {}
84
93
  ): Promise<bigint> {
85
94
  if (feeCalculatorVersion === ContractVersion.V2) {
86
95
  return client.readContract({
@@ -88,6 +97,7 @@ export async function convertUnitsToTokenIfActive(
88
97
  abi: priceAndFeeCalculatorV2Abi,
89
98
  functionName: 'convertUnitsToTokenIfActive',
90
99
  args: [vault, token, unitsAmount, rounding],
100
+ ...readAtBlock(options),
91
101
  });
92
102
  }
93
103
 
@@ -96,6 +106,7 @@ export async function convertUnitsToTokenIfActive(
96
106
  abi: priceAndFeeCalculatorAbi,
97
107
  functionName: 'convertUnitsToTokenIfActive',
98
108
  args: [vault, token, unitsAmount, rounding],
109
+ ...readAtBlock(options),
99
110
  });
100
111
  }
101
112
 
@@ -105,7 +116,8 @@ export async function convertTokenToUnits(
105
116
  feeCalculatorVersion: ContractVersion,
106
117
  vault: Address,
107
118
  token: Address,
108
- tokenAmount: bigint
119
+ tokenAmount: bigint,
120
+ options: AeraPriceReadOptions = {}
109
121
  ): Promise<bigint> {
110
122
  if (feeCalculatorVersion === ContractVersion.V2) {
111
123
  return client.readContract({
@@ -113,6 +125,7 @@ export async function convertTokenToUnits(
113
125
  abi: priceAndFeeCalculatorV2Abi,
114
126
  functionName: 'convertTokenToUnits',
115
127
  args: [vault, token, tokenAmount],
128
+ ...readAtBlock(options),
116
129
  });
117
130
  }
118
131
 
@@ -121,6 +134,7 @@ export async function convertTokenToUnits(
121
134
  abi: priceAndFeeCalculatorAbi,
122
135
  functionName: 'convertTokenToUnits',
123
136
  args: [vault, token, tokenAmount],
137
+ ...readAtBlock(options),
124
138
  });
125
139
  }
126
140
 
@@ -130,7 +144,8 @@ export async function convertUnitsToToken(
130
144
  feeCalculatorVersion: ContractVersion,
131
145
  vault: Address,
132
146
  token: Address,
133
- unitsAmount: bigint
147
+ unitsAmount: bigint,
148
+ options: AeraPriceReadOptions = {}
134
149
  ): Promise<bigint> {
135
150
  if (feeCalculatorVersion === ContractVersion.V2) {
136
151
  return client.readContract({
@@ -138,6 +153,7 @@ export async function convertUnitsToToken(
138
153
  abi: priceAndFeeCalculatorV2Abi,
139
154
  functionName: 'convertUnitsToToken',
140
155
  args: [vault, token, unitsAmount],
156
+ ...readAtBlock(options),
141
157
  });
142
158
  }
143
159
 
@@ -146,6 +162,55 @@ export async function convertUnitsToToken(
146
162
  abi: priceAndFeeCalculatorAbi,
147
163
  functionName: 'convertUnitsToToken',
148
164
  args: [vault, token, unitsAmount],
165
+ ...readAtBlock(options),
166
+ });
167
+ }
168
+
169
+ export async function convertTokenToNumeraire(
170
+ client: PublicClient,
171
+ feeCalculator: Address,
172
+ feeCalculatorVersion: ContractVersion,
173
+ vault: Address,
174
+ token: Address,
175
+ tokenAmount: bigint,
176
+ options: AeraPriceReadOptions = {}
177
+ ): Promise<bigint> {
178
+ if (feeCalculatorVersion !== ContractVersion.V2) {
179
+ throw new UnsupportedFeatureError(
180
+ 'Aera: numeraire conversion requires V2 price and fee calculator'
181
+ );
182
+ }
183
+
184
+ return client.readContract({
185
+ address: feeCalculator,
186
+ abi: priceAndFeeCalculatorV2Abi,
187
+ functionName: 'convertTokenToNumeraire',
188
+ args: [vault, token, tokenAmount],
189
+ ...readAtBlock(options),
190
+ });
191
+ }
192
+
193
+ export async function convertNumeraireToToken(
194
+ client: PublicClient,
195
+ feeCalculator: Address,
196
+ feeCalculatorVersion: ContractVersion,
197
+ vault: Address,
198
+ token: Address,
199
+ numeraireAmount: bigint,
200
+ options: AeraPriceReadOptions = {}
201
+ ): Promise<bigint> {
202
+ if (feeCalculatorVersion !== ContractVersion.V2) {
203
+ throw new UnsupportedFeatureError(
204
+ 'Aera: numeraire conversion requires V2 price and fee calculator'
205
+ );
206
+ }
207
+
208
+ return client.readContract({
209
+ address: feeCalculator,
210
+ abi: priceAndFeeCalculatorV2Abi,
211
+ functionName: 'convertNumeraireToToken',
212
+ args: [vault, token, numeraireAmount],
213
+ ...readAtBlock(options),
149
214
  });
150
215
  }
151
216
 
@@ -153,7 +218,8 @@ export async function getAnchorTimestamp(
153
218
  client: PublicClient,
154
219
  feeCalculator: Address,
155
220
  feeCalculatorVersion: ContractVersion,
156
- vault: Address
221
+ vault: Address,
222
+ options: AeraPriceReadOptions = {}
157
223
  ): Promise<number> {
158
224
  if (feeCalculatorVersion !== ContractVersion.V2) {
159
225
  throw new UnsupportedFeatureError('Aera: sync redeem requires V2 price and fee calculator');
@@ -164,6 +230,7 @@ export async function getAnchorTimestamp(
164
230
  abi: priceAndFeeCalculatorV2Abi,
165
231
  functionName: 'getAnchorTimestamp',
166
232
  args: [vault],
233
+ ...readAtBlock(options),
167
234
  });
168
235
  }
169
236
 
@@ -0,0 +1,3 @@
1
+ export function readAtBlock(options: { blockNumber?: bigint } = {}) {
2
+ return options.blockNumber === undefined ? {} : { blockNumber: options.blockNumber };
3
+ }
@@ -13,8 +13,9 @@ import {
13
13
  convertUnitsToTokenIfActive,
14
14
  } from './priceAndFeeCalculator';
15
15
  import { MAX_BPS } from '../../constants';
16
- import { StalePriceError } from '../../errors';
16
+ import { StalePriceError, UnsupportedFeatureError } from '../../errors';
17
17
  import type { ContractVersion } from '../types';
18
+ import { readAtBlock } from './readOptions';
18
19
 
19
20
  export type PriceAndFeeCalculatorContract<T extends Client> = GetContractReturnType<
20
21
  typeof priceAndFeeCalculatorV2Abi,
@@ -33,16 +34,44 @@ export function getPriceAndFeeCalculator<T extends Client>(
33
34
  });
34
35
  }
35
36
 
36
- function getProvisioner<T extends Client>(client: T, address: Address) {
37
- return getContract({
38
- address,
39
- abi: provisionerV2Abi,
40
- client,
41
- });
37
+ type SyncRedeemReadOptions = {
38
+ blockNumber?: bigint;
39
+ blockTimestamp?: bigint;
40
+ };
41
+
42
+ async function resolveReadContext(
43
+ client: PublicClient,
44
+ options: SyncRedeemReadOptions = {}
45
+ ): Promise<Required<SyncRedeemReadOptions>> {
46
+ if (options.blockNumber !== undefined && options.blockTimestamp !== undefined) {
47
+ return { blockNumber: options.blockNumber, blockTimestamp: options.blockTimestamp };
48
+ }
49
+
50
+ const block =
51
+ options.blockNumber === undefined ? await client.getBlock() : await client.getBlock(options);
52
+ if (block.number === null) {
53
+ throw new UnsupportedFeatureError('Aera: sync redeem reads require a numbered block');
54
+ }
55
+
56
+ return {
57
+ blockNumber: options.blockNumber ?? block.number,
58
+ blockTimestamp: options.blockTimestamp ?? BigInt(block.timestamp),
59
+ };
42
60
  }
43
61
 
44
- async function getTokenDetails(client: PublicClient, provisioner: Address, token: Address) {
45
- return getProvisioner(client, provisioner).read.tokensDetails([token]);
62
+ async function getTokenDetails(
63
+ client: PublicClient,
64
+ provisioner: Address,
65
+ token: Address,
66
+ options: SyncRedeemReadOptions = {}
67
+ ) {
68
+ return client.readContract({
69
+ address: provisioner,
70
+ abi: provisionerV2Abi,
71
+ functionName: 'tokensDetails',
72
+ args: [token],
73
+ ...readAtBlock(options),
74
+ });
46
75
  }
47
76
 
48
77
  async function getDepositUnitsOut({
@@ -54,6 +83,7 @@ async function getDepositUnitsOut({
54
83
  token,
55
84
  tokensIn,
56
85
  multiplierIndex,
86
+ options,
57
87
  }: {
58
88
  client: PublicClient;
59
89
  provisioner: Address;
@@ -63,8 +93,9 @@ async function getDepositUnitsOut({
63
93
  token: Address;
64
94
  tokensIn: bigint;
65
95
  multiplierIndex: 4 | 6;
96
+ options?: SyncRedeemReadOptions;
66
97
  }): Promise<bigint> {
67
- const tokenDetails = await getTokenDetails(client, provisioner, token);
98
+ const tokenDetails = await getTokenDetails(client, provisioner, token, options);
68
99
  const multiplier = BigInt(tokenDetails[multiplierIndex]);
69
100
  const adjustedTokensIn = (tokensIn * multiplier) / MAX_BPS;
70
101
 
@@ -75,7 +106,8 @@ async function getDepositUnitsOut({
75
106
  vault,
76
107
  token,
77
108
  adjustedTokensIn,
78
- Rounding.Floor
109
+ Rounding.Floor,
110
+ options
79
111
  );
80
112
  }
81
113
 
@@ -88,6 +120,7 @@ async function getRedeemTokenOut({
88
120
  token,
89
121
  unitsIn,
90
122
  multiplierIndex,
123
+ options,
91
124
  }: {
92
125
  client: PublicClient;
93
126
  provisioner: Address;
@@ -97,9 +130,10 @@ async function getRedeemTokenOut({
97
130
  token: Address;
98
131
  unitsIn: bigint;
99
132
  multiplierIndex: 5 | 7;
133
+ options?: SyncRedeemReadOptions;
100
134
  }): Promise<bigint> {
101
135
  const [tokenDetails, tokensOut] = await Promise.all([
102
- getTokenDetails(client, provisioner, token),
136
+ getTokenDetails(client, provisioner, token, options),
103
137
  convertUnitsToTokenIfActive(
104
138
  client,
105
139
  feeCalculator,
@@ -107,7 +141,8 @@ async function getRedeemTokenOut({
107
141
  vault,
108
142
  token,
109
143
  unitsIn,
110
- Rounding.Floor
144
+ Rounding.Floor,
145
+ options
111
146
  ),
112
147
  ]);
113
148
  const multiplier = BigInt(tokenDetails[multiplierIndex]);
@@ -124,6 +159,7 @@ async function getWithdrawUnitsIn({
124
159
  token,
125
160
  tokensOut,
126
161
  multiplierIndex,
162
+ options,
127
163
  }: {
128
164
  client: PublicClient;
129
165
  provisioner: Address;
@@ -133,8 +169,9 @@ async function getWithdrawUnitsIn({
133
169
  token: Address;
134
170
  tokensOut: bigint;
135
171
  multiplierIndex: 5 | 7;
172
+ options?: SyncRedeemReadOptions;
136
173
  }): Promise<bigint> {
137
- const tokenDetails = await getTokenDetails(client, provisioner, token);
174
+ const tokenDetails = await getTokenDetails(client, provisioner, token, options);
138
175
  const multiplier = BigInt(tokenDetails[multiplierIndex]);
139
176
  const preMultiplierTokens = (tokensOut * MAX_BPS + multiplier - 1n) / multiplier;
140
177
 
@@ -145,17 +182,37 @@ async function getWithdrawUnitsIn({
145
182
  vault,
146
183
  token,
147
184
  preMultiplierTokens,
148
- Rounding.Ceil
185
+ Rounding.Ceil,
186
+ options
149
187
  );
150
188
  }
151
189
 
152
- async function getSyncRedeemMultiplier(
190
+ /** Breakdown of the effective sync redeem multiplier, in basis points. */
191
+ export interface SyncRedeemRate {
192
+ /** Base sync redeem multiplier (tokensDetails.syncRedeemMultiplier). */
193
+ baseMultiplierBps: bigint;
194
+ /** Dynamic premium charged for the current oracle price age. */
195
+ dynamicPremiumBps: bigint;
196
+ /** Multiplier actually applied to size the redemption (base - dynamic premium). */
197
+ effectiveMultiplierBps: bigint;
198
+ }
199
+
200
+ /**
201
+ * Reads the live sync redeem rate, replicating the provisioner's effective-multiplier
202
+ * derivation (base multiplier minus a price-age-proportional dynamic premium).
203
+ *
204
+ * @throws {StalePriceError} If the oracle price is older than the configured max age,
205
+ * in which case the on-chain sync redeem would revert.
206
+ */
207
+ export async function getSyncRedeemRate(
153
208
  client: PublicClient,
154
209
  provisioner: Address,
155
210
  vault: Address,
156
211
  token: Address,
157
- feeCalculator: Address
158
- ): Promise<bigint> {
212
+ feeCalculator: Address,
213
+ options: SyncRedeemReadOptions = {}
214
+ ): Promise<SyncRedeemRate> {
215
+ const readContext = await resolveReadContext(client, options);
159
216
  const [tokenDetails, syncRedeemDetails, anchorTimestamp] = await client.multicall({
160
217
  contracts: [
161
218
  {
@@ -177,12 +234,12 @@ async function getSyncRedeemMultiplier(
177
234
  },
178
235
  ],
179
236
  allowFailure: false,
237
+ blockNumber: readContext.blockNumber,
180
238
  });
181
- const block = await client.getBlock();
182
- const syncRedeemMultiplier = BigInt(tokenDetails[7]);
239
+ const baseMultiplierBps = BigInt(tokenDetails[7]);
183
240
  const maxPriceAge = BigInt(syncRedeemDetails[0]);
184
241
  const maxDynamicPremiumBps = BigInt(syncRedeemDetails[2]);
185
- const blockTimestamp = BigInt(block.timestamp);
242
+ const blockTimestamp = readContext.blockTimestamp;
186
243
  const priceTimestamp = BigInt(anchorTimestamp);
187
244
 
188
245
  if (priceTimestamp + maxPriceAge < blockTimestamp) {
@@ -198,7 +255,23 @@ async function getSyncRedeemMultiplier(
198
255
  ? 0n
199
256
  : ((blockTimestamp - priceTimestamp) * maxDynamicPremiumBps + maxPriceAge - 1n) / maxPriceAge;
200
257
 
201
- return syncRedeemMultiplier - dynamicPremiumBps;
258
+ return {
259
+ baseMultiplierBps,
260
+ dynamicPremiumBps,
261
+ effectiveMultiplierBps: baseMultiplierBps - dynamicPremiumBps,
262
+ };
263
+ }
264
+
265
+ async function getSyncRedeemMultiplier(
266
+ client: PublicClient,
267
+ provisioner: Address,
268
+ vault: Address,
269
+ token: Address,
270
+ feeCalculator: Address,
271
+ options: SyncRedeemReadOptions = {}
272
+ ): Promise<bigint> {
273
+ return (await getSyncRedeemRate(client, provisioner, vault, token, feeCalculator, options))
274
+ .effectiveMultiplierBps;
202
275
  }
203
276
 
204
277
  export async function getSyncRedeemTokenOut(
@@ -208,8 +281,12 @@ export async function getSyncRedeemTokenOut(
208
281
  feeCalculatorVersion: ContractVersion,
209
282
  vault: Address,
210
283
  token: Address,
211
- unitsIn: bigint
284
+ unitsIn: bigint,
285
+ effectiveMultiplierBps?: bigint,
286
+ options: SyncRedeemReadOptions = {}
212
287
  ): Promise<bigint> {
288
+ const readContext =
289
+ effectiveMultiplierBps === undefined ? await resolveReadContext(client, options) : options;
213
290
  const [tokenAmount, multiplier] = await Promise.all([
214
291
  convertUnitsToTokenIfActive(
215
292
  client,
@@ -218,9 +295,12 @@ export async function getSyncRedeemTokenOut(
218
295
  vault,
219
296
  token,
220
297
  unitsIn,
221
- Rounding.Floor
298
+ Rounding.Floor,
299
+ readContext
222
300
  ),
223
- getSyncRedeemMultiplier(client, provisioner, vault, token, feeCalculator),
301
+ effectiveMultiplierBps !== undefined
302
+ ? Promise.resolve(effectiveMultiplierBps)
303
+ : getSyncRedeemMultiplier(client, provisioner, vault, token, feeCalculator, readContext),
224
304
  ]);
225
305
 
226
306
  return (tokenAmount * multiplier) / MAX_BPS;
@@ -317,15 +397,15 @@ export async function getSyncWithdrawUnitsIn(
317
397
  feeCalculatorVersion: ContractVersion,
318
398
  vault: Address,
319
399
  token: Address,
320
- tokensOut: bigint
400
+ tokensOut: bigint,
401
+ effectiveMultiplierBps?: bigint,
402
+ options: SyncRedeemReadOptions = {}
321
403
  ): Promise<bigint> {
322
- const multiplier = await getSyncRedeemMultiplier(
323
- client,
324
- provisioner,
325
- vault,
326
- token,
327
- feeCalculator
328
- );
404
+ const readContext =
405
+ effectiveMultiplierBps === undefined ? await resolveReadContext(client, options) : options;
406
+ const multiplier =
407
+ effectiveMultiplierBps ??
408
+ (await getSyncRedeemMultiplier(client, provisioner, vault, token, feeCalculator, readContext));
329
409
  const prePremiumTokens = (tokensOut * MAX_BPS + multiplier - 1n) / multiplier;
330
410
 
331
411
  return convertTokenToUnitsIfActive(
@@ -335,10 +415,54 @@ export async function getSyncWithdrawUnitsIn(
335
415
  vault,
336
416
  token,
337
417
  prePremiumTokens,
338
- Rounding.Ceil
418
+ Rounding.Ceil,
419
+ readContext
339
420
  );
340
421
  }
341
422
 
423
+ /** Current sync redeem epoch accounting, in numeraire (all values uint256). */
424
+ export interface SyncRedeemEpochState {
425
+ epochTimestamp: bigint;
426
+ epochStartTvlNumeraire: bigint;
427
+ epochRedeemedNumeraire: bigint;
428
+ epochCapNumeraire: bigint;
429
+ }
430
+
431
+ export async function getSyncRedeemEpochState(
432
+ client: PublicClient,
433
+ provisioner: Address,
434
+ options: SyncRedeemReadOptions = {}
435
+ ): Promise<SyncRedeemEpochState> {
436
+ const [epochTimestamp, epochStartTvlNumeraire, epochRedeemedNumeraire, epochCapNumeraire] =
437
+ await client.readContract({
438
+ address: provisioner,
439
+ abi: provisionerV2Abi,
440
+ functionName: 'getSyncRedeemEpochState',
441
+ ...readAtBlock(options),
442
+ });
443
+
444
+ return { epochTimestamp, epochStartTvlNumeraire, epochRedeemedNumeraire, epochCapNumeraire };
445
+ }
446
+
447
+ /**
448
+ * Raw expiry timestamp for the user's latest sync-deposit lock (0 if none was recorded).
449
+ * The user is locked while this value is greater than or equal to the relevant block timestamp.
450
+ */
451
+ export async function getUserUnitsRefundableUntil(
452
+ client: PublicClient,
453
+ provisioner: Address,
454
+ user: Address,
455
+ options: SyncRedeemReadOptions = {}
456
+ ): Promise<bigint> {
457
+ return client.readContract({
458
+ address: provisioner,
459
+ abi: provisionerV2Abi,
460
+ functionName: 'userUnitsRefundableUntil',
461
+ args: [user],
462
+ ...readAtBlock(options),
463
+ });
464
+ }
465
+
342
466
  export function depositTxRequest(
343
467
  provisioner: Address,
344
468
  token: Address,
@@ -15,6 +15,7 @@ import { provisionerAbi } from './abis/provisioner';
15
15
  import { provisionerV2Abi } from './abis/provisionerV2';
16
16
  import { MAX_BPS } from '../constants';
17
17
  import { ContractVersion, type EvmVaultDeployment } from './types';
18
+ import { readAtBlock } from './aeraContracts/readOptions';
18
19
 
19
20
  const contractVersionCache = new Map<string, ContractVersion>();
20
21
 
@@ -28,6 +29,19 @@ const contractVersionAbi = [
28
29
  },
29
30
  ] as const;
30
31
 
32
+ const solvingGateAbi = [
33
+ {
34
+ type: 'function',
35
+ inputs: [
36
+ { name: 'provisioner', internalType: 'address', type: 'address' },
37
+ { name: 'token', internalType: 'address', type: 'address' },
38
+ ],
39
+ name: 'paused',
40
+ outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
41
+ stateMutability: 'view',
42
+ },
43
+ ] as const;
44
+
31
45
  export interface AeraRuntimeContracts {
32
46
  provisioner: {
33
47
  address: Address;
@@ -46,6 +60,19 @@ export interface AeraTokenModeSupport {
46
60
  syncRedeem: boolean;
47
61
  }
48
62
 
63
+ export function requireAeraSyncRedeemRuntime(runtime: AeraRuntimeContracts): void {
64
+ if (runtime.provisioner.version !== ContractVersion.V2) {
65
+ throw new UnsupportedFeatureError('Aera: sync redeem requires a V2 provisioner');
66
+ }
67
+ if (runtime.feeCalculator.version !== ContractVersion.V2) {
68
+ throw new UnsupportedFeatureError('Aera: sync redeem requires a V2 price and fee calculator');
69
+ }
70
+ }
71
+
72
+ export type AeraReadOptions = {
73
+ blockNumber?: bigint;
74
+ };
75
+
49
76
  export function applySlippageDown(amount: bigint, slippageBps: number): bigint {
50
77
  return (amount * (MAX_BPS - BigInt(slippageBps))) / MAX_BPS;
51
78
  }
@@ -97,7 +124,8 @@ export function getMultiDepositorVault<T extends Client>(
97
124
 
98
125
  export async function resolveContractVersion(
99
126
  client: PublicClient,
100
- address: Address
127
+ address: Address,
128
+ options: AeraReadOptions = {}
101
129
  ): Promise<ContractVersion> {
102
130
  const cacheKey = contractVersionCacheKey(client, address);
103
131
  const cached = contractVersionCache.get(cacheKey);
@@ -108,6 +136,7 @@ export async function resolveContractVersion(
108
136
  address,
109
137
  abi: contractVersionAbi,
110
138
  functionName: 'version',
139
+ ...readAtBlock(options),
111
140
  });
112
141
  const version = versionString.startsWith('2.') ? ContractVersion.V2 : ContractVersion.V1;
113
142
  contractVersionCache.set(cacheKey, version);
@@ -123,7 +152,8 @@ export async function resolveContractVersion(
123
152
 
124
153
  export async function resolveAeraRuntimeContracts(
125
154
  client: PublicClient,
126
- vault: EvmVaultDeployment
155
+ vault: EvmVaultDeployment,
156
+ options: AeraReadOptions = {}
127
157
  ): Promise<AeraRuntimeContracts> {
128
158
  if (vault.vaultType !== 'multi-depositor') {
129
159
  throw new UnimplementedFeatureError('Aera: single depositor vaults');
@@ -134,11 +164,13 @@ export async function resolveAeraRuntimeContracts(
134
164
  address: vault.vaultAddress,
135
165
  abi: multiDepositorVaultAbi,
136
166
  functionName: 'provisioner',
167
+ ...readAtBlock(options),
137
168
  }),
138
169
  client.readContract({
139
170
  address: vault.vaultAddress,
140
171
  abi: multiDepositorVaultAbi,
141
172
  functionName: 'feeCalculator',
173
+ ...readAtBlock(options),
142
174
  }),
143
175
  ]);
144
176
 
@@ -150,8 +182,8 @@ export async function resolveAeraRuntimeContracts(
150
182
  }
151
183
 
152
184
  const [provisionerVersion, feeCalculatorVersion] = await Promise.all([
153
- resolveContractVersion(client, provisionerAddress),
154
- resolveContractVersion(client, feeCalculatorAddress),
185
+ resolveContractVersion(client, provisionerAddress, options),
186
+ resolveContractVersion(client, feeCalculatorAddress, options),
155
187
  ]);
156
188
 
157
189
  return {
@@ -169,21 +201,61 @@ export async function resolveAeraRuntimeContracts(
169
201
  export async function resolveAeraTokenModeSupport(
170
202
  client: PublicClient,
171
203
  runtime: AeraRuntimeContracts,
172
- token: Address
204
+ token: Address,
205
+ options: AeraReadOptions & { includeSyncModes?: boolean } = {}
173
206
  ): Promise<AeraTokenModeSupport> {
174
207
  if (runtime.provisioner.version === ContractVersion.V2) {
175
- const tokenDetails = await client.readContract({
208
+ const tokenDetailsPromise = client.readContract({
176
209
  address: runtime.provisioner.address,
177
210
  abi: provisionerV2Abi,
178
211
  functionName: 'tokensDetails',
179
212
  args: [token],
213
+ ...readAtBlock(options),
180
214
  });
215
+ if (options.includeSyncModes === false) {
216
+ const tokenDetails = await tokenDetailsPromise;
217
+ return {
218
+ asyncDeposit: Boolean(tokenDetails[0]),
219
+ asyncRedeem: Boolean(tokenDetails[1]),
220
+ syncDeposit: false,
221
+ syncRedeem: false,
222
+ };
223
+ }
224
+
225
+ const [tokenDetails, solvingGateEnabled, solvingGate] = await Promise.all([
226
+ tokenDetailsPromise,
227
+ client.readContract({
228
+ address: runtime.provisioner.address,
229
+ abi: provisionerV2Abi,
230
+ functionName: 'SOLVING_GATE_ENABLED',
231
+ ...readAtBlock(options),
232
+ }),
233
+ client.readContract({
234
+ address: runtime.provisioner.address,
235
+ abi: provisionerV2Abi,
236
+ functionName: 'solvingGate',
237
+ ...readAtBlock(options),
238
+ }),
239
+ ]);
240
+ const solvingPaused =
241
+ solvingGateEnabled && solvingGate !== zeroAddress
242
+ ? await client.readContract({
243
+ address: solvingGate,
244
+ abi: solvingGateAbi,
245
+ functionName: 'paused',
246
+ args: [runtime.provisioner.address, token],
247
+ ...readAtBlock(options),
248
+ })
249
+ : false;
181
250
 
182
251
  return {
183
252
  asyncDeposit: Boolean(tokenDetails[0]),
184
253
  asyncRedeem: Boolean(tokenDetails[1]),
185
- syncDeposit: Boolean(tokenDetails[2]),
186
- syncRedeem: Boolean(tokenDetails[3]),
254
+ syncDeposit: Boolean(tokenDetails[2]) && !solvingPaused,
255
+ syncRedeem:
256
+ Boolean(tokenDetails[3]) &&
257
+ !solvingPaused &&
258
+ runtime.feeCalculator.version === ContractVersion.V2,
187
259
  };
188
260
  }
189
261
 
@@ -192,6 +264,7 @@ export async function resolveAeraTokenModeSupport(
192
264
  abi: provisionerAbi,
193
265
  functionName: 'tokensDetails',
194
266
  args: [token],
267
+ ...readAtBlock(options),
195
268
  });
196
269
 
197
270
  return {