@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.
- package/README.md +134 -1
- package/dist/api/activity.d.ts +70 -0
- package/dist/api/activity.d.ts.map +1 -0
- package/dist/api/activity.js +154 -0
- package/dist/api/activity.js.map +1 -0
- package/dist/api/caip.d.ts +29 -0
- package/dist/api/caip.d.ts.map +1 -0
- package/dist/api/caip.js +38 -0
- package/dist/api/caip.js.map +1 -0
- package/dist/api/client.d.ts +135 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +119 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/decimal.d.ts +21 -0
- package/dist/api/decimal.d.ts.map +1 -0
- package/dist/api/decimal.js +44 -0
- package/dist/api/decimal.js.map +1 -0
- package/dist/api/errors.d.ts +32 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +41 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/generated.d.ts +1505 -0
- package/dist/api/generated.d.ts.map +1 -0
- package/dist/api/generated.js +6 -0
- package/dist/api/generated.js.map +1 -0
- package/dist/api/http.d.ts +16 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +39 -0
- package/dist/api/http.js.map +1 -0
- package/dist/api/index.d.ts +14 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/positionHistory.d.ts +44 -0
- package/dist/api/positionHistory.d.ts.map +1 -0
- package/dist/api/positionHistory.js +126 -0
- package/dist/api/positionHistory.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -2
- package/dist/errors.js.map +1 -1
- package/dist/evm/abis/priceAndFeeCalculatorV2.d.ts +44 -0
- package/dist/evm/abis/priceAndFeeCalculatorV2.d.ts.map +1 -1
- package/dist/evm/abis/priceAndFeeCalculatorV2.js +22 -0
- package/dist/evm/abis/priceAndFeeCalculatorV2.js.map +1 -1
- package/dist/evm/abis/provisionerV2.d.ts +280 -0
- package/dist/evm/abis/provisionerV2.d.ts.map +1 -1
- package/dist/evm/abis/provisionerV2.js +92 -0
- package/dist/evm/abis/provisionerV2.js.map +1 -1
- package/dist/evm/adapters/aera.d.ts.map +1 -1
- package/dist/evm/adapters/aera.js +41 -10
- package/dist/evm/adapters/aera.js.map +1 -1
- package/dist/evm/adapters/types.d.ts +2 -0
- package/dist/evm/adapters/types.d.ts.map +1 -1
- package/dist/evm/aeraContracts/priceAndFeeCalculator.d.ts +10 -5
- package/dist/evm/aeraContracts/priceAndFeeCalculator.d.ts.map +1 -1
- package/dist/evm/aeraContracts/priceAndFeeCalculator.js +39 -5
- package/dist/evm/aeraContracts/priceAndFeeCalculator.js.map +1 -1
- package/dist/evm/aeraContracts/readOptions.d.ts +8 -0
- package/dist/evm/aeraContracts/readOptions.d.ts.map +1 -0
- package/dist/evm/aeraContracts/readOptions.js +4 -0
- package/dist/evm/aeraContracts/readOptions.js.map +1 -0
- package/dist/evm/aeraContracts/v2.d.ts +1932 -217
- package/dist/evm/aeraContracts/v2.d.ts.map +1 -1
- package/dist/evm/aeraContracts/v2.js +84 -28
- package/dist/evm/aeraContracts/v2.js.map +1 -1
- package/dist/evm/aeraContracts.d.ts +9 -3
- package/dist/evm/aeraContracts.d.ts.map +1 -1
- package/dist/evm/aeraContracts.js +69 -8
- package/dist/evm/aeraContracts.js.map +1 -1
- package/dist/evm/deposit.d.ts.map +1 -1
- package/dist/evm/deposit.js +1 -1
- package/dist/evm/deposit.js.map +1 -1
- package/dist/evm/index.d.ts +8 -0
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +3 -0
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/tokenModes.d.ts +22 -0
- package/dist/evm/tokenModes.d.ts.map +1 -0
- package/dist/evm/tokenModes.js +34 -0
- package/dist/evm/tokenModes.js.map +1 -0
- package/dist/evm/withdraw.d.ts +8 -0
- package/dist/evm/withdraw.d.ts.map +1 -1
- package/dist/evm/withdraw.js +85 -5
- package/dist/evm/withdraw.js.map +1 -1
- package/dist/evm/withdrawQuote.d.ts +127 -0
- package/dist/evm/withdrawQuote.d.ts.map +1 -0
- package/dist/evm/withdrawQuote.js +164 -0
- package/dist/evm/withdrawQuote.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/privy/index.d.ts +52 -0
- package/dist/privy/index.d.ts.map +1 -0
- package/dist/privy/index.js +51 -0
- package/dist/privy/index.js.map +1 -0
- package/manifest/vaults.json +255 -3
- package/package.json +2 -2
- package/src/api/activity.ts +219 -0
- package/src/api/caip.ts +61 -0
- package/src/api/client.ts +234 -0
- package/src/api/decimal.ts +49 -0
- package/src/api/errors.ts +64 -0
- package/src/api/generated.ts +1505 -0
- package/src/api/http.ts +62 -0
- package/src/api/index.ts +70 -0
- package/src/api/positionHistory.ts +167 -0
- package/src/client.ts +7 -1
- package/src/errors.ts +32 -2
- package/src/evm/abis/priceAndFeeCalculatorV2.ts +22 -0
- package/src/evm/abis/provisionerV2.ts +92 -0
- package/src/evm/adapters/aera.ts +50 -23
- package/src/evm/adapters/types.ts +2 -0
- package/src/evm/aeraContracts/priceAndFeeCalculator.ts +72 -5
- package/src/evm/aeraContracts/readOptions.ts +3 -0
- package/src/evm/aeraContracts/v2.ts +158 -34
- package/src/evm/aeraContracts.ts +81 -8
- package/src/evm/deposit.ts +2 -1
- package/src/evm/index.ts +32 -0
- package/src/evm/tokenModes.ts +54 -0
- package/src/evm/withdraw.ts +131 -7
- package/src/evm/withdrawQuote.ts +360 -0
- package/src/index.ts +18 -0
- package/src/privy/index.ts +95 -0
package/src/api/http.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { GauntletApiError } from './errors';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_API_URL = 'https://api.gauntlet.xyz';
|
|
4
|
+
|
|
5
|
+
export interface GauntletApiConfig {
|
|
6
|
+
/** Kong consumer key sent as `x-api-key`. Anonymous access is rate-limited. */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Override the API origin. Defaults to `https://api.gauntlet.xyz`. In a
|
|
10
|
+
* browser this may be a relative path (e.g. a Next.js rewrite like
|
|
11
|
+
* `/gauntlet-api`), which resolves against the page origin.
|
|
12
|
+
*/
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
/** Custom fetch implementation (testing, instrumentation). Defaults to global fetch. */
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|
19
|
+
|
|
20
|
+
export async function apiGet<T>(
|
|
21
|
+
config: GauntletApiConfig,
|
|
22
|
+
path: string,
|
|
23
|
+
query?: QueryParams
|
|
24
|
+
): Promise<T> {
|
|
25
|
+
// Concatenate instead of `new URL(path, base)`: the paths are absolute, so
|
|
26
|
+
// URL resolution would silently drop any path prefix on a proxy apiUrl.
|
|
27
|
+
const base = (config.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, '');
|
|
28
|
+
// Relative bases resolve against the browser origin.
|
|
29
|
+
const origin = (globalThis as { location?: { origin: string } }).location?.origin;
|
|
30
|
+
if (origin === undefined && !/^[a-z][a-z0-9+.-]*:\/\//i.test(base)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`relative apiUrl "${base}" requires a browser origin; pass an absolute apiUrl outside the browser`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const url = new URL(base + path, origin);
|
|
36
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
37
|
+
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
41
|
+
const response = await fetchImpl(url, {
|
|
42
|
+
headers: {
|
|
43
|
+
accept: 'application/json',
|
|
44
|
+
...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
let message = response.statusText || 'request failed';
|
|
50
|
+
let code: string | undefined;
|
|
51
|
+
try {
|
|
52
|
+
const body = (await response.json()) as { error?: { code?: string; message?: string } };
|
|
53
|
+
message = body.error?.message ?? message;
|
|
54
|
+
code = body.error?.code;
|
|
55
|
+
} catch {
|
|
56
|
+
// non-JSON error body — keep the status text
|
|
57
|
+
}
|
|
58
|
+
throw new GauntletApiError({ status: response.status, path, message, code });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (await response.json()) as T;
|
|
62
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export { GauntletApi } from './client';
|
|
2
|
+
export type {
|
|
3
|
+
ActivityOptions,
|
|
4
|
+
AmountPair,
|
|
5
|
+
ChainSyncStatus,
|
|
6
|
+
FeaturedVault,
|
|
7
|
+
FeaturedVaultsResponse,
|
|
8
|
+
HealthResponse,
|
|
9
|
+
LatestPriceOptions,
|
|
10
|
+
LatestPriceResponse,
|
|
11
|
+
PageOptions,
|
|
12
|
+
PartialResponseError,
|
|
13
|
+
PositionsOptions,
|
|
14
|
+
PositionTimeseriesPoint,
|
|
15
|
+
PriceTimeseriesOptions,
|
|
16
|
+
PriceTimeseriesResponse,
|
|
17
|
+
StrategyCard,
|
|
18
|
+
StrategyListResponse,
|
|
19
|
+
TimeseriesMeta,
|
|
20
|
+
TimeWindowOptions,
|
|
21
|
+
TokenRef,
|
|
22
|
+
TvlResponse,
|
|
23
|
+
UserActivity,
|
|
24
|
+
UserActivityResponse,
|
|
25
|
+
UserAllPositionsResponse,
|
|
26
|
+
UserPosition,
|
|
27
|
+
UserPositionLatestResponse,
|
|
28
|
+
UserPositionsTimeseriesResponse,
|
|
29
|
+
VaultDetail,
|
|
30
|
+
VaultDetailResponse,
|
|
31
|
+
VaultMetrics,
|
|
32
|
+
VaultTimeseriesPoint,
|
|
33
|
+
VaultTimeseriesResponse,
|
|
34
|
+
} from './client';
|
|
35
|
+
|
|
36
|
+
export { DEFAULT_API_URL } from './http';
|
|
37
|
+
export type { GauntletApiConfig } from './http';
|
|
38
|
+
|
|
39
|
+
export { bigIntToDecimal, decimalToBigInt, SHARE_DECIMALS, sharesToBigInt } from './decimal';
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
apiVaultIdFromVaultId,
|
|
43
|
+
formatApiVaultId,
|
|
44
|
+
parseApiVaultId,
|
|
45
|
+
vaultIdFromApiVaultId,
|
|
46
|
+
} from './caip';
|
|
47
|
+
export type { ApiVaultId } from './caip';
|
|
48
|
+
|
|
49
|
+
export { getActivityFlows, stitchActivityFlows, waitForRequestSettlement } from './activity';
|
|
50
|
+
export type {
|
|
51
|
+
ActivityFlow,
|
|
52
|
+
ActivityFlowKind,
|
|
53
|
+
ActivityFlowsOptions,
|
|
54
|
+
ActivityFlowStatus,
|
|
55
|
+
AssetAmount,
|
|
56
|
+
WaitForSettlementOptions,
|
|
57
|
+
} from './activity';
|
|
58
|
+
|
|
59
|
+
export { buildPositionHistory, getPositionHistory } from './positionHistory';
|
|
60
|
+
export type { PositionHistory, PositionHistoryPoint } from './positionHistory';
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
DecimalPrecisionError,
|
|
64
|
+
GauntletApiError,
|
|
65
|
+
InvalidCaipIdError,
|
|
66
|
+
InvalidDecimalError,
|
|
67
|
+
SettlementTimeoutError,
|
|
68
|
+
} from './errors';
|
|
69
|
+
|
|
70
|
+
export type { components as GaiaApiComponents, paths as GaiaApiPaths } from './generated';
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { GauntletSDKError } from '../errors';
|
|
2
|
+
import type { GauntletApi, TokenRef, UserActivity } from './client';
|
|
3
|
+
import { bigIntToDecimal, decimalToBigInt, sharesToBigInt } from './decimal';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal accounting scale for asset amounts. The API emits human-unit
|
|
7
|
+
* decimal strings with at most `token.decimals` fractional digits, so 18
|
|
8
|
+
* digits is exact for every supported asset; a token with more than 18
|
|
9
|
+
* decimals would throw `DecimalPrecisionError` rather than lose precision.
|
|
10
|
+
*/
|
|
11
|
+
const ASSET_SCALE = 18;
|
|
12
|
+
|
|
13
|
+
/** The wallet's reconstructed position state immediately after one activity row. */
|
|
14
|
+
export interface PositionHistoryPoint {
|
|
15
|
+
timestamp: Date;
|
|
16
|
+
txHash: string;
|
|
17
|
+
type: UserActivity['type'];
|
|
18
|
+
/** Signed share movement of this row, 18-decimal base units. */
|
|
19
|
+
sharesDelta: bigint;
|
|
20
|
+
/** Signed asset movement of this row (deposit-ledger convention), human decimal string. */
|
|
21
|
+
assetsDelta: string;
|
|
22
|
+
/** Shares held by the wallet after this row (escrowed redeem shares excluded). */
|
|
23
|
+
sharesBalance: bigint;
|
|
24
|
+
/** Assets escrowed at the Provisioner awaiting share mint, human decimal string. */
|
|
25
|
+
pendingDepositAssets: string;
|
|
26
|
+
/** Shares escrowed at the Provisioner awaiting asset return, 18-decimal base units. */
|
|
27
|
+
pendingRedeemShares: bigint;
|
|
28
|
+
/** Cumulative settled deposits minus settled withdrawals, human decimal string. */
|
|
29
|
+
netAssetsIn: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PositionHistory {
|
|
33
|
+
/** CAIP-10 vault id the history belongs to. */
|
|
34
|
+
vaultId: string;
|
|
35
|
+
/** The vault's asset token, when the indexer reports it on any row. */
|
|
36
|
+
token: TokenRef | null;
|
|
37
|
+
/** Chronological (oldest-first) position states, one per activity row. */
|
|
38
|
+
points: PositionHistoryPoint[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pure builder: replays a single vault's activity rows (any order) into a
|
|
43
|
+
* chronological position timeline — running share balance, escrowed pending
|
|
44
|
+
* amounts, and cumulative net asset flows.
|
|
45
|
+
*
|
|
46
|
+
* Async lifecycles are paired via `request_hash` so settled deposits count
|
|
47
|
+
* the requested asset amount and terminal rows release the matching escrow.
|
|
48
|
+
*/
|
|
49
|
+
export function buildPositionHistory(rows: UserActivity[]): PositionHistory {
|
|
50
|
+
if (rows.length === 0) {
|
|
51
|
+
throw new GauntletSDKError('Cannot build position history from zero activity rows');
|
|
52
|
+
}
|
|
53
|
+
const vaultIds = new Set(rows.map((r) => r.vault_id));
|
|
54
|
+
if (vaultIds.size > 1) {
|
|
55
|
+
throw new GauntletSDKError(
|
|
56
|
+
`Position history requires rows from a single vault; got ${[...vaultIds].join(', ')}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ordered = [...rows].sort((a, b) => a.block_timestamp - b.block_timestamp);
|
|
61
|
+
const token = ordered.find((r) => r.assets_delta.token)?.assets_delta.token ?? null;
|
|
62
|
+
|
|
63
|
+
let sharesBalance = 0n;
|
|
64
|
+
let pendingDepositAssets = 0n;
|
|
65
|
+
let pendingRedeemShares = 0n;
|
|
66
|
+
let netAssetsIn = 0n;
|
|
67
|
+
const pendingDepositByHash = new Map<string, bigint>();
|
|
68
|
+
const pendingRedeemByHash = new Map<string, bigint>();
|
|
69
|
+
|
|
70
|
+
const points: PositionHistoryPoint[] = [];
|
|
71
|
+
for (const row of ordered) {
|
|
72
|
+
const sharesDelta = sharesToBigInt(row.shares_delta);
|
|
73
|
+
const assetsDelta = decimalToBigInt(row.assets_delta.native, ASSET_SCALE);
|
|
74
|
+
sharesBalance += sharesDelta;
|
|
75
|
+
|
|
76
|
+
switch (row.type) {
|
|
77
|
+
case 'deposit':
|
|
78
|
+
if (row.request_hash != null) {
|
|
79
|
+
// Async terminal solve: assets moved at request time; release escrow.
|
|
80
|
+
const requested = pendingDepositByHash.get(row.request_hash);
|
|
81
|
+
if (requested === undefined) {
|
|
82
|
+
throw new GauntletSDKError(
|
|
83
|
+
`Deposit settlement ${row.tx_hash} references request_hash "${row.request_hash}" with no matching deposit_pending row`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
pendingDepositByHash.delete(row.request_hash);
|
|
87
|
+
pendingDepositAssets -= requested;
|
|
88
|
+
netAssetsIn += requested;
|
|
89
|
+
} else {
|
|
90
|
+
netAssetsIn += assetsDelta;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case 'deposit_pending':
|
|
94
|
+
pendingDepositAssets += assetsDelta;
|
|
95
|
+
if (row.request_hash != null) pendingDepositByHash.set(row.request_hash, assetsDelta);
|
|
96
|
+
break;
|
|
97
|
+
case 'deposit_refunded':
|
|
98
|
+
// Row carries the unwound request as a negative delta.
|
|
99
|
+
pendingDepositAssets += assetsDelta;
|
|
100
|
+
if (row.request_hash != null) pendingDepositByHash.delete(row.request_hash);
|
|
101
|
+
break;
|
|
102
|
+
case 'withdraw':
|
|
103
|
+
// Sync withdraws and async terminal solves both carry the asset
|
|
104
|
+
// payout as a negative delta on this row.
|
|
105
|
+
netAssetsIn += assetsDelta;
|
|
106
|
+
if (row.request_hash != null) {
|
|
107
|
+
const escrowed = pendingRedeemByHash.get(row.request_hash);
|
|
108
|
+
if (escrowed === undefined) {
|
|
109
|
+
throw new GauntletSDKError(
|
|
110
|
+
`Withdraw settlement ${row.tx_hash} references request_hash "${row.request_hash}" with no matching withdraw_pending row`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
pendingRedeemByHash.delete(row.request_hash);
|
|
114
|
+
pendingRedeemShares -= escrowed;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case 'withdraw_pending': {
|
|
118
|
+
const escrowed = -sharesDelta;
|
|
119
|
+
pendingRedeemShares += escrowed;
|
|
120
|
+
if (row.request_hash != null) pendingRedeemByHash.set(row.request_hash, escrowed);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'withdraw_refunded':
|
|
124
|
+
pendingRedeemShares -= sharesDelta;
|
|
125
|
+
if (row.request_hash != null) pendingRedeemByHash.delete(row.request_hash);
|
|
126
|
+
break;
|
|
127
|
+
case 'transfer_in':
|
|
128
|
+
case 'transfer_out':
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
throw new GauntletSDKError(`Unknown activity row type "${row.type}"`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
points.push({
|
|
135
|
+
timestamp: new Date(row.block_timestamp * 1000),
|
|
136
|
+
txHash: row.tx_hash,
|
|
137
|
+
type: row.type,
|
|
138
|
+
sharesDelta,
|
|
139
|
+
assetsDelta: row.assets_delta.native,
|
|
140
|
+
sharesBalance,
|
|
141
|
+
pendingDepositAssets: bigIntToDecimal(pendingDepositAssets, ASSET_SCALE),
|
|
142
|
+
pendingRedeemShares,
|
|
143
|
+
netAssetsIn: bigIntToDecimal(netAssetsIn, ASSET_SCALE),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { vaultId: ordered[0].vault_id, token, points };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Fetches a wallet's complete activity for one vault and replays it into a
|
|
152
|
+
* position timeline. `vaultId` is the CAIP-10 id; use `apiVaultIdFromVaultId`
|
|
153
|
+
* to convert a manifest vault id. A wallet that has never touched the vault
|
|
154
|
+
* gets an empty timeline, not an error.
|
|
155
|
+
*/
|
|
156
|
+
export async function getPositionHistory(
|
|
157
|
+
api: GauntletApi,
|
|
158
|
+
walletAddress: string,
|
|
159
|
+
vaultId: string
|
|
160
|
+
): Promise<PositionHistory> {
|
|
161
|
+
const rows: UserActivity[] = [];
|
|
162
|
+
for await (const row of api.activityRows(walletAddress, { vaultId, order: 'asc', limit: 1000 })) {
|
|
163
|
+
rows.push(row);
|
|
164
|
+
}
|
|
165
|
+
if (rows.length === 0) return { vaultId, token: null, points: [] };
|
|
166
|
+
return buildPositionHistory(rows);
|
|
167
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type PublicClient, type WalletClient } from 'viem';
|
|
2
2
|
import bundledManifest from '../manifest/vaults.json';
|
|
3
|
+
import { GauntletApi } from './api/client';
|
|
3
4
|
import { RpcNotConfiguredError, UnimplementedFeatureError } from './errors';
|
|
4
5
|
import type { VaultManifest } from './evm/types';
|
|
5
6
|
import { base } from 'viem/chains';
|
|
@@ -20,7 +21,10 @@ export enum AttributionMode {
|
|
|
20
21
|
export type ChainId = string | number;
|
|
21
22
|
|
|
22
23
|
export interface GauntletClientConfig {
|
|
24
|
+
/** Gaia API key, sent as `x-api-key` on `client.api` requests. Anonymous access is rate-limited. */
|
|
23
25
|
apiKey?: string;
|
|
26
|
+
/** Override the Gaia API origin (e.g. a same-origin proxy route). Defaults to `https://api.gauntlet.xyz`. */
|
|
27
|
+
apiUrl?: string;
|
|
24
28
|
/** Map of chainId → PublicClient, e.g. { [base.id]: createPublicClient(...) } */
|
|
25
29
|
evmClients?: Record<ChainId, PublicClient>;
|
|
26
30
|
attributionMode?: AttributionMode;
|
|
@@ -56,12 +60,13 @@ export interface GauntletClientConfig {
|
|
|
56
60
|
* ```
|
|
57
61
|
*/
|
|
58
62
|
export class GauntletClient {
|
|
59
|
-
// used for future core api and encoding attribution
|
|
60
63
|
readonly apiKey?: string;
|
|
61
64
|
readonly builderCode?: string;
|
|
62
65
|
readonly attributionMode: AttributionMode;
|
|
63
66
|
readonly evmClients: Record<ChainId, PublicClient>;
|
|
64
67
|
readonly wallet?: WalletClient;
|
|
68
|
+
/** Typed client for the Gaia REST API (vaults, positions, activity, tvl, prices). */
|
|
69
|
+
readonly api: GauntletApi;
|
|
65
70
|
|
|
66
71
|
private _manifest: VaultManifest = bundledManifest as VaultManifest;
|
|
67
72
|
|
|
@@ -71,6 +76,7 @@ export class GauntletClient {
|
|
|
71
76
|
this.attributionMode = config.attributionMode ?? AttributionMode.PUBLIC;
|
|
72
77
|
this.builderCode = config.builderCode;
|
|
73
78
|
this.wallet = config.wallet;
|
|
79
|
+
this.api = new GauntletApi({ apiKey: config.apiKey, apiUrl: config.apiUrl });
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
get manifest(): Promise<VaultManifest> {
|
package/src/errors.ts
CHANGED
|
@@ -53,7 +53,7 @@ export class UnsupportedDepositModeError extends GauntletSDKError {
|
|
|
53
53
|
readonly available: string;
|
|
54
54
|
|
|
55
55
|
constructor(vaultId: string, requested: string, available: string) {
|
|
56
|
-
super(`Vault "${vaultId}" does not support ${requested}
|
|
56
|
+
super(`Vault "${vaultId}" does not support ${requested} operations (available: ${available})`);
|
|
57
57
|
this.name = 'UnsupportedDepositModeError';
|
|
58
58
|
this.vaultId = vaultId;
|
|
59
59
|
this.requested = requested;
|
|
@@ -73,11 +73,25 @@ export class RpcNotConfiguredError extends GauntletSDKError {
|
|
|
73
73
|
|
|
74
74
|
export class AccountRequiredError extends GauntletSDKError {
|
|
75
75
|
constructor() {
|
|
76
|
-
super(
|
|
76
|
+
super(
|
|
77
|
+
'No account specified. Pass the required account, or configure a wallet when building transactions.'
|
|
78
|
+
);
|
|
77
79
|
this.name = 'AccountRequiredError';
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
export class AccountMismatchError extends GauntletSDKError {
|
|
84
|
+
readonly expected: string;
|
|
85
|
+
readonly received: string;
|
|
86
|
+
|
|
87
|
+
constructor(expected: string, received: string) {
|
|
88
|
+
super(`Account mismatch: expected "${expected}", received "${received}"`);
|
|
89
|
+
this.name = 'AccountMismatchError';
|
|
90
|
+
this.expected = expected;
|
|
91
|
+
this.received = received;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
81
95
|
export class UnsupportedProtocolError extends GauntletSDKError {
|
|
82
96
|
readonly protocol: string;
|
|
83
97
|
|
|
@@ -95,6 +109,22 @@ export class InvalidWithdrawParamsError extends GauntletSDKError {
|
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
|
|
112
|
+
export type SyncWithdrawBound = 'tokensOut' | 'minTokensOut' | 'unitsIn' | 'maxUnitsIn';
|
|
113
|
+
|
|
114
|
+
export class InvalidSyncWithdrawBoundError extends GauntletSDKError {
|
|
115
|
+
readonly bound: SyncWithdrawBound;
|
|
116
|
+
|
|
117
|
+
constructor(bound: SyncWithdrawBound) {
|
|
118
|
+
super(`Sync withdraw cannot submit zero ${bound}; increase the amount or reduce slippage`);
|
|
119
|
+
this.name = 'InvalidSyncWithdrawBoundError';
|
|
120
|
+
this.bound = bound;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function requireNonZeroSyncWithdrawBound(value: bigint, bound: SyncWithdrawBound) {
|
|
125
|
+
if (value === 0n) throw new InvalidSyncWithdrawBoundError(bound);
|
|
126
|
+
}
|
|
127
|
+
|
|
98
128
|
export class UnimplementedFeatureError extends GauntletSDKError {
|
|
99
129
|
readonly feature: string;
|
|
100
130
|
|
|
@@ -47,6 +47,28 @@ export const priceAndFeeCalculatorV2Abi = [
|
|
|
47
47
|
outputs: [{ name: 'tokenAmount', internalType: 'uint256', type: 'uint256' }],
|
|
48
48
|
stateMutability: 'view',
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
type: 'function',
|
|
52
|
+
inputs: [
|
|
53
|
+
{ name: 'vault', internalType: 'address', type: 'address' },
|
|
54
|
+
{ name: 'token', internalType: 'contract IERC20', type: 'address' },
|
|
55
|
+
{ name: 'tokenAmount', internalType: 'uint256', type: 'uint256' },
|
|
56
|
+
],
|
|
57
|
+
name: 'convertTokenToNumeraire',
|
|
58
|
+
outputs: [{ name: 'numeraireAmount', internalType: 'uint256', type: 'uint256' }],
|
|
59
|
+
stateMutability: 'view',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'function',
|
|
63
|
+
inputs: [
|
|
64
|
+
{ name: 'vault', internalType: 'address', type: 'address' },
|
|
65
|
+
{ name: 'token', internalType: 'contract IERC20', type: 'address' },
|
|
66
|
+
{ name: 'numeraireAmount', internalType: 'uint256', type: 'uint256' },
|
|
67
|
+
],
|
|
68
|
+
name: 'convertNumeraireToToken',
|
|
69
|
+
outputs: [{ name: 'tokenAmount', internalType: 'uint256', type: 'uint256' }],
|
|
70
|
+
stateMutability: 'view',
|
|
71
|
+
},
|
|
50
72
|
{
|
|
51
73
|
type: 'function',
|
|
52
74
|
inputs: [{ name: 'vault', internalType: 'address', type: 'address' }],
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// Compact ABI derived from aera-finance/aera-contracts-v3@f4e3433058c29c3a0b1348ffbd358cc036a1687f
|
|
2
2
|
// source: src/core/interfaces/IProvisionerV2.sol
|
|
3
3
|
export const provisionerV2Abi = [
|
|
4
|
+
{
|
|
5
|
+
type: 'function',
|
|
6
|
+
inputs: [],
|
|
7
|
+
name: 'SOLVING_GATE_ENABLED',
|
|
8
|
+
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
|
|
9
|
+
stateMutability: 'view',
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
type: 'function',
|
|
6
13
|
inputs: [
|
|
@@ -53,6 +60,25 @@ export const provisionerV2Abi = [
|
|
|
53
60
|
],
|
|
54
61
|
stateMutability: 'view',
|
|
55
62
|
},
|
|
63
|
+
{
|
|
64
|
+
type: 'function',
|
|
65
|
+
inputs: [],
|
|
66
|
+
name: 'getSyncRedeemEpochState',
|
|
67
|
+
outputs: [
|
|
68
|
+
{ name: 'epochTimestamp', internalType: 'uint256', type: 'uint256' },
|
|
69
|
+
{ name: 'epochStartTvlNumeraire', internalType: 'uint256', type: 'uint256' },
|
|
70
|
+
{ name: 'epochRedeemedNumeraire', internalType: 'uint256', type: 'uint256' },
|
|
71
|
+
{ name: 'epochCapNumeraire', internalType: 'uint256', type: 'uint256' },
|
|
72
|
+
],
|
|
73
|
+
stateMutability: 'view',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'function',
|
|
77
|
+
inputs: [{ name: 'user', internalType: 'address', type: 'address' }],
|
|
78
|
+
name: 'userUnitsRefundableUntil',
|
|
79
|
+
outputs: [{ name: 'unitsLockedUntil', internalType: 'uint256', type: 'uint256' }],
|
|
80
|
+
stateMutability: 'view',
|
|
81
|
+
},
|
|
56
82
|
{
|
|
57
83
|
type: 'function',
|
|
58
84
|
inputs: [
|
|
@@ -63,6 +89,13 @@ export const provisionerV2Abi = [
|
|
|
63
89
|
outputs: [],
|
|
64
90
|
stateMutability: 'nonpayable',
|
|
65
91
|
},
|
|
92
|
+
{
|
|
93
|
+
type: 'function',
|
|
94
|
+
inputs: [],
|
|
95
|
+
name: 'solvingGate',
|
|
96
|
+
outputs: [{ name: '', internalType: 'address', type: 'address' }],
|
|
97
|
+
stateMutability: 'view',
|
|
98
|
+
},
|
|
66
99
|
{
|
|
67
100
|
type: 'function',
|
|
68
101
|
inputs: [{ name: 'token', internalType: 'contract IERC20', type: 'address' }],
|
|
@@ -199,4 +232,63 @@ export const provisionerV2Abi = [
|
|
|
199
232
|
name: 'Redeemed',
|
|
200
233
|
anonymous: false,
|
|
201
234
|
},
|
|
235
|
+
{ type: 'error', inputs: [], name: 'Aera__AsyncDepositDisabled' },
|
|
236
|
+
{ type: 'error', inputs: [], name: 'Aera__AsyncRedeemDisabled' },
|
|
237
|
+
{ type: 'error', inputs: [], name: 'Aera__AutoPriceSolveNotAllowed' },
|
|
238
|
+
{ type: 'error', inputs: [], name: 'Aera__CallerIsNotRequestUser' },
|
|
239
|
+
{ type: 'error', inputs: [], name: 'Aera__CallerIsVault' },
|
|
240
|
+
{ type: 'error', inputs: [], name: 'Aera__CancellationFeeExceedsRequestAmount' },
|
|
241
|
+
{ type: 'error', inputs: [], name: 'Aera__DeadlineInFutureAndUnauthorized' },
|
|
242
|
+
{ type: 'error', inputs: [], name: 'Aera__DeadlineInPast' },
|
|
243
|
+
{ type: 'error', inputs: [], name: 'Aera__DeadlineTooFarInFuture' },
|
|
244
|
+
{ type: 'error', inputs: [], name: 'Aera__DepositCancellationDetailsNotZero' },
|
|
245
|
+
{ type: 'error', inputs: [], name: 'Aera__DepositCapExceeded' },
|
|
246
|
+
{ type: 'error', inputs: [], name: 'Aera__DepositCapZero' },
|
|
247
|
+
{ type: 'error', inputs: [], name: 'Aera__DepositHashNotFound' },
|
|
248
|
+
{ type: 'error', inputs: [], name: 'Aera__DepositRequestCancellationDisabled' },
|
|
249
|
+
{ type: 'error', inputs: [], name: 'Aera__FixedPriceSolverTipNotAllowed' },
|
|
250
|
+
{ type: 'error', inputs: [], name: 'Aera__HashCollision' },
|
|
251
|
+
{ type: 'error', inputs: [], name: 'Aera__HashNotFound' },
|
|
252
|
+
{ type: 'error', inputs: [], name: 'Aera__InvalidToken' },
|
|
253
|
+
{ type: 'error', inputs: [], name: 'Aera__MaxDepositRefundTimeoutExceeded' },
|
|
254
|
+
{ type: 'error', inputs: [], name: 'Aera__MaxTokensInExceeded' },
|
|
255
|
+
{ type: 'error', inputs: [], name: 'Aera__MaxUnitsInExceeded' },
|
|
256
|
+
{ type: 'error', inputs: [], name: 'Aera__MinTokensOutNotMet' },
|
|
257
|
+
{ type: 'error', inputs: [], name: 'Aera__MinUnitsOutNotMet' },
|
|
258
|
+
{ type: 'error', inputs: [], name: 'Aera__MultiplierOutOfRange' },
|
|
259
|
+
{ type: 'error', inputs: [], name: 'Aera__PriceAndFeeCalculatorVaultPaused' },
|
|
260
|
+
{ type: 'error', inputs: [], name: 'Aera__PullFundsSubmitDataNotSet' },
|
|
261
|
+
{ type: 'error', inputs: [], name: 'Aera__ReceiverNotApproved' },
|
|
262
|
+
{ type: 'error', inputs: [], name: 'Aera__RedeemCancellationCapNumeraireZero' },
|
|
263
|
+
{ type: 'error', inputs: [], name: 'Aera__RedeemCancellationDetailsNotZero' },
|
|
264
|
+
{ type: 'error', inputs: [], name: 'Aera__RedeemRequestCancellationDisabled' },
|
|
265
|
+
{ type: 'error', inputs: [], name: 'Aera__RefundPeriodExpired' },
|
|
266
|
+
{ type: 'error', inputs: [], name: 'Aera__RequestAmountExceedsRefundCap' },
|
|
267
|
+
{ type: 'error', inputs: [], name: 'Aera__SolvingGateDisabled' },
|
|
268
|
+
{ type: 'error', inputs: [], name: 'Aera__SolvingPaused' },
|
|
269
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncDepositDisabled' },
|
|
270
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemAbsoluteCapNumeraireZero' },
|
|
271
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemDisabled' },
|
|
272
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemEpochCapExceeded' },
|
|
273
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemMaxDynamicPremiumBpsTooHigh' },
|
|
274
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemMaxPriceAgeExceeded' },
|
|
275
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemMaxPriceAgeZero' },
|
|
276
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemNotConfigured' },
|
|
277
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemRelativeCapBpsTooHigh' },
|
|
278
|
+
{ type: 'error', inputs: [], name: 'Aera__SyncRedeemRelativeCapBpsZero' },
|
|
279
|
+
{ type: 'error', inputs: [], name: 'Aera__TokenCantBePriced' },
|
|
280
|
+
{ type: 'error', inputs: [], name: 'Aera__TokensZero' },
|
|
281
|
+
{ type: 'error', inputs: [], name: 'Aera__Unauthorized' },
|
|
282
|
+
{ type: 'error', inputs: [], name: 'Aera__UnitsLocked' },
|
|
283
|
+
{ type: 'error', inputs: [], name: 'Aera__UnitsZero' },
|
|
284
|
+
{ type: 'error', inputs: [], name: 'Aera__ZeroAddressAuthority' },
|
|
285
|
+
{ type: 'error', inputs: [], name: 'Aera__ZeroAddressMultiDepositorVault' },
|
|
286
|
+
{ type: 'error', inputs: [], name: 'Aera__ZeroAddressPriceAndFeeCalculator' },
|
|
287
|
+
{ type: 'error', inputs: [], name: 'Aera__ZeroAddressReceiver' },
|
|
288
|
+
{ type: 'error', inputs: [], name: 'ReentrancyGuardReentrantCall' },
|
|
289
|
+
{
|
|
290
|
+
type: 'error',
|
|
291
|
+
inputs: [{ name: 'token', internalType: 'address', type: 'address' }],
|
|
292
|
+
name: 'SafeERC20FailedOperation',
|
|
293
|
+
},
|
|
202
294
|
] as const;
|
package/src/evm/adapters/aera.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getMultiDepositorVault,
|
|
4
4
|
applySlippageDown,
|
|
5
5
|
applySlippageUp,
|
|
6
|
+
requireAeraSyncRedeemRuntime,
|
|
6
7
|
resolveContractVersion,
|
|
7
8
|
type AeraRuntimeContracts,
|
|
8
9
|
} from '../aeraContracts';
|
|
@@ -12,6 +13,7 @@ import * as provisionerV2 from '../aeraContracts/v2';
|
|
|
12
13
|
import {
|
|
13
14
|
InvalidWithdrawParamsError,
|
|
14
15
|
InvalidSolverTipError,
|
|
16
|
+
requireNonZeroSyncWithdrawBound,
|
|
15
17
|
UnimplementedFeatureError,
|
|
16
18
|
UnsupportedFeatureError,
|
|
17
19
|
} from '../../errors';
|
|
@@ -321,9 +323,7 @@ export const aeraAdapter: EvmProtocolAdapter = {
|
|
|
321
323
|
throw new UnsupportedFeatureError('Aera: separate receiver address on V1');
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
if (!isAsync
|
|
325
|
-
throw new UnsupportedFeatureError('Aera: sync operations on V1');
|
|
326
|
-
}
|
|
326
|
+
if (!isAsync) requireAeraSyncRedeemRuntime(runtime);
|
|
327
327
|
|
|
328
328
|
const isV2ExactTokenWithdraw =
|
|
329
329
|
isContractV2 && !isAsync && 'amount' in params && params.amount != null;
|
|
@@ -332,40 +332,66 @@ export const aeraAdapter: EvmProtocolAdapter = {
|
|
|
332
332
|
let shares: bigint | undefined;
|
|
333
333
|
|
|
334
334
|
if (isContractV2 && !isAsync) {
|
|
335
|
+
const quote = params.syncWithdrawQuote;
|
|
335
336
|
if ('entireAmount' in params && params.entireAmount) {
|
|
336
|
-
|
|
337
|
+
const balance = await publicClient.readContract({
|
|
337
338
|
address: vault.vaultAddress,
|
|
338
339
|
abi: multiDepositorVaultAbi,
|
|
339
340
|
functionName: 'balanceOf',
|
|
340
341
|
args: [account],
|
|
341
342
|
});
|
|
343
|
+
if (quote !== undefined) {
|
|
344
|
+
if (quote.kind !== 'redeem' || quote.shares !== balance) {
|
|
345
|
+
throw new InvalidWithdrawParamsError();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
shares = balance;
|
|
342
349
|
} else if ('shares' in params && params.shares != null) {
|
|
350
|
+
if (quote !== undefined && (quote.kind !== 'redeem' || quote.shares !== params.shares)) {
|
|
351
|
+
throw new InvalidWithdrawParamsError();
|
|
352
|
+
}
|
|
343
353
|
shares = params.shares;
|
|
344
354
|
} else if (!('amount' in params && params.amount != null)) {
|
|
345
355
|
throw new InvalidWithdrawParamsError();
|
|
346
356
|
}
|
|
347
357
|
|
|
348
358
|
if (isV2ExactTokenWithdraw) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
requireNonZeroSyncWithdrawBound(params.amount, 'tokensOut');
|
|
360
|
+
if (quote !== undefined) {
|
|
361
|
+
if (quote.kind !== 'withdraw' || quote.tokensOut !== params.amount) {
|
|
362
|
+
throw new InvalidWithdrawParamsError();
|
|
363
|
+
}
|
|
364
|
+
minTokenOut = quote.tokensOut;
|
|
365
|
+
maxUnitsIn = quote.maxUnitsIn;
|
|
366
|
+
} else {
|
|
367
|
+
minTokenOut = params.amount;
|
|
368
|
+
maxUnitsIn = applySlippageUp(
|
|
369
|
+
await provisionerV2.getSyncWithdrawUnitsIn(
|
|
370
|
+
publicClient,
|
|
371
|
+
provisionerAddress,
|
|
372
|
+
runtime.feeCalculator.address,
|
|
373
|
+
runtime.feeCalculator.version,
|
|
374
|
+
vault.vaultAddress,
|
|
375
|
+
asset.address,
|
|
376
|
+
params.amount
|
|
377
|
+
),
|
|
378
|
+
slippageBps
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
requireNonZeroSyncWithdrawBound(maxUnitsIn, 'maxUnitsIn');
|
|
362
382
|
} else {
|
|
363
383
|
if (shares === undefined) {
|
|
364
384
|
throw new InvalidWithdrawParamsError();
|
|
365
385
|
}
|
|
366
386
|
|
|
367
|
-
|
|
368
|
-
|
|
387
|
+
requireNonZeroSyncWithdrawBound(shares, 'unitsIn');
|
|
388
|
+
if (quote !== undefined) {
|
|
389
|
+
if (quote.kind !== 'redeem' || quote.shares !== shares) {
|
|
390
|
+
throw new InvalidWithdrawParamsError();
|
|
391
|
+
}
|
|
392
|
+
minTokenOut = quote.minTokensOut;
|
|
393
|
+
} else {
|
|
394
|
+
const tokensOut = await provisionerV2.getSyncRedeemTokenOut(
|
|
369
395
|
publicClient,
|
|
370
396
|
provisionerAddress,
|
|
371
397
|
runtime.feeCalculator.address,
|
|
@@ -373,10 +399,11 @@ export const aeraAdapter: EvmProtocolAdapter = {
|
|
|
373
399
|
vault.vaultAddress,
|
|
374
400
|
asset.address,
|
|
375
401
|
shares
|
|
376
|
-
)
|
|
377
|
-
slippageBps
|
|
378
|
-
|
|
379
|
-
|
|
402
|
+
);
|
|
403
|
+
minTokenOut = applySlippageDown(tokensOut, slippageBps);
|
|
404
|
+
}
|
|
405
|
+
requireNonZeroSyncWithdrawBound(minTokenOut, 'minTokensOut');
|
|
406
|
+
maxUnitsIn = shares;
|
|
380
407
|
}
|
|
381
408
|
} else {
|
|
382
409
|
if ('entireAmount' in params && params.entireAmount) {
|