@black-market/sdk 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -20
- package/dist/abyss.d.ts +4 -2748
- package/dist/abyss.d.ts.map +1 -1
- package/dist/abyss.js +3 -1029
- package/dist/abyss.js.map +1 -1
- package/dist/addresses.d.ts +3 -7
- package/dist/addresses.d.ts.map +1 -1
- package/dist/addresses.js +6 -21
- package/dist/addresses.js.map +1 -1
- package/dist/auction.d.ts +2 -2
- package/dist/auction.d.ts.map +1 -1
- package/dist/auction.js +2 -2
- package/dist/auction.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/launch-api.d.ts +151 -0
- package/dist/launch-api.d.ts.map +1 -0
- package/dist/launch-api.js +117 -0
- package/dist/launch-api.js.map +1 -0
- package/dist/launch.d.ts +23 -185
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +35 -272
- package/dist/launch.js.map +1 -1
- package/dist/lifecycle/abi.d.ts +6391 -0
- package/dist/lifecycle/abi.d.ts.map +1 -0
- package/dist/lifecycle/abi.js +335 -0
- package/dist/lifecycle/abi.js.map +1 -0
- package/dist/lifecycle/authors.d.ts +133 -0
- package/dist/lifecycle/authors.d.ts.map +1 -0
- package/dist/lifecycle/authors.js +217 -0
- package/dist/lifecycle/authors.js.map +1 -0
- package/dist/lifecycle/fork.d.ts +15 -0
- package/dist/lifecycle/fork.d.ts.map +1 -0
- package/dist/lifecycle/fork.js +36 -0
- package/dist/lifecycle/fork.js.map +1 -0
- package/dist/lifecycle/index.d.ts +10 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +10 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/markets.d.ts +349 -0
- package/dist/lifecycle/markets.d.ts.map +1 -0
- package/dist/lifecycle/markets.js +186 -0
- package/dist/lifecycle/markets.js.map +1 -0
- package/dist/lifecycle/planner.d.ts +20 -0
- package/dist/lifecycle/planner.d.ts.map +1 -0
- package/dist/lifecycle/planner.js +481 -0
- package/dist/lifecycle/planner.js.map +1 -0
- package/dist/lifecycle/progress.d.ts +43 -0
- package/dist/lifecycle/progress.d.ts.map +1 -0
- package/dist/lifecycle/progress.js +449 -0
- package/dist/lifecycle/progress.js.map +1 -0
- package/dist/lifecycle/rpc.d.ts +17 -0
- package/dist/lifecycle/rpc.d.ts.map +1 -0
- package/dist/lifecycle/rpc.js +100 -0
- package/dist/lifecycle/rpc.js.map +1 -0
- package/dist/lifecycle/schema.d.ts +1068 -0
- package/dist/lifecycle/schema.d.ts.map +1 -0
- package/dist/lifecycle/schema.js +335 -0
- package/dist/lifecycle/schema.js.map +1 -0
- package/dist/lifecycle/simulation.d.ts +3 -0
- package/dist/lifecycle/simulation.d.ts.map +1 -0
- package/dist/lifecycle/simulation.js +393 -0
- package/dist/lifecycle/simulation.js.map +1 -0
- package/dist/lifecycle/types.d.ts +273 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lifecycle/types.js +11 -0
- package/dist/lifecycle/types.js.map +1 -0
- package/docs/launch-lifecycle.md +243 -0
- package/package.json +12 -3
- package/src/abyss.ts +3 -1059
- package/src/addresses.ts +9 -26
- package/src/auction.ts +2 -2
- package/src/index.ts +2 -0
- package/src/launch-api.ts +110 -0
- package/src/launch.ts +43 -411
- package/src/lifecycle/abi.ts +343 -0
- package/src/lifecycle/authors.ts +203 -0
- package/src/lifecycle/fork.ts +36 -0
- package/src/lifecycle/index.ts +9 -0
- package/src/lifecycle/markets.ts +205 -0
- package/src/lifecycle/planner.ts +396 -0
- package/src/lifecycle/progress.ts +378 -0
- package/src/lifecycle/rpc.ts +88 -0
- package/src/lifecycle/schema.ts +387 -0
- package/src/lifecycle/simulation.ts +329 -0
- package/src/lifecycle/types.ts +132 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LifecyclePlanningError, type ControlledLifecycleFork, type LifecycleRpcClient } from "./types.js";
|
|
2
|
+
import { rpcObject } from "./rpc.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Connects an explicitly disposable loopback fork. Only the fork receives resets/transactions;
|
|
6
|
+
* sourceRpcUrl is used by the fork node for read-only canonical state synchronization.
|
|
7
|
+
*/
|
|
8
|
+
export function createControlledLifecycleFork(options: {
|
|
9
|
+
sourceRpcUrl: string; forkRpcUrl: string; allowTransactions: true;
|
|
10
|
+
fetch?: typeof globalThis.fetch; impersonation?: "anvil" | "hardhat" | "none";
|
|
11
|
+
resetMethod?: "anvil_reset" | "hardhat_reset"; receiptTimeoutMs?: number;
|
|
12
|
+
}): ControlledLifecycleFork {
|
|
13
|
+
const source = new URL(options.sourceRpcUrl);
|
|
14
|
+
const fork = new URL(options.forkRpcUrl);
|
|
15
|
+
if (!["http:", "https:"].includes(source.protocol) || !["http:", "https:"].includes(fork.protocol)) throw new LifecyclePlanningError("UNSAFE_FORK", "Controlled fork RPCs must use HTTP(S)");
|
|
16
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]", "::1"];
|
|
17
|
+
const sourcePort = source.port || (source.protocol === "https:" ? "443" : "80");
|
|
18
|
+
const forkPort = fork.port || (fork.protocol === "https:" ? "443" : "80");
|
|
19
|
+
if (!loopback.includes(fork.hostname) || (loopback.includes(source.hostname) && sourcePort === forkPort) || source.href === fork.href || options.allowTransactions !== true) throw new LifecyclePlanningError("UNSAFE_FORK", "Controlled fork must be an explicit, distinct disposable loopback RPC, not a localhost/IP alias of the source");
|
|
20
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
21
|
+
let id = 0;
|
|
22
|
+
const client: LifecycleRpcClient = {
|
|
23
|
+
async request({ method, params }: { method: string; params?: readonly unknown[] }) {
|
|
24
|
+
const response = await fetch(fork.href, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params: params ?? [] }) });
|
|
25
|
+
if (!response.ok) throw new LifecyclePlanningError("FORK_RPC_ERROR", `Disposable fork RPC returned HTTP ${response.status}`);
|
|
26
|
+
const result = rpcObject(await response.json(), "fork JSON-RPC envelope");
|
|
27
|
+
if (result.error !== undefined) {
|
|
28
|
+
const error = rpcObject(result.error, "fork JSON-RPC error");
|
|
29
|
+
throw new LifecyclePlanningError("FORK_RPC_ERROR", typeof error.message === "string" ? error.message : "Disposable fork RPC returned an error");
|
|
30
|
+
}
|
|
31
|
+
if (!("result" in result)) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Disposable fork RPC omitted its result");
|
|
32
|
+
return result.result;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
return { client, isolation: "disposable", allowTransactions: true, sourceRpcUrl: source.href, forkRpcUrl: fork.href, resetMethod: options.resetMethod ?? "anvil_reset", impersonation: options.impersonation ?? "anvil", receiptTimeoutMs: options.receiptTimeoutMs };
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Explicit versioned launch stack. Importing this entry never selects a deployed address. */
|
|
2
|
+
export * from "./schema.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
export * from "./abi.js";
|
|
5
|
+
export * from "./markets.js";
|
|
6
|
+
export * from "./progress.js";
|
|
7
|
+
export * from "./fork.js";
|
|
8
|
+
export * from "./authors.js";
|
|
9
|
+
export { planLaunch, simulateLaunchPlan, buildNextTransaction, preparePoolBoundLifecyclePlan } from "./planner.js";
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { concatHex, decodeAbiParameters, encodeAbiParameters, getCreate2Address, keccak256, stringToHex, toHex, zeroHash, type Address, type Hex } from "viem";
|
|
2
|
+
import type { MarketConfigV1 } from "./schema.js";
|
|
3
|
+
import { LifecyclePlanningError, type LifecycleProfile, type PoolBoundHookSaltProgress } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export type V4LifecyclePositionConfig = {
|
|
6
|
+
tickLower: number; tickUpper: number; liquidity: bigint; salt: Hex; maxTokenAmount: bigint;
|
|
7
|
+
};
|
|
8
|
+
export type V4LifecycleMarketConfig = {
|
|
9
|
+
version: 4; lpFeePips: number; tickSpacing: number; sqrtPriceX96: bigint; hookFeePips: number;
|
|
10
|
+
feeMode: number; protocolFeeDenominator: number; treasury: Address; externalLiquidityDisabled: boolean;
|
|
11
|
+
oracleConfigId: Hex; profileId: Hex; termsDigest: Hex; developerBeneficiary: Address; developerFeeBps: number;
|
|
12
|
+
positions: readonly V4LifecyclePositionConfig[];
|
|
13
|
+
};
|
|
14
|
+
export const v4LifecyclePositionComponents = [
|
|
15
|
+
{ name: "tickLower", type: "int24" }, { name: "tickUpper", type: "int24" },
|
|
16
|
+
{ name: "liquidity", type: "uint128" }, { name: "salt", type: "bytes32" }, { name: "maxTokenAmount", type: "uint256" },
|
|
17
|
+
] as const;
|
|
18
|
+
export const v4LifecycleMarketComponents = [
|
|
19
|
+
{ name: "version", type: "uint16" }, { name: "lpFeePips", type: "uint24" }, { name: "tickSpacing", type: "int24" },
|
|
20
|
+
{ name: "sqrtPriceX96", type: "uint160" }, { name: "hookFeePips", type: "uint24" }, { name: "feeMode", type: "uint8" },
|
|
21
|
+
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
22
|
+
{ name: "externalLiquidityDisabled", type: "bool" },
|
|
23
|
+
{ name: "oracleConfigId", type: "bytes32" },
|
|
24
|
+
{ name: "profileId", type: "bytes32" }, { name: "termsDigest", type: "bytes32" },
|
|
25
|
+
{ name: "developerBeneficiary", type: "address" }, { name: "developerFeeBps", type: "uint16" },
|
|
26
|
+
{ name: "positions", type: "tuple[]", components: v4LifecyclePositionComponents },
|
|
27
|
+
] as const;
|
|
28
|
+
export function encodeV4LifecycleMarketConfig(config: V4LifecycleMarketConfig): Hex {
|
|
29
|
+
if (config.version !== 4) throw new Error("Shared V4 lifecycle market config version must be 4");
|
|
30
|
+
if (!Number.isInteger(config.developerFeeBps) || config.developerFeeBps < 0 || config.developerFeeBps > 65535) throw new Error("developerFeeBps must be an explicit uint16");
|
|
31
|
+
return encodeAbiParameters([{ type: "tuple", components: v4LifecycleMarketComponents }], [config]);
|
|
32
|
+
}
|
|
33
|
+
export function decodeV4LifecycleMarketConfig(encoded: Hex): V4LifecycleMarketConfig {
|
|
34
|
+
const config = decodeAbiParameters([{ type: "tuple", components: v4LifecycleMarketComponents }], encoded)[0];
|
|
35
|
+
if (config.version !== 4) throw new Error("Shared V4 lifecycle market config version must be 4");
|
|
36
|
+
return config as V4LifecycleMarketConfig;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type V4PoolBoundLifecycleMarketConfig = Omit<V4LifecycleMarketConfig, "version"> & { version: 5; hookSalt: Hex };
|
|
40
|
+
export const V4_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
41
|
+
export const V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
42
|
+
export const V4_POOL_BOUND_MARKET_ECONOMICS_DOMAIN = keccak256(stringToHex("black-market.pool-bound-market-economics.v1"));
|
|
43
|
+
export const V4_LIFECYCLE_HOOK_PERMISSION_MASK = 0x3fffn;
|
|
44
|
+
export const V4_LIFECYCLE_HOOK_PERMISSIONS = 0x1afcn;
|
|
45
|
+
export const poolBoundV4LifecycleMarketComponents = [
|
|
46
|
+
{ name: "version", type: "uint16" }, { name: "lpFeePips", type: "uint24" }, { name: "tickSpacing", type: "int24" },
|
|
47
|
+
{ name: "sqrtPriceX96", type: "uint160" }, { name: "hookFeePips", type: "uint24" }, { name: "feeMode", type: "uint8" },
|
|
48
|
+
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
49
|
+
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
50
|
+
{ name: "hookSalt", type: "bytes32" },
|
|
51
|
+
{ name: "profileId", type: "bytes32" }, { name: "termsDigest", type: "bytes32" },
|
|
52
|
+
{ name: "developerBeneficiary", type: "address" }, { name: "developerFeeBps", type: "uint16" },
|
|
53
|
+
{ name: "positions", type: "tuple[]", components: v4LifecyclePositionComponents },
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
export function encodePoolBoundV4LifecycleMarketConfig(config: V4PoolBoundLifecycleMarketConfig): Hex {
|
|
57
|
+
if (config.version !== 5) throw new Error("Pool-bound V4 lifecycle market config version must be 5");
|
|
58
|
+
if (!Number.isInteger(config.developerFeeBps) || config.developerFeeBps < 0 || config.developerFeeBps > 65535) throw new Error("developerFeeBps must be an explicit uint16");
|
|
59
|
+
return encodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponents }], [config]);
|
|
60
|
+
}
|
|
61
|
+
export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex): V4PoolBoundLifecycleMarketConfig {
|
|
62
|
+
const config = decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponents }], encoded)[0];
|
|
63
|
+
if (config.version !== 5) throw new Error("Pool-bound V4 lifecycle market config version must be 5");
|
|
64
|
+
return config as V4PoolBoundLifecycleMarketConfig;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Exact pending-source admission terms; author identity is never the live payout address. */
|
|
68
|
+
export function validateV4LifecycleMarket(options: {
|
|
69
|
+
market: MarketConfigV1; config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig; profile: LifecycleProfile; token: Address;
|
|
70
|
+
}): void {
|
|
71
|
+
const { market, config, profile, token } = options;
|
|
72
|
+
if (config.version !== 4 && config.version !== 5) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Reviewed V4 config version must be 4 or 5");
|
|
73
|
+
const { envelope, developerTerms: terms, protocolMaximumDeveloperFeeBps: protocolCeiling } = profile;
|
|
74
|
+
if (envelope === undefined || terms === undefined || protocolCeiling === undefined || !terms.enabled ||
|
|
75
|
+
profile.topology.hookTopology !== (config.version === 4 ? 1 : 2) || market.configVersion !== config.version ||
|
|
76
|
+
profile.topology.configVersion !== config.version || profile.adapter.configVersion !== config.version ||
|
|
77
|
+
envelope.topology !== profile.topology.hookTopology || envelope.configVersion !== config.version || envelope.economicVersion !== 3 ||
|
|
78
|
+
market.adapterId.toLowerCase() !== profile.registration.adapterId.toLowerCase() ||
|
|
79
|
+
market.profileId.toLowerCase() !== profile.id.toLowerCase() || config.profileId.toLowerCase() !== profile.id.toLowerCase() ||
|
|
80
|
+
terms.adapter.toLowerCase() !== profile.adapter.implementation.toLowerCase() ||
|
|
81
|
+
config.termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || config.termsDigest.toLowerCase() !== terms.termsDigest.toLowerCase() ||
|
|
82
|
+
config.developerBeneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
|
|
83
|
+
config.developerBeneficiary.toLowerCase() !== terms.beneficiary.toLowerCase() ||
|
|
84
|
+
BigInt(config.developerBeneficiary) === 0n || config.developerBeneficiary.toLowerCase() === token.toLowerCase() ||
|
|
85
|
+
config.developerBeneficiary.toLowerCase() === market.quoteAsset.toLowerCase()) {
|
|
86
|
+
throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Market must explicitly bind the admitted profile, frozen terms and stable author identity");
|
|
87
|
+
}
|
|
88
|
+
if (!Number.isInteger(config.developerFeeBps) || config.developerFeeBps < 0 ||
|
|
89
|
+
config.developerFeeBps > envelope.maximumDeveloperFeeBps || config.developerFeeBps > terms.maximumDeveloperFeeBps ||
|
|
90
|
+
config.developerFeeBps > protocolCeiling) throw new LifecyclePlanningError("DEVELOPER_FEE_CEILING", "Explicit developer fee exceeds the reviewed or protocol ceiling");
|
|
91
|
+
const b = envelope.bounds;
|
|
92
|
+
if (config.oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
|
|
93
|
+
if (config.treasury.toLowerCase() !== envelope.protocolTreasury.toLowerCase() ||
|
|
94
|
+
config.protocolFeeDenominator !== envelope.protocolFeeDenominator || !Number.isInteger(config.hookFeePips) || config.hookFeePips < 0 || config.hookFeePips >= 1000000 ||
|
|
95
|
+
!Number.isInteger(config.lpFeePips) || config.lpFeePips < 0 || config.lpFeePips >= 1000000 ||
|
|
96
|
+
!Number.isInteger(config.feeMode) || config.feeMode < 0 || config.feeMode > 1 || (b.feeModeFlags & (1 << config.feeMode)) === 0 ||
|
|
97
|
+
config.tickSpacing < b.minimumTickSpacing || config.tickSpacing > b.maximumTickSpacing ||
|
|
98
|
+
config.positions.length === 0 || config.positions.length > b.maximumPositions) {
|
|
99
|
+
throw new LifecyclePlanningError("PROFILE_BOUNDS_MISMATCH", "Market economics exceed or differ from the exact admitted envelope bounds");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type PoolBoundHookParametersV1 = {
|
|
104
|
+
poolManager: Address; registrar: Address; oracleFactory: Address; core: Address; liquidityLocker: Address;
|
|
105
|
+
token: Address; quoteCurrency: Address; lpFeePips: number; tickSpacing: number; sqrtPriceX96: bigint;
|
|
106
|
+
hookFeePips: number; feeMode: number; protocolFeeDenominator: number; treasury: Address;
|
|
107
|
+
externalLiquidityDisabled: boolean; oracleConfigId: Hex; marketCommitment: Hex; expectedPositionCount: number;
|
|
108
|
+
};
|
|
109
|
+
export const poolBoundHookParametersV1Components = [
|
|
110
|
+
{ name: "poolManager", type: "address" }, { name: "registrar", type: "address" },
|
|
111
|
+
{ name: "oracleFactory", type: "address" }, { name: "core", type: "address" },
|
|
112
|
+
{ name: "liquidityLocker", type: "address" }, { name: "token", type: "address" },
|
|
113
|
+
{ name: "quoteCurrency", type: "address" }, { name: "lpFeePips", type: "uint24" },
|
|
114
|
+
{ name: "tickSpacing", type: "int24" }, { name: "sqrtPriceX96", type: "uint160" },
|
|
115
|
+
{ name: "hookFeePips", type: "uint24" }, { name: "feeMode", type: "uint8" },
|
|
116
|
+
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
117
|
+
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
118
|
+
{ name: "marketCommitment", type: "bytes32" }, { name: "expectedPositionCount", type: "uint32" },
|
|
119
|
+
] as const;
|
|
120
|
+
export function encodePoolBoundHookParameters(parameters: PoolBoundHookParametersV1): Hex {
|
|
121
|
+
return encodeAbiParameters([{ type: "tuple", components: poolBoundHookParametersV1Components }], [parameters]);
|
|
122
|
+
}
|
|
123
|
+
export function hashPoolBoundV4MarketCommitment(options: { chainId: bigint; core: Address; registrar: Address; token: Address; market: MarketConfigV1 }): Hex {
|
|
124
|
+
const { chainId, core, registrar, token, market } = options;
|
|
125
|
+
if (market.configVersion !== 5) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound V4 market requires reviewed config version 5");
|
|
126
|
+
const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
|
|
127
|
+
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Config profile differs from the committed market profile");
|
|
128
|
+
const configHash = keccak256(encodePoolBoundV4LifecycleMarketConfig({ ...config, hookSalt: zeroHash }));
|
|
129
|
+
return keccak256(encodeAbiParameters([
|
|
130
|
+
{ type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "address" }, { type: "address" },
|
|
131
|
+
{ type: "bytes32" }, { type: "bytes32" }, { type: "address" }, { type: "uint256" }, { type: "uint32" }, { type: "bytes32" },
|
|
132
|
+
], [V4_POOL_BOUND_MARKET_ECONOMICS_DOMAIN, chainId, core, registrar, token, market.adapterId, market.profileId, market.quoteAsset, market.tokenBudget, market.configVersion, configHash]));
|
|
133
|
+
}
|
|
134
|
+
export function hasLifecycleV4HookPermissions(hook: Address): boolean {
|
|
135
|
+
return (BigInt(hook) & V4_LIFECYCLE_HOOK_PERMISSION_MASK) === V4_LIFECYCLE_HOOK_PERMISSIONS;
|
|
136
|
+
}
|
|
137
|
+
export function predictPoolBoundHookAddress(options: { deployer: Address; initCodeHash: Hex; salt: Hex }): Address {
|
|
138
|
+
return getCreate2Address({ from: options.deployer, bytecodeHash: options.initCodeHash, salt: options.salt });
|
|
139
|
+
}
|
|
140
|
+
export function poolBoundHookInitCodeHash(creationCode: Hex, parameters: PoolBoundHookParametersV1): Hex {
|
|
141
|
+
return keccak256(concatHex([creationCode, encodePoolBoundHookParameters(parameters)]));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Deterministic CREATE2 search with a macrotask boundary before work and between bounded batches. */
|
|
145
|
+
export async function minePoolBoundHookSalt(options: {
|
|
146
|
+
deployer: Address; initCodeHash: Hex; startSalt?: bigint; signal?: AbortSignal;
|
|
147
|
+
onProgress?: (progress: PoolBoundHookSaltProgress) => void;
|
|
148
|
+
}): Promise<{ salt: Hex; predictedHook: Address }> {
|
|
149
|
+
const { signal, onProgress } = options;
|
|
150
|
+
let candidate = options.startSalt ?? 0n;
|
|
151
|
+
if (candidate < 0n || candidate >= 1n << 256n) throw new LifecyclePlanningError("INVALID_HOOK_SALT", "Starting hook salt must fit uint256");
|
|
152
|
+
const checkCancelled = () => {
|
|
153
|
+
if (signal?.aborted) throw Object.assign(new Error("Pool-bound hook salt mining cancelled"), { name: "AbortError" });
|
|
154
|
+
};
|
|
155
|
+
let attempts = 0n;
|
|
156
|
+
checkCancelled();
|
|
157
|
+
const initialSalt = toHex(candidate, { size: 32 });
|
|
158
|
+
onProgress?.({ attempts, salt: initialSalt, predictedHook: predictPoolBoundHookAddress({ deployer: options.deployer, initCodeHash: options.initCodeHash, salt: initialSalt }) });
|
|
159
|
+
for (;;) {
|
|
160
|
+
checkCancelled();
|
|
161
|
+
await new Promise<void>((resolve) => globalThis.setTimeout(resolve, 0));
|
|
162
|
+
checkCancelled();
|
|
163
|
+
const deadline = Date.now() + 8;
|
|
164
|
+
let last: PoolBoundHookSaltProgress | undefined;
|
|
165
|
+
for (let batch = 0; batch < 256 && (batch === 0 || Date.now() < deadline); batch += 1) {
|
|
166
|
+
checkCancelled();
|
|
167
|
+
if (candidate >= 1n << 256n) throw new LifecyclePlanningError("HOOK_SALT_EXHAUSTED", "No valid hook address remains in the uint256 salt range");
|
|
168
|
+
const salt = toHex(candidate, { size: 32 });
|
|
169
|
+
const predictedHook = predictPoolBoundHookAddress({ deployer: options.deployer, initCodeHash: options.initCodeHash, salt });
|
|
170
|
+
attempts += 1n;
|
|
171
|
+
last = { attempts, salt, predictedHook };
|
|
172
|
+
if (hasLifecycleV4HookPermissions(predictedHook)) {
|
|
173
|
+
onProgress?.(last);
|
|
174
|
+
checkCancelled();
|
|
175
|
+
return { salt, predictedHook };
|
|
176
|
+
}
|
|
177
|
+
candidate += 1n;
|
|
178
|
+
}
|
|
179
|
+
if (last !== undefined) onProgress?.(last);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type AbyssLifecyclePositionConfig = {
|
|
184
|
+
tickLower: number; tickUpper: number; liquidity: bigint; tokenAmountMaximum: bigint;
|
|
185
|
+
};
|
|
186
|
+
export type AbyssLifecycleMarketConfig = {
|
|
187
|
+
profile: number; fee: number; oracleConfigId: Hex; openingSqrtPriceX96: bigint;
|
|
188
|
+
positions: readonly AbyssLifecyclePositionConfig[];
|
|
189
|
+
};
|
|
190
|
+
export const abyssLifecyclePositionComponents = [
|
|
191
|
+
{ name: "tickLower", type: "int24" }, { name: "tickUpper", type: "int24" },
|
|
192
|
+
{ name: "liquidity", type: "uint128" }, { name: "tokenAmountMaximum", type: "uint256" },
|
|
193
|
+
] as const;
|
|
194
|
+
export const abyssLifecycleMarketComponents = [
|
|
195
|
+
{ name: "profile", type: "uint8" }, { name: "fee", type: "uint24" }, { name: "oracleConfigId", type: "bytes32" },
|
|
196
|
+
{ name: "openingSqrtPriceX96", type: "uint160" },
|
|
197
|
+
{ name: "positions", type: "tuple[]", components: abyssLifecyclePositionComponents },
|
|
198
|
+
] as const;
|
|
199
|
+
export function encodeAbyssLifecycleMarketConfig(config: AbyssLifecycleMarketConfig): Hex {
|
|
200
|
+
if (!Number.isInteger(config.profile) || config.profile < 0 || config.profile > 3) throw new Error("Unsupported Abyss lifecycle pool profile");
|
|
201
|
+
return encodeAbiParameters([{ type: "tuple", components: abyssLifecycleMarketComponents }], [config]);
|
|
202
|
+
}
|
|
203
|
+
export function decodeAbyssLifecycleMarketConfig(encoded: Hex): AbyssLifecycleMarketConfig {
|
|
204
|
+
return decodeAbiParameters([{ type: "tuple", components: abyssLifecycleMarketComponents }], encoded)[0];
|
|
205
|
+
}
|