@gauntlet-xyz/sdk 0.0.1
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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/manifest/vaults.json +139 -0
- package/dist/src/attribution/erc8021.d.ts +2 -0
- package/dist/src/attribution/erc8021.js +7 -0
- package/dist/src/attribution/erc8021.js.map +1 -0
- package/dist/src/attribution/index.d.ts +16 -0
- package/dist/src/attribution/index.js +37 -0
- package/dist/src/attribution/index.js.map +1 -0
- package/dist/src/client.d.ts +28 -0
- package/dist/src/client.js +35 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/errors.d.ts +55 -0
- package/dist/src/errors.js +97 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/evm/abis/erc20.d.ts +40 -0
- package/dist/src/evm/abis/erc20.js +30 -0
- package/dist/src/evm/abis/erc20.js.map +1 -0
- package/dist/src/evm/abis/erc4626.d.ts +104 -0
- package/dist/src/evm/abis/erc4626.js +70 -0
- package/dist/src/evm/abis/erc4626.js.map +1 -0
- package/dist/src/evm/abis/multiDepositorVault.d.ts +1047 -0
- package/dist/src/evm/abis/multiDepositorVault.js +587 -0
- package/dist/src/evm/abis/multiDepositorVault.js.map +1 -0
- package/dist/src/evm/abis/priceAndFeeCalculator.d.ts +909 -0
- package/dist/src/evm/abis/priceAndFeeCalculator.js +490 -0
- package/dist/src/evm/abis/priceAndFeeCalculator.js.map +1 -0
- package/dist/src/evm/abis/provisioner.d.ts +4983 -0
- package/dist/src/evm/abis/provisioner.js +2738 -0
- package/dist/src/evm/abis/provisioner.js.map +1 -0
- package/dist/src/evm/adapters/aera.d.ts +5 -0
- package/dist/src/evm/adapters/aera.js +144 -0
- package/dist/src/evm/adapters/aera.js.map +1 -0
- package/dist/src/evm/adapters/index.d.ts +2 -0
- package/dist/src/evm/adapters/index.js +14 -0
- package/dist/src/evm/adapters/index.js.map +1 -0
- package/dist/src/evm/adapters/morpho.d.ts +2 -0
- package/dist/src/evm/adapters/morpho.js +79 -0
- package/dist/src/evm/adapters/morpho.js.map +1 -0
- package/dist/src/evm/adapters/types.d.ts +65 -0
- package/dist/src/evm/adapters/types.js +2 -0
- package/dist/src/evm/adapters/types.js.map +1 -0
- package/dist/src/evm/aeraContracts.d.ts +2315 -0
- package/dist/src/evm/aeraContracts.js +37 -0
- package/dist/src/evm/aeraContracts.js.map +1 -0
- package/dist/src/evm/deposit.d.ts +13 -0
- package/dist/src/evm/deposit.js +66 -0
- package/dist/src/evm/deposit.js.map +1 -0
- package/dist/src/evm/index.d.ts +10 -0
- package/dist/src/evm/index.js +5 -0
- package/dist/src/evm/index.js.map +1 -0
- package/dist/src/evm/types.d.ts +27 -0
- package/dist/src/evm/types.js +2 -0
- package/dist/src/evm/types.js.map +1 -0
- package/dist/src/evm/userCurrentBalance.d.ts +16 -0
- package/dist/src/evm/userCurrentBalance.js +164 -0
- package/dist/src/evm/userCurrentBalance.js.map +1 -0
- package/dist/src/evm/vaults.d.ts +17 -0
- package/dist/src/evm/vaults.js +39 -0
- package/dist/src/evm/vaults.js.map +1 -0
- package/dist/src/evm/withdraw.d.ts +24 -0
- package/dist/src/evm/withdraw.js +51 -0
- package/dist/src/evm/withdraw.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/manifest/vaults.json +139 -0
- package/package.json +64 -0
- package/src/attribution/erc8021.ts +14 -0
- package/src/attribution/index.ts +62 -0
- package/src/client.ts +95 -0
- package/src/errors.ts +135 -0
- package/src/evm/abis/erc20.ts +29 -0
- package/src/evm/abis/erc4626.ts +69 -0
- package/src/evm/abis/multiDepositorVault.ts +586 -0
- package/src/evm/abis/priceAndFeeCalculator.ts +489 -0
- package/src/evm/abis/provisioner.ts +2787 -0
- package/src/evm/adapters/aera.ts +254 -0
- package/src/evm/adapters/index.ts +15 -0
- package/src/evm/adapters/morpho.ts +92 -0
- package/src/evm/adapters/types.ts +64 -0
- package/src/evm/aeraContracts.ts +82 -0
- package/src/evm/deposit.ts +137 -0
- package/src/evm/index.ts +11 -0
- package/src/evm/types.ts +34 -0
- package/src/evm/userCurrentBalance.ts +265 -0
- package/src/evm/vaults.ts +88 -0
- package/src/evm/withdraw.ts +127 -0
- package/src/index.ts +28 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { parseAbi, type Address } from 'viem';
|
|
2
|
+
import type { GauntletClient } from '../client';
|
|
3
|
+
import type { EvmVaultDeployment } from './types';
|
|
4
|
+
import { ChainMismatchError, UnsupportedProtocolError, VaultNotFoundError } from '../errors';
|
|
5
|
+
import { getMultiDepositorVault, getPriceAndFeeCalculator } from './aeraContracts';
|
|
6
|
+
|
|
7
|
+
// Inline as-const ABI items give viem full type inference for args and return types.
|
|
8
|
+
const DEPOSIT_REQUESTED_ABI = [
|
|
9
|
+
{
|
|
10
|
+
type: 'event',
|
|
11
|
+
name: 'DepositRequested',
|
|
12
|
+
inputs: [
|
|
13
|
+
{ type: 'address', name: 'user', indexed: true },
|
|
14
|
+
{ type: 'address', name: 'token', indexed: true },
|
|
15
|
+
{ type: 'uint256', name: 'tokensIn', indexed: false },
|
|
16
|
+
{ type: 'uint256', name: 'minUnitsOut', indexed: false },
|
|
17
|
+
{ type: 'uint256', name: 'solverTip', indexed: false },
|
|
18
|
+
{ type: 'uint256', name: 'deadline', indexed: false },
|
|
19
|
+
{ type: 'uint256', name: 'maxPriceAge', indexed: false },
|
|
20
|
+
{ type: 'bool', name: 'isFixedPrice', indexed: false },
|
|
21
|
+
{ type: 'bytes32', name: 'depositRequestHash', indexed: false },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
const REDEEM_REQUESTED_ABI = [
|
|
27
|
+
{
|
|
28
|
+
type: 'event',
|
|
29
|
+
name: 'RedeemRequested',
|
|
30
|
+
inputs: [
|
|
31
|
+
{ type: 'address', name: 'user', indexed: true },
|
|
32
|
+
{ type: 'address', name: 'token', indexed: true },
|
|
33
|
+
{ type: 'uint256', name: 'minTokensOut', indexed: false },
|
|
34
|
+
{ type: 'uint256', name: 'unitsIn', indexed: false },
|
|
35
|
+
{ type: 'uint256', name: 'solverTip', indexed: false },
|
|
36
|
+
{ type: 'uint256', name: 'deadline', indexed: false },
|
|
37
|
+
{ type: 'uint256', name: 'maxPriceAge', indexed: false },
|
|
38
|
+
{ type: 'bool', name: 'isFixedPrice', indexed: false },
|
|
39
|
+
{ type: 'bytes32', name: 'redeemRequestHash', indexed: false },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
// ABI for provisioner state queries
|
|
45
|
+
const provisionerStateAbi = parseAbi([
|
|
46
|
+
'function asyncDepositHashes(bytes32 asyncDepositHash) view returns (bool exists)',
|
|
47
|
+
'function asyncRedeemHashes(bytes32 asyncRedeemHash) view returns (bool exists)',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
export interface UserCurrentBalanceParams {
|
|
51
|
+
vaultId: string;
|
|
52
|
+
address: Address;
|
|
53
|
+
chainId?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UserCurrentBalance {
|
|
57
|
+
/** Chain identifier, e.g. "base" */
|
|
58
|
+
chain: string;
|
|
59
|
+
token: Address;
|
|
60
|
+
decimals: number;
|
|
61
|
+
/** Assets queued via async deposit, not yet earning */
|
|
62
|
+
pendingDeposit: bigint;
|
|
63
|
+
/** Assets actively earning in the vault */
|
|
64
|
+
balance: bigint;
|
|
65
|
+
/** Assets queued via async withdraw, not yet claimable */
|
|
66
|
+
pendingWithdraw: bigint;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const CHAIN_NAMES: Record<number, string> = {
|
|
70
|
+
1: 'ethereum',
|
|
71
|
+
8453: 'base',
|
|
72
|
+
42161: 'arbitrum',
|
|
73
|
+
10: 'optimism',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Approximate 3-day block lookback per chain based on avg block time
|
|
77
|
+
const BLOCKS_3_DAYS: Record<number, bigint> = {
|
|
78
|
+
1: 21_600n,
|
|
79
|
+
8453: 129_600n,
|
|
80
|
+
42161: 1_036_800n,
|
|
81
|
+
10: 129_600n,
|
|
82
|
+
};
|
|
83
|
+
const DEFAULT_BLOCKS_3_DAYS = 129_600n;
|
|
84
|
+
|
|
85
|
+
// Many RPC providers cap eth_getLogs to 10,000 blocks per request.
|
|
86
|
+
const LOG_PAGE_SIZE = 9_999n;
|
|
87
|
+
|
|
88
|
+
async function queryDeploymentBalance(
|
|
89
|
+
client: GauntletClient,
|
|
90
|
+
deployment: EvmVaultDeployment,
|
|
91
|
+
address: Address
|
|
92
|
+
): Promise<UserCurrentBalance> {
|
|
93
|
+
const publicClient = client.getPublicClient(deployment.chainId);
|
|
94
|
+
const token = deployment.supplyToken[0];
|
|
95
|
+
const provisionerAddress = deployment.provisionerAddress!;
|
|
96
|
+
|
|
97
|
+
const vaultContract = getMultiDepositorVault(publicClient, deployment.vaultAddress);
|
|
98
|
+
const [units, feeCalculatorAddress] = await Promise.all([
|
|
99
|
+
vaultContract.read.balanceOf([address]),
|
|
100
|
+
vaultContract.read.feeCalculator(),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const feeCalc = getPriceAndFeeCalculator(publicClient, feeCalculatorAddress);
|
|
104
|
+
const balance = await feeCalc.read.convertUnitsToToken([
|
|
105
|
+
deployment.vaultAddress,
|
|
106
|
+
token.address,
|
|
107
|
+
units,
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// Look back ~3 days for pending async requests
|
|
111
|
+
const currentBlock = await publicClient.getBlockNumber();
|
|
112
|
+
const lookback = BLOCKS_3_DAYS[deployment.chainId] ?? DEFAULT_BLOCKS_3_DAYS;
|
|
113
|
+
const fromBlock = currentBlock > lookback ? currentBlock - lookback : 0n;
|
|
114
|
+
|
|
115
|
+
// Compute block-range chunks to stay within the 10,000-block RPC limit, then fetch in parallel
|
|
116
|
+
const chunks: [bigint, bigint][] = [];
|
|
117
|
+
for (let cursor = fromBlock; cursor <= currentBlock; cursor += LOG_PAGE_SIZE + 1n) {
|
|
118
|
+
const end = cursor + LOG_PAGE_SIZE <= currentBlock ? cursor + LOG_PAGE_SIZE : currentBlock;
|
|
119
|
+
chunks.push([cursor, end]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const [depositPages, redeemPages] = await Promise.all([
|
|
123
|
+
Promise.all(
|
|
124
|
+
chunks.map(([from, to]) =>
|
|
125
|
+
publicClient.getContractEvents({
|
|
126
|
+
address: provisionerAddress,
|
|
127
|
+
abi: DEPOSIT_REQUESTED_ABI,
|
|
128
|
+
eventName: 'DepositRequested',
|
|
129
|
+
args: { user: address },
|
|
130
|
+
fromBlock: from,
|
|
131
|
+
toBlock: to,
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
),
|
|
135
|
+
Promise.all(
|
|
136
|
+
chunks.map(([from, to]) =>
|
|
137
|
+
publicClient.getContractEvents({
|
|
138
|
+
address: provisionerAddress,
|
|
139
|
+
abi: REDEEM_REQUESTED_ABI,
|
|
140
|
+
eventName: 'RedeemRequested',
|
|
141
|
+
args: { user: address },
|
|
142
|
+
fromBlock: from,
|
|
143
|
+
toBlock: to,
|
|
144
|
+
})
|
|
145
|
+
)
|
|
146
|
+
),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const depositLogs = depositPages.flat().filter((log) => log.args.depositRequestHash != null);
|
|
150
|
+
const redeemLogs = redeemPages.flat().filter((log) => log.args.redeemRequestHash != null);
|
|
151
|
+
|
|
152
|
+
// Check which requests are still pending on-chain via a single multicall
|
|
153
|
+
const pendingChecks = await publicClient.multicall({
|
|
154
|
+
contracts: [
|
|
155
|
+
...depositLogs.map((log) => ({
|
|
156
|
+
address: provisionerAddress,
|
|
157
|
+
abi: provisionerStateAbi,
|
|
158
|
+
functionName: 'asyncDepositHashes' as const,
|
|
159
|
+
args: [log.args.depositRequestHash!] as const,
|
|
160
|
+
})),
|
|
161
|
+
...redeemLogs.map((log) => ({
|
|
162
|
+
address: provisionerAddress,
|
|
163
|
+
abi: provisionerStateAbi,
|
|
164
|
+
functionName: 'asyncRedeemHashes' as const,
|
|
165
|
+
args: [log.args.redeemRequestHash!] as const,
|
|
166
|
+
})),
|
|
167
|
+
],
|
|
168
|
+
allowFailure: false,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const depositChecks = pendingChecks.slice(0, depositLogs.length);
|
|
172
|
+
const redeemChecks = pendingChecks.slice(depositLogs.length);
|
|
173
|
+
|
|
174
|
+
let pendingDeposit = 0n;
|
|
175
|
+
for (let i = 0; i < depositLogs.length; i++) {
|
|
176
|
+
if (depositChecks[i]) {
|
|
177
|
+
pendingDeposit += depositLogs[i].args.tokensIn ?? 0n;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let pendingWithdrawUnits = 0n;
|
|
182
|
+
for (let i = 0; i < redeemLogs.length; i++) {
|
|
183
|
+
if (redeemChecks[i]) {
|
|
184
|
+
pendingWithdrawUnits += redeemLogs[i].args.unitsIn ?? 0n;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const pendingWithdraw =
|
|
189
|
+
pendingWithdrawUnits > 0n
|
|
190
|
+
? await feeCalc.read.convertUnitsToToken([
|
|
191
|
+
deployment.vaultAddress,
|
|
192
|
+
token.address,
|
|
193
|
+
pendingWithdrawUnits,
|
|
194
|
+
])
|
|
195
|
+
: 0n;
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
chain: CHAIN_NAMES[deployment.chainId] ?? `chain-${deployment.chainId}`,
|
|
199
|
+
token: token.address,
|
|
200
|
+
decimals: token.decimals,
|
|
201
|
+
pendingDeposit,
|
|
202
|
+
balance,
|
|
203
|
+
pendingWithdraw,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Returns the current balance breakdown for a user across all EVM deployments of a vault.
|
|
209
|
+
*
|
|
210
|
+
* Queries the vault's on-chain state to split the user's position into three buckets:
|
|
211
|
+
* - `balance` — assets actively earning inside the vault (converted from vault units to tokens).
|
|
212
|
+
* - `pendingDeposit` — tokens submitted via async deposit but not yet processed by a solver.
|
|
213
|
+
* - `pendingWithdraw` — vault units locked in a pending async redeem request (converted to tokens).
|
|
214
|
+
*
|
|
215
|
+
* Only supported for Aera multi-depositor vaults. Pending amounts are detected by scanning
|
|
216
|
+
* the last ~3 days of provisioner events and cross-checking hash existence on-chain.
|
|
217
|
+
*
|
|
218
|
+
* @param client - A configured `GauntletClient` instance with an EVM public client for each
|
|
219
|
+
* chain the vault is deployed on.
|
|
220
|
+
* @param params - Query parameters including `vaultId`, the user `address`, and an optional
|
|
221
|
+
* `chainId` to limit the query to a single deployment.
|
|
222
|
+
* @returns One `UserCurrentBalance` entry per chain queried.
|
|
223
|
+
*
|
|
224
|
+
* @throws {VaultNotFoundError} If the vault ID is not found in the manifest.
|
|
225
|
+
* @throws {UnsupportedProtocolError} If the vault is not an Aera multi-depositor vault.
|
|
226
|
+
* @throws {ChainMismatchError} If `chainId` is specified but no deployment exists for that chain.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const balances = await getUserCurrentBalance(client, {
|
|
231
|
+
* vaultId: 'baseUsdcPrime',
|
|
232
|
+
* address: '0xYourWalletAddress',
|
|
233
|
+
* });
|
|
234
|
+
* // [{ chain: 'base', token: '0x...', decimals: 6, balance: 100_000_000n, pendingDeposit: 0n, pendingWithdraw: 0n }]
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export async function getUserCurrentBalance(
|
|
238
|
+
client: GauntletClient,
|
|
239
|
+
params: UserCurrentBalanceParams
|
|
240
|
+
): Promise<UserCurrentBalance[]> {
|
|
241
|
+
const manifest = await client.manifest;
|
|
242
|
+
const vaultInfo = manifest.vaults.find((v) => v.vaultId === params.vaultId);
|
|
243
|
+
if (!vaultInfo) throw new VaultNotFoundError(params.vaultId);
|
|
244
|
+
|
|
245
|
+
const evmDeployments = vaultInfo.deployments.filter(
|
|
246
|
+
(d): d is EvmVaultDeployment => d.chain === 'evm' && d.provisionerAddress !== undefined
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (vaultInfo.protocol !== 'aera' || evmDeployments.length === 0) {
|
|
250
|
+
throw new UnsupportedProtocolError(vaultInfo.protocol);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (params.chainId !== undefined) {
|
|
254
|
+
const deployment = evmDeployments.find((d) => d.chainId === params.chainId);
|
|
255
|
+
if (!deployment) {
|
|
256
|
+
throw new ChainMismatchError(
|
|
257
|
+
`${params.chainId}`,
|
|
258
|
+
evmDeployments.map((d) => `${d.chainId}`).join(', ')
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return [await queryDeploymentBalance(client, deployment, params.address)];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return Promise.all(evmDeployments.map((d) => queryDeploymentBalance(client, d, params.address)));
|
|
265
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { base } from 'viem/chains';
|
|
2
|
+
import type { GauntletClient } from '../client';
|
|
3
|
+
import type { EvmVaultDeployment, VaultDeployment, VaultInfo } from './types';
|
|
4
|
+
import { ChainMismatchError } from '../errors';
|
|
5
|
+
|
|
6
|
+
export enum VaultId {
|
|
7
|
+
BaseUsdcPrime = 'baseUsdcPrime',
|
|
8
|
+
EthUsdcPrime = 'ethUsdcPrime',
|
|
9
|
+
AeraUsdAlpha = 'gtusda',
|
|
10
|
+
EthUsdcPrimeV2 = 'ethUsdcPrimeV2',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface VaultFilter {
|
|
14
|
+
/** Filter to vaults that have at least one EVM deployment on this chainId. */
|
|
15
|
+
chainId?: number;
|
|
16
|
+
protocol?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns vaults from the bundled manifest, optionally filtered by chain and/or protocol.
|
|
21
|
+
* Does not make any network requests.
|
|
22
|
+
*
|
|
23
|
+
* @param client - A configured `GauntletClient` instance.
|
|
24
|
+
* @param filter - Optional filter criteria. `chainId` restricts results to vaults that have
|
|
25
|
+
* at least one EVM deployment on that chain. `protocol` restricts results to a specific
|
|
26
|
+
* protocol (e.g. `'aera'`, `'morpho'`).
|
|
27
|
+
* @returns Array of `VaultInfo` objects matching the filter, or all vaults if no filter is given.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // All vaults on Base
|
|
32
|
+
* const vaults = await getVaults(client, { chainId: base.id });
|
|
33
|
+
*
|
|
34
|
+
* // All Aera vaults
|
|
35
|
+
* const aeraVaults = await getVaults(client, { protocol: 'aera' });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export async function getVaults(
|
|
39
|
+
client: GauntletClient,
|
|
40
|
+
filter?: VaultFilter
|
|
41
|
+
): Promise<VaultInfo[]> {
|
|
42
|
+
const manifest = await client.manifest;
|
|
43
|
+
let vaults = manifest.vaults;
|
|
44
|
+
|
|
45
|
+
if (filter?.chainId) {
|
|
46
|
+
vaults = vaults.filter((v) =>
|
|
47
|
+
v.deployments.some((d) => d.chain === 'evm' && d.chainId === filter.chainId)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (filter?.protocol) {
|
|
52
|
+
vaults = vaults.filter((v) => v.protocol === filter.protocol);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return vaults;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function resolveVault(
|
|
59
|
+
client: GauntletClient,
|
|
60
|
+
vaultId: string,
|
|
61
|
+
chainId?: string | number
|
|
62
|
+
): Promise<{ vault: VaultDeployment; protocol: string } | undefined> {
|
|
63
|
+
const manifest = await client.manifest;
|
|
64
|
+
const vaultInfo = manifest.vaults.find((v) => v.vaultId === vaultId);
|
|
65
|
+
if (!vaultInfo) return undefined;
|
|
66
|
+
|
|
67
|
+
const evmDeployments = vaultInfo.deployments.filter(
|
|
68
|
+
(d): d is EvmVaultDeployment => d.chain === 'evm'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (evmDeployments.length === 1) {
|
|
72
|
+
// if user specifies a chain we need to make sure they get it
|
|
73
|
+
if (chainId !== undefined && evmDeployments[0].chainId !== chainId) {
|
|
74
|
+
throw new ChainMismatchError(`${chainId}`, `${evmDeployments[0].chainId}`);
|
|
75
|
+
}
|
|
76
|
+
// otherwise they're expected to know what chain
|
|
77
|
+
// the single chain vault they request is on
|
|
78
|
+
return { vault: evmDeployments[0], protocol: vaultInfo.protocol };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// now we default to base
|
|
82
|
+
const defaultChainId = chainId ?? base.id;
|
|
83
|
+
|
|
84
|
+
const deployment = evmDeployments.find((d) => d.chainId === defaultChainId);
|
|
85
|
+
if (!deployment) return undefined;
|
|
86
|
+
|
|
87
|
+
return { vault: deployment, protocol: vaultInfo.protocol };
|
|
88
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Address } from 'viem';
|
|
2
|
+
import type { GauntletClient } from '../client';
|
|
3
|
+
import type { AdapterWithdrawParams } from './adapters/types';
|
|
4
|
+
import { getAdapter } from './adapters';
|
|
5
|
+
import { encodeTransactionWithAttribution, PreparedTx } from '../attribution';
|
|
6
|
+
import {
|
|
7
|
+
AccountRequiredError,
|
|
8
|
+
VaultNotFoundError,
|
|
9
|
+
UnsupportedAssetError,
|
|
10
|
+
UnsupportedDepositModeError,
|
|
11
|
+
InvalidSlippageBPSError,
|
|
12
|
+
} from '../errors';
|
|
13
|
+
import { resolveVault } from './vaults';
|
|
14
|
+
|
|
15
|
+
export type EvmWithdrawParams = {
|
|
16
|
+
vaultId: string;
|
|
17
|
+
// Required for multichain vaults, will default to base
|
|
18
|
+
chainId?: number;
|
|
19
|
+
// Required for multiasset vaults, not utilized yet
|
|
20
|
+
assetSymbol?: string;
|
|
21
|
+
/** Request async (queued) withdraw. Only valid for vaults with depositMode 'async' or 'both'. */
|
|
22
|
+
depositMode?: string;
|
|
23
|
+
// So a developer is able to specify a separate receiver than the tx sender
|
|
24
|
+
receiver?: Address;
|
|
25
|
+
/** Slippage tolerance in basis points (e.g. 100 = 1%). Defaults to 100. */
|
|
26
|
+
slippageBps?: number;
|
|
27
|
+
} & (
|
|
28
|
+
| { shares: bigint; amount?: never; entireAmount?: never }
|
|
29
|
+
| { amount: bigint; shares?: never; entireAmount?: never }
|
|
30
|
+
| { entireAmount: true; shares?: never; amount?: never }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds the ordered list of EVM transactions required to withdraw from a Gauntlet vault.
|
|
35
|
+
*
|
|
36
|
+
* Specify the withdrawal amount as one of: `shares` (vault units), `amount` (underlying
|
|
37
|
+
* token amount), or `entireAmount: true` (full balance). Returns an ERC-20 approval
|
|
38
|
+
* (only when needed for async redeem) followed by the withdraw or async redeem request,
|
|
39
|
+
* each returned as a `PreparedTx` ready to be signed and sent.
|
|
40
|
+
*
|
|
41
|
+
* @param client - A configured `GauntletClient` instance with an EVM public client and wallet.
|
|
42
|
+
* @param params - Withdrawal parameters including the vault identifier and exactly one of
|
|
43
|
+
* `shares`, `amount`, or `entireAmount`, plus optional chain, asset symbol, deposit mode,
|
|
44
|
+
* and receiver address.
|
|
45
|
+
* @returns Ordered array of prepared transactions to execute in sequence.
|
|
46
|
+
*
|
|
47
|
+
* @throws {VaultNotFoundError} If the vault ID is not found in the manifest.
|
|
48
|
+
* @throws {AccountRequiredError} If no wallet account is set on the client.
|
|
49
|
+
* @throws {UnsupportedDepositModeError} If the requested withdrawal mode is not supported.
|
|
50
|
+
* @throws {UnsupportedAssetError} If the specified asset symbol is not accepted by the vault.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // Withdraw by token amount
|
|
55
|
+
* const txs = await getWithdrawTx(client, {
|
|
56
|
+
* vaultId: 'baseUsdcPrime',
|
|
57
|
+
* amount: 50_000_000n, // 50 USDC (6 decimals)
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // Withdraw entire balance
|
|
61
|
+
* const txs = await getWithdrawTx(client, {
|
|
62
|
+
* vaultId: 'baseUsdcPrime',
|
|
63
|
+
* entireAmount: true,
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export async function getWithdrawTx(
|
|
68
|
+
client: GauntletClient,
|
|
69
|
+
params: EvmWithdrawParams
|
|
70
|
+
): Promise<PreparedTx[]> {
|
|
71
|
+
if (
|
|
72
|
+
params.slippageBps !== undefined &&
|
|
73
|
+
(!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps > 10000)
|
|
74
|
+
) {
|
|
75
|
+
throw new InvalidSlippageBPSError(params.slippageBps);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const resolved = await resolveVault(client, params.vaultId, params.chainId);
|
|
79
|
+
if (!resolved) throw new VaultNotFoundError(params.vaultId);
|
|
80
|
+
|
|
81
|
+
const chainId = params.chainId ?? resolved.vault.chainId;
|
|
82
|
+
|
|
83
|
+
const account = client.wallet?.account?.address;
|
|
84
|
+
if (!account) throw new AccountRequiredError();
|
|
85
|
+
|
|
86
|
+
const { vault, protocol } = resolved;
|
|
87
|
+
|
|
88
|
+
if (params.depositMode === 'sync' && vault.depositMode === 'async') {
|
|
89
|
+
throw new UnsupportedDepositModeError(params.vaultId, 'sync', vault.depositMode);
|
|
90
|
+
}
|
|
91
|
+
if (params.depositMode === 'async' && vault.depositMode === 'sync') {
|
|
92
|
+
throw new UnsupportedDepositModeError(params.vaultId, 'async', vault.depositMode);
|
|
93
|
+
}
|
|
94
|
+
let modifiedDepositMode = params.depositMode;
|
|
95
|
+
if (params.depositMode === undefined) {
|
|
96
|
+
modifiedDepositMode = vault.depositMode === 'both' ? 'async' : vault.depositMode;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const token =
|
|
100
|
+
vault.supplyToken.length > 1
|
|
101
|
+
? vault.supplyToken.find((tInfo) => tInfo.symbol === params.assetSymbol)
|
|
102
|
+
: vault.supplyToken[0];
|
|
103
|
+
|
|
104
|
+
if (token === undefined) {
|
|
105
|
+
throw new UnsupportedAssetError(params.assetSymbol ?? 'unknown', params.vaultId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const adapter = getAdapter(protocol);
|
|
109
|
+
const publicClient = client.getPublicClient(chainId);
|
|
110
|
+
|
|
111
|
+
const withdrawParams = {
|
|
112
|
+
vault,
|
|
113
|
+
receiver: params.receiver ?? account,
|
|
114
|
+
account,
|
|
115
|
+
async: modifiedDepositMode === 'async',
|
|
116
|
+
asset: token,
|
|
117
|
+
publicClient,
|
|
118
|
+
slippageBps: params.slippageBps,
|
|
119
|
+
...('shares' in params && params.shares != null ? { shares: params.shares } : {}),
|
|
120
|
+
...('amount' in params && params.amount != null ? { amount: params.amount } : {}),
|
|
121
|
+
...('entireAmount' in params && params.entireAmount ? { entireAmount: true as const } : {}),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const steps = await adapter.buildWithdraw(withdrawParams as AdapterWithdrawParams);
|
|
125
|
+
|
|
126
|
+
return await Promise.all(steps.map((step) => encodeTransactionWithAttribution(client, step)));
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { GauntletClient, AttributionMode } from './client';
|
|
2
|
+
export type { GauntletClientConfig } from './client';
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
VaultInfo,
|
|
6
|
+
VaultDeployment,
|
|
7
|
+
EvmVaultDeployment,
|
|
8
|
+
TokenInfo,
|
|
9
|
+
VaultManifest,
|
|
10
|
+
} from './evm/types';
|
|
11
|
+
export type { VaultFilter } from './evm/vaults';
|
|
12
|
+
|
|
13
|
+
export { getUserCurrentBalance } from './evm/userCurrentBalance';
|
|
14
|
+
export type { UserCurrentBalanceParams, UserCurrentBalance } from './evm/userCurrentBalance';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
GauntletSDKError,
|
|
18
|
+
VaultNotFoundError,
|
|
19
|
+
UnsupportedAssetError,
|
|
20
|
+
ChainMismatchError,
|
|
21
|
+
UnsupportedDepositModeError,
|
|
22
|
+
RpcNotConfiguredError,
|
|
23
|
+
AccountRequiredError,
|
|
24
|
+
UnsupportedProtocolError,
|
|
25
|
+
InvalidWithdrawParamsError,
|
|
26
|
+
UnimplementedFeatureError,
|
|
27
|
+
UnitConversionError,
|
|
28
|
+
} from './errors';
|