@masterpeach/market-sdk 0.2.0-beta.20260930.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/README.md +109 -0
- package/README.zh-CN.md +155 -0
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/abi/index.cjs +14051 -0
- package/dist/abi/index.cjs.map +1 -0
- package/dist/abi/index.d.cts +10882 -0
- package/dist/abi/index.d.ts +10882 -0
- package/dist/abi/index.js +27 -0
- package/dist/abi/index.js.map +1 -0
- package/dist/chunk-TMU4HSZR.js +231 -0
- package/dist/chunk-TMU4HSZR.js.map +1 -0
- package/dist/chunk-TTVBOGKZ.js +14015 -0
- package/dist/chunk-TTVBOGKZ.js.map +1 -0
- package/dist/core.cjs +271 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +99 -0
- package/dist/core.d.ts +99 -0
- package/dist/core.js +35 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +13327 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +840 -0
- package/dist/index.d.ts +840 -0
- package/dist/index.js +2705 -0
- package/dist/index.js.map +1 -0
- package/docs/earnings.md +71 -0
- package/docs/frontend.md +153 -0
- package/docs/latest-contract.md +38 -0
- package/docs/liquidity.md +87 -0
- package/docs/management.md +73 -0
- package/docs/pool-state.md +30 -0
- package/docs/pools.md +118 -0
- package/docs/positions.md +94 -0
- package/docs/variants.md +24 -0
- package/package.json +82 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2705 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPreparedTransactionStore,
|
|
3
|
+
immutableClone,
|
|
4
|
+
resolveLiveWalletAccount,
|
|
5
|
+
transactionResultFromReceipt
|
|
6
|
+
} from "./chunk-TMU4HSZR.js";
|
|
7
|
+
import {
|
|
8
|
+
marketAccessManagerAbi,
|
|
9
|
+
marketClaimFeePolicyAbi,
|
|
10
|
+
marketExtsloadAbi,
|
|
11
|
+
marketMarketCalendarAbi,
|
|
12
|
+
marketPoolRegistryAbi,
|
|
13
|
+
marketRampAbi,
|
|
14
|
+
marketRampReaderAbi,
|
|
15
|
+
marketRwaRampAbi,
|
|
16
|
+
marketStateViewAbi
|
|
17
|
+
} from "./chunk-TTVBOGKZ.js";
|
|
18
|
+
|
|
19
|
+
// src/actions/authorizations.ts
|
|
20
|
+
import { getAddress as getAddress3 } from "viem";
|
|
21
|
+
|
|
22
|
+
// src/errors.ts
|
|
23
|
+
var InvalidMarketConfigError = class extends Error {
|
|
24
|
+
constructor(field, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.field = field;
|
|
27
|
+
this.name = "InvalidMarketConfigError";
|
|
28
|
+
}
|
|
29
|
+
field;
|
|
30
|
+
};
|
|
31
|
+
var MarketDeploymentVerificationError = class extends Error {
|
|
32
|
+
constructor(field, expected, actual) {
|
|
33
|
+
super(`Market deployment mismatch: ${field}.`);
|
|
34
|
+
this.field = field;
|
|
35
|
+
this.expected = expected;
|
|
36
|
+
this.actual = actual;
|
|
37
|
+
this.name = "MarketDeploymentVerificationError";
|
|
38
|
+
}
|
|
39
|
+
field;
|
|
40
|
+
expected;
|
|
41
|
+
actual;
|
|
42
|
+
};
|
|
43
|
+
var MarketReadError = class extends Error {
|
|
44
|
+
constructor(cause) {
|
|
45
|
+
super("Market RPC read failed. Inspect cause for transport or contract revert details.", {
|
|
46
|
+
cause
|
|
47
|
+
});
|
|
48
|
+
this.name = "MarketReadError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var MarketFeatureUnavailableError = class extends Error {
|
|
52
|
+
constructor(feature) {
|
|
53
|
+
super(`Market feature is unavailable for this configuration: ${feature}.`);
|
|
54
|
+
this.feature = feature;
|
|
55
|
+
this.name = "MarketFeatureUnavailableError";
|
|
56
|
+
}
|
|
57
|
+
feature;
|
|
58
|
+
};
|
|
59
|
+
var MarketSettlingError = class extends Error {
|
|
60
|
+
constructor(hook2) {
|
|
61
|
+
super("The Market ledger is settling; a consistent position snapshot is unavailable.");
|
|
62
|
+
this.hook = hook2;
|
|
63
|
+
this.name = "MarketSettlingError";
|
|
64
|
+
}
|
|
65
|
+
hook;
|
|
66
|
+
};
|
|
67
|
+
var MarketTransactionError = class extends Error {
|
|
68
|
+
constructor(stage, cause) {
|
|
69
|
+
super(
|
|
70
|
+
`Market transaction ${stage} failed. Inspect cause for RPC, signature or contract details.`,
|
|
71
|
+
{ cause }
|
|
72
|
+
);
|
|
73
|
+
this.stage = stage;
|
|
74
|
+
this.name = "MarketTransactionError";
|
|
75
|
+
}
|
|
76
|
+
stage;
|
|
77
|
+
};
|
|
78
|
+
var MarketTransactionReplacedError = class extends Error {
|
|
79
|
+
constructor(originalHash, replacementHash, reason) {
|
|
80
|
+
super(`Market transaction was ${reason}; inspect the replacement before continuing.`);
|
|
81
|
+
this.originalHash = originalHash;
|
|
82
|
+
this.replacementHash = replacementHash;
|
|
83
|
+
this.reason = reason;
|
|
84
|
+
this.name = "MarketTransactionReplacedError";
|
|
85
|
+
}
|
|
86
|
+
originalHash;
|
|
87
|
+
replacementHash;
|
|
88
|
+
reason;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/math/identifiers.ts
|
|
92
|
+
import {
|
|
93
|
+
encodeAbiParameters,
|
|
94
|
+
getAddress,
|
|
95
|
+
isAddress,
|
|
96
|
+
isAddressEqual,
|
|
97
|
+
keccak256,
|
|
98
|
+
zeroAddress
|
|
99
|
+
} from "viem";
|
|
100
|
+
function assertMarketUint(value, field, bits = 256) {
|
|
101
|
+
if (typeof value !== "bigint" || value < 0n || value >= 1n << BigInt(bits)) {
|
|
102
|
+
throw new InvalidMarketConfigError(field, `Expected a uint${bits} bigint.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function assertMarketPoolId(value) {
|
|
106
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
107
|
+
throw new InvalidMarketConfigError("poolId", "Expected a 32-byte pool ID.");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function assertMarketAddress(address, field, allowNative = false) {
|
|
111
|
+
if (!isAddress(address) || !allowNative && isAddressEqual(address, zeroAddress)) {
|
|
112
|
+
throw new InvalidMarketConfigError(field, "Expected a valid non-zero address.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function defineMarketPoolKey(key) {
|
|
116
|
+
assertMarketAddress(key.currency0, "currency0", true);
|
|
117
|
+
assertMarketAddress(key.currency1, "currency1");
|
|
118
|
+
assertMarketAddress(key.hooks, "hooks");
|
|
119
|
+
if (BigInt(key.currency0) >= BigInt(key.currency1)) {
|
|
120
|
+
throw new InvalidMarketConfigError(
|
|
121
|
+
"currencies",
|
|
122
|
+
"Currencies must be distinct and sorted by address."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (key.fee !== 8388608) {
|
|
126
|
+
throw new InvalidMarketConfigError(
|
|
127
|
+
"fee",
|
|
128
|
+
"Peach Market pools require the v4 dynamic fee flag."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isInteger(key.tickSpacing) || key.tickSpacing < 1 || key.tickSpacing > 32767) {
|
|
132
|
+
throw new InvalidMarketConfigError("tickSpacing", "Expected v4 tick spacing in [1, 32767].");
|
|
133
|
+
}
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
currency0: getAddress(key.currency0),
|
|
136
|
+
currency1: getAddress(key.currency1),
|
|
137
|
+
fee: key.fee,
|
|
138
|
+
tickSpacing: key.tickSpacing,
|
|
139
|
+
hooks: getAddress(key.hooks)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function getMarketPoolId(input) {
|
|
143
|
+
const key = defineMarketPoolKey(input);
|
|
144
|
+
return keccak256(
|
|
145
|
+
encodeAbiParameters(
|
|
146
|
+
[
|
|
147
|
+
{ type: "address" },
|
|
148
|
+
{ type: "address" },
|
|
149
|
+
{ type: "uint24" },
|
|
150
|
+
{ type: "int24" },
|
|
151
|
+
{ type: "address" }
|
|
152
|
+
],
|
|
153
|
+
[key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks]
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
function getMarketRangeId(poolId, tickLower, tickUpper, tickSpacing = 1) {
|
|
158
|
+
assertMarketPoolId(poolId);
|
|
159
|
+
if (!Number.isInteger(tickSpacing) || tickSpacing < 1 || tickSpacing > 32767) {
|
|
160
|
+
throw new InvalidMarketConfigError("tickSpacing", "Expected v4 tick spacing in [1, 32767].");
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) || tickLower < -887272 || tickUpper > 887272 || tickLower >= tickUpper || tickLower % tickSpacing !== 0 || tickUpper % tickSpacing !== 0) {
|
|
163
|
+
throw new InvalidMarketConfigError(
|
|
164
|
+
"ticks",
|
|
165
|
+
"Expected ordered, aligned ticks in the v4 tick range."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return BigInt(
|
|
169
|
+
keccak256(
|
|
170
|
+
encodeAbiParameters(
|
|
171
|
+
[{ type: "bytes32" }, { type: "int24" }, { type: "int24" }],
|
|
172
|
+
[poolId, tickLower, tickUpper]
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function getMarketSwapDirection(key, base, quote, side) {
|
|
178
|
+
const normalized = defineMarketPoolKey(key);
|
|
179
|
+
assertMarketAddress(base, "base", true);
|
|
180
|
+
assertMarketAddress(quote, "quote", true);
|
|
181
|
+
const baseIs0 = isAddressEqual(base, normalized.currency0) && isAddressEqual(quote, normalized.currency1);
|
|
182
|
+
const baseIs1 = isAddressEqual(base, normalized.currency1) && isAddressEqual(quote, normalized.currency0);
|
|
183
|
+
if (!baseIs0 && !baseIs1 || side !== "buy" && side !== "sell") {
|
|
184
|
+
throw new InvalidMarketConfigError(
|
|
185
|
+
"direction",
|
|
186
|
+
"Base/quote must match both pool currencies and side must be buy/sell."
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return side === "sell" ? baseIs0 : baseIs1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/actions/snapshot.ts
|
|
193
|
+
import { isAddressEqual as isAddressEqual2, keccak256 as keccak2562 } from "viem";
|
|
194
|
+
|
|
195
|
+
// src/deployment.ts
|
|
196
|
+
import { getAddress as getAddress2, isAddress as isAddress2, zeroAddress as zeroAddress2 } from "viem";
|
|
197
|
+
function invalid(field, message) {
|
|
198
|
+
throw new InvalidMarketConfigError(field, message);
|
|
199
|
+
}
|
|
200
|
+
function contract(input, field) {
|
|
201
|
+
if (!isAddress2(input.address) || input.address.toLowerCase() === zeroAddress2) {
|
|
202
|
+
invalid(`${field}.address`, "Expected a non-zero EVM address.");
|
|
203
|
+
}
|
|
204
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(input.runtimeCodeHash) || /^0x0+$/.test(input.runtimeCodeHash)) {
|
|
205
|
+
invalid(`${field}.runtimeCodeHash`, "Expected a non-zero 32-byte runtime code hash.");
|
|
206
|
+
}
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
address: getAddress2(input.address),
|
|
209
|
+
runtimeCodeHash: input.runtimeCodeHash
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
var capabilityKeys = ["liquidity", "nativeLiquidity", "claimOperator", "management"];
|
|
213
|
+
function hook(input, index) {
|
|
214
|
+
const field = `hooks.${index}`;
|
|
215
|
+
if (!["ramp", "ramp-eth", "rwa-ramp"].includes(input.kind)) {
|
|
216
|
+
invalid(`${field}.kind`, "Unsupported Market Hook kind.");
|
|
217
|
+
}
|
|
218
|
+
if (input.implementation !== "ramp-v1") {
|
|
219
|
+
invalid(`${field}.implementation`, "Unsupported Market implementation.");
|
|
220
|
+
}
|
|
221
|
+
if (input.kind === "rwa-ramp" !== (input.calendar !== void 0)) {
|
|
222
|
+
invalid(`${field}.calendar`, "Only RWA Hooks must include a calendar.");
|
|
223
|
+
}
|
|
224
|
+
for (const key of capabilityKeys) {
|
|
225
|
+
if (!["disabled", "unverified", "verified"].includes(input.capabilities[key])) {
|
|
226
|
+
invalid(`${field}.capabilities.${key}`, "Explicit capability acceptance status is required.");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (input.kind !== "ramp-eth" && input.capabilities.nativeLiquidity !== "disabled") {
|
|
230
|
+
invalid(`${field}.capabilities.nativeLiquidity`, "Native liquidity requires RampETH.");
|
|
231
|
+
}
|
|
232
|
+
const capabilities = Object.freeze({
|
|
233
|
+
liquidity: input.capabilities.liquidity,
|
|
234
|
+
nativeLiquidity: input.capabilities.nativeLiquidity,
|
|
235
|
+
claimOperator: input.capabilities.claimOperator,
|
|
236
|
+
management: input.capabilities.management
|
|
237
|
+
});
|
|
238
|
+
return Object.freeze({
|
|
239
|
+
...contract(input, field),
|
|
240
|
+
kind: input.kind,
|
|
241
|
+
implementation: input.implementation,
|
|
242
|
+
claimFeePolicy: contract(input.claimFeePolicy, `${field}.claimFeePolicy`),
|
|
243
|
+
...input.calendar ? { calendar: contract(input.calendar, `${field}.calendar`) } : {},
|
|
244
|
+
capabilities
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function defineMarketDeployment(input) {
|
|
248
|
+
if (!Number.isSafeInteger(input.chainId) || input.chainId <= 0) {
|
|
249
|
+
invalid("chainId", "Expected a positive safe integer.");
|
|
250
|
+
}
|
|
251
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(input.deploymentId)) {
|
|
252
|
+
invalid("deploymentId", "Expected a non-empty lowercase deployment identifier.");
|
|
253
|
+
}
|
|
254
|
+
if (input.hooks.length === 0) invalid("hooks", "At least one supported Hook is required.");
|
|
255
|
+
const hooks = input.hooks.map(hook);
|
|
256
|
+
if (new Set(hooks.map((entry) => entry.address)).size !== hooks.length) {
|
|
257
|
+
invalid("hooks", "Duplicate Hook addresses are not allowed.");
|
|
258
|
+
}
|
|
259
|
+
let poolStateReader;
|
|
260
|
+
if (input.poolStateReader?.kind === "state-view") {
|
|
261
|
+
poolStateReader = Object.freeze({
|
|
262
|
+
kind: "state-view",
|
|
263
|
+
contract: contract(input.poolStateReader.contract, "poolStateReader.contract")
|
|
264
|
+
});
|
|
265
|
+
} else if (input.poolStateReader?.kind === "extsload") {
|
|
266
|
+
if (input.poolStateReader.layout !== "uniswap-v4-v1") {
|
|
267
|
+
invalid("poolStateReader.layout", "Unsupported PoolManager storage layout.");
|
|
268
|
+
}
|
|
269
|
+
poolStateReader = Object.freeze({ kind: "extsload", layout: "uniswap-v4-v1" });
|
|
270
|
+
} else if (input.poolStateReader !== void 0) {
|
|
271
|
+
invalid("poolStateReader.kind", "Unsupported pool state reader.");
|
|
272
|
+
}
|
|
273
|
+
return Object.freeze({
|
|
274
|
+
chainId: input.chainId,
|
|
275
|
+
deploymentId: input.deploymentId,
|
|
276
|
+
poolManager: contract(input.poolManager, "poolManager"),
|
|
277
|
+
registry: contract(input.registry, "registry"),
|
|
278
|
+
authority: contract(input.authority, "authority"),
|
|
279
|
+
...input.reader ? { reader: contract(input.reader, "reader") } : {},
|
|
280
|
+
...poolStateReader ? { poolStateReader } : {},
|
|
281
|
+
hooks: Object.freeze(hooks)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/actions/snapshot.ts
|
|
286
|
+
async function readMarketSnapshot(context, options, read) {
|
|
287
|
+
const deployment = defineMarketDeployment(context.deployment);
|
|
288
|
+
const publicClient = context.publicClient;
|
|
289
|
+
const { blockNumber, blockHash } = options;
|
|
290
|
+
if (blockNumber !== void 0) assertMarketUint(blockNumber, "blockNumber");
|
|
291
|
+
if (blockHash !== void 0 && (blockNumber === void 0 || !/^0x[0-9a-fA-F]{64}$/.test(blockHash))) {
|
|
292
|
+
throw new InvalidMarketConfigError(
|
|
293
|
+
"blockHash",
|
|
294
|
+
"A block hash requires a valid explicit block number."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const chainId = await publicClient.getChainId();
|
|
299
|
+
if (chainId !== deployment.chainId)
|
|
300
|
+
throw new MarketDeploymentVerificationError("chainId", deployment.chainId, chainId);
|
|
301
|
+
const block = await publicClient.getBlock(
|
|
302
|
+
blockNumber === void 0 ? { blockTag: "latest" } : { blockNumber }
|
|
303
|
+
);
|
|
304
|
+
if (block.number === null || block.hash === null)
|
|
305
|
+
throw new MarketDeploymentVerificationError("block", "mined block", block);
|
|
306
|
+
if (blockNumber !== void 0 && block.number !== blockNumber)
|
|
307
|
+
throw new MarketDeploymentVerificationError("blockNumber", blockNumber, block.number);
|
|
308
|
+
if (blockHash !== void 0 && block.hash.toLowerCase() !== blockHash.toLowerCase())
|
|
309
|
+
throw new MarketDeploymentVerificationError("blockHash", blockHash, block.hash);
|
|
310
|
+
const snapshot = Object.freeze({
|
|
311
|
+
chainId,
|
|
312
|
+
deploymentId: deployment.deploymentId,
|
|
313
|
+
blockNumber: block.number,
|
|
314
|
+
blockHash: block.hash,
|
|
315
|
+
timestamp: block.timestamp
|
|
316
|
+
});
|
|
317
|
+
const data = await read({ publicClient, deployment, block: snapshot });
|
|
318
|
+
const finalBlock = await publicClient.getBlock({ blockNumber: block.number });
|
|
319
|
+
if (finalBlock.hash !== block.hash)
|
|
320
|
+
throw new MarketDeploymentVerificationError("blockHash", block.hash, finalBlock.hash);
|
|
321
|
+
return Object.freeze({ block: snapshot, data });
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof InvalidMarketConfigError || error instanceof MarketDeploymentVerificationError || error instanceof MarketFeatureUnavailableError || error instanceof MarketSettlingError || error instanceof MarketReadError || error instanceof MarketTransactionError)
|
|
324
|
+
throw error;
|
|
325
|
+
throw new MarketReadError(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function requireMarketHook(scope, address) {
|
|
329
|
+
assertMarketAddress(address, "hook");
|
|
330
|
+
const hook2 = scope.deployment.hooks.find((entry) => isAddressEqual2(entry.address, address));
|
|
331
|
+
if (!hook2) throw new MarketFeatureUnavailableError(`unsupported Hook ${address}`);
|
|
332
|
+
return hook2;
|
|
333
|
+
}
|
|
334
|
+
async function assertMarketCode(scope, contract2) {
|
|
335
|
+
const code = await scope.publicClient.getCode({
|
|
336
|
+
address: contract2.address,
|
|
337
|
+
blockNumber: scope.block.blockNumber
|
|
338
|
+
});
|
|
339
|
+
const actual = code && code !== "0x" ? keccak2562(code) : null;
|
|
340
|
+
if (actual !== contract2.runtimeCodeHash.toLowerCase())
|
|
341
|
+
throw new MarketDeploymentVerificationError(contract2.address, contract2.runtimeCodeHash, actual);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/actions/authorizations.ts
|
|
345
|
+
function getMarketShareAuthorization(context, params) {
|
|
346
|
+
assertMarketAddress(params.owner, "owner");
|
|
347
|
+
assertMarketAddress(params.operator, "operator");
|
|
348
|
+
assertMarketUint(params.rangeId, "rangeId");
|
|
349
|
+
const owner = getAddress3(params.owner);
|
|
350
|
+
const operator = getAddress3(params.operator);
|
|
351
|
+
const rangeId = params.rangeId;
|
|
352
|
+
const hook2 = requireMarketHook(context, params.hook).address;
|
|
353
|
+
return readMarketSnapshot(context, params, async (scope) => {
|
|
354
|
+
const target = {
|
|
355
|
+
address: hook2,
|
|
356
|
+
abi: marketRampAbi,
|
|
357
|
+
blockNumber: scope.block.blockNumber
|
|
358
|
+
};
|
|
359
|
+
if (await scope.publicClient.readContract({ ...target, functionName: "isSettling" }))
|
|
360
|
+
throw new MarketSettlingError(hook2);
|
|
361
|
+
const [shares, allowance, isShareOperator, isClaimOperator] = await Promise.all([
|
|
362
|
+
scope.publicClient.readContract({
|
|
363
|
+
...target,
|
|
364
|
+
functionName: "balanceOf",
|
|
365
|
+
args: [owner, rangeId]
|
|
366
|
+
}),
|
|
367
|
+
scope.publicClient.readContract({
|
|
368
|
+
...target,
|
|
369
|
+
functionName: "allowance",
|
|
370
|
+
args: [owner, operator, rangeId]
|
|
371
|
+
}),
|
|
372
|
+
scope.publicClient.readContract({
|
|
373
|
+
...target,
|
|
374
|
+
functionName: "isOperator",
|
|
375
|
+
args: [owner, operator]
|
|
376
|
+
}),
|
|
377
|
+
scope.publicClient.readContract({
|
|
378
|
+
...target,
|
|
379
|
+
functionName: "isClaimOperator",
|
|
380
|
+
args: [owner, rangeId, operator]
|
|
381
|
+
})
|
|
382
|
+
]);
|
|
383
|
+
return { hook: hook2, rangeId, owner, operator, shares, allowance, isShareOperator, isClaimOperator };
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function getMarketClaimOperator(context, params) {
|
|
387
|
+
const { owner, operator, rangeId } = params;
|
|
388
|
+
assertMarketAddress(owner, "owner");
|
|
389
|
+
assertMarketAddress(operator, "operator");
|
|
390
|
+
assertMarketUint(rangeId, "rangeId");
|
|
391
|
+
const hook2 = requireMarketHook(context, params.hook).address;
|
|
392
|
+
return readMarketSnapshot(context, params, async (scope) => ({
|
|
393
|
+
hook: hook2,
|
|
394
|
+
owner,
|
|
395
|
+
operator,
|
|
396
|
+
rangeId,
|
|
397
|
+
approved: await scope.publicClient.readContract({
|
|
398
|
+
address: hook2,
|
|
399
|
+
abi: marketRampAbi,
|
|
400
|
+
functionName: "isClaimOperator",
|
|
401
|
+
args: [owner, rangeId, operator],
|
|
402
|
+
blockNumber: scope.block.blockNumber
|
|
403
|
+
})
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/actions/earnings.ts
|
|
408
|
+
import { encodeAbiParameters as encodeAbiParameters3, encodePacked, isAddressEqual as isAddressEqual4, keccak256 as keccak2564, toHex as toHex2 } from "viem";
|
|
409
|
+
|
|
410
|
+
// src/math/earnings.ts
|
|
411
|
+
var Q128 = 1n << 128n;
|
|
412
|
+
function calculateMarketClaimableFees(ledger) {
|
|
413
|
+
const { position } = ledger;
|
|
414
|
+
const { state, effectiveClaimFeeBps } = position.range;
|
|
415
|
+
assertMarketUint(state.totalShares, "totalShares", 128);
|
|
416
|
+
assertMarketUint(position.shares, "shares", 128);
|
|
417
|
+
assertMarketUint(ledger.managerLiquidity, "managerLiquidity", 128);
|
|
418
|
+
if (position.shares > state.totalShares || state.totalShares !== ledger.managerLiquidity)
|
|
419
|
+
throw new InvalidMarketConfigError(
|
|
420
|
+
"liquidity",
|
|
421
|
+
"Hook shares and manager liquidity are inconsistent."
|
|
422
|
+
);
|
|
423
|
+
if (!Number.isInteger(effectiveClaimFeeBps) || effectiveClaimFeeBps < 0 || effectiveClaimFeeBps > 2e3)
|
|
424
|
+
throw new InvalidMarketConfigError("effectiveClaimFeeBps", "Invalid effective fee rate.");
|
|
425
|
+
function token(owed, acc, checkpoint, remainder, live, last) {
|
|
426
|
+
for (const [name, value] of [
|
|
427
|
+
["acc", acc],
|
|
428
|
+
["checkpoint", checkpoint],
|
|
429
|
+
["live", live],
|
|
430
|
+
["last", last]
|
|
431
|
+
])
|
|
432
|
+
assertMarketUint(value, name);
|
|
433
|
+
assertMarketUint(owed, "owed", 128);
|
|
434
|
+
assertMarketUint(remainder, "remainder", 128);
|
|
435
|
+
if (checkpoint > acc)
|
|
436
|
+
throw new InvalidMarketConfigError(
|
|
437
|
+
"checkpoint",
|
|
438
|
+
"Net checkpoint exceeds the checked Hook accumulator."
|
|
439
|
+
);
|
|
440
|
+
const gross = state.totalShares === 0n ? 0n : BigInt.asUintN(256, live - last) * state.totalShares / Q128;
|
|
441
|
+
assertMarketUint(gross, "pendingRangeGross", 127);
|
|
442
|
+
const cut = gross * BigInt(effectiveClaimFeeBps) / 10000n;
|
|
443
|
+
const nextAcc = acc + (state.totalShares === 0n ? 0n : (gross - cut) * Q128 / state.totalShares);
|
|
444
|
+
assertMarketUint(nextAcc, "nextAccumulator");
|
|
445
|
+
const syncedNumerator = position.shares * (acc - checkpoint) + remainder;
|
|
446
|
+
const numerator = position.shares * (nextAcc - checkpoint) + remainder;
|
|
447
|
+
const synchronized = syncedNumerator / Q128;
|
|
448
|
+
const earned = numerator / Q128;
|
|
449
|
+
assertMarketUint(earned, "earned", 128);
|
|
450
|
+
assertMarketUint(owed + earned, "claimable", 128);
|
|
451
|
+
return {
|
|
452
|
+
settled: owed,
|
|
453
|
+
synchronized,
|
|
454
|
+
pending: earned - synchronized,
|
|
455
|
+
claimable: owed + earned,
|
|
456
|
+
remainderX128: numerator % Q128,
|
|
457
|
+
pendingRangeGross: gross,
|
|
458
|
+
pendingRangeProtocolFee: cut
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const token0 = token(
|
|
462
|
+
position.settledOwed0,
|
|
463
|
+
state.accFee0X128,
|
|
464
|
+
position.checkpoint0X128,
|
|
465
|
+
position.remainder0X128,
|
|
466
|
+
ledger.growth0X128,
|
|
467
|
+
ledger.lastGrowth0X128
|
|
468
|
+
);
|
|
469
|
+
const token1 = token(
|
|
470
|
+
position.settledOwed1,
|
|
471
|
+
state.accFee1X128,
|
|
472
|
+
position.checkpoint1X128,
|
|
473
|
+
position.remainder1X128,
|
|
474
|
+
ledger.growth1X128,
|
|
475
|
+
ledger.lastGrowth1X128
|
|
476
|
+
);
|
|
477
|
+
return {
|
|
478
|
+
ledger,
|
|
479
|
+
token0,
|
|
480
|
+
token1,
|
|
481
|
+
paused: position.range.globalPaused || position.range.poolPaused,
|
|
482
|
+
hasClaimableAmount: token0.claimable > 0n || token1.claimable > 0n
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/math/pool-state.ts
|
|
487
|
+
import { encodeAbiParameters as encodeAbiParameters2, keccak256 as keccak2563, toHex } from "viem";
|
|
488
|
+
function marketPoolStorageSlots(poolId) {
|
|
489
|
+
assertMarketPoolId(poolId);
|
|
490
|
+
const slot = keccak2563(
|
|
491
|
+
encodeAbiParameters2([{ type: "bytes32" }, { type: "uint256" }], [poolId, 6n])
|
|
492
|
+
);
|
|
493
|
+
return [slot, toHex((BigInt(slot) + 3n) % (1n << 256n), { size: 32 })];
|
|
494
|
+
}
|
|
495
|
+
function decodeMarketPoolStateWords(words) {
|
|
496
|
+
const [slot0, liquidity] = words;
|
|
497
|
+
if (words.length !== 2 || slot0 === void 0 || liquidity === void 0 || !/^0x[0-9a-fA-F]{64}$/.test(slot0) || !/^0x[0-9a-fA-F]{64}$/.test(liquidity)) {
|
|
498
|
+
throw new InvalidMarketConfigError("extsload", "Expected exactly two 32-byte storage words.");
|
|
499
|
+
}
|
|
500
|
+
const raw = BigInt(slot0);
|
|
501
|
+
return {
|
|
502
|
+
sqrtPriceX96: raw & (1n << 160n) - 1n,
|
|
503
|
+
tick: Number(BigInt.asIntN(24, raw >> 160n)),
|
|
504
|
+
protocolFeePacked: Number(raw >> 184n & 0xffffffn),
|
|
505
|
+
storedLpFeePips: Number(raw >> 208n & 0xffffffn),
|
|
506
|
+
activeLiquidity: BigInt(liquidity) & (1n << 128n) - 1n
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/actions/positions.ts
|
|
511
|
+
import { getAddress as getAddress4, isAddressEqual as isAddressEqual3 } from "viem";
|
|
512
|
+
async function readRange(scope, params) {
|
|
513
|
+
const hook2 = requireMarketHook(scope, params.hook);
|
|
514
|
+
const target = {
|
|
515
|
+
address: hook2.address,
|
|
516
|
+
abi: marketRampAbi,
|
|
517
|
+
blockNumber: scope.block.blockNumber
|
|
518
|
+
};
|
|
519
|
+
if (await scope.publicClient.readContract({ ...target, functionName: "isSettling" }))
|
|
520
|
+
throw new MarketSettlingError(hook2.address);
|
|
521
|
+
const [rawKey, tickLower, tickUpper, set] = await scope.publicClient.readContract({
|
|
522
|
+
...target,
|
|
523
|
+
functionName: "rangeKey",
|
|
524
|
+
args: [params.rangeId]
|
|
525
|
+
});
|
|
526
|
+
if (!set) return { status: "unset", hook: hook2.address, rangeId: params.rangeId };
|
|
527
|
+
const key = defineMarketPoolKey(rawKey);
|
|
528
|
+
if (!isAddressEqual3(key.hooks, hook2.address))
|
|
529
|
+
throw new MarketDeploymentVerificationError("rangeKey.hooks", hook2.address, key.hooks);
|
|
530
|
+
const poolId = getMarketPoolId(key);
|
|
531
|
+
const expectedId = getMarketRangeId(poolId, tickLower, tickUpper, key.tickSpacing);
|
|
532
|
+
if (expectedId !== params.rangeId)
|
|
533
|
+
throw new MarketDeploymentVerificationError("rangeId", params.rangeId, expectedId);
|
|
534
|
+
const [state, globalPaused, poolPaused, effectiveClaimFeeBps] = await Promise.all([
|
|
535
|
+
scope.publicClient.readContract({
|
|
536
|
+
...target,
|
|
537
|
+
functionName: "rangeState",
|
|
538
|
+
args: [params.rangeId]
|
|
539
|
+
}),
|
|
540
|
+
scope.publicClient.readContract({ ...target, functionName: "paused" }),
|
|
541
|
+
scope.publicClient.readContract({ ...target, functionName: "pausedFor", args: [poolId] }),
|
|
542
|
+
scope.publicClient.readContract({
|
|
543
|
+
...target,
|
|
544
|
+
functionName: "effectiveClaimFee",
|
|
545
|
+
args: [params.rangeId]
|
|
546
|
+
})
|
|
547
|
+
]);
|
|
548
|
+
return {
|
|
549
|
+
status: "set",
|
|
550
|
+
hook: hook2.address,
|
|
551
|
+
rangeId: params.rangeId,
|
|
552
|
+
key,
|
|
553
|
+
poolId,
|
|
554
|
+
tickLower,
|
|
555
|
+
tickUpper,
|
|
556
|
+
state,
|
|
557
|
+
globalPaused,
|
|
558
|
+
poolPaused,
|
|
559
|
+
effectiveClaimFeeBps
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function getMarketRange(context, params) {
|
|
563
|
+
const { hook: hook2, rangeId } = params;
|
|
564
|
+
assertMarketAddress(hook2, "hook");
|
|
565
|
+
assertMarketUint(rangeId, "rangeId");
|
|
566
|
+
return readMarketSnapshot(context, params, (scope) => readRange(scope, { hook: hook2, rangeId }));
|
|
567
|
+
}
|
|
568
|
+
function getMarketPosition(context, params) {
|
|
569
|
+
const { hook: hook2, rangeId } = params;
|
|
570
|
+
assertMarketAddress(hook2, "hook");
|
|
571
|
+
assertMarketUint(rangeId, "rangeId");
|
|
572
|
+
assertMarketAddress(params.owner, "owner");
|
|
573
|
+
const owner = getAddress4(params.owner);
|
|
574
|
+
return readMarketSnapshot(context, params, async (scope) => {
|
|
575
|
+
const range = await readRange(scope, { hook: hook2, rangeId });
|
|
576
|
+
const target = {
|
|
577
|
+
address: range.hook,
|
|
578
|
+
abi: marketRampAbi,
|
|
579
|
+
blockNumber: scope.block.blockNumber
|
|
580
|
+
};
|
|
581
|
+
const [shares, position, remainder] = await Promise.all([
|
|
582
|
+
scope.publicClient.readContract({
|
|
583
|
+
...target,
|
|
584
|
+
functionName: "balanceOf",
|
|
585
|
+
args: [owner, rangeId]
|
|
586
|
+
}),
|
|
587
|
+
scope.publicClient.readContract({
|
|
588
|
+
...target,
|
|
589
|
+
functionName: "userPosition",
|
|
590
|
+
args: [rangeId, owner]
|
|
591
|
+
}),
|
|
592
|
+
scope.publicClient.readContract({
|
|
593
|
+
...target,
|
|
594
|
+
functionName: "userFeeRemainder",
|
|
595
|
+
args: [rangeId, owner]
|
|
596
|
+
})
|
|
597
|
+
]);
|
|
598
|
+
return {
|
|
599
|
+
range,
|
|
600
|
+
owner,
|
|
601
|
+
shares,
|
|
602
|
+
settledOwed0: position.owed0,
|
|
603
|
+
settledOwed1: position.owed1,
|
|
604
|
+
checkpoint0X128: position.checkpoint0X128,
|
|
605
|
+
checkpoint1X128: position.checkpoint1X128,
|
|
606
|
+
remainder0X128: remainder[0],
|
|
607
|
+
remainder1X128: remainder[1]
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/actions/earnings.ts
|
|
613
|
+
function getMarketFeeSnapshot(context, input) {
|
|
614
|
+
const params = immutableClone(input);
|
|
615
|
+
return readMarketSnapshot(context, params, async (scope) => {
|
|
616
|
+
const position = (await getMarketPosition(context, { ...params, ...scope.block })).data;
|
|
617
|
+
const range = position.range;
|
|
618
|
+
if (range.status !== "set")
|
|
619
|
+
throw new MarketFeatureUnavailableError("earnings for an unset range");
|
|
620
|
+
const hook2 = requireMarketHook(scope, range.hook);
|
|
621
|
+
const reader = scope.deployment.poolStateReader;
|
|
622
|
+
if (!reader)
|
|
623
|
+
throw new MarketFeatureUnavailableError("poolStateReader for gross position baseline");
|
|
624
|
+
for (const contract2 of [hook2, hook2.claimFeePolicy, scope.deployment.poolManager])
|
|
625
|
+
await assertMarketCode(scope, contract2);
|
|
626
|
+
const target = {
|
|
627
|
+
address: hook2.address,
|
|
628
|
+
abi: marketRampAbi,
|
|
629
|
+
blockNumber: scope.block.blockNumber
|
|
630
|
+
};
|
|
631
|
+
const policy = {
|
|
632
|
+
address: hook2.claimFeePolicy.address,
|
|
633
|
+
abi: marketClaimFeePolicyAbi,
|
|
634
|
+
blockNumber: scope.block.blockNumber
|
|
635
|
+
};
|
|
636
|
+
const [
|
|
637
|
+
manager,
|
|
638
|
+
policyAddress,
|
|
639
|
+
ledgerAddress,
|
|
640
|
+
growth,
|
|
641
|
+
claimFeeSyncIndex,
|
|
642
|
+
policyHistoryLength,
|
|
643
|
+
currentClaimFeeBps,
|
|
644
|
+
allTimeMinClaimFeeBps
|
|
645
|
+
] = await Promise.all([
|
|
646
|
+
scope.publicClient.readContract({ ...target, functionName: "poolManager" }),
|
|
647
|
+
scope.publicClient.readContract({ ...target, functionName: "claimFeePolicy" }),
|
|
648
|
+
scope.publicClient.readContract({ ...policy, functionName: "ledger" }),
|
|
649
|
+
scope.publicClient.readContract({
|
|
650
|
+
...target,
|
|
651
|
+
functionName: "rangeGrowth",
|
|
652
|
+
args: [range.rangeId]
|
|
653
|
+
}),
|
|
654
|
+
scope.publicClient.readContract({
|
|
655
|
+
...target,
|
|
656
|
+
functionName: "claimFeeSyncIndex",
|
|
657
|
+
args: [range.rangeId]
|
|
658
|
+
}),
|
|
659
|
+
scope.publicClient.readContract({ ...policy, functionName: "length", args: [range.poolId] }),
|
|
660
|
+
scope.publicClient.readContract({ ...policy, functionName: "current", args: [range.poolId] }),
|
|
661
|
+
scope.publicClient.readContract({ ...policy, functionName: "minimum", args: [range.poolId] })
|
|
662
|
+
]);
|
|
663
|
+
for (const [name, expected, actual] of [
|
|
664
|
+
["manager", scope.deployment.poolManager.address, manager],
|
|
665
|
+
["policy", hook2.claimFeePolicy.address, policyAddress],
|
|
666
|
+
["ledger", hook2.address, ledgerAddress]
|
|
667
|
+
]) {
|
|
668
|
+
if (!isAddressEqual4(expected, actual))
|
|
669
|
+
throw new MarketDeploymentVerificationError(name, expected, actual);
|
|
670
|
+
}
|
|
671
|
+
const positionId = keccak2564(
|
|
672
|
+
encodePacked(
|
|
673
|
+
["address", "int24", "int24", "bytes32"],
|
|
674
|
+
[hook2.address, range.tickLower, range.tickUpper, toHex2(range.rangeId, { size: 32 })]
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
let managerState;
|
|
678
|
+
if (reader.kind === "state-view") {
|
|
679
|
+
await assertMarketCode(scope, reader.contract);
|
|
680
|
+
const view = {
|
|
681
|
+
address: reader.contract.address,
|
|
682
|
+
abi: marketStateViewAbi,
|
|
683
|
+
blockNumber: scope.block.blockNumber
|
|
684
|
+
};
|
|
685
|
+
const bound = await scope.publicClient.readContract({ ...view, functionName: "poolManager" });
|
|
686
|
+
if (!isAddressEqual4(bound, manager))
|
|
687
|
+
throw new MarketDeploymentVerificationError("stateView.poolManager", manager, bound);
|
|
688
|
+
managerState = await scope.publicClient.readContract({
|
|
689
|
+
...view,
|
|
690
|
+
functionName: "getPositionInfo",
|
|
691
|
+
args: [range.poolId, positionId]
|
|
692
|
+
});
|
|
693
|
+
} else {
|
|
694
|
+
const poolSlot = marketPoolStorageSlots(range.poolId)[0];
|
|
695
|
+
const base = BigInt(
|
|
696
|
+
keccak2564(
|
|
697
|
+
encodeAbiParameters3(
|
|
698
|
+
[{ type: "bytes32" }, { type: "uint256" }],
|
|
699
|
+
[positionId, BigInt.asUintN(256, BigInt(poolSlot) + 6n)]
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
);
|
|
703
|
+
const slots = [0n, 1n, 2n].map(
|
|
704
|
+
(offset) => toHex2(BigInt.asUintN(256, base + offset), { size: 32 })
|
|
705
|
+
);
|
|
706
|
+
const words = await scope.publicClient.readContract({
|
|
707
|
+
address: manager,
|
|
708
|
+
abi: marketExtsloadAbi,
|
|
709
|
+
functionName: "extsload",
|
|
710
|
+
args: [slots],
|
|
711
|
+
blockNumber: scope.block.blockNumber
|
|
712
|
+
});
|
|
713
|
+
const [liquidity, last0, last1] = words;
|
|
714
|
+
if (words.length !== 3 || !liquidity || !last0 || !last1 || words.some((word) => !/^0x[0-9a-fA-F]{64}$/.test(word)))
|
|
715
|
+
throw new MarketDeploymentVerificationError(
|
|
716
|
+
"position storage",
|
|
717
|
+
"three 32-byte words",
|
|
718
|
+
words
|
|
719
|
+
);
|
|
720
|
+
managerState = [BigInt(liquidity), BigInt(last0), BigInt(last1)];
|
|
721
|
+
}
|
|
722
|
+
if (managerState[0] !== range.state.totalShares)
|
|
723
|
+
throw new MarketDeploymentVerificationError(
|
|
724
|
+
"managerLiquidity",
|
|
725
|
+
range.state.totalShares,
|
|
726
|
+
managerState[0]
|
|
727
|
+
);
|
|
728
|
+
return immutableClone({
|
|
729
|
+
position: { ...position, range },
|
|
730
|
+
managerLiquidity: managerState[0],
|
|
731
|
+
lastGrowth0X128: managerState[1],
|
|
732
|
+
lastGrowth1X128: managerState[2],
|
|
733
|
+
growth0X128: growth[0],
|
|
734
|
+
growth1X128: growth[1],
|
|
735
|
+
claimFeeSyncIndex,
|
|
736
|
+
policyHistoryLength,
|
|
737
|
+
currentClaimFeeBps,
|
|
738
|
+
allTimeMinClaimFeeBps
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
async function estimateMarketClaimableFees(context, params) {
|
|
743
|
+
const snapshot = await getMarketFeeSnapshot(context, params);
|
|
744
|
+
return immutableClone({
|
|
745
|
+
block: snapshot.block,
|
|
746
|
+
data: calculateMarketClaimableFees(snapshot.data)
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/actions/pools.ts
|
|
751
|
+
import { isAddressEqual as isAddressEqual5, zeroAddress as zeroAddress3 } from "viem";
|
|
752
|
+
function normalizePool(scope, raw) {
|
|
753
|
+
const key = defineMarketPoolKey(raw.key);
|
|
754
|
+
const expected = getMarketPoolId(key);
|
|
755
|
+
if (expected.toLowerCase() !== raw.id.toLowerCase())
|
|
756
|
+
throw new MarketDeploymentVerificationError("poolId", expected, raw.id);
|
|
757
|
+
const hookKind = scope.deployment.hooks.find((entry) => isAddressEqual5(entry.address, key.hooks))?.kind ?? null;
|
|
758
|
+
return { key, poolId: raw.id, active: raw.active, hookKind };
|
|
759
|
+
}
|
|
760
|
+
async function readMarketPoolAt(scope, poolId) {
|
|
761
|
+
const raw = await scope.publicClient.readContract({
|
|
762
|
+
address: scope.deployment.registry.address,
|
|
763
|
+
abi: marketPoolRegistryAbi,
|
|
764
|
+
functionName: "poolById",
|
|
765
|
+
args: [poolId],
|
|
766
|
+
blockNumber: scope.block.blockNumber
|
|
767
|
+
});
|
|
768
|
+
const pool = normalizePool(scope, raw);
|
|
769
|
+
if (pool.poolId.toLowerCase() !== poolId.toLowerCase())
|
|
770
|
+
throw new MarketDeploymentVerificationError("registry.poolId", poolId, pool.poolId);
|
|
771
|
+
return pool;
|
|
772
|
+
}
|
|
773
|
+
function requireMarketPoolHook(scope, pool) {
|
|
774
|
+
const hook2 = requireMarketHook(scope, pool.key.hooks);
|
|
775
|
+
if (isAddressEqual5(pool.key.currency0, zeroAddress3) && hook2.kind !== "ramp-eth") {
|
|
776
|
+
throw new InvalidMarketConfigError("currency0", "Native currency requires RampETH.");
|
|
777
|
+
}
|
|
778
|
+
return hook2;
|
|
779
|
+
}
|
|
780
|
+
function getMarketPool(context, poolId, options = {}) {
|
|
781
|
+
assertMarketPoolId(poolId);
|
|
782
|
+
return readMarketSnapshot(context, options, (scope) => readMarketPoolAt(scope, poolId));
|
|
783
|
+
}
|
|
784
|
+
function listMarketPools(context, params = {}) {
|
|
785
|
+
const { offset = 0n, limit = 20, activeOnly = false } = params;
|
|
786
|
+
assertMarketUint(offset, "offset");
|
|
787
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
788
|
+
throw new InvalidMarketConfigError("limit", "Page size must be in [1, 100].");
|
|
789
|
+
return readMarketSnapshot(context, params, async (scope) => {
|
|
790
|
+
const target = {
|
|
791
|
+
address: scope.deployment.registry.address,
|
|
792
|
+
abi: marketPoolRegistryAbi,
|
|
793
|
+
blockNumber: scope.block.blockNumber
|
|
794
|
+
};
|
|
795
|
+
const totalCount = await scope.publicClient.readContract({
|
|
796
|
+
...target,
|
|
797
|
+
functionName: "poolCount"
|
|
798
|
+
});
|
|
799
|
+
const end = offset + BigInt(limit) < totalCount ? offset + BigInt(limit) : totalCount;
|
|
800
|
+
const pools = [];
|
|
801
|
+
for (let start = offset; start < end; start += 8n) {
|
|
802
|
+
const count = Number(end - start < 8n ? end - start : 8n);
|
|
803
|
+
const batch = await Promise.all(
|
|
804
|
+
Array.from(
|
|
805
|
+
{ length: count },
|
|
806
|
+
(_, index) => scope.publicClient.readContract({
|
|
807
|
+
...target,
|
|
808
|
+
functionName: "poolAt",
|
|
809
|
+
args: [start + BigInt(index)]
|
|
810
|
+
})
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
for (const entry of batch) {
|
|
814
|
+
const pool = normalizePool(scope, entry);
|
|
815
|
+
if (!activeOnly || pool.active) pools.push(pool);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return { pools, totalCount, nextOffset: end < totalCount ? end : null };
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/actions/fees.ts
|
|
823
|
+
function sessionName(value) {
|
|
824
|
+
if (value === 0) return "open";
|
|
825
|
+
if (value === 1) return "overnight";
|
|
826
|
+
if (value === 2) return "closed";
|
|
827
|
+
throw new MarketDeploymentVerificationError("session", "0, 1 or 2", value);
|
|
828
|
+
}
|
|
829
|
+
function getMarketCurrentFees(context, poolId, options = {}) {
|
|
830
|
+
assertMarketPoolId(poolId);
|
|
831
|
+
return readMarketSnapshot(context, options, async (scope) => {
|
|
832
|
+
const pool = await readMarketPoolAt(scope, poolId);
|
|
833
|
+
const hook2 = requireMarketPoolHook(scope, pool);
|
|
834
|
+
const target = {
|
|
835
|
+
address: hook2.address,
|
|
836
|
+
abi: marketRampAbi,
|
|
837
|
+
blockNumber: scope.block.blockNumber
|
|
838
|
+
};
|
|
839
|
+
const [fee0For1Pips, fee1For0Pips, globalPaused, poolPaused] = await Promise.all([
|
|
840
|
+
scope.publicClient.readContract({
|
|
841
|
+
...target,
|
|
842
|
+
functionName: "currentFee",
|
|
843
|
+
args: [poolId, true]
|
|
844
|
+
}),
|
|
845
|
+
scope.publicClient.readContract({
|
|
846
|
+
...target,
|
|
847
|
+
functionName: "currentFee",
|
|
848
|
+
args: [poolId, false]
|
|
849
|
+
}),
|
|
850
|
+
scope.publicClient.readContract({ ...target, functionName: "paused" }),
|
|
851
|
+
scope.publicClient.readContract({ ...target, functionName: "pausedFor", args: [poolId] })
|
|
852
|
+
]);
|
|
853
|
+
return { pool, fee0For1Pips, fee1For0Pips, globalPaused, poolPaused };
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async function readFeeBreakdown(scope, poolId, zeroForOne, timestamp, mode) {
|
|
857
|
+
const reader = scope.deployment.reader;
|
|
858
|
+
if (!reader) throw new MarketFeatureUnavailableError("feeBreakdown requires a Reader");
|
|
859
|
+
const pool = await readMarketPoolAt(scope, poolId);
|
|
860
|
+
const hook2 = requireMarketPoolHook(scope, pool);
|
|
861
|
+
const result = await scope.publicClient.readContract({
|
|
862
|
+
address: reader.address,
|
|
863
|
+
abi: marketRampReaderAbi,
|
|
864
|
+
functionName: "feeBreakdown",
|
|
865
|
+
args: [poolId, zeroForOne, timestamp],
|
|
866
|
+
blockNumber: scope.block.blockNumber
|
|
867
|
+
});
|
|
868
|
+
if (result.hasSession !== (hook2.kind === "rwa-ramp"))
|
|
869
|
+
throw new MarketDeploymentVerificationError(
|
|
870
|
+
"reader.hasSession",
|
|
871
|
+
hook2.kind === "rwa-ramp",
|
|
872
|
+
result.hasSession
|
|
873
|
+
);
|
|
874
|
+
return {
|
|
875
|
+
pool,
|
|
876
|
+
zeroForOne,
|
|
877
|
+
timestamp,
|
|
878
|
+
mode,
|
|
879
|
+
session: result.hasSession ? sessionName(result.session) : null,
|
|
880
|
+
baseFeePips: result.baseFee,
|
|
881
|
+
directionalPremiumPips: result.directionalPremium,
|
|
882
|
+
cappedAutonomousFeePips: result.cappedAutonomousFee,
|
|
883
|
+
overrideActive: result.overrideActive,
|
|
884
|
+
overrideFeePips: result.overrideFee,
|
|
885
|
+
overrideExpiry: BigInt(result.overrideExpiry),
|
|
886
|
+
overrideMinFeePips: result.overrideMinFee,
|
|
887
|
+
maxFeePips: result.maxFee,
|
|
888
|
+
finalFeePips: result.finalFee
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function assertDirection(value) {
|
|
892
|
+
if (typeof value !== "boolean")
|
|
893
|
+
throw new InvalidMarketConfigError("zeroForOne", "Expected an explicit direction boolean.");
|
|
894
|
+
}
|
|
895
|
+
function getMarketFeeBreakdown(context, poolId, zeroForOne, options = {}) {
|
|
896
|
+
assertMarketPoolId(poolId);
|
|
897
|
+
assertDirection(zeroForOne);
|
|
898
|
+
return readMarketSnapshot(
|
|
899
|
+
context,
|
|
900
|
+
options,
|
|
901
|
+
(scope) => readFeeBreakdown(scope, poolId, zeroForOne, scope.block.timestamp, "current")
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
function previewMarketFeeAt(context, poolId, zeroForOne, timestamp, options = {}) {
|
|
905
|
+
assertMarketPoolId(poolId);
|
|
906
|
+
assertDirection(zeroForOne);
|
|
907
|
+
assertMarketUint(timestamp, "timestamp");
|
|
908
|
+
return readMarketSnapshot(
|
|
909
|
+
context,
|
|
910
|
+
options,
|
|
911
|
+
(scope) => readFeeBreakdown(scope, poolId, zeroForOne, timestamp, "preview")
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
function getMarketSession(context, poolId, options = {}) {
|
|
915
|
+
assertMarketPoolId(poolId);
|
|
916
|
+
const timestamp = options.timestamp;
|
|
917
|
+
if (timestamp !== void 0) assertMarketUint(timestamp, "timestamp");
|
|
918
|
+
return readMarketSnapshot(context, options, async (scope) => {
|
|
919
|
+
const pool = await readMarketPoolAt(scope, poolId);
|
|
920
|
+
const hook2 = requireMarketPoolHook(scope, pool);
|
|
921
|
+
const at = timestamp ?? scope.block.timestamp;
|
|
922
|
+
const session = hook2.kind === "rwa-ramp" ? sessionName(
|
|
923
|
+
await scope.publicClient.readContract({
|
|
924
|
+
address: hook2.address,
|
|
925
|
+
abi: marketRwaRampAbi,
|
|
926
|
+
functionName: "sessionAt",
|
|
927
|
+
args: [poolId, at],
|
|
928
|
+
blockNumber: scope.block.blockNumber
|
|
929
|
+
})
|
|
930
|
+
) : null;
|
|
931
|
+
return { pool, session, timestamp: at, mode: timestamp === void 0 ? "current" : "preview" };
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/actions/pool-state.ts
|
|
936
|
+
import { isAddressEqual as isAddressEqual6 } from "viem";
|
|
937
|
+
function getMarketPoolState(context, poolId, options = {}) {
|
|
938
|
+
assertMarketPoolId(poolId);
|
|
939
|
+
return readMarketSnapshot(context, options, async (scope) => {
|
|
940
|
+
const pool = await readMarketPoolAt(scope, poolId);
|
|
941
|
+
requireMarketPoolHook(scope, pool);
|
|
942
|
+
return { pool, ...await readMarketPoolStateAt(scope, poolId) };
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
async function readMarketPoolStateAt(scope, poolId) {
|
|
946
|
+
const reader = scope.deployment.poolStateReader;
|
|
947
|
+
if (!reader) throw new MarketFeatureUnavailableError("poolStateReader");
|
|
948
|
+
await assertMarketCode(scope, scope.deployment.poolManager);
|
|
949
|
+
const blockNumber = scope.block.blockNumber;
|
|
950
|
+
let state;
|
|
951
|
+
if (reader.kind === "state-view") {
|
|
952
|
+
await assertMarketCode(scope, reader.contract);
|
|
953
|
+
const target = {
|
|
954
|
+
address: reader.contract.address,
|
|
955
|
+
abi: marketStateViewAbi,
|
|
956
|
+
blockNumber
|
|
957
|
+
};
|
|
958
|
+
const manager = await scope.publicClient.readContract({
|
|
959
|
+
...target,
|
|
960
|
+
functionName: "poolManager"
|
|
961
|
+
});
|
|
962
|
+
if (!isAddressEqual6(manager, scope.deployment.poolManager.address))
|
|
963
|
+
throw new MarketDeploymentVerificationError(
|
|
964
|
+
"stateView.poolManager",
|
|
965
|
+
scope.deployment.poolManager.address,
|
|
966
|
+
manager
|
|
967
|
+
);
|
|
968
|
+
const [slot0, activeLiquidity] = await Promise.all([
|
|
969
|
+
scope.publicClient.readContract({ ...target, functionName: "getSlot0", args: [poolId] }),
|
|
970
|
+
scope.publicClient.readContract({
|
|
971
|
+
...target,
|
|
972
|
+
functionName: "getLiquidity",
|
|
973
|
+
args: [poolId]
|
|
974
|
+
})
|
|
975
|
+
]);
|
|
976
|
+
state = {
|
|
977
|
+
sqrtPriceX96: slot0[0],
|
|
978
|
+
tick: slot0[1],
|
|
979
|
+
protocolFeePacked: slot0[2],
|
|
980
|
+
storedLpFeePips: slot0[3],
|
|
981
|
+
activeLiquidity
|
|
982
|
+
};
|
|
983
|
+
} else {
|
|
984
|
+
const words = await scope.publicClient.readContract({
|
|
985
|
+
address: scope.deployment.poolManager.address,
|
|
986
|
+
abi: marketExtsloadAbi,
|
|
987
|
+
functionName: "extsload",
|
|
988
|
+
args: [marketPoolStorageSlots(poolId)],
|
|
989
|
+
blockNumber
|
|
990
|
+
});
|
|
991
|
+
state = decodeMarketPoolStateWords(words);
|
|
992
|
+
}
|
|
993
|
+
if (state.tick < -887272 || state.tick > 887272)
|
|
994
|
+
throw new MarketDeploymentVerificationError("pool.tick", "v4 tick bounds", state.tick);
|
|
995
|
+
return { source: reader.kind, initialized: state.sqrtPriceX96 !== 0n, ...state };
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/actions/write/preview.ts
|
|
999
|
+
import { erc20Abi, isAddressEqual as isAddressEqual8, zeroAddress as zeroAddress5 } from "viem";
|
|
1000
|
+
|
|
1001
|
+
// src/math/liquidity.ts
|
|
1002
|
+
var Q96 = 1n << 96n;
|
|
1003
|
+
var MIN_SQRT = 4295128739n;
|
|
1004
|
+
var MAX_SQRT = 1461446703485210103287273052203988822378723970342n;
|
|
1005
|
+
var FACTORS = [
|
|
1006
|
+
0xfffcb933bd6fad37aa2d162d1a594001n,
|
|
1007
|
+
0xfff97272373d413259a46990580e213an,
|
|
1008
|
+
0xfff2e50f5f656932ef12357cf3c7fdccn,
|
|
1009
|
+
0xffe5caca7e10e4e61c3624eaa0941cd0n,
|
|
1010
|
+
0xffcb9843d60f6159c9db58835c926644n,
|
|
1011
|
+
0xff973b41fa98c081472e6896dfb254c0n,
|
|
1012
|
+
0xff2ea16466c96a3843ec78b326b52861n,
|
|
1013
|
+
0xfe5dee046a99a2a811c461f1969c3053n,
|
|
1014
|
+
0xfcbe86c7900a88aedcffc83b479aa3a4n,
|
|
1015
|
+
0xf987a7253ac413176f2b074cf7815e54n,
|
|
1016
|
+
0xf3392b0822b70005940c7a398e4b70f3n,
|
|
1017
|
+
0xe7159475a2c29b7443b29c7fa6e889d9n,
|
|
1018
|
+
0xd097f3bdfd2022b8845ad8f792aa5825n,
|
|
1019
|
+
0xa9f746462d870fdf8a65dc1f90e061e5n,
|
|
1020
|
+
0x70d869a156d2a1b890bb3df62baf32f7n,
|
|
1021
|
+
0x31be135f97d08fd981231505542fcfa6n,
|
|
1022
|
+
0x9aa508b5b7a84e1c677de54f3e99bc9n,
|
|
1023
|
+
0x5d6af8dedb81196699c329225ee604n,
|
|
1024
|
+
0x2216e584f5fa1ea926041bedfe98n,
|
|
1025
|
+
0x48a170391f7dc42444e8fa2n
|
|
1026
|
+
];
|
|
1027
|
+
function getMarketSqrtPriceAtTick(tick) {
|
|
1028
|
+
if (!Number.isInteger(tick) || Math.abs(tick) > 887272)
|
|
1029
|
+
throw new InvalidMarketConfigError("tick", "Tick must be an integer in v4 bounds.");
|
|
1030
|
+
const absolute = Math.abs(tick);
|
|
1031
|
+
let ratio = 1n << 128n;
|
|
1032
|
+
for (let i = 0; i < FACTORS.length; i++) {
|
|
1033
|
+
const factor = FACTORS[i];
|
|
1034
|
+
if (factor !== void 0 && (absolute & 1 << i) !== 0) ratio = ratio * factor >> 128n;
|
|
1035
|
+
}
|
|
1036
|
+
if (tick > 0) ratio = ((1n << 256n) - 1n) / ratio;
|
|
1037
|
+
return ratio + (1n << 32n) - 1n >> 32n;
|
|
1038
|
+
}
|
|
1039
|
+
function assertPrice(price) {
|
|
1040
|
+
if (typeof price !== "bigint" || price < MIN_SQRT || price >= MAX_SQRT)
|
|
1041
|
+
throw new InvalidMarketConfigError(
|
|
1042
|
+
"sqrtPriceX96",
|
|
1043
|
+
"Price must be initialized and inside v4 bounds."
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
function getMarketTickAtSqrtPrice(sqrtPriceX96) {
|
|
1047
|
+
assertPrice(sqrtPriceX96);
|
|
1048
|
+
let lower = -887272;
|
|
1049
|
+
let upper = 887272;
|
|
1050
|
+
while (lower < upper) {
|
|
1051
|
+
const middle = Math.ceil((lower + upper) / 2);
|
|
1052
|
+
if (getMarketSqrtPriceAtTick(middle) <= sqrtPriceX96) lower = middle;
|
|
1053
|
+
else upper = middle - 1;
|
|
1054
|
+
}
|
|
1055
|
+
return lower;
|
|
1056
|
+
}
|
|
1057
|
+
function getMarketPriceRatio(sqrtPriceX96, decimals0, decimals1) {
|
|
1058
|
+
assertPrice(sqrtPriceX96);
|
|
1059
|
+
for (const decimals of [decimals0, decimals1]) {
|
|
1060
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255)
|
|
1061
|
+
throw new InvalidMarketConfigError("decimals", "Decimals must fit uint8.");
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
numerator: sqrtPriceX96 ** 2n * 10n ** BigInt(decimals0),
|
|
1065
|
+
denominator: (1n << 192n) * 10n ** BigInt(decimals1)
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function boundaries(sqrtPriceX96, tickLower, tickUpper) {
|
|
1069
|
+
assertPrice(sqrtPriceX96);
|
|
1070
|
+
const lower = getMarketSqrtPriceAtTick(tickLower);
|
|
1071
|
+
const upper = getMarketSqrtPriceAtTick(tickUpper);
|
|
1072
|
+
if (lower >= upper)
|
|
1073
|
+
throw new InvalidMarketConfigError("ticks", "Lower tick must precede upper tick.");
|
|
1074
|
+
return {
|
|
1075
|
+
lower,
|
|
1076
|
+
upper,
|
|
1077
|
+
current: sqrtPriceX96 < lower ? lower : sqrtPriceX96 > upper ? upper : sqrtPriceX96
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
function getMarketAmountsForLiquidity(params) {
|
|
1081
|
+
assertMarketUint(params.liquidity, "liquidity", 128);
|
|
1082
|
+
if (params.rounding !== "up" && params.rounding !== "down")
|
|
1083
|
+
throw new InvalidMarketConfigError("rounding", "Expected up or down.");
|
|
1084
|
+
const { lower, upper, current } = boundaries(
|
|
1085
|
+
params.sqrtPriceX96,
|
|
1086
|
+
params.tickLower,
|
|
1087
|
+
params.tickUpper
|
|
1088
|
+
);
|
|
1089
|
+
const divide = (n, d) => params.rounding === "up" ? (n + d - 1n) / d : n / d;
|
|
1090
|
+
return {
|
|
1091
|
+
amount0: divide(params.liquidity * Q96 * (upper - current), current * upper),
|
|
1092
|
+
amount1: divide(params.liquidity * (current - lower), Q96)
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function getMarketLiquidityForAmounts(params) {
|
|
1096
|
+
assertMarketUint(params.amount0, "amount0");
|
|
1097
|
+
assertMarketUint(params.amount1, "amount1");
|
|
1098
|
+
const { lower, upper, current } = boundaries(
|
|
1099
|
+
params.sqrtPriceX96,
|
|
1100
|
+
params.tickLower,
|
|
1101
|
+
params.tickUpper
|
|
1102
|
+
);
|
|
1103
|
+
const l0 = current === upper ? void 0 : params.amount0 * (current * upper / Q96) / (upper - current);
|
|
1104
|
+
const l1 = current === lower ? void 0 : params.amount1 * Q96 / (current - lower);
|
|
1105
|
+
if (l0 !== void 0) assertMarketUint(l0, "liquidity0", 128);
|
|
1106
|
+
if (l1 !== void 0) assertMarketUint(l1, "liquidity1", 128);
|
|
1107
|
+
return l0 === void 0 ? l1 ?? 0n : l1 === void 0 || l0 < l1 ? l0 : l1;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/actions/write/guards.ts
|
|
1111
|
+
import { isAddressEqual as isAddressEqual7, zeroAddress as zeroAddress4 } from "viem";
|
|
1112
|
+
function liquidityKey(context, input) {
|
|
1113
|
+
const key = defineMarketPoolKey(input);
|
|
1114
|
+
const hook2 = requireMarketHook(context, key.hooks);
|
|
1115
|
+
if (isAddressEqual7(key.currency0, zeroAddress4) && hook2.kind !== "ramp-eth")
|
|
1116
|
+
throw new MarketFeatureUnavailableError("This Hook supports ERC-20 only");
|
|
1117
|
+
return key;
|
|
1118
|
+
}
|
|
1119
|
+
function expiry(timestamp, seconds = 60) {
|
|
1120
|
+
if (!Number.isInteger(seconds) || seconds < 1 || seconds > 300)
|
|
1121
|
+
throw new InvalidMarketConfigError("validForSeconds", "Lifetime must be 1..300 seconds.");
|
|
1122
|
+
return timestamp + BigInt(seconds);
|
|
1123
|
+
}
|
|
1124
|
+
function recipient(context, key, to) {
|
|
1125
|
+
assertMarketAddress(to, "to");
|
|
1126
|
+
if ([key.hooks, context.deployment.poolManager.address, zeroAddress4].some(
|
|
1127
|
+
(value) => isAddressEqual7(value, to)
|
|
1128
|
+
))
|
|
1129
|
+
throw new InvalidMarketConfigError("to", "Recipient cannot be zero, the Hook or PoolManager.");
|
|
1130
|
+
return to;
|
|
1131
|
+
}
|
|
1132
|
+
function claimFee(value) {
|
|
1133
|
+
if (!Number.isInteger(value) || value < 0 || value > 2e3)
|
|
1134
|
+
throw new InvalidMarketConfigError("maxFeeBps", "Claim fee tolerance must be 0..2000 bps.");
|
|
1135
|
+
}
|
|
1136
|
+
async function assertWriteDeployment(scope, key, claimOperator = false, management = false) {
|
|
1137
|
+
const hook2 = requireMarketHook(scope, key.hooks);
|
|
1138
|
+
if (management && hook2.capabilities.management !== "verified")
|
|
1139
|
+
throw new MarketFeatureUnavailableError("verified management capability");
|
|
1140
|
+
if (!management && hook2.capabilities.liquidity !== "verified")
|
|
1141
|
+
throw new MarketFeatureUnavailableError("verified liquidity capability");
|
|
1142
|
+
if (!management && isAddressEqual7(key.currency0, zeroAddress4) && hook2.capabilities.nativeLiquidity !== "verified")
|
|
1143
|
+
throw new MarketFeatureUnavailableError("verified nativeLiquidity capability");
|
|
1144
|
+
if (claimOperator && hook2.capabilities.claimOperator !== "verified")
|
|
1145
|
+
throw new MarketFeatureUnavailableError("verified claimOperator capability");
|
|
1146
|
+
for (const contract2 of [
|
|
1147
|
+
hook2,
|
|
1148
|
+
scope.deployment.poolManager,
|
|
1149
|
+
scope.deployment.authority,
|
|
1150
|
+
hook2.claimFeePolicy
|
|
1151
|
+
])
|
|
1152
|
+
await assertMarketCode(scope, contract2);
|
|
1153
|
+
if (hook2.kind === "rwa-ramp") {
|
|
1154
|
+
if (!hook2.calendar) throw new MarketFeatureUnavailableError("RWA calendar");
|
|
1155
|
+
await assertMarketCode(scope, hook2.calendar);
|
|
1156
|
+
const [calendar, controller] = await Promise.all([
|
|
1157
|
+
scope.publicClient.readContract({
|
|
1158
|
+
address: hook2.address,
|
|
1159
|
+
abi: marketRwaRampAbi,
|
|
1160
|
+
functionName: "marketCalendar",
|
|
1161
|
+
blockNumber: scope.block.blockNumber
|
|
1162
|
+
}),
|
|
1163
|
+
scope.publicClient.readContract({
|
|
1164
|
+
address: hook2.calendar.address,
|
|
1165
|
+
abi: marketMarketCalendarAbi,
|
|
1166
|
+
functionName: "controller",
|
|
1167
|
+
blockNumber: scope.block.blockNumber
|
|
1168
|
+
})
|
|
1169
|
+
]);
|
|
1170
|
+
if (!isAddressEqual7(calendar, hook2.calendar.address) || !isAddressEqual7(controller, hook2.address))
|
|
1171
|
+
throw new MarketDeploymentVerificationError(
|
|
1172
|
+
"write.calendar",
|
|
1173
|
+
[hook2.calendar.address, hook2.address],
|
|
1174
|
+
[calendar, controller]
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
const target = {
|
|
1178
|
+
address: hook2.address,
|
|
1179
|
+
abi: marketRampAbi,
|
|
1180
|
+
blockNumber: scope.block.blockNumber
|
|
1181
|
+
};
|
|
1182
|
+
const actual = await Promise.all([
|
|
1183
|
+
scope.publicClient.readContract({ ...target, functionName: "poolManager" }),
|
|
1184
|
+
scope.publicClient.readContract({ ...target, functionName: "authority" }),
|
|
1185
|
+
scope.publicClient.readContract({ ...target, functionName: "claimFeePolicy" }),
|
|
1186
|
+
scope.publicClient.readContract({
|
|
1187
|
+
address: hook2.claimFeePolicy.address,
|
|
1188
|
+
abi: marketClaimFeePolicyAbi,
|
|
1189
|
+
functionName: "ledger",
|
|
1190
|
+
blockNumber: scope.block.blockNumber
|
|
1191
|
+
})
|
|
1192
|
+
]);
|
|
1193
|
+
const expected = [
|
|
1194
|
+
scope.deployment.poolManager.address,
|
|
1195
|
+
scope.deployment.authority.address,
|
|
1196
|
+
hook2.claimFeePolicy.address,
|
|
1197
|
+
hook2.address
|
|
1198
|
+
];
|
|
1199
|
+
for (let index = 0; index < expected.length; index++) {
|
|
1200
|
+
const want = expected[index];
|
|
1201
|
+
const got = actual[index];
|
|
1202
|
+
if (!want || !got || !isAddressEqual7(want, got))
|
|
1203
|
+
throw new MarketDeploymentVerificationError(`write.binding.${index}`, want, got);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/actions/write/preview.ts
|
|
1208
|
+
async function previewMarketLiquidity(context, action, input) {
|
|
1209
|
+
const params = immutableClone(input);
|
|
1210
|
+
const key = liquidityKey(context, params.key);
|
|
1211
|
+
const poolId = getMarketPoolId(key);
|
|
1212
|
+
const rangeId = getMarketRangeId(poolId, params.tickLower, params.tickUpper, key.tickSpacing);
|
|
1213
|
+
assertMarketUint(params.liquidity, "liquidity", 128);
|
|
1214
|
+
assertMarketUint(params.deadline, "deadline");
|
|
1215
|
+
if (params.liquidity === 0n)
|
|
1216
|
+
throw new InvalidMarketConfigError("liquidity", "Liquidity must be positive.");
|
|
1217
|
+
if (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 1e4)
|
|
1218
|
+
throw new InvalidMarketConfigError(
|
|
1219
|
+
"slippageBps",
|
|
1220
|
+
"Slippage must be an integer from 0 to 9999 bps."
|
|
1221
|
+
);
|
|
1222
|
+
return immutableClone(
|
|
1223
|
+
await readMarketSnapshot(context, {}, async (scope) => {
|
|
1224
|
+
const lifetime = expiry(scope.block.timestamp, params.validForSeconds);
|
|
1225
|
+
if (params.deadline < scope.block.timestamp)
|
|
1226
|
+
throw new InvalidMarketConfigError("deadline", "Deadline has expired.");
|
|
1227
|
+
const state = await readMarketPoolStateAt(scope, poolId);
|
|
1228
|
+
const amounts = getMarketAmountsForLiquidity({
|
|
1229
|
+
...params,
|
|
1230
|
+
sqrtPriceX96: state.sqrtPriceX96,
|
|
1231
|
+
rounding: action === "deposit" ? "up" : "down"
|
|
1232
|
+
});
|
|
1233
|
+
const limit = (amount) => action === "deposit" ? (amount * BigInt(1e4 + params.slippageBps) + 9999n) / 10000n : amount * BigInt(1e4 - params.slippageBps) / 10000n;
|
|
1234
|
+
const limit0 = limit(amounts.amount0);
|
|
1235
|
+
const limit1 = limit(amounts.amount1);
|
|
1236
|
+
assertMarketUint(limit0, "limit0", 128);
|
|
1237
|
+
assertMarketUint(limit1, "limit1", 128);
|
|
1238
|
+
return {
|
|
1239
|
+
...params,
|
|
1240
|
+
key,
|
|
1241
|
+
action,
|
|
1242
|
+
rangeId,
|
|
1243
|
+
sqrtPriceX96: state.sqrtPriceX96,
|
|
1244
|
+
...amounts,
|
|
1245
|
+
limit0,
|
|
1246
|
+
limit1,
|
|
1247
|
+
nativeValue: action === "deposit" && isAddressEqual8(key.currency0, zeroAddress5) ? limit0 : 0n,
|
|
1248
|
+
estimatedNativeRefund: action === "deposit" && isAddressEqual8(key.currency0, zeroAddress5) ? limit0 - amounts.amount0 : 0n,
|
|
1249
|
+
expiresAt: params.deadline < lifetime ? params.deadline : lifetime
|
|
1250
|
+
};
|
|
1251
|
+
})
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
function getMarketSpendStatus(context, params) {
|
|
1255
|
+
const key = liquidityKey(context, params.key);
|
|
1256
|
+
const { owner, amount0, amount1 } = params;
|
|
1257
|
+
assertMarketAddress(owner, "owner");
|
|
1258
|
+
assertMarketUint(amount0, "amount0", 128);
|
|
1259
|
+
assertMarketUint(amount1, "amount1", 128);
|
|
1260
|
+
return readMarketSnapshot(context, params, async (scope) => ({
|
|
1261
|
+
owner,
|
|
1262
|
+
spender: key.hooks,
|
|
1263
|
+
tokens: await Promise.all(
|
|
1264
|
+
[
|
|
1265
|
+
{ token: key.currency0, requiredAmount: amount0 },
|
|
1266
|
+
{ token: key.currency1, requiredAmount: amount1 }
|
|
1267
|
+
].map(async ({ token, requiredAmount }) => {
|
|
1268
|
+
if (isAddressEqual8(token, zeroAddress5)) {
|
|
1269
|
+
const balance2 = await scope.publicClient.getBalance({
|
|
1270
|
+
address: owner,
|
|
1271
|
+
blockNumber: scope.block.blockNumber
|
|
1272
|
+
});
|
|
1273
|
+
return {
|
|
1274
|
+
token,
|
|
1275
|
+
kind: "native",
|
|
1276
|
+
balance: balance2,
|
|
1277
|
+
allowance: null,
|
|
1278
|
+
requiredAmount,
|
|
1279
|
+
needsApproval: false,
|
|
1280
|
+
sufficientBalance: balance2 >= requiredAmount
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const target = {
|
|
1284
|
+
address: token,
|
|
1285
|
+
abi: erc20Abi,
|
|
1286
|
+
blockNumber: scope.block.blockNumber
|
|
1287
|
+
};
|
|
1288
|
+
const [balance, allowance] = await Promise.all([
|
|
1289
|
+
scope.publicClient.readContract({ ...target, functionName: "balanceOf", args: [owner] }),
|
|
1290
|
+
scope.publicClient.readContract({
|
|
1291
|
+
...target,
|
|
1292
|
+
functionName: "allowance",
|
|
1293
|
+
args: [owner, key.hooks]
|
|
1294
|
+
})
|
|
1295
|
+
]);
|
|
1296
|
+
return {
|
|
1297
|
+
token,
|
|
1298
|
+
kind: "erc20",
|
|
1299
|
+
balance,
|
|
1300
|
+
allowance,
|
|
1301
|
+
requiredAmount,
|
|
1302
|
+
needsApproval: allowance < requiredAmount,
|
|
1303
|
+
sufficientBalance: balance >= requiredAmount
|
|
1304
|
+
};
|
|
1305
|
+
})
|
|
1306
|
+
)
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/chains.ts
|
|
1311
|
+
function defineMarketChain(input) {
|
|
1312
|
+
const deployment = defineMarketDeployment(input.deployment);
|
|
1313
|
+
if (input.chainId !== deployment.chainId) {
|
|
1314
|
+
throw new InvalidMarketConfigError("chainId", "Chain and deployment chain IDs must match.");
|
|
1315
|
+
}
|
|
1316
|
+
if (!Number.isSafeInteger(input.confirmations) || input.confirmations < 1) {
|
|
1317
|
+
throw new InvalidMarketConfigError("confirmations", "Expected at least one confirmation.");
|
|
1318
|
+
}
|
|
1319
|
+
return Object.freeze({ chainId: input.chainId, deployment, confirmations: input.confirmations });
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/actions/write/transactions.ts
|
|
1323
|
+
import {
|
|
1324
|
+
decodeEventLog,
|
|
1325
|
+
encodeFunctionData as encodeFunctionData2,
|
|
1326
|
+
erc20Abi as erc20Abi2,
|
|
1327
|
+
isAddressEqual as isAddressEqual10,
|
|
1328
|
+
zeroAddress as zeroAddress7
|
|
1329
|
+
} from "viem";
|
|
1330
|
+
|
|
1331
|
+
// src/actions/write/management.ts
|
|
1332
|
+
import {
|
|
1333
|
+
encodeAbiParameters as encodeAbiParameters4,
|
|
1334
|
+
encodeFunctionData,
|
|
1335
|
+
isAddressEqual as isAddressEqual9,
|
|
1336
|
+
keccak256 as keccak2565,
|
|
1337
|
+
zeroAddress as zeroAddress6
|
|
1338
|
+
} from "viem";
|
|
1339
|
+
|
|
1340
|
+
// src/math/calendar.ts
|
|
1341
|
+
function marketDaysInMonth(year, month) {
|
|
1342
|
+
if (!Number.isInteger(year) || year < 1970 || year > 9999 || !Number.isInteger(month) || month < 1 || month > 12)
|
|
1343
|
+
throw new InvalidMarketConfigError("date", "Expected year 1970..9999 and month 1..12.");
|
|
1344
|
+
if (month === 2) return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
|
|
1345
|
+
return [4, 6, 9, 11].includes(month) ? 30 : 31;
|
|
1346
|
+
}
|
|
1347
|
+
function packMarketDayOverrides(year, month, days) {
|
|
1348
|
+
const maximum = marketDaysInMonth(year, month);
|
|
1349
|
+
if (!Array.isArray(days) || days.length > 10)
|
|
1350
|
+
throw new InvalidMarketConfigError("days", "At most ten overrides per month.");
|
|
1351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1352
|
+
let packed = 0n;
|
|
1353
|
+
for (const { day, status } of days) {
|
|
1354
|
+
if (!Number.isInteger(day) || day < 1 || day > maximum || seen.has(day) || status !== "closed" && status !== "open")
|
|
1355
|
+
throw new InvalidMarketConfigError("days", "Invalid or duplicate override date/status.");
|
|
1356
|
+
seen.add(day);
|
|
1357
|
+
packed |= (status === "closed" ? 1n : 2n) << BigInt((day - 1) * 2);
|
|
1358
|
+
}
|
|
1359
|
+
return packed;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/actions/write/management.ts
|
|
1363
|
+
function integer(value, field, min = 0, max = 16777215) {
|
|
1364
|
+
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
1365
|
+
throw new InvalidMarketConfigError(field, `Expected integer ${min}..${max}.`);
|
|
1366
|
+
}
|
|
1367
|
+
function condition(ok, field) {
|
|
1368
|
+
if (!ok)
|
|
1369
|
+
throw new InvalidMarketConfigError(
|
|
1370
|
+
field,
|
|
1371
|
+
`Invalid management parameter combination: ${field}.`
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
function encodeMarketManagement(context, a) {
|
|
1375
|
+
const key = liquidityKey(context, a.key);
|
|
1376
|
+
const hook2 = requireMarketHook(context, key.hooks);
|
|
1377
|
+
const id = getMarketPoolId(key);
|
|
1378
|
+
const target = hook2.address;
|
|
1379
|
+
const ramp = marketRampAbi;
|
|
1380
|
+
const rwa = marketRwaRampAbi;
|
|
1381
|
+
const result = (data, to = target) => ({ key, to, data, value: 0n });
|
|
1382
|
+
const requireRwa = () => {
|
|
1383
|
+
if (hook2.kind !== "rwa-ramp") throw new MarketFeatureUnavailableError("RWA Hook required");
|
|
1384
|
+
};
|
|
1385
|
+
switch (a.kind) {
|
|
1386
|
+
case "pokeFee":
|
|
1387
|
+
integer(a.fee0For1, "fee0For1");
|
|
1388
|
+
integer(a.fee1For0, "fee1For0");
|
|
1389
|
+
integer(a.ttl, "ttl", 1, 259200);
|
|
1390
|
+
condition(a.fee0For1 !== 0 || a.fee1For0 !== 0, "pokeFee");
|
|
1391
|
+
return result(
|
|
1392
|
+
encodeFunctionData({
|
|
1393
|
+
abi: ramp,
|
|
1394
|
+
functionName: "pokeFee",
|
|
1395
|
+
args: [id, a.fee0For1, a.fee1For0, a.ttl]
|
|
1396
|
+
})
|
|
1397
|
+
);
|
|
1398
|
+
case "clearPoke":
|
|
1399
|
+
return result(encodeFunctionData({ abi: ramp, functionName: "clearPoke", args: [id] }));
|
|
1400
|
+
case "setPoolAsymmetry":
|
|
1401
|
+
integer(a.premiumPips, "premiumPips");
|
|
1402
|
+
condition(typeof a.premiumZeroForOne === "boolean", "direction");
|
|
1403
|
+
return result(
|
|
1404
|
+
encodeFunctionData({
|
|
1405
|
+
abi: ramp,
|
|
1406
|
+
functionName: "setPoolAsymmetry",
|
|
1407
|
+
args: [key, a.premiumPips, a.premiumZeroForOne]
|
|
1408
|
+
})
|
|
1409
|
+
);
|
|
1410
|
+
case "setCryptoPoolConfig":
|
|
1411
|
+
if (hook2.kind === "rwa-ramp") throw new MarketFeatureUnavailableError("Crypto Hook required");
|
|
1412
|
+
integer(a.floorPips, "floorPips", 100);
|
|
1413
|
+
integer(a.flatPips, "flatPips", a.floorPips);
|
|
1414
|
+
integer(a.cap, "cap", a.flatPips);
|
|
1415
|
+
return result(
|
|
1416
|
+
encodeFunctionData({
|
|
1417
|
+
abi: ramp,
|
|
1418
|
+
functionName: "setPoolConfig",
|
|
1419
|
+
args: [key, a.floorPips, a.flatPips, a.cap]
|
|
1420
|
+
})
|
|
1421
|
+
);
|
|
1422
|
+
case "setRwaPoolConfig": {
|
|
1423
|
+
requireRwa();
|
|
1424
|
+
integer(a.cap, "cap", 100);
|
|
1425
|
+
const c = a.config;
|
|
1426
|
+
for (const field of ["openFloor", "overnightFloor", "closedFloor"])
|
|
1427
|
+
integer(c[field], field, 100, a.cap);
|
|
1428
|
+
integer(c.spikeMult, "spikeMult", 0, 20);
|
|
1429
|
+
integer(c.closedSpike, "closedSpike");
|
|
1430
|
+
integer(c.descentWindow, "descentWindow", 0, 21600);
|
|
1431
|
+
integer(c.closeFloor, "closeFloor", 0, a.cap);
|
|
1432
|
+
integer(c.closeBefore, "closeBefore", 0, 21600);
|
|
1433
|
+
integer(c.closeAfter, "closeAfter", 0, 21600);
|
|
1434
|
+
condition(
|
|
1435
|
+
c.descentWindow === 0 ? c.spikeMult === 0 && c.closedSpike === 0 : c.overnightFloor * c.spikeMult > c.openFloor && c.closedSpike > c.openFloor,
|
|
1436
|
+
"descentWindow"
|
|
1437
|
+
);
|
|
1438
|
+
condition(
|
|
1439
|
+
c.closeFloor === 0 ? c.closeBefore === 0 && c.closeAfter === 0 : c.closeFloor >= c.openFloor && (c.closeBefore !== 0 || c.closeAfter !== 0),
|
|
1440
|
+
"closeFloor"
|
|
1441
|
+
);
|
|
1442
|
+
return result(
|
|
1443
|
+
encodeFunctionData({
|
|
1444
|
+
abi: rwa.filter(
|
|
1445
|
+
(item) => item.type !== "function" || item.name !== "setPoolConfig" || item.inputs.length === 3
|
|
1446
|
+
),
|
|
1447
|
+
functionName: "setPoolConfig",
|
|
1448
|
+
args: [key, c, a.cap]
|
|
1449
|
+
})
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
case "setSessionHours":
|
|
1453
|
+
requireRwa();
|
|
1454
|
+
integer(a.openSec, "openSec", 32400, 36e3);
|
|
1455
|
+
integer(a.closeSec, "closeSec", 55800, 59400);
|
|
1456
|
+
condition(a.closeSec - a.openSec >= 3600 && a.closeSec - a.openSec <= 43200, "sessionHours");
|
|
1457
|
+
return result(
|
|
1458
|
+
encodeFunctionData({
|
|
1459
|
+
abi: rwa,
|
|
1460
|
+
functionName: "setSessionHours",
|
|
1461
|
+
args: [id, a.openSec, a.closeSec]
|
|
1462
|
+
})
|
|
1463
|
+
);
|
|
1464
|
+
case "setDayOverrides": {
|
|
1465
|
+
requireRwa();
|
|
1466
|
+
const packed = packMarketDayOverrides(a.year, a.month, a.days);
|
|
1467
|
+
return result(
|
|
1468
|
+
encodeFunctionData({
|
|
1469
|
+
abi: rwa,
|
|
1470
|
+
functionName: "setDayOverrides",
|
|
1471
|
+
args: [id, BigInt(a.year * 100 + a.month), packed]
|
|
1472
|
+
})
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
case "setDstMode":
|
|
1476
|
+
requireRwa();
|
|
1477
|
+
condition(["auto", "est", "edt"].includes(a.mode), "mode");
|
|
1478
|
+
return result(
|
|
1479
|
+
encodeFunctionData({
|
|
1480
|
+
abi: rwa,
|
|
1481
|
+
functionName: "setDstMode",
|
|
1482
|
+
args: [id, a.mode === "auto" ? 0 : a.mode === "est" ? 1 : 2]
|
|
1483
|
+
})
|
|
1484
|
+
);
|
|
1485
|
+
case "setEarlyClose":
|
|
1486
|
+
requireRwa();
|
|
1487
|
+
integer(a.day, "day", 1, marketDaysInMonth(a.year, a.month));
|
|
1488
|
+
integer(a.closeSec, "closeSec", 0, 86400);
|
|
1489
|
+
return result(
|
|
1490
|
+
encodeFunctionData({
|
|
1491
|
+
abi: rwa,
|
|
1492
|
+
functionName: "setEarlyClose",
|
|
1493
|
+
args: [id, BigInt(a.year), BigInt(a.month), BigInt(a.day), a.closeSec]
|
|
1494
|
+
})
|
|
1495
|
+
);
|
|
1496
|
+
case "setPaused":
|
|
1497
|
+
case "setPoolPaused":
|
|
1498
|
+
integer(a.duration, "duration", 0, 604800);
|
|
1499
|
+
return result(
|
|
1500
|
+
a.kind === "setPaused" ? encodeFunctionData({ abi: ramp, functionName: "setPaused", args: [BigInt(a.duration)] }) : encodeFunctionData({
|
|
1501
|
+
abi: ramp,
|
|
1502
|
+
functionName: "setPoolPaused",
|
|
1503
|
+
args: [key, BigInt(a.duration)]
|
|
1504
|
+
})
|
|
1505
|
+
);
|
|
1506
|
+
case "setClaimFee":
|
|
1507
|
+
claimFee(a.bps);
|
|
1508
|
+
assertMarketAddress(a.recipient, "recipient", a.bps === 0);
|
|
1509
|
+
if (!isAddressEqual9(a.recipient, zeroAddress6)) recipient(context, key, a.recipient);
|
|
1510
|
+
condition(Array.isArray(a.settleFirst), "settleFirst");
|
|
1511
|
+
for (const currency of a.settleFirst)
|
|
1512
|
+
assertMarketAddress(currency, "settleFirst", hook2.kind === "ramp-eth");
|
|
1513
|
+
condition(
|
|
1514
|
+
new Set(a.settleFirst.map((v) => v.toLowerCase())).size === a.settleFirst.length,
|
|
1515
|
+
"settleFirst"
|
|
1516
|
+
);
|
|
1517
|
+
return result(
|
|
1518
|
+
encodeFunctionData({
|
|
1519
|
+
abi: ramp,
|
|
1520
|
+
functionName: "setClaimFee",
|
|
1521
|
+
args: [key, a.settleFirst, a.bps, a.recipient]
|
|
1522
|
+
})
|
|
1523
|
+
);
|
|
1524
|
+
case "setClaimFeeBps":
|
|
1525
|
+
claimFee(a.bps);
|
|
1526
|
+
return result(
|
|
1527
|
+
encodeFunctionData({ abi: ramp, functionName: "setClaimFeeBps", args: [key, a.bps] })
|
|
1528
|
+
);
|
|
1529
|
+
case "sweepClaimFee":
|
|
1530
|
+
assertMarketAddress(a.currency, "currency", hook2.kind === "ramp-eth");
|
|
1531
|
+
return result(
|
|
1532
|
+
encodeFunctionData({ abi: ramp, functionName: "sweepClaimFee", args: [a.currency] })
|
|
1533
|
+
);
|
|
1534
|
+
case "register":
|
|
1535
|
+
return result(
|
|
1536
|
+
encodeFunctionData({ abi: marketPoolRegistryAbi, functionName: "register", args: [key] }),
|
|
1537
|
+
context.deployment.registry.address
|
|
1538
|
+
);
|
|
1539
|
+
case "deregister":
|
|
1540
|
+
case "reregister":
|
|
1541
|
+
return result(
|
|
1542
|
+
encodeFunctionData({ abi: marketPoolRegistryAbi, functionName: a.kind, args: [id] }),
|
|
1543
|
+
context.deployment.registry.address
|
|
1544
|
+
);
|
|
1545
|
+
case "setHook":
|
|
1546
|
+
case "disableHook": {
|
|
1547
|
+
const reader = context.deployment.reader;
|
|
1548
|
+
if (!reader) throw new MarketFeatureUnavailableError("Reader required");
|
|
1549
|
+
if (a.kind === "disableHook")
|
|
1550
|
+
return result(
|
|
1551
|
+
encodeFunctionData({
|
|
1552
|
+
abi: marketRampReaderAbi,
|
|
1553
|
+
functionName: "disableHook",
|
|
1554
|
+
args: [hook2.address]
|
|
1555
|
+
}),
|
|
1556
|
+
reader.address
|
|
1557
|
+
);
|
|
1558
|
+
condition(
|
|
1559
|
+
/^0x[0-9a-fA-F]{64}$/.test(a.expectedCodeHash) && a.expectedCodeHash.toLowerCase() === hook2.runtimeCodeHash.toLowerCase(),
|
|
1560
|
+
"expectedCodeHash"
|
|
1561
|
+
);
|
|
1562
|
+
return result(
|
|
1563
|
+
encodeFunctionData({
|
|
1564
|
+
abi: marketRampReaderAbi,
|
|
1565
|
+
functionName: "setHook",
|
|
1566
|
+
args: [hook2.address, hook2.kind === "rwa-ramp" ? 2 : 1, a.expectedCodeHash]
|
|
1567
|
+
}),
|
|
1568
|
+
reader.address
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
default:
|
|
1572
|
+
throw new InvalidMarketConfigError("kind", "Unknown management action.");
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
async function inspectMarketManagementAt(scope, a, account, when = 0) {
|
|
1576
|
+
assertMarketAddress(account, "account");
|
|
1577
|
+
integer(when, "when", 0, 2 ** 48 - 1);
|
|
1578
|
+
const call = encodeMarketManagement(scope, a);
|
|
1579
|
+
const hook2 = requireMarketHook(scope, call.key.hooks);
|
|
1580
|
+
if (hook2.capabilities.management !== "verified")
|
|
1581
|
+
throw new MarketFeatureUnavailableError("verified management capability");
|
|
1582
|
+
const registryAction = [
|
|
1583
|
+
"register",
|
|
1584
|
+
"deregister",
|
|
1585
|
+
"reregister",
|
|
1586
|
+
"setHook",
|
|
1587
|
+
"disableHook"
|
|
1588
|
+
].includes(a.kind);
|
|
1589
|
+
const readerAction = a.kind === "setHook" || a.kind === "disableHook";
|
|
1590
|
+
if (a.kind !== "disableHook" && a.kind !== "deregister" && a.kind !== "reregister")
|
|
1591
|
+
await assertWriteDeployment(scope, call.key, false, true);
|
|
1592
|
+
else await assertMarketCode(scope, scope.deployment.authority);
|
|
1593
|
+
const read = { blockNumber: scope.block.blockNumber };
|
|
1594
|
+
if (registryAction) {
|
|
1595
|
+
await assertMarketCode(scope, scope.deployment.registry);
|
|
1596
|
+
const actual = await scope.publicClient.readContract({
|
|
1597
|
+
...read,
|
|
1598
|
+
address: scope.deployment.registry.address,
|
|
1599
|
+
abi: marketPoolRegistryAbi,
|
|
1600
|
+
functionName: "authority"
|
|
1601
|
+
});
|
|
1602
|
+
if (!isAddressEqual9(actual, scope.deployment.authority.address))
|
|
1603
|
+
throw new MarketDeploymentVerificationError(
|
|
1604
|
+
"registry.authority",
|
|
1605
|
+
scope.deployment.authority.address,
|
|
1606
|
+
actual
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
if (readerAction) {
|
|
1610
|
+
const reader = scope.deployment.reader;
|
|
1611
|
+
if (!reader) throw new MarketFeatureUnavailableError("Reader required");
|
|
1612
|
+
await assertMarketCode(scope, reader);
|
|
1613
|
+
const [registry, authority] = await Promise.all([
|
|
1614
|
+
scope.publicClient.readContract({
|
|
1615
|
+
...read,
|
|
1616
|
+
address: reader.address,
|
|
1617
|
+
abi: marketRampReaderAbi,
|
|
1618
|
+
functionName: "registry"
|
|
1619
|
+
}),
|
|
1620
|
+
scope.publicClient.readContract({
|
|
1621
|
+
...read,
|
|
1622
|
+
address: reader.address,
|
|
1623
|
+
abi: marketRampReaderAbi,
|
|
1624
|
+
functionName: "authority"
|
|
1625
|
+
})
|
|
1626
|
+
]);
|
|
1627
|
+
if (!isAddressEqual9(registry, scope.deployment.registry.address) || !isAddressEqual9(authority, scope.deployment.authority.address))
|
|
1628
|
+
throw new MarketDeploymentVerificationError("reader.bindings", scope.deployment, [
|
|
1629
|
+
registry,
|
|
1630
|
+
authority
|
|
1631
|
+
]);
|
|
1632
|
+
}
|
|
1633
|
+
const hookRead = { ...read, address: hook2.address, abi: marketRampAbi };
|
|
1634
|
+
if (a.kind === "setCryptoPoolConfig" || a.kind === "setRwaPoolConfig") {
|
|
1635
|
+
const absolute = await scope.publicClient.readContract({
|
|
1636
|
+
...hookRead,
|
|
1637
|
+
functionName: "ABSOLUTE_MAX_FEE"
|
|
1638
|
+
});
|
|
1639
|
+
condition(a.cap <= absolute, "cap");
|
|
1640
|
+
}
|
|
1641
|
+
if (a.kind === "pokeFee" || a.kind === "setPoolAsymmetry") {
|
|
1642
|
+
const id = getMarketPoolId(call.key);
|
|
1643
|
+
const [floor, cap] = await Promise.all([
|
|
1644
|
+
scope.publicClient.readContract({ ...hookRead, functionName: "pokeFloor", args: [id] }),
|
|
1645
|
+
scope.publicClient.readContract({ ...hookRead, functionName: "maxFee", args: [id] })
|
|
1646
|
+
]);
|
|
1647
|
+
if (a.kind === "pokeFee")
|
|
1648
|
+
condition(
|
|
1649
|
+
[a.fee0For1, a.fee1For0].every((fee) => fee === 0 || fee >= floor && fee <= cap),
|
|
1650
|
+
"pokeFee.band"
|
|
1651
|
+
);
|
|
1652
|
+
else condition(a.premiumPips <= cap - floor, "premiumPips.band");
|
|
1653
|
+
}
|
|
1654
|
+
if (a.kind === "setEarlyClose" && a.closeSec !== 0) {
|
|
1655
|
+
const id = getMarketPoolId(call.key);
|
|
1656
|
+
const [open, close] = await Promise.all([
|
|
1657
|
+
scope.publicClient.readContract({
|
|
1658
|
+
...read,
|
|
1659
|
+
address: hook2.address,
|
|
1660
|
+
abi: marketRwaRampAbi,
|
|
1661
|
+
functionName: "openSec",
|
|
1662
|
+
args: [id]
|
|
1663
|
+
}),
|
|
1664
|
+
scope.publicClient.readContract({
|
|
1665
|
+
...read,
|
|
1666
|
+
address: hook2.address,
|
|
1667
|
+
abi: marketRwaRampAbi,
|
|
1668
|
+
functionName: "closeSec",
|
|
1669
|
+
args: [id]
|
|
1670
|
+
})
|
|
1671
|
+
]);
|
|
1672
|
+
condition(a.closeSec <= close && a.closeSec - open >= 10800, "earlyClose");
|
|
1673
|
+
}
|
|
1674
|
+
const target = {
|
|
1675
|
+
...read,
|
|
1676
|
+
address: scope.deployment.authority.address,
|
|
1677
|
+
abi: marketAccessManagerAbi
|
|
1678
|
+
};
|
|
1679
|
+
const [immediate, delay] = a.kind === "sweepClaimFee" ? [true, 0] : await scope.publicClient.readContract({
|
|
1680
|
+
...target,
|
|
1681
|
+
functionName: "canCall",
|
|
1682
|
+
args: [account, call.to, call.data.slice(0, 10)]
|
|
1683
|
+
});
|
|
1684
|
+
const operationId = keccak2565(
|
|
1685
|
+
encodeAbiParameters4(
|
|
1686
|
+
[{ type: "address" }, { type: "address" }, { type: "bytes" }],
|
|
1687
|
+
[account, call.to, call.data]
|
|
1688
|
+
)
|
|
1689
|
+
);
|
|
1690
|
+
const scheduledAt = await scope.publicClient.readContract({
|
|
1691
|
+
...target,
|
|
1692
|
+
functionName: "getSchedule",
|
|
1693
|
+
args: [operationId]
|
|
1694
|
+
});
|
|
1695
|
+
return {
|
|
1696
|
+
permission: immediate ? "immediate" : delay > 0 ? "delayed" : "denied",
|
|
1697
|
+
delay,
|
|
1698
|
+
operationId,
|
|
1699
|
+
scheduledAt,
|
|
1700
|
+
direct: { to: call.to, data: call.data, value: 0n },
|
|
1701
|
+
schedule: {
|
|
1702
|
+
to: target.address,
|
|
1703
|
+
value: 0n,
|
|
1704
|
+
data: encodeFunctionData({
|
|
1705
|
+
abi: marketAccessManagerAbi,
|
|
1706
|
+
functionName: "schedule",
|
|
1707
|
+
args: [call.to, call.data, when]
|
|
1708
|
+
})
|
|
1709
|
+
},
|
|
1710
|
+
execute: {
|
|
1711
|
+
to: target.address,
|
|
1712
|
+
value: 0n,
|
|
1713
|
+
data: encodeFunctionData({
|
|
1714
|
+
abi: marketAccessManagerAbi,
|
|
1715
|
+
functionName: "execute",
|
|
1716
|
+
args: [call.to, call.data]
|
|
1717
|
+
})
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
function managementRequest(auth, execution, timestamp) {
|
|
1722
|
+
if (auth.permission === "denied")
|
|
1723
|
+
throw new InvalidMarketConfigError(
|
|
1724
|
+
"permission",
|
|
1725
|
+
"Account is not authorized for this management selector."
|
|
1726
|
+
);
|
|
1727
|
+
if (execution.mode === "direct") {
|
|
1728
|
+
condition(auth.permission === "immediate", "permission.scheduleRequired");
|
|
1729
|
+
return auth.direct;
|
|
1730
|
+
}
|
|
1731
|
+
if (execution.mode === "schedule") {
|
|
1732
|
+
condition(auth.permission === "delayed" && auth.scheduledAt === 0, "schedule");
|
|
1733
|
+
const when = execution.when ?? 0;
|
|
1734
|
+
condition(when === 0 || BigInt(when) >= timestamp + BigInt(auth.delay), "when");
|
|
1735
|
+
return auth.schedule;
|
|
1736
|
+
}
|
|
1737
|
+
if (execution.mode === "execute") {
|
|
1738
|
+
condition(
|
|
1739
|
+
auth.scheduledAt !== 0 && BigInt(auth.scheduledAt) <= timestamp,
|
|
1740
|
+
"schedule.notReadyOrExpired"
|
|
1741
|
+
);
|
|
1742
|
+
return auth.execute;
|
|
1743
|
+
}
|
|
1744
|
+
throw new InvalidMarketConfigError("mode", "Unknown management execution mode.");
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// src/actions/write/transactions.ts
|
|
1748
|
+
function createMarketWriteActions(publicClient, walletClient, chain) {
|
|
1749
|
+
const context = { publicClient, deployment: chain.deployment };
|
|
1750
|
+
const store = createPreparedTransactionStore();
|
|
1751
|
+
const previews = /* @__PURE__ */ new WeakSet();
|
|
1752
|
+
const sent = /* @__PURE__ */ new Map();
|
|
1753
|
+
const submitting = /* @__PURE__ */ new WeakSet();
|
|
1754
|
+
const submitted = /* @__PURE__ */ new WeakSet();
|
|
1755
|
+
const account = () => resolveLiveWalletAccount(
|
|
1756
|
+
walletClient,
|
|
1757
|
+
chain.chainId,
|
|
1758
|
+
(expected, actual) => new MarketDeploymentVerificationError("wallet.chainId", expected, actual)
|
|
1759
|
+
);
|
|
1760
|
+
async function simulate(request, blockNumber) {
|
|
1761
|
+
try {
|
|
1762
|
+
const result = await publicClient.call({ ...request, blockNumber, batch: false });
|
|
1763
|
+
if (request.data.startsWith("0x095ea7b3") && result.data && result.data !== "0x" && BigInt(result.data) !== 1n)
|
|
1764
|
+
throw new InvalidMarketConfigError("approve", "Token returned false for approve.");
|
|
1765
|
+
} catch (cause) {
|
|
1766
|
+
throw new MarketTransactionError("simulate", cause);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async function checkNativeBudget(request) {
|
|
1770
|
+
if (request.gas === void 0 || request.maxFeePerGas === void 0) return;
|
|
1771
|
+
const balance = await publicClient.getBalance({ address: request.account });
|
|
1772
|
+
if (balance < request.value + request.gas * request.maxFeePerGas)
|
|
1773
|
+
throw new InvalidMarketConfigError(
|
|
1774
|
+
"nativeBalance",
|
|
1775
|
+
"Insufficient native balance for prepaid value plus gas budget."
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
async function fresh(block, expiresAt) {
|
|
1779
|
+
const [latest, original, chainId] = await Promise.all([
|
|
1780
|
+
publicClient.getBlock({ blockTag: "latest" }),
|
|
1781
|
+
publicClient.getBlock({ blockNumber: block.blockNumber }),
|
|
1782
|
+
publicClient.getChainId()
|
|
1783
|
+
]);
|
|
1784
|
+
if (chainId !== chain.chainId)
|
|
1785
|
+
throw new MarketDeploymentVerificationError("publicClient.chainId", chain.chainId, chainId);
|
|
1786
|
+
if (original.hash !== block.blockHash)
|
|
1787
|
+
throw new MarketDeploymentVerificationError(
|
|
1788
|
+
"preview.blockHash",
|
|
1789
|
+
block.blockHash,
|
|
1790
|
+
original.hash
|
|
1791
|
+
);
|
|
1792
|
+
if (latest.timestamp < block.timestamp || latest.timestamp > expiresAt)
|
|
1793
|
+
throw new InvalidMarketConfigError(
|
|
1794
|
+
"expiry",
|
|
1795
|
+
"Preview or prepared transaction has expired; prepare again."
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
async function preview(action, params) {
|
|
1799
|
+
const result = await previewMarketLiquidity(context, action, params);
|
|
1800
|
+
previews.add(result);
|
|
1801
|
+
return result;
|
|
1802
|
+
}
|
|
1803
|
+
async function prepare(input) {
|
|
1804
|
+
const key = liquidityKey(context, input.key);
|
|
1805
|
+
const live = await account();
|
|
1806
|
+
if (input.preview) await fresh(input.preview.block, input.preview.data.expiresAt);
|
|
1807
|
+
let managementContext;
|
|
1808
|
+
const snapshot = await readMarketSnapshot(context, {}, async (scope) => {
|
|
1809
|
+
if (!input.management)
|
|
1810
|
+
await assertWriteDeployment(
|
|
1811
|
+
scope,
|
|
1812
|
+
key,
|
|
1813
|
+
input.action === "setClaimOperator" || input.action === "claimFor"
|
|
1814
|
+
);
|
|
1815
|
+
let request = {
|
|
1816
|
+
account: live.address,
|
|
1817
|
+
to: input.target ?? key.hooks,
|
|
1818
|
+
data: input.data,
|
|
1819
|
+
value: input.action === "deposit" ? input.preview?.data.nativeValue ?? 0n : 0n,
|
|
1820
|
+
chainId: chain.chainId
|
|
1821
|
+
};
|
|
1822
|
+
if (input.management) {
|
|
1823
|
+
const { action, execution } = input.management;
|
|
1824
|
+
const auth = await inspectMarketManagementAt(
|
|
1825
|
+
scope,
|
|
1826
|
+
action,
|
|
1827
|
+
live.address,
|
|
1828
|
+
execution.mode === "schedule" ? execution.when : 0
|
|
1829
|
+
);
|
|
1830
|
+
request = { ...request, ...managementRequest(auth, execution, scope.block.timestamp) };
|
|
1831
|
+
managementContext = {
|
|
1832
|
+
action,
|
|
1833
|
+
execution,
|
|
1834
|
+
target: auth.direct.to,
|
|
1835
|
+
operationId: auth.operationId
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
if (input.action === "deposit" && input.preview) {
|
|
1839
|
+
const status = await getMarketSpendStatus(context, {
|
|
1840
|
+
key,
|
|
1841
|
+
owner: live.address,
|
|
1842
|
+
amount0: input.preview.data.limit0,
|
|
1843
|
+
amount1: input.preview.data.limit1,
|
|
1844
|
+
...scope.block
|
|
1845
|
+
});
|
|
1846
|
+
if (status.data.tokens.some((token) => !token.sufficientBalance || token.needsApproval))
|
|
1847
|
+
throw new InvalidMarketConfigError(
|
|
1848
|
+
"spend",
|
|
1849
|
+
"Insufficient token balance or allowance for deposit maxima."
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
await simulate(request, scope.block.blockNumber);
|
|
1853
|
+
if (isAddressEqual10(key.currency0, zeroAddress7)) {
|
|
1854
|
+
const fees = await publicClient.estimateFeesPerGas({ chain: void 0, type: "eip1559" });
|
|
1855
|
+
const estimatedGas = await publicClient.estimateGas({ ...request, ...fees });
|
|
1856
|
+
request = { ...request, ...fees, gas: (estimatedGas * 125n + 99n) / 100n };
|
|
1857
|
+
await checkNativeBudget(request);
|
|
1858
|
+
}
|
|
1859
|
+
return request;
|
|
1860
|
+
});
|
|
1861
|
+
const after = await account();
|
|
1862
|
+
if (!isAddressEqual10(live.address, after.address))
|
|
1863
|
+
throw new InvalidMarketConfigError("account", "Wallet account changed during preparation.");
|
|
1864
|
+
const expiresAt = input.preview?.data.expiresAt ?? expiry(snapshot.block.timestamp, input.validForSeconds);
|
|
1865
|
+
const preparedContext = immutableClone({
|
|
1866
|
+
action: input.action,
|
|
1867
|
+
...managementContext ? { management: managementContext } : {},
|
|
1868
|
+
key,
|
|
1869
|
+
rangeId: input.rangeId,
|
|
1870
|
+
block: snapshot.block,
|
|
1871
|
+
expiresAt,
|
|
1872
|
+
owner: input.owner ?? live.address,
|
|
1873
|
+
recipient: input.action === "deposit" || input.action === "approve" ? live.address : input.to
|
|
1874
|
+
});
|
|
1875
|
+
return store.create(snapshot.data, input.preview ?? null, preparedContext);
|
|
1876
|
+
}
|
|
1877
|
+
function requirePreview(value, action) {
|
|
1878
|
+
if (!previews.has(value) || value.data.action !== action)
|
|
1879
|
+
throw new InvalidMarketConfigError(
|
|
1880
|
+
"preview",
|
|
1881
|
+
"Use an unchanged preview from this client for the requested action."
|
|
1882
|
+
);
|
|
1883
|
+
return value.data;
|
|
1884
|
+
}
|
|
1885
|
+
async function prepareWithdraw(previewValue, to, maxFeeBps) {
|
|
1886
|
+
const q = requirePreview(previewValue, "withdraw");
|
|
1887
|
+
recipient(context, q.key, to);
|
|
1888
|
+
if (maxFeeBps !== void 0) claimFee(maxFeeBps);
|
|
1889
|
+
const args = [
|
|
1890
|
+
q.key,
|
|
1891
|
+
q.tickLower,
|
|
1892
|
+
q.tickUpper,
|
|
1893
|
+
q.liquidity,
|
|
1894
|
+
to,
|
|
1895
|
+
q.limit0,
|
|
1896
|
+
q.limit1,
|
|
1897
|
+
q.deadline
|
|
1898
|
+
];
|
|
1899
|
+
return prepare({
|
|
1900
|
+
action: maxFeeBps === void 0 ? "withdraw" : "withdrawAndClaim",
|
|
1901
|
+
key: q.key,
|
|
1902
|
+
to,
|
|
1903
|
+
rangeId: q.rangeId,
|
|
1904
|
+
preview: previewValue,
|
|
1905
|
+
data: maxFeeBps === void 0 ? encodeFunctionData2({ abi: marketRampAbi, functionName: "withdraw", args }) : encodeFunctionData2({
|
|
1906
|
+
abi: marketRampAbi,
|
|
1907
|
+
functionName: "withdrawAndClaim",
|
|
1908
|
+
args: [...args, maxFeeBps]
|
|
1909
|
+
})
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
async function send(prepared) {
|
|
1913
|
+
if (!prepared || typeof prepared !== "object" || !store.has(prepared))
|
|
1914
|
+
throw new InvalidMarketConfigError(
|
|
1915
|
+
"prepared",
|
|
1916
|
+
"Transaction was not prepared by this client."
|
|
1917
|
+
);
|
|
1918
|
+
if (submitting.has(prepared) || submitted.has(prepared))
|
|
1919
|
+
throw new InvalidMarketConfigError(
|
|
1920
|
+
"prepared",
|
|
1921
|
+
"Transaction is submitting or already submitted; prepare again for another action."
|
|
1922
|
+
);
|
|
1923
|
+
const request = store.getCanonical(prepared);
|
|
1924
|
+
if (!request || !walletClient)
|
|
1925
|
+
throw new InvalidMarketConfigError("wallet", "A wallet is required.");
|
|
1926
|
+
submitting.add(prepared);
|
|
1927
|
+
try {
|
|
1928
|
+
const live = await account();
|
|
1929
|
+
if (!isAddressEqual10(live.address, request.account))
|
|
1930
|
+
throw new InvalidMarketConfigError("account", "Wallet account changed; prepare again.");
|
|
1931
|
+
await fresh(prepared.context.block, prepared.context.expiresAt);
|
|
1932
|
+
await readMarketSnapshot(context, {}, async (scope) => {
|
|
1933
|
+
const management = prepared.context.management;
|
|
1934
|
+
if (management) {
|
|
1935
|
+
const execution = management.execution;
|
|
1936
|
+
const auth = await inspectMarketManagementAt(
|
|
1937
|
+
scope,
|
|
1938
|
+
management.action,
|
|
1939
|
+
live.address,
|
|
1940
|
+
execution.mode === "schedule" ? execution.when : 0
|
|
1941
|
+
);
|
|
1942
|
+
const canonical = managementRequest(auth, execution, scope.block.timestamp);
|
|
1943
|
+
if (!isAddressEqual10(canonical.to, request.to) || canonical.data !== request.data || auth.operationId !== management.operationId)
|
|
1944
|
+
throw new InvalidMarketConfigError(
|
|
1945
|
+
"management",
|
|
1946
|
+
"Management request changed; prepare again."
|
|
1947
|
+
);
|
|
1948
|
+
} else
|
|
1949
|
+
await assertWriteDeployment(
|
|
1950
|
+
scope,
|
|
1951
|
+
prepared.context.key,
|
|
1952
|
+
prepared.context.action === "setClaimOperator" || prepared.context.action === "claimFor"
|
|
1953
|
+
);
|
|
1954
|
+
});
|
|
1955
|
+
await simulate(request);
|
|
1956
|
+
const finalAccount = await account();
|
|
1957
|
+
if (!isAddressEqual10(finalAccount.address, request.account))
|
|
1958
|
+
throw new InvalidMarketConfigError("account", "Wallet account changed before signing.");
|
|
1959
|
+
await fresh(prepared.context.block, prepared.context.expiresAt);
|
|
1960
|
+
await checkNativeBudget(request);
|
|
1961
|
+
let hash;
|
|
1962
|
+
try {
|
|
1963
|
+
hash = await walletClient.sendTransaction({
|
|
1964
|
+
...request,
|
|
1965
|
+
account: finalAccount,
|
|
1966
|
+
chain: walletClient.chain ?? null
|
|
1967
|
+
});
|
|
1968
|
+
} catch (cause) {
|
|
1969
|
+
throw new MarketTransactionError("send", cause);
|
|
1970
|
+
}
|
|
1971
|
+
sent.set(hash, prepared);
|
|
1972
|
+
submitted.add(prepared);
|
|
1973
|
+
return hash;
|
|
1974
|
+
} finally {
|
|
1975
|
+
submitting.delete(prepared);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
async function waitForTransaction(hash, options = {}) {
|
|
1979
|
+
const prepared = sent.get(hash);
|
|
1980
|
+
if (!prepared)
|
|
1981
|
+
throw new InvalidMarketConfigError(
|
|
1982
|
+
"hash",
|
|
1983
|
+
"Only hashes sent by this client can be reconciled with its prepared action."
|
|
1984
|
+
);
|
|
1985
|
+
if (await publicClient.getChainId() !== chain.chainId)
|
|
1986
|
+
throw new InvalidMarketConfigError("chainId", "Receipt client is on a different chain.");
|
|
1987
|
+
try {
|
|
1988
|
+
let replacement;
|
|
1989
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
1990
|
+
hash,
|
|
1991
|
+
confirmations: chain.confirmations,
|
|
1992
|
+
timeout: options.timeout,
|
|
1993
|
+
onReplaced: ({ reason, transaction }) => {
|
|
1994
|
+
if (reason !== "repriced")
|
|
1995
|
+
replacement = new MarketTransactionReplacedError(hash, transaction.hash, reason);
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
if (replacement) throw replacement;
|
|
1999
|
+
const result = transactionResultFromReceipt(receipt);
|
|
2000
|
+
const managementEvents = [];
|
|
2001
|
+
const management = prepared.context.management;
|
|
2002
|
+
if (receipt.status === "success" && management) {
|
|
2003
|
+
const eventAbi = [
|
|
2004
|
+
...marketRampAbi,
|
|
2005
|
+
...marketRwaRampAbi,
|
|
2006
|
+
...marketPoolRegistryAbi,
|
|
2007
|
+
...marketRampReaderAbi,
|
|
2008
|
+
...marketAccessManagerAbi
|
|
2009
|
+
];
|
|
2010
|
+
for (const log of receipt.logs) {
|
|
2011
|
+
const isAuthority = isAddressEqual10(log.address, chain.deployment.authority.address);
|
|
2012
|
+
if (!isAuthority && !isAddressEqual10(log.address, management.target)) continue;
|
|
2013
|
+
try {
|
|
2014
|
+
const decoded = decodeEventLog({
|
|
2015
|
+
abi: eventAbi,
|
|
2016
|
+
data: log.data,
|
|
2017
|
+
topics: log.topics,
|
|
2018
|
+
strict: true
|
|
2019
|
+
});
|
|
2020
|
+
const args = Object.fromEntries(Object.entries(decoded.args ?? {}));
|
|
2021
|
+
if (isAuthority && args.operationId !== management.operationId) continue;
|
|
2022
|
+
if (!isAuthority && management.execution.mode === "schedule") continue;
|
|
2023
|
+
managementEvents.push({ address: log.address, eventName: decoded.eventName, args });
|
|
2024
|
+
} catch {
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (management.execution.mode !== "direct") {
|
|
2028
|
+
const eventName = management.execution.mode === "schedule" ? "OperationScheduled" : "OperationExecuted";
|
|
2029
|
+
if (!managementEvents.some(
|
|
2030
|
+
(event) => event.eventName === eventName && isAddressEqual10(event.address, chain.deployment.authority.address)
|
|
2031
|
+
))
|
|
2032
|
+
throw new InvalidMarketConfigError(
|
|
2033
|
+
"receipt.events",
|
|
2034
|
+
"Missing matching AccessManager operation event."
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
const events = [];
|
|
2039
|
+
const shareEvents = [];
|
|
2040
|
+
if (receipt.status === "success")
|
|
2041
|
+
for (const log of receipt.logs) {
|
|
2042
|
+
if (!isAddressEqual10(log.address, prepared.context.key.hooks)) continue;
|
|
2043
|
+
try {
|
|
2044
|
+
const decoded = decodeEventLog({
|
|
2045
|
+
abi: marketRampAbi,
|
|
2046
|
+
data: log.data,
|
|
2047
|
+
topics: log.topics,
|
|
2048
|
+
strict: true
|
|
2049
|
+
});
|
|
2050
|
+
const expected = prepared.context;
|
|
2051
|
+
if (decoded.eventName === "Transfer") {
|
|
2052
|
+
const a = decoded.args;
|
|
2053
|
+
if ((expected.action === "transferShares" || expected.action === "transferSharesFrom") && a.id === expected.rangeId && isAddressEqual10(a.from, expected.owner) && isAddressEqual10(a.to, expected.recipient) && isAddressEqual10(a.caller, prepared.request.account))
|
|
2054
|
+
shareEvents.push({
|
|
2055
|
+
eventName: "Transfer",
|
|
2056
|
+
caller: a.caller,
|
|
2057
|
+
from: a.from,
|
|
2058
|
+
to: a.to,
|
|
2059
|
+
rangeId: a.id,
|
|
2060
|
+
amount: a.amount
|
|
2061
|
+
});
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
2064
|
+
if (decoded.eventName === "Approval") {
|
|
2065
|
+
const a = decoded.args;
|
|
2066
|
+
if (expected.action === "approveShares" && a.id === expected.rangeId && isAddressEqual10(a.owner, expected.owner) && isAddressEqual10(a.spender, expected.recipient))
|
|
2067
|
+
shareEvents.push({
|
|
2068
|
+
eventName: "Approval",
|
|
2069
|
+
owner: a.owner,
|
|
2070
|
+
spender: a.spender,
|
|
2071
|
+
rangeId: a.id,
|
|
2072
|
+
amount: a.amount
|
|
2073
|
+
});
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
if (decoded.eventName === "OperatorSet" || decoded.eventName === "ClaimOperatorSet") {
|
|
2077
|
+
const a = decoded.args;
|
|
2078
|
+
if (isAddressEqual10(a.owner, expected.owner) && isAddressEqual10(a.operator, expected.recipient)) {
|
|
2079
|
+
if (decoded.eventName === "OperatorSet" && expected.action === "setShareOperator")
|
|
2080
|
+
shareEvents.push({ eventName: "OperatorSet", ...decoded.args });
|
|
2081
|
+
if (decoded.eventName === "ClaimOperatorSet" && expected.action === "setClaimOperator" && decoded.args.rangeId === expected.rangeId)
|
|
2082
|
+
shareEvents.push({ eventName: "ClaimOperatorSet", ...decoded.args });
|
|
2083
|
+
}
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
if (decoded.eventName !== "Deposited" && decoded.eventName !== "Withdrawn" && decoded.eventName !== "FeesClaimed")
|
|
2087
|
+
continue;
|
|
2088
|
+
const args = decoded.args;
|
|
2089
|
+
const to = "to" in args ? args.to : args.owner;
|
|
2090
|
+
if (!isAddressEqual10(args.owner, prepared.context.owner) || args.rangeId !== prepared.context.rangeId || !isAddressEqual10(to, prepared.context.recipient))
|
|
2091
|
+
continue;
|
|
2092
|
+
events.push({
|
|
2093
|
+
eventName: decoded.eventName,
|
|
2094
|
+
owner: args.owner,
|
|
2095
|
+
rangeId: args.rangeId,
|
|
2096
|
+
amount0: args.amount0,
|
|
2097
|
+
amount1: args.amount1,
|
|
2098
|
+
liquidity: "liquidity" in args ? args.liquidity : null,
|
|
2099
|
+
to
|
|
2100
|
+
});
|
|
2101
|
+
} catch {
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
const requiredEvent = prepared.context.action === "deposit" ? "Deposited" : prepared.context.action === "withdraw" || prepared.context.action === "withdrawAndClaim" ? "Withdrawn" : null;
|
|
2105
|
+
if (receipt.status === "success" && requiredEvent && !events.some((event) => event.eventName === requiredEvent))
|
|
2106
|
+
throw new InvalidMarketConfigError(
|
|
2107
|
+
"receipt.events",
|
|
2108
|
+
"Successful receipt is missing the matching liquidity event."
|
|
2109
|
+
);
|
|
2110
|
+
if (receipt.status === "success" && [
|
|
2111
|
+
"transferShares",
|
|
2112
|
+
"transferSharesFrom",
|
|
2113
|
+
"approveShares",
|
|
2114
|
+
"setShareOperator",
|
|
2115
|
+
"setClaimOperator"
|
|
2116
|
+
].includes(prepared.context.action) && shareEvents.length === 0)
|
|
2117
|
+
throw new InvalidMarketConfigError(
|
|
2118
|
+
"receipt.events",
|
|
2119
|
+
"Successful receipt is missing the matching share/authorization event."
|
|
2120
|
+
);
|
|
2121
|
+
return {
|
|
2122
|
+
hash: result.hash,
|
|
2123
|
+
status: receipt.status === "success" ? "confirmed" : "reverted",
|
|
2124
|
+
receipt,
|
|
2125
|
+
events,
|
|
2126
|
+
shareEvents,
|
|
2127
|
+
managementEvents
|
|
2128
|
+
};
|
|
2129
|
+
} catch (cause) {
|
|
2130
|
+
if (cause instanceof MarketTransactionReplacedError) throw cause;
|
|
2131
|
+
throw new MarketTransactionError("receipt", cause);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
function shareRange(params) {
|
|
2135
|
+
const key = liquidityKey(context, params.key);
|
|
2136
|
+
return {
|
|
2137
|
+
key,
|
|
2138
|
+
rangeId: getMarketRangeId(
|
|
2139
|
+
getMarketPoolId(key),
|
|
2140
|
+
params.tickLower,
|
|
2141
|
+
params.tickUpper,
|
|
2142
|
+
key.tickSpacing
|
|
2143
|
+
)
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
function transferShares(input) {
|
|
2147
|
+
const params = immutableClone(input);
|
|
2148
|
+
const { key, rangeId } = shareRange(params);
|
|
2149
|
+
recipient(context, key, params.to);
|
|
2150
|
+
assertMarketUint(params.amount, "shares", 128);
|
|
2151
|
+
if (params.amount === 0n)
|
|
2152
|
+
throw new InvalidMarketConfigError("shares", "Share transfer amount must be positive.");
|
|
2153
|
+
if (params.owner) assertMarketAddress(params.owner, "owner");
|
|
2154
|
+
return prepare({
|
|
2155
|
+
action: params.owner ? "transferSharesFrom" : "transferShares",
|
|
2156
|
+
key,
|
|
2157
|
+
rangeId,
|
|
2158
|
+
owner: params.owner,
|
|
2159
|
+
to: params.to,
|
|
2160
|
+
validForSeconds: params.validForSeconds,
|
|
2161
|
+
data: params.owner ? encodeFunctionData2({
|
|
2162
|
+
abi: marketRampAbi,
|
|
2163
|
+
functionName: "transferFrom",
|
|
2164
|
+
args: [params.owner, params.to, rangeId, params.amount]
|
|
2165
|
+
}) : encodeFunctionData2({
|
|
2166
|
+
abi: marketRampAbi,
|
|
2167
|
+
functionName: "transfer",
|
|
2168
|
+
args: [params.to, rangeId, params.amount]
|
|
2169
|
+
})
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
return {
|
|
2173
|
+
getManagementAuthorization: (input, owner, options = {}) => {
|
|
2174
|
+
const action = immutableClone(input);
|
|
2175
|
+
return readMarketSnapshot(
|
|
2176
|
+
context,
|
|
2177
|
+
{},
|
|
2178
|
+
(scope) => inspectMarketManagementAt(scope, action, owner, options.when)
|
|
2179
|
+
);
|
|
2180
|
+
},
|
|
2181
|
+
prepareManagement: async (input, options = {}) => {
|
|
2182
|
+
const action = immutableClone(input);
|
|
2183
|
+
const execution = immutableClone(options.execution ?? { mode: "direct" });
|
|
2184
|
+
const call = encodeMarketManagement(context, action);
|
|
2185
|
+
return prepare({
|
|
2186
|
+
action: "management",
|
|
2187
|
+
key: call.key,
|
|
2188
|
+
to: call.to,
|
|
2189
|
+
target: call.to,
|
|
2190
|
+
data: call.data,
|
|
2191
|
+
rangeId: null,
|
|
2192
|
+
validForSeconds: options.validForSeconds,
|
|
2193
|
+
management: { action, execution }
|
|
2194
|
+
});
|
|
2195
|
+
},
|
|
2196
|
+
previewDeposit: (params) => preview("deposit", params),
|
|
2197
|
+
previewWithdraw: (params) => preview("withdraw", params),
|
|
2198
|
+
getSpendStatus: (params) => getMarketSpendStatus(context, params),
|
|
2199
|
+
prepareApproval: async (input) => {
|
|
2200
|
+
const params = immutableClone(input);
|
|
2201
|
+
const key = liquidityKey(context, params.key);
|
|
2202
|
+
if (params.currency !== 0 && params.currency !== 1)
|
|
2203
|
+
throw new InvalidMarketConfigError("currency", "Choose currency 0 or 1.");
|
|
2204
|
+
if (params.currency === 0 && isAddressEqual10(key.currency0, zeroAddress7))
|
|
2205
|
+
throw new InvalidMarketConfigError(
|
|
2206
|
+
"currency",
|
|
2207
|
+
"Native currency does not support approval."
|
|
2208
|
+
);
|
|
2209
|
+
assertMarketUint(params.amount, "amount", 128);
|
|
2210
|
+
const live = await account();
|
|
2211
|
+
return prepare({
|
|
2212
|
+
action: "approve",
|
|
2213
|
+
key,
|
|
2214
|
+
to: live.address,
|
|
2215
|
+
target: params.currency === 0 ? key.currency0 : key.currency1,
|
|
2216
|
+
rangeId: null,
|
|
2217
|
+
validForSeconds: params.validForSeconds,
|
|
2218
|
+
data: encodeFunctionData2({
|
|
2219
|
+
abi: erc20Abi2,
|
|
2220
|
+
functionName: "approve",
|
|
2221
|
+
args: [key.hooks, params.amount]
|
|
2222
|
+
})
|
|
2223
|
+
});
|
|
2224
|
+
},
|
|
2225
|
+
prepareDeposit: async (value) => {
|
|
2226
|
+
const q = requirePreview(value, "deposit");
|
|
2227
|
+
const live = await account();
|
|
2228
|
+
return prepare({
|
|
2229
|
+
action: "deposit",
|
|
2230
|
+
key: q.key,
|
|
2231
|
+
to: live.address,
|
|
2232
|
+
rangeId: q.rangeId,
|
|
2233
|
+
preview: value,
|
|
2234
|
+
data: encodeFunctionData2({
|
|
2235
|
+
abi: marketRampAbi,
|
|
2236
|
+
functionName: "deposit",
|
|
2237
|
+
args: [q.key, q.tickLower, q.tickUpper, q.liquidity, q.limit0, q.limit1, q.deadline]
|
|
2238
|
+
})
|
|
2239
|
+
});
|
|
2240
|
+
},
|
|
2241
|
+
prepareWithdraw: (value, to) => prepareWithdraw(value, to),
|
|
2242
|
+
prepareWithdrawAndClaim: (value, to, maxFeeBps) => prepareWithdraw(value, to, maxFeeBps),
|
|
2243
|
+
prepareClaim: (input) => {
|
|
2244
|
+
const params = immutableClone(input);
|
|
2245
|
+
const key = liquidityKey(context, params.key);
|
|
2246
|
+
recipient(context, key, params.to);
|
|
2247
|
+
claimFee(params.maxFeeBps);
|
|
2248
|
+
const rangeId = getMarketRangeId(
|
|
2249
|
+
getMarketPoolId(key),
|
|
2250
|
+
params.tickLower,
|
|
2251
|
+
params.tickUpper,
|
|
2252
|
+
key.tickSpacing
|
|
2253
|
+
);
|
|
2254
|
+
return prepare({
|
|
2255
|
+
action: "claim",
|
|
2256
|
+
key,
|
|
2257
|
+
to: params.to,
|
|
2258
|
+
rangeId,
|
|
2259
|
+
validForSeconds: params.validForSeconds,
|
|
2260
|
+
data: encodeFunctionData2({
|
|
2261
|
+
abi: marketRampAbi,
|
|
2262
|
+
functionName: "claimFees",
|
|
2263
|
+
args: [key, params.tickLower, params.tickUpper, params.to, params.maxFeeBps]
|
|
2264
|
+
})
|
|
2265
|
+
});
|
|
2266
|
+
},
|
|
2267
|
+
prepareTransferShares: (params) => transferShares({ ...params, owner: void 0 }),
|
|
2268
|
+
prepareTransferSharesFrom: (params) => {
|
|
2269
|
+
assertMarketAddress(params.owner, "owner");
|
|
2270
|
+
return transferShares(params);
|
|
2271
|
+
},
|
|
2272
|
+
prepareApproveShares: (input) => {
|
|
2273
|
+
const params = immutableClone(input);
|
|
2274
|
+
const { key, rangeId } = shareRange(params);
|
|
2275
|
+
assertMarketAddress(params.spender, "spender");
|
|
2276
|
+
assertMarketUint(params.amount, "allowance");
|
|
2277
|
+
return prepare({
|
|
2278
|
+
action: "approveShares",
|
|
2279
|
+
key,
|
|
2280
|
+
rangeId,
|
|
2281
|
+
to: params.spender,
|
|
2282
|
+
validForSeconds: params.validForSeconds,
|
|
2283
|
+
data: encodeFunctionData2({
|
|
2284
|
+
abi: marketRampAbi,
|
|
2285
|
+
functionName: "approve",
|
|
2286
|
+
args: [params.spender, rangeId, params.amount]
|
|
2287
|
+
})
|
|
2288
|
+
});
|
|
2289
|
+
},
|
|
2290
|
+
prepareSetShareOperator: (input) => {
|
|
2291
|
+
const params = immutableClone(input);
|
|
2292
|
+
const key = liquidityKey(context, params.key);
|
|
2293
|
+
assertMarketAddress(params.operator, "operator");
|
|
2294
|
+
if (typeof params.approved !== "boolean")
|
|
2295
|
+
throw new InvalidMarketConfigError("approved", "Expected a boolean.");
|
|
2296
|
+
return prepare({
|
|
2297
|
+
action: "setShareOperator",
|
|
2298
|
+
key,
|
|
2299
|
+
rangeId: null,
|
|
2300
|
+
to: params.operator,
|
|
2301
|
+
validForSeconds: params.validForSeconds,
|
|
2302
|
+
data: encodeFunctionData2({
|
|
2303
|
+
abi: marketRampAbi,
|
|
2304
|
+
functionName: "setOperator",
|
|
2305
|
+
args: [params.operator, params.approved]
|
|
2306
|
+
})
|
|
2307
|
+
});
|
|
2308
|
+
},
|
|
2309
|
+
prepareSetClaimOperator: (input) => {
|
|
2310
|
+
const params = immutableClone(input);
|
|
2311
|
+
const { key, rangeId } = shareRange(params);
|
|
2312
|
+
assertMarketAddress(params.operator, "operator");
|
|
2313
|
+
if (typeof params.approved !== "boolean")
|
|
2314
|
+
throw new InvalidMarketConfigError("approved", "Expected a boolean.");
|
|
2315
|
+
return prepare({
|
|
2316
|
+
action: "setClaimOperator",
|
|
2317
|
+
key,
|
|
2318
|
+
rangeId,
|
|
2319
|
+
to: params.operator,
|
|
2320
|
+
validForSeconds: params.validForSeconds,
|
|
2321
|
+
data: encodeFunctionData2({
|
|
2322
|
+
abi: marketRampAbi,
|
|
2323
|
+
functionName: "setClaimOperator",
|
|
2324
|
+
args: [rangeId, params.operator, params.approved]
|
|
2325
|
+
})
|
|
2326
|
+
});
|
|
2327
|
+
},
|
|
2328
|
+
prepareClaimFeesFor: (input) => {
|
|
2329
|
+
const params = immutableClone(input);
|
|
2330
|
+
const { key, rangeId } = shareRange(params);
|
|
2331
|
+
assertMarketAddress(params.owner, "owner");
|
|
2332
|
+
recipient(context, key, params.to);
|
|
2333
|
+
claimFee(params.maxFeeBps);
|
|
2334
|
+
return prepare({
|
|
2335
|
+
action: "claimFor",
|
|
2336
|
+
key,
|
|
2337
|
+
rangeId,
|
|
2338
|
+
owner: params.owner,
|
|
2339
|
+
to: params.to,
|
|
2340
|
+
validForSeconds: params.validForSeconds,
|
|
2341
|
+
data: encodeFunctionData2({
|
|
2342
|
+
abi: marketRampAbi,
|
|
2343
|
+
functionName: "claimFeesFor",
|
|
2344
|
+
args: [
|
|
2345
|
+
key,
|
|
2346
|
+
params.tickLower,
|
|
2347
|
+
params.tickUpper,
|
|
2348
|
+
params.owner,
|
|
2349
|
+
params.to,
|
|
2350
|
+
params.maxFeeBps
|
|
2351
|
+
]
|
|
2352
|
+
})
|
|
2353
|
+
});
|
|
2354
|
+
},
|
|
2355
|
+
send,
|
|
2356
|
+
waitForTransaction
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// src/verify.ts
|
|
2361
|
+
import { isAddressEqual as isAddressEqual11, keccak256 as keccak2566 } from "viem";
|
|
2362
|
+
function assertEqual(field, expected, actual) {
|
|
2363
|
+
if (expected !== actual) throw new MarketDeploymentVerificationError(field, expected, actual);
|
|
2364
|
+
}
|
|
2365
|
+
async function verifyMarketDeployment(publicClient, input, options = {}) {
|
|
2366
|
+
const deployment = defineMarketDeployment(input);
|
|
2367
|
+
try {
|
|
2368
|
+
assertEqual("chainId", deployment.chainId, await publicClient.getChainId());
|
|
2369
|
+
const block = await publicClient.getBlock(
|
|
2370
|
+
options.blockNumber === void 0 ? { blockTag: "latest" } : { blockNumber: options.blockNumber }
|
|
2371
|
+
);
|
|
2372
|
+
if (block.number === null || block.hash === null) {
|
|
2373
|
+
throw new MarketDeploymentVerificationError("block", "mined block", block);
|
|
2374
|
+
}
|
|
2375
|
+
const blockNumber = block.number;
|
|
2376
|
+
const contracts = [
|
|
2377
|
+
deployment.poolManager,
|
|
2378
|
+
deployment.registry,
|
|
2379
|
+
deployment.authority,
|
|
2380
|
+
...deployment.reader ? [deployment.reader] : [],
|
|
2381
|
+
...deployment.poolStateReader?.kind === "state-view" ? [deployment.poolStateReader.contract] : [],
|
|
2382
|
+
...deployment.hooks.flatMap((hook2) => [
|
|
2383
|
+
hook2,
|
|
2384
|
+
hook2.claimFeePolicy,
|
|
2385
|
+
...hook2.calendar ? [hook2.calendar] : []
|
|
2386
|
+
])
|
|
2387
|
+
];
|
|
2388
|
+
for (const contract2 of contracts) {
|
|
2389
|
+
const code = await publicClient.getCode({ address: contract2.address, blockNumber });
|
|
2390
|
+
if (!code || code === "0x") {
|
|
2391
|
+
throw new MarketDeploymentVerificationError(contract2.address, "deployed code", code);
|
|
2392
|
+
}
|
|
2393
|
+
assertEqual(contract2.address, contract2.runtimeCodeHash.toLowerCase(), keccak2566(code));
|
|
2394
|
+
}
|
|
2395
|
+
const assertAddress = (field, expected, actual) => {
|
|
2396
|
+
if (!isAddressEqual11(expected, actual)) {
|
|
2397
|
+
throw new MarketDeploymentVerificationError(field, expected, actual);
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
if (deployment.poolStateReader?.kind === "state-view") {
|
|
2401
|
+
assertAddress(
|
|
2402
|
+
"stateView.poolManager",
|
|
2403
|
+
deployment.poolManager.address,
|
|
2404
|
+
await publicClient.readContract({
|
|
2405
|
+
address: deployment.poolStateReader.contract.address,
|
|
2406
|
+
abi: marketStateViewAbi,
|
|
2407
|
+
functionName: "poolManager",
|
|
2408
|
+
blockNumber
|
|
2409
|
+
})
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
assertAddress(
|
|
2413
|
+
"registry.authority",
|
|
2414
|
+
deployment.authority.address,
|
|
2415
|
+
await publicClient.readContract({
|
|
2416
|
+
address: deployment.registry.address,
|
|
2417
|
+
abi: marketPoolRegistryAbi,
|
|
2418
|
+
functionName: "authority",
|
|
2419
|
+
blockNumber
|
|
2420
|
+
})
|
|
2421
|
+
);
|
|
2422
|
+
if (deployment.reader) {
|
|
2423
|
+
const reader = {
|
|
2424
|
+
address: deployment.reader.address,
|
|
2425
|
+
abi: marketRampReaderAbi,
|
|
2426
|
+
blockNumber
|
|
2427
|
+
};
|
|
2428
|
+
assertAddress(
|
|
2429
|
+
"reader.registry",
|
|
2430
|
+
deployment.registry.address,
|
|
2431
|
+
await publicClient.readContract({ ...reader, functionName: "registry" })
|
|
2432
|
+
);
|
|
2433
|
+
assertAddress(
|
|
2434
|
+
"reader.authority",
|
|
2435
|
+
deployment.authority.address,
|
|
2436
|
+
await publicClient.readContract({ ...reader, functionName: "authority" })
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
for (const hook2 of deployment.hooks) {
|
|
2440
|
+
const target = { address: hook2.address, abi: marketRampAbi, blockNumber };
|
|
2441
|
+
assertAddress(
|
|
2442
|
+
`${hook2.address}.poolManager`,
|
|
2443
|
+
deployment.poolManager.address,
|
|
2444
|
+
await publicClient.readContract({ ...target, functionName: "poolManager" })
|
|
2445
|
+
);
|
|
2446
|
+
assertAddress(
|
|
2447
|
+
`${hook2.address}.authority`,
|
|
2448
|
+
deployment.authority.address,
|
|
2449
|
+
await publicClient.readContract({ ...target, functionName: "authority" })
|
|
2450
|
+
);
|
|
2451
|
+
assertAddress(
|
|
2452
|
+
`${hook2.address}.claimFeePolicy`,
|
|
2453
|
+
hook2.claimFeePolicy.address,
|
|
2454
|
+
await publicClient.readContract({ ...target, functionName: "claimFeePolicy" })
|
|
2455
|
+
);
|
|
2456
|
+
assertAddress(
|
|
2457
|
+
`${hook2.address}.policy.ledger`,
|
|
2458
|
+
hook2.address,
|
|
2459
|
+
await publicClient.readContract({
|
|
2460
|
+
address: hook2.claimFeePolicy.address,
|
|
2461
|
+
abi: marketClaimFeePolicyAbi,
|
|
2462
|
+
functionName: "ledger",
|
|
2463
|
+
blockNumber
|
|
2464
|
+
})
|
|
2465
|
+
);
|
|
2466
|
+
assertEqual(
|
|
2467
|
+
`${hook2.address}.MAX_POKE_DISCOUNT_BPS`,
|
|
2468
|
+
5e3,
|
|
2469
|
+
await publicClient.readContract({ ...target, functionName: "MAX_POKE_DISCOUNT_BPS" })
|
|
2470
|
+
);
|
|
2471
|
+
if (hook2.calendar) {
|
|
2472
|
+
assertAddress(
|
|
2473
|
+
`${hook2.address}.calendar`,
|
|
2474
|
+
hook2.calendar.address,
|
|
2475
|
+
await publicClient.readContract({
|
|
2476
|
+
address: hook2.address,
|
|
2477
|
+
abi: marketRwaRampAbi,
|
|
2478
|
+
functionName: "marketCalendar",
|
|
2479
|
+
blockNumber
|
|
2480
|
+
})
|
|
2481
|
+
);
|
|
2482
|
+
assertAddress(
|
|
2483
|
+
`${hook2.address}.calendar.controller`,
|
|
2484
|
+
hook2.address,
|
|
2485
|
+
await publicClient.readContract({
|
|
2486
|
+
address: hook2.calendar.address,
|
|
2487
|
+
abi: marketMarketCalendarAbi,
|
|
2488
|
+
functionName: "controller",
|
|
2489
|
+
blockNumber
|
|
2490
|
+
})
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
if (deployment.reader) {
|
|
2494
|
+
assertEqual(
|
|
2495
|
+
`${hook2.address}.readerKind`,
|
|
2496
|
+
hook2.kind === "rwa-ramp" ? 2 : 1,
|
|
2497
|
+
await publicClient.readContract({
|
|
2498
|
+
address: deployment.reader.address,
|
|
2499
|
+
abi: marketRampReaderAbi,
|
|
2500
|
+
functionName: "hookKind",
|
|
2501
|
+
args: [hook2.address],
|
|
2502
|
+
blockNumber
|
|
2503
|
+
})
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
const finalBlock = await publicClient.getBlock({ blockNumber });
|
|
2508
|
+
assertEqual("blockHash", block.hash, finalBlock.hash);
|
|
2509
|
+
return Object.freeze({
|
|
2510
|
+
chainId: deployment.chainId,
|
|
2511
|
+
deploymentId: deployment.deploymentId,
|
|
2512
|
+
blockNumber,
|
|
2513
|
+
blockHash: block.hash,
|
|
2514
|
+
scope: "code-and-wiring",
|
|
2515
|
+
checkedContracts: contracts.length
|
|
2516
|
+
});
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
if (error instanceof MarketDeploymentVerificationError) throw error;
|
|
2519
|
+
throw new MarketReadError(error);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// src/client.ts
|
|
2524
|
+
function createMarketClient(options) {
|
|
2525
|
+
const chain = defineMarketChain(options.chain);
|
|
2526
|
+
const publicClient = options.publicClient;
|
|
2527
|
+
if (publicClient.chain && publicClient.chain.id !== chain.chainId) {
|
|
2528
|
+
throw new InvalidMarketConfigError("publicClient.chain", "Public client chain does not match.");
|
|
2529
|
+
}
|
|
2530
|
+
const context = { publicClient, deployment: chain.deployment };
|
|
2531
|
+
return Object.freeze({
|
|
2532
|
+
chain,
|
|
2533
|
+
getClaimOperator: (params) => getMarketClaimOperator(context, params),
|
|
2534
|
+
getShareAuthorization: (params) => getMarketShareAuthorization(context, params),
|
|
2535
|
+
getFeeSnapshot: (params) => getMarketFeeSnapshot(context, params),
|
|
2536
|
+
estimateClaimableFees: (params) => estimateMarketClaimableFees(context, params),
|
|
2537
|
+
...createMarketWriteActions(publicClient, options.walletClient, chain),
|
|
2538
|
+
verifyDeployment: (readOptions) => verifyMarketDeployment(publicClient, chain.deployment, readOptions),
|
|
2539
|
+
getPool: (poolId, readOptions) => getMarketPool(context, poolId, readOptions),
|
|
2540
|
+
listPools: (params) => listMarketPools(context, params),
|
|
2541
|
+
getCurrentFees: (poolId, readOptions) => getMarketCurrentFees(context, poolId, readOptions),
|
|
2542
|
+
getFeeBreakdown: (poolId, zeroForOne, readOptions) => getMarketFeeBreakdown(context, poolId, zeroForOne, readOptions),
|
|
2543
|
+
previewFeeAt: (poolId, zeroForOne, timestamp, readOptions) => previewMarketFeeAt(context, poolId, zeroForOne, timestamp, readOptions),
|
|
2544
|
+
getSession: (poolId, readOptions) => getMarketSession(context, poolId, readOptions),
|
|
2545
|
+
getRange: (params) => getMarketRange(context, params),
|
|
2546
|
+
getPosition: (params) => getMarketPosition(context, params),
|
|
2547
|
+
getPoolState: (poolId, readOptions) => getMarketPoolState(context, poolId, readOptions)
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/test-pools.ts
|
|
2552
|
+
var marketTestPools = [
|
|
2553
|
+
{
|
|
2554
|
+
chainId: 5042,
|
|
2555
|
+
name: "tBTC/tUSDC",
|
|
2556
|
+
testAssets: true,
|
|
2557
|
+
permissionlessMint: true,
|
|
2558
|
+
hookKind: "ramp",
|
|
2559
|
+
poolId: "0x1a325fe4671d6584778464cbe5ea68b111c2a72718f128a9427717298e85e2cd",
|
|
2560
|
+
key: {
|
|
2561
|
+
currency0: "0x442fc5b4d43fb7ec18ff8ac0a8fb9fdd2efec0db",
|
|
2562
|
+
currency1: "0x44e90eb500868af9b0b16406f3ce165c90e1e85e",
|
|
2563
|
+
fee: 8388608,
|
|
2564
|
+
tickSpacing: 1,
|
|
2565
|
+
hooks: "0x928f4c9b7f80dcbe95b606867e76336a956fa080"
|
|
2566
|
+
},
|
|
2567
|
+
base: {
|
|
2568
|
+
symbol: "tBTC",
|
|
2569
|
+
address: "0x442fc5b4d43fb7ec18ff8ac0a8fb9fdd2efec0db",
|
|
2570
|
+
decimals: 8
|
|
2571
|
+
},
|
|
2572
|
+
quote: {
|
|
2573
|
+
symbol: "tUSDC",
|
|
2574
|
+
address: "0x44e90eb500868af9b0b16406f3ce165c90e1e85e",
|
|
2575
|
+
decimals: 6
|
|
2576
|
+
},
|
|
2577
|
+
components: {
|
|
2578
|
+
poolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
|
|
2579
|
+
registry: "0x3386feb5016983790ef05410e888a8b7bf5a722d",
|
|
2580
|
+
reader: "0xbd782f3de07f2c8f27ef3a2cb165f36c5a505e45",
|
|
2581
|
+
accessManager: "0x005710955b542c11fa2259721840329cddf44914"
|
|
2582
|
+
},
|
|
2583
|
+
source: {
|
|
2584
|
+
commit: "cf16bd9376c12ea50de04658b279176d8d56e4f5",
|
|
2585
|
+
path: "docs/test-pools/arc.json",
|
|
2586
|
+
blockNumber: 23523593,
|
|
2587
|
+
blockHash: "0xea3eb1e8cd308955ad999df48212f3b839e3eec7b55cf7702aba213d548f847d"
|
|
2588
|
+
}
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
chainId: 56,
|
|
2592
|
+
name: "tBNB/tUSDT",
|
|
2593
|
+
testAssets: true,
|
|
2594
|
+
permissionlessMint: true,
|
|
2595
|
+
hookKind: "ramp",
|
|
2596
|
+
poolId: "0xb1cf54cb728bb02bb40def7d4f088a7274f35ef05b6257cb518d5f10be438bee",
|
|
2597
|
+
key: {
|
|
2598
|
+
currency0: "0x78e708f1f8fe842abaaeaf23ade31e524e153980",
|
|
2599
|
+
currency1: "0x8a5218b1b296ced5c88cd55c256fcb58c32f227d",
|
|
2600
|
+
fee: 8388608,
|
|
2601
|
+
tickSpacing: 1,
|
|
2602
|
+
hooks: "0xa6a75d930f7a920391c4afffe744069233c4e080"
|
|
2603
|
+
},
|
|
2604
|
+
base: {
|
|
2605
|
+
symbol: "tBNB",
|
|
2606
|
+
address: "0x78e708f1f8fe842abaaeaf23ade31e524e153980",
|
|
2607
|
+
decimals: 18
|
|
2608
|
+
},
|
|
2609
|
+
quote: {
|
|
2610
|
+
symbol: "tUSDT",
|
|
2611
|
+
address: "0x8a5218b1b296ced5c88cd55c256fcb58c32f227d",
|
|
2612
|
+
decimals: 18
|
|
2613
|
+
},
|
|
2614
|
+
components: {
|
|
2615
|
+
poolManager: "0x28e2Ea090877bF75740558f6BFB36A5ffeE9e9dF",
|
|
2616
|
+
registry: "0xe75be888ae3d92be2f962f62f78775228eb3cbc9",
|
|
2617
|
+
reader: "0x2fd8d887a67bf61166e5aacae375cda0ad72d292",
|
|
2618
|
+
accessManager: "0x09e9f944a86c5c239eb0dceac85e1f4805d61256"
|
|
2619
|
+
},
|
|
2620
|
+
source: {
|
|
2621
|
+
commit: "cf16bd9376c12ea50de04658b279176d8d56e4f5",
|
|
2622
|
+
path: "docs/test-pools/bsc.json",
|
|
2623
|
+
blockNumber: 124890195,
|
|
2624
|
+
blockHash: "0xba1ae7df7b719fc6d0cf4b49a2278926fc6c62aaf12e2bc49c9788579bba948e"
|
|
2625
|
+
}
|
|
2626
|
+
},
|
|
2627
|
+
{
|
|
2628
|
+
chainId: 56,
|
|
2629
|
+
name: "tQQQ/tUSDT",
|
|
2630
|
+
testAssets: true,
|
|
2631
|
+
permissionlessMint: true,
|
|
2632
|
+
hookKind: "rwa-ramp",
|
|
2633
|
+
poolId: "0x9ccf485776611256dd717db8c53808e370160d8c90668a75c6d456dd07f21423",
|
|
2634
|
+
key: {
|
|
2635
|
+
currency0: "0x51984c455a78c072f9a23453e236bd3729dc3efd",
|
|
2636
|
+
currency1: "0x8a5218b1b296ced5c88cd55c256fcb58c32f227d",
|
|
2637
|
+
fee: 8388608,
|
|
2638
|
+
tickSpacing: 1,
|
|
2639
|
+
hooks: "0x7b144d9583c71a1b20cb26954d2ccd330af46080"
|
|
2640
|
+
},
|
|
2641
|
+
base: {
|
|
2642
|
+
symbol: "tQQQ",
|
|
2643
|
+
address: "0x51984c455a78c072f9a23453e236bd3729dc3efd",
|
|
2644
|
+
decimals: 18
|
|
2645
|
+
},
|
|
2646
|
+
quote: {
|
|
2647
|
+
symbol: "tUSDT",
|
|
2648
|
+
address: "0x8a5218b1b296ced5c88cd55c256fcb58c32f227d",
|
|
2649
|
+
decimals: 18
|
|
2650
|
+
},
|
|
2651
|
+
components: {
|
|
2652
|
+
poolManager: "0x28e2Ea090877bF75740558f6BFB36A5ffeE9e9dF",
|
|
2653
|
+
registry: "0xe75be888ae3d92be2f962f62f78775228eb3cbc9",
|
|
2654
|
+
reader: "0x2fd8d887a67bf61166e5aacae375cda0ad72d292",
|
|
2655
|
+
accessManager: "0x09e9f944a86c5c239eb0dceac85e1f4805d61256"
|
|
2656
|
+
},
|
|
2657
|
+
source: {
|
|
2658
|
+
commit: "cf16bd9376c12ea50de04658b279176d8d56e4f5",
|
|
2659
|
+
path: "docs/test-pools/bsc.json",
|
|
2660
|
+
blockNumber: 124890195,
|
|
2661
|
+
blockHash: "0xba1ae7df7b719fc6d0cf4b49a2278926fc6c62aaf12e2bc49c9788579bba948e"
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
];
|
|
2665
|
+
export {
|
|
2666
|
+
InvalidMarketConfigError,
|
|
2667
|
+
MarketDeploymentVerificationError,
|
|
2668
|
+
MarketFeatureUnavailableError,
|
|
2669
|
+
MarketReadError,
|
|
2670
|
+
MarketSettlingError,
|
|
2671
|
+
MarketTransactionError,
|
|
2672
|
+
MarketTransactionReplacedError,
|
|
2673
|
+
calculateMarketClaimableFees,
|
|
2674
|
+
createMarketClient,
|
|
2675
|
+
defineMarketChain,
|
|
2676
|
+
defineMarketDeployment,
|
|
2677
|
+
defineMarketPoolKey,
|
|
2678
|
+
estimateMarketClaimableFees,
|
|
2679
|
+
getMarketAmountsForLiquidity,
|
|
2680
|
+
getMarketClaimOperator,
|
|
2681
|
+
getMarketCurrentFees,
|
|
2682
|
+
getMarketFeeBreakdown,
|
|
2683
|
+
getMarketFeeSnapshot,
|
|
2684
|
+
getMarketLiquidityForAmounts,
|
|
2685
|
+
getMarketPool,
|
|
2686
|
+
getMarketPoolId,
|
|
2687
|
+
getMarketPoolState,
|
|
2688
|
+
getMarketPosition,
|
|
2689
|
+
getMarketPriceRatio,
|
|
2690
|
+
getMarketRange,
|
|
2691
|
+
getMarketRangeId,
|
|
2692
|
+
getMarketSession,
|
|
2693
|
+
getMarketShareAuthorization,
|
|
2694
|
+
getMarketSpendStatus,
|
|
2695
|
+
getMarketSqrtPriceAtTick,
|
|
2696
|
+
getMarketSwapDirection,
|
|
2697
|
+
getMarketTickAtSqrtPrice,
|
|
2698
|
+
listMarketPools,
|
|
2699
|
+
marketDaysInMonth,
|
|
2700
|
+
marketTestPools,
|
|
2701
|
+
packMarketDayOverrides,
|
|
2702
|
+
previewMarketFeeAt,
|
|
2703
|
+
verifyMarketDeployment
|
|
2704
|
+
};
|
|
2705
|
+
//# sourceMappingURL=index.js.map
|