@black-market/sdk 0.6.3 → 0.7.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 +28 -4
- package/dist/addresses.d.ts +1 -1
- package/dist/addresses.js +4 -4
- package/dist/launch.d.ts +61 -0
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +171 -6
- package/dist/launch.js.map +1 -1
- package/dist/lifecycle/abi.d.ts +1999 -253
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +43 -14
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/allocations.d.ts +11 -0
- package/dist/lifecycle/allocations.d.ts.map +1 -0
- package/dist/lifecycle/allocations.js +36 -0
- package/dist/lifecycle/allocations.js.map +1 -0
- package/dist/lifecycle/calibration.d.ts +19 -0
- package/dist/lifecycle/calibration.d.ts.map +1 -0
- package/dist/lifecycle/calibration.js +65 -0
- package/dist/lifecycle/calibration.js.map +1 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts +11 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts.map +1 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js +10 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js.map +1 -0
- package/dist/lifecycle/index.d.ts +4 -2
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -2
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +170 -6
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +164 -37
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts +7 -14
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +355 -392
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +62 -0
- package/dist/lifecycle/presets.d.ts.map +1 -0
- package/dist/lifecycle/presets.js +195 -0
- package/dist/lifecycle/presets.js.map +1 -0
- package/dist/lifecycle/progress.d.ts +5 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +286 -118
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +13 -2
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +92 -8
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +14 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +400 -100
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +6 -0
- package/dist/lifecycle/transactions.d.ts.map +1 -0
- package/dist/lifecycle/transactions.js +40 -0
- package/dist/lifecycle/transactions.js.map +1 -0
- package/dist/lifecycle/types.d.ts +39 -2
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +236 -39
- package/examples/README.md +108 -18
- package/examples/generate-lifecycle-commitment-fixture.mjs +3 -1
- package/examples/launch-example-support.mjs +1 -1
- package/examples/launch-example.mjs +73 -26
- package/examples/launch-lifecycle-smoke.mjs +13 -11
- package/examples/launch.mjs +12 -11
- package/examples/quickstart.mjs +1 -1
- package/package.json +1 -1
- package/src/addresses.ts +4 -4
- package/src/launch.ts +213 -6
- package/src/lifecycle/abi.ts +43 -14
- package/src/lifecycle/allocations.ts +35 -0
- package/src/lifecycle/calibration.ts +59 -0
- package/src/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.ts +11 -0
- package/src/lifecycle/index.ts +4 -2
- package/src/lifecycle/markets.ts +169 -35
- package/src/lifecycle/planner.ts +326 -321
- package/src/lifecycle/presets.ts +243 -0
- package/src/lifecycle/progress.ts +254 -102
- package/src/lifecycle/rpc.ts +97 -9
- package/src/lifecycle/simulation.ts +391 -94
- package/src/lifecycle/transactions.ts +34 -0
- package/src/lifecycle/types.ts +35 -3
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { encodeAbiParameters, keccak256, parseAbiParameters, stringToHex } from "viem";
|
|
2
2
|
import { writeFile } from "node:fs/promises";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Frozen historical config4 wire-format reference: does not import the SDK encoder under test.
|
|
5
|
+
// Deliberately preserves the original bytes/hash, including pre-cutover allocations.
|
|
6
|
+
// This vector is not an allocation-valid 0.7.0 construction or an executable launch.
|
|
5
7
|
const wire = "(uint256 chainId,address orchestrator,address creator,uint256 nonce,(uint8 kind,uint8 rewardMode,string name,string symbol,uint256 supply,uint256 nftUnit,string metadataURI,bytes32 salt,address inventoryRecipient,bool burnOnCancel) token,(address asset,uint256 amount,uint8 kind,address inputAsset,uint256 inputAmount,address target,bytes data)[] funding,(address asset,uint16 ownerBps,uint16 rewardsBps,uint16 burnBps)[] feeAssets,(bytes32 adapterId,bytes32 profileId,address quoteAsset,uint256 tokenBudget,uint32 configVersion,bytes config)[] markets,(uint32 marketIndex,uint256 quoteAmountIn,uint256 minTokenOut,address recipient,uint160 sqrtPriceLimitX96)[] buys,uint256 deadline,uint16 executorFeeBps) plan";
|
|
6
8
|
const address = (value) => `0x${value.toString(16).padStart(40, "0")}`;
|
|
7
9
|
const bytes32 = (value) => `0x${value.toString(16).padStart(64, "0")}`;
|
|
@@ -93,7 +93,7 @@ export function readExampleConfiguration(sdk, redactor, env = process.env) {
|
|
|
93
93
|
};
|
|
94
94
|
return {
|
|
95
95
|
chainId: "4663", rpcUrl, apiUrl: launchApiUrl(env.LAUNCH_API_URL),
|
|
96
|
-
orchestrator: addresses.launchOrchestrator, creator: account.address,
|
|
96
|
+
orchestrator: addresses.launchOrchestrator, implementationRegistry: addresses.launchImplementationRegistry, creator: account.address,
|
|
97
97
|
quoteAsset: addresses.weth, quoteDecimals: 18, oracleFactory: addresses.abyssFactory,
|
|
98
98
|
nftBaseUri: env.NFT_BASE_URI || "",
|
|
99
99
|
executionLimits: {
|
|
@@ -4,7 +4,7 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
4
4
|
import { mkdirSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import {
|
|
7
|
-
decodeEventLog, decodeFunctionResult, encodeAbiParameters, encodeFunctionData, keccak256,
|
|
7
|
+
decodeEventLog, decodeFunctionResult, encodeAbiParameters, encodeFunctionData, erc20Abi, keccak256,
|
|
8
8
|
recoverTypedDataAddress, toHex, zeroAddress, zeroHash,
|
|
9
9
|
} from "viem";
|
|
10
10
|
import {
|
|
@@ -17,9 +17,26 @@ const UNIT = 10n ** 18n;
|
|
|
17
17
|
const OPENING_PRICE = 2n ** 96n;
|
|
18
18
|
const MIN_SQRT_RATIO = 4295128739n;
|
|
19
19
|
const MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n;
|
|
20
|
+
const BUY_SLIPPAGE_BPS = 50;
|
|
20
21
|
const same = (left, right) => typeof left === "string" && typeof right === "string" && left.toLowerCase() === right.toLowerCase();
|
|
21
22
|
const addressEqual = (actual, expected, label) => assert.ok(same(actual, expected), `${label}: expected ${expected}, got ${actual}`);
|
|
22
23
|
|
|
24
|
+
// Receipt accounting, not a creator reserve or an unchanged-total-supply assumption.
|
|
25
|
+
export function verifyLaunchInventory({ supply, totalSupply, orchestrator, creator, transfers, buys, orchestratorBalance, creatorBalance }) {
|
|
26
|
+
const minted = transfers.filter((transfer) => same(transfer.from, zeroAddress));
|
|
27
|
+
assert.equal(minted.reduce((sum, transfer) => sum + transfer.value, 0n), supply, "Exact minted supply");
|
|
28
|
+
assert.ok(minted.every((transfer) => same(transfer.to, orchestrator)), "Initial inventory belongs only to the launch core");
|
|
29
|
+
const burns = transfers.filter((transfer) => same(transfer.to, zeroAddress));
|
|
30
|
+
assert.ok(burns.every((transfer) => same(transfer.from, orchestrator)), "Launch residual burns belong only to the launch core");
|
|
31
|
+
const burned = burns.reduce((sum, transfer) => sum + transfer.value, 0n);
|
|
32
|
+
assert.ok(burned <= supply, "Residual inventory cannot exceed minted supply");
|
|
33
|
+
assert.equal(totalSupply, supply - burned, "Actual remaining supply accounts for residual burns");
|
|
34
|
+
assert.equal(orchestratorBalance, 0n, "No launch inventory remains in the core");
|
|
35
|
+
const bought = buys.filter((buy) => same(buy.recipient, creator)).reduce((sum, buy) => sum + buy.tokenOut, 0n);
|
|
36
|
+
assert.equal(creatorBalance, bought, "Creator receives only paid opening buys, never residual inventory");
|
|
37
|
+
return { minted: supply, burned, remainingSupply: totalSupply, creatorBought: bought };
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
|
|
24
41
|
function validateCase(scenario) {
|
|
25
42
|
if (![0, 1].includes(scenario.tokenKind) || ![0, 1, 2].includes(scenario.rewardMode) || !["atomic", "staged"].includes(scenario.mode)) throw exampleError("UNSUPPORTED_CASE", "Case token kind, reward mode and execution mode must be explicit supported values");
|
|
@@ -34,7 +51,7 @@ function validateCase(scenario) {
|
|
|
34
51
|
if (market.feeMode !== undefined && (market.venue !== "v4" || ![0, 1].includes(market.feeMode))) throw exampleError("UNSUPPORTED_CASE", "Only V4 markets support explicit feeMode 0/1");
|
|
35
52
|
positions += market.positions;
|
|
36
53
|
}
|
|
37
|
-
if (positions >
|
|
54
|
+
if (positions > 32) throw exampleError("UNSUPPORTED_CASE", "Case exceeds 32 permanent positions");
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
function rpcClient(url, artifacts, signal, label) {
|
|
@@ -85,7 +102,7 @@ function receiptEvents(receipt, address, abi, eventName) {
|
|
|
85
102
|
});
|
|
86
103
|
}
|
|
87
104
|
|
|
88
|
-
async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal }) {
|
|
105
|
+
async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal, limits }) {
|
|
89
106
|
const read = contractReader(client);
|
|
90
107
|
artifacts.stage("profile-discovery");
|
|
91
108
|
const profiles = await sdk.readLifecycleProfiles({ client, orchestrator: fixture.orchestrator });
|
|
@@ -93,8 +110,10 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
93
110
|
artifacts.event("registry-profiles", { profiles });
|
|
94
111
|
const selected = {};
|
|
95
112
|
if (scenario.markets.some((row) => row.venue === "v4")) {
|
|
96
|
-
const bound = profiles.filter((row) => row.venueKind === "uniswap-v4" &&
|
|
97
|
-
|
|
113
|
+
const bound = profiles.filter((row) => row.venueKind === "uniswap-v4" && row.topology.hookTopology === 2 &&
|
|
114
|
+
row.topology.configVersion === 6 && same(row.registration.configSchema, sdk.V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6) &&
|
|
115
|
+
row.adapter.configVersion === 6 && row.envelope?.configVersion === 6);
|
|
116
|
+
if (bound.length !== 1 || !bound[0].admitted || !bound[0].envelope || !bound[0].developerTerms) throw exampleError("PROFILE_NOT_ADMITTED", "An unambiguous admitted pool-bound V4 config6/V2 profile with its exact schema and frozen terms is required", { data: bound });
|
|
98
117
|
selected.v4 = bound[0];
|
|
99
118
|
}
|
|
100
119
|
if (scenario.markets.some((row) => row.venue === "abyss")) {
|
|
@@ -109,6 +128,7 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
109
128
|
selected.abyss = canonical[0];
|
|
110
129
|
}
|
|
111
130
|
const escrow = await read(fixture.orchestrator, sdk.launchLifecycleAbi, "fundingEscrow");
|
|
131
|
+
addressEqual(await read(escrow, sdk.lifecycleFundingEscrowAbi, "core"), fixture.orchestrator, "Funding escrow core");
|
|
112
132
|
addressEqual(await read(escrow, sdk.lifecycleFundingEscrowAbi, "wrappedNative"), fixture.quoteAsset, "Actual native-wrap funding binding");
|
|
113
133
|
assert.equal(await read(fixture.quoteAsset, sdk.erc20Abi, "decimals"), fixture.quoteDecimals, "Actual quote decimals");
|
|
114
134
|
if (selected.v4) addressEqual(await read(selected.v4.adapter.implementation, sdk.poolMarketAdapterV1Abi, "oracleFactory"), fixture.oracleFactory, "V4 registered oracle factory");
|
|
@@ -137,6 +157,12 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
137
157
|
const token0 = BigInt(predictedToken) < BigInt(fixture.quoteAsset);
|
|
138
158
|
const markets = scenario.markets.map((spec, marketIndex) => {
|
|
139
159
|
const profile = selected[spec.venue];
|
|
160
|
+
const baseBudget = token.supply / BigInt(scenario.markets.length);
|
|
161
|
+
const tokenBudget = marketIndex === scenario.markets.length - 1
|
|
162
|
+
? token.supply - baseBudget * BigInt(marketIndex) : baseBudget;
|
|
163
|
+
const baseMaximum = tokenBudget / BigInt(spec.positions);
|
|
164
|
+
const maximum = (index) => index === spec.positions - 1
|
|
165
|
+
? tokenBudget - baseMaximum * BigInt(index) : baseMaximum;
|
|
140
166
|
const positions = Array.from({ length: spec.positions }, (_unused, index) => ({
|
|
141
167
|
tickLower: token0 ? index * 120 : index === spec.positions - 1 ? -887220 : -(index + 1) * 120,
|
|
142
168
|
tickUpper: token0 ? index === spec.positions - 1 ? 887220 : (index + 1) * 120 : -index * 120,
|
|
@@ -148,17 +174,19 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
148
174
|
const feeMode = spec.feeMode ?? 0;
|
|
149
175
|
if (spec.positions > envelope.bounds.maximumPositions || 60 < envelope.bounds.minimumTickSpacing || 60 > envelope.bounds.maximumTickSpacing || !(envelope.bounds.feeModeFlags & 1 << feeMode)) throw exampleError("UNSUPPORTED_CASE", "Exact V4 positions, spacing or fee mode exceed the actual certified envelope", { data: { spec, bounds: envelope.bounds } });
|
|
150
176
|
config = sdk.encodePoolBoundV4LifecycleMarketConfig({
|
|
151
|
-
version:
|
|
177
|
+
version: 6, lpFeePips: 3000, hookFeePips: 10000,
|
|
178
|
+
minimumHookFeePips: 1000, feeSensitivityPipsSecondsPerTick: 7654321,
|
|
179
|
+
tickSpacing: 60, sqrtPriceX96: OPENING_PRICE,
|
|
152
180
|
feeMode, protocolFeeDenominator: envelope.protocolFeeDenominator, treasury: envelope.protocolTreasury,
|
|
153
181
|
externalLiquidityDisabled: true, oracleConfigId: fixture.oracleConfigId, profileId: profile.id,
|
|
154
182
|
termsDigest: envelope.termsDigest, developerBeneficiary: envelope.beneficiary, developerFeeBps: 0, hookSalt: zeroHash,
|
|
155
|
-
positions: positions.map((position, index) => ({ ...position, salt: toHex(BigInt(marketIndex * 32 + index + 1), { size: 32 }), maxTokenAmount:
|
|
183
|
+
positions: positions.map((position, index) => ({ ...position, salt: toHex(BigInt(marketIndex * 32 + index + 1), { size: 32 }), maxTokenAmount: maximum(index) })),
|
|
156
184
|
});
|
|
157
185
|
} else config = sdk.encodeAbyssLifecycleMarketConfig({
|
|
158
186
|
profile: 3, fee: 3000, oracleConfigId: fixture.oracleConfigId, openingSqrtPriceX96: OPENING_PRICE,
|
|
159
|
-
positions: positions.map((position) => ({ ...position, tokenAmountMaximum:
|
|
187
|
+
positions: positions.map((position, index) => ({ ...position, tokenAmountMaximum: maximum(index) })),
|
|
160
188
|
});
|
|
161
|
-
return { adapterId: profile.registration.adapterId, profileId: profile.id, quoteAsset: fixture.quoteAsset, tokenBudget
|
|
189
|
+
return { adapterId: profile.registration.adapterId, profileId: profile.id, quoteAsset: fixture.quoteAsset, tokenBudget, configVersion: spec.venue === "v4" ? profile.topology.configVersion : 1, config };
|
|
162
190
|
});
|
|
163
191
|
const buys = markets.flatMap((_market, marketIndex) => Array.from({ length: scenario.buysPerMarket }, () => ({
|
|
164
192
|
marketIndex, quoteAmountIn: 10n ** 15n, minTokenOut: 1n, recipient: fixture.creator,
|
|
@@ -173,18 +201,17 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
173
201
|
const plan = { ...draft, markets, buys, feeAssets, funding: [{ asset: fixture.quoteAsset, amount, kind: sdk.LifecycleFundingKind.NativeWrap, inputAsset: fixture.quoteAsset, inputAmount: amount, target: zeroAddress, data: "0x" }] };
|
|
174
202
|
artifacts.save("draft-plan.json", plan);
|
|
175
203
|
artifacts.result.chain.predictedToken = predictedToken;
|
|
176
|
-
artifacts.stage("
|
|
177
|
-
const
|
|
204
|
+
artifacts.stage("preparation-and-planning", { predictedToken, selectedProfiles: Object.fromEntries(Object.entries(selected).map(([key, profile]) => [key, profile.id])) });
|
|
205
|
+
const planned = await sdk.prepareAndPlanLifecycleLaunch({ client, account: fixture.creator, plan, mode: scenario.mode, limits, confirmations: 1, buySlippageBps: BUY_SLIPPAGE_BPS, signal,
|
|
178
206
|
onProgress: (progress) => artifacts.event("salt-mining", progress),
|
|
179
207
|
});
|
|
180
|
-
artifacts.save("plan.json", JSON.parse(sdk.serializeLaunchPlan(
|
|
181
|
-
artifacts.save("hook-deployments.json",
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
artifacts.result.chain.
|
|
185
|
-
artifacts.
|
|
186
|
-
|
|
187
|
-
return { plan: prepared.plan, predictedToken };
|
|
208
|
+
artifacts.save("plan.json", JSON.parse(sdk.serializeLaunchPlan(planned.plan)));
|
|
209
|
+
artifacts.save("hook-deployments.json", planned.hookDeployments);
|
|
210
|
+
addressEqual(planned.predictedToken, predictedToken, "Finalized token prediction");
|
|
211
|
+
artifacts.result.chain.planHash = planned.planHash;
|
|
212
|
+
artifacts.result.chain.launchId = planned.launchId;
|
|
213
|
+
artifacts.event("plan-finalized", { predictedToken, planHash: planned.planHash, launchId: planned.launchId, mode: planned.mode, markets: markets.length, positions: scenario.markets.reduce((sum, market) => sum + market.positions, 0), buys: buys.length, deployments: planned.hookDeployments });
|
|
214
|
+
return { planned, predictedToken };
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
function apiTransport({ artifacts, signal, apiUrl }) {
|
|
@@ -247,6 +274,7 @@ async function stageMetadata({ fixture, plan, sdk, artifacts, transport, wallet
|
|
|
247
274
|
assert.equal(session.chainId, Number(plan.chainId)); addressEqual(session.wallet, plan.creator, "Session wallet");
|
|
248
275
|
assert.equal(session.status, "ready_to_launch"); assert.equal(session.metadata.name, plan.token.name); assert.equal(session.metadata.symbol, plan.token.symbol);
|
|
249
276
|
assert.equal(session.image, null, "This example stages metadata only");
|
|
277
|
+
assert.equal(session.metadata.description, metadata.description); assert.ok(session.transactionHash == null, "New metadata session cannot already bind a transaction");
|
|
250
278
|
artifacts.result.api.status = "session-ready";
|
|
251
279
|
return { api, sessionId: session.sessionId, capability: session.capability };
|
|
252
280
|
}
|
|
@@ -271,8 +299,11 @@ async function executePlan({ client, fixture, planned, sdk, artifacts, signal, l
|
|
|
271
299
|
let activation;
|
|
272
300
|
for (let step = 0; step < 64; step += 1) {
|
|
273
301
|
artifacts.stage("build-next", { step });
|
|
274
|
-
const
|
|
302
|
+
const reviewedTransaction = planned.transactions[step];
|
|
303
|
+
const next = await sdk.buildNextTransaction({ client, planned, receipts: references, limits, submissionClient: client, reviewedTransaction, signal });
|
|
275
304
|
if (!next) return { references, activation };
|
|
305
|
+
assert.ok(reviewedTransaction, "No transaction outside the saved review may be submitted");
|
|
306
|
+
for (const field of ["id", "kind", "chainId", "from", "to", "data", "value", "gas", "gasPrice"]) assert.equal(next[field], reviewedTransaction[field], `Fresh ${field} must match the saved review`);
|
|
276
307
|
artifacts.event("next-admitted", { transaction: next });
|
|
277
308
|
assert.equal(next.chainId, Number(planned.chainId));
|
|
278
309
|
assert.equal(BigInt(await client.request({ method: "eth_chainId" })), planned.chainId, "Recheck execution chain before every send");
|
|
@@ -309,6 +340,8 @@ async function executePlan({ client, fixture, planned, sdk, artifacts, signal, l
|
|
|
309
340
|
});
|
|
310
341
|
const { row, receipt } = submitted;
|
|
311
342
|
addressEqual(receipt.transactionHash, row.transactionHash, "Receipt transaction hash");
|
|
343
|
+
addressEqual(receipt.from, next.from, "Receipt sender");
|
|
344
|
+
addressEqual(receipt.to, next.to, "Receipt destination");
|
|
312
345
|
if (BigInt(receipt.status) !== 1n) {
|
|
313
346
|
let trace;
|
|
314
347
|
try { trace = await client.request({ method: "debug_traceTransaction", params: [row.transactionHash, { tracer: "callTracer" }] }); }
|
|
@@ -371,7 +404,7 @@ async function verifyChain({ client, planned, references, activation, fixture, s
|
|
|
371
404
|
artifacts.result.chain.tokenState = tokenState;
|
|
372
405
|
artifacts.event("actual-token", { token, tokenState });
|
|
373
406
|
assert.equal(tokenState.name, planned.plan.token.name); assert.equal(tokenState.symbol, planned.plan.token.symbol); assert.equal(tokenState.decimals, 18);
|
|
374
|
-
assert.equal(tokenState.initialSupply, planned.plan.token.supply);
|
|
407
|
+
assert.equal(tokenState.initialSupply, planned.plan.token.supply);
|
|
375
408
|
assert.equal(tokenState.rewardMode, scenario.rewardMode); assert.equal(tokenState.burnOnCancel, false);
|
|
376
409
|
assert.equal(tokenState.active, true); assert.equal(tokenState.cancelled, false); assert.equal(tokenState.exclusionsFinalized, true);
|
|
377
410
|
addressEqual(tokenState.authority, fixture.orchestrator, "Token authority"); addressEqual(tokenState.tokenFactory, planned.tokenFactory, "Token factory"); addressEqual(tokenState.launchId, planned.launchId, "Token launch ID");
|
|
@@ -416,6 +449,14 @@ async function verifyChain({ client, planned, references, activation, fixture, s
|
|
|
416
449
|
assert.equal(activated.length, 1); addressEqual(activated[0].launchId, planned.launchId, "Activation launch ID"); addressEqual(activated[0].planHash, planned.planHash, "Activation commitment"); addressEqual(activated[0].token, token, "Activation token");
|
|
417
450
|
assert.equal(activated[0].marketCount, scenario.markets.length); assert.equal(activated[0].positionCount, expectedPositions);
|
|
418
451
|
const buys = receiptEvents(activation.receipt, fixture.orchestrator, sdk.launchLifecycleAbi, "InitialBuyExecuted");
|
|
452
|
+
const transfers = artifacts.transactions.flatMap((row) => receiptEvents(row.receipt, token, erc20Abi, "Transfer"));
|
|
453
|
+
tokenState.inventory = verifyLaunchInventory({
|
|
454
|
+
supply: planned.plan.token.supply, totalSupply: tokenState.totalSupply,
|
|
455
|
+
orchestrator: fixture.orchestrator, creator: fixture.creator, transfers, buys,
|
|
456
|
+
orchestratorBalance: await read(token, sdk.lifecycleErc20Abi, "balanceOf", [fixture.orchestrator]),
|
|
457
|
+
creatorBalance: await read(token, sdk.lifecycleErc20Abi, "balanceOf", [fixture.creator]),
|
|
458
|
+
});
|
|
459
|
+
artifacts.event("actual-inventory-accounting", tokenState.inventory);
|
|
419
460
|
artifacts.result.chain.buys = buys;
|
|
420
461
|
artifacts.event("actual-ordered-buys", { buys });
|
|
421
462
|
assert.equal(buys.length, planned.plan.buys.length);
|
|
@@ -482,7 +523,12 @@ async function publishAndVerify({ fixture, planned, activation, session, sdk, ar
|
|
|
482
523
|
transport.setTimeoutMs(remainingMs);
|
|
483
524
|
try { return await session.api.publishUploadSession(Number(planned.chainId), session.sessionId, session.capability, { chainId: Number(planned.chainId), transactionHash: activation.transactionHash }); }
|
|
484
525
|
catch (error) {
|
|
485
|
-
if (error instanceof sdk.LaunchPublishPending
|
|
526
|
+
if (error instanceof sdk.LaunchPublishPending) {
|
|
527
|
+
assert.equal(error.session.chainId, Number(planned.chainId));
|
|
528
|
+
addressEqual(error.session.wallet, planned.plan.creator, "Pending API wallet");
|
|
529
|
+
if (error.session.token != null) addressEqual(error.session.token, planned.predictedToken, "Pending API token");
|
|
530
|
+
if (error.session.transactionHash != null) addressEqual(error.session.transactionHash, activation.transactionHash, "Pending API activation");
|
|
531
|
+
}
|
|
486
532
|
throw error;
|
|
487
533
|
}
|
|
488
534
|
},
|
|
@@ -493,6 +539,7 @@ async function publishAndVerify({ fixture, planned, activation, session, sdk, ar
|
|
|
493
539
|
addressEqual(published.transactionHash, activation.transactionHash, "Published activation transaction");
|
|
494
540
|
assert.equal(published.chainId, Number(planned.chainId)); addressEqual(published.wallet, planned.plan.creator, "Published wallet");
|
|
495
541
|
assert.ok(["optimistic", "final"].includes(published.status)); assert.ok(["optimistic", "final"].includes(published.canonicalStatus));
|
|
542
|
+
assert.equal(published.metadataStatus, "ready", "Publication must actually make owner metadata available");
|
|
496
543
|
assert.equal(published.metadata.name, planned.plan.token.name); assert.equal(published.metadata.symbol, planned.plan.token.symbol);
|
|
497
544
|
artifacts.stage("api-token-verification");
|
|
498
545
|
transport.setTimeoutMs(30_000);
|
|
@@ -528,14 +575,14 @@ async function executeExample({ fixture, scenario, sdk, artifacts, signal }) {
|
|
|
528
575
|
assert.equal(chainId, 4663n, "Examples require Robinhood chain 4663");
|
|
529
576
|
assert.equal(await client.request({ method: "eth_getCode", params: [fixture.creator, "latest"] }), "0x", "Creator must be an EOA");
|
|
530
577
|
assert.notEqual(await client.request({ method: "eth_getCode", params: [fixture.orchestrator, "latest"] }), "0x", "Current orchestrator must be deployed");
|
|
578
|
+
addressEqual(await contractReader(client)(fixture.orchestrator, sdk.launchLifecycleAbi, "registry"), fixture.implementationRegistry, "Configured deployment registry");
|
|
531
579
|
const wallet = createLocalLaunchWallet({ privateKey: process.env.PRIVATE_KEY, creator: fixture.creator, chainId, orchestrator: fixture.orchestrator, client, redactor: artifacts.redactor });
|
|
532
580
|
artifacts.event("deployment-verified", { chainId, creator: fixture.creator, orchestrator: fixture.orchestrator });
|
|
533
|
-
const { plan, predictedToken } = await constructPlan({ client, fixture, scenario, sdk, artifacts, signal });
|
|
534
|
-
artifacts.stage("execution-planning");
|
|
535
581
|
const limits = limitSource(fixture, artifacts);
|
|
536
|
-
const planned = await
|
|
582
|
+
const { planned, predictedToken } = await constructPlan({ client, fixture, scenario, sdk, artifacts, signal, limits });
|
|
583
|
+
const { plan } = planned;
|
|
537
584
|
artifacts.save("planning.json", planned);
|
|
538
|
-
artifacts.event("execution-plan", { mode: planned.mode, simulation: planned.simulation,
|
|
585
|
+
artifacts.event("execution-plan", { mode: planned.mode, simulation: planned.simulation, prerequisites: planned.prerequisites, transactions: planned.transactions });
|
|
539
586
|
assert.equal(planned.mode, scenario.mode, "Never silently change requested mode"); addressEqual(planned.predictedToken, predictedToken, "Planner token prediction");
|
|
540
587
|
if (!planned.simulation.admitted) throw exampleError("PLAN_NOT_ADMITTED", planned.simulation.reason ?? "The actual SDK did not admit this exact launch", { simulation: planned.simulation });
|
|
541
588
|
const transport = apiTransport({ artifacts, signal, apiUrl: fixture.apiUrl });
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
createControlledLifecycleFork, decodeDeveloperClaimReceipt, decodePoolBoundV4LifecycleMarketConfig,
|
|
8
8
|
encodeLaunchPlan, encodePoolBoundV4LifecycleMarketConfig, hashLaunchIdentity, hashLaunchPlan,
|
|
9
9
|
launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleFeeHubAbi,
|
|
10
|
-
LifecyclePhase, parseLaunchPlan, planLaunch, predictLifecycleToken,
|
|
10
|
+
LifecyclePhase, parseLaunchPlan, planLaunch, predictLifecycleToken, prepareAndPlanLifecycleLaunch,
|
|
11
11
|
readAuthorHubs, readDeveloperFees, readLaunchProgress, readLifecycleProfiles, readPoolBoundHookDeployment,
|
|
12
|
-
simulateLaunchPlan,
|
|
13
|
-
} from "
|
|
12
|
+
simulateLaunchPlan, isPoolBoundV4ConfigVersion,
|
|
13
|
+
} from "@black-market/sdk/lifecycle";
|
|
14
14
|
const [manifestFile, plansFile] = process.argv.slice(2);
|
|
15
15
|
if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.mjs manifest.json sdk-plans.json");
|
|
16
16
|
const manifest = JSON.parse(await readFile(manifestFile, "utf8"));
|
|
@@ -42,6 +42,7 @@ async function readContract(to, abi, functionName, args = []) {
|
|
|
42
42
|
}
|
|
43
43
|
async function send(transaction) {
|
|
44
44
|
assert.equal(BigInt(transaction.chainId), chainId);
|
|
45
|
+
assert.equal(BigInt(await client.request({ method: "eth_chainId" })), chainId, "Owned execution RPC must remain on the reviewed chain");
|
|
45
46
|
const gasPrice = transaction.gasPrice ?? BigInt(await client.request({ method: "eth_gasPrice" }));
|
|
46
47
|
const head = await client.request({ method: "eth_getBlockByNumber", params: ["latest", false] });
|
|
47
48
|
const ceiling = [BigInt(head.gasLimit), BigInt(policy.chainTransactionGasLimit), BigInt(policy.rpcTransactionGasLimit), BigInt(policy.accountTransactionGasLimit)].reduce((a, b) => a < b ? a : b);
|
|
@@ -77,21 +78,21 @@ for (const row of exported.plans) for (const mode of ["atomic", "staged"]) {
|
|
|
77
78
|
assert.equal(hashLaunchPlan(plan).toLowerCase(), row.planHash.toLowerCase());
|
|
78
79
|
assert.equal(hashLaunchIdentity(plan).toLowerCase(), row.launchId.toLowerCase());
|
|
79
80
|
assert.equal((await predictLifecycleToken({ client, plan })).toLowerCase(), row.predictedToken.toLowerCase());
|
|
80
|
-
const draft = { ...plan, markets: plan.markets.map((market) => market.configVersion
|
|
81
|
-
...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config), hookSalt: zeroHash }),
|
|
81
|
+
const draft = { ...plan, markets: plan.markets.map((market) => isPoolBoundV4ConfigVersion(market.configVersion) ? {
|
|
82
|
+
...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion), hookSalt: zeroHash }),
|
|
82
83
|
} : market) };
|
|
83
|
-
|
|
84
|
-
plan =
|
|
84
|
+
let planned = await prepareAndPlanLifecycleLaunch({ client, account: draft.creator, plan: draft, mode, limits, fork });
|
|
85
|
+
plan = planned.plan;
|
|
85
86
|
assert.equal(hashLaunchPlan(plan).toLowerCase(), row.planHash.toLowerCase(), "Offchain mining reproduces the independent constructor/salt vector");
|
|
86
|
-
if (mode === "staged") for (const deployment of
|
|
87
|
+
if (mode === "staged") for (const deployment of planned.hookDeployments) {
|
|
87
88
|
const unsigned = await buildPoolBoundHookDeploymentTransaction({ client, plan, marketIndex: deployment.marketIndex });
|
|
88
89
|
await send({ chainId: Number(chainId), from: plan.creator, ...unsigned });
|
|
89
90
|
const actual = await readPoolBoundHookDeployment({ client, plan, marketIndex: deployment.marketIndex });
|
|
90
91
|
assert.equal(actual.predictedHook.toLowerCase(), deployment.predictedHook.toLowerCase());
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
if (mode === "staged" && planned.hookDeployments.length > 0) planned = await planLaunch({ client, account: plan.creator, plan, mode, limits, fork });
|
|
93
94
|
assert.equal(planned.simulation.admitted, true, planned.simulation.reason);
|
|
94
|
-
const atomicReceipt = planned.
|
|
95
|
+
const atomicReceipt = planned.simulation.steps.find((step) => step.transactionId === "atomic")?.returnData;
|
|
95
96
|
if (atomicReceipt) {
|
|
96
97
|
const decoded = decodeFunctionResult({ abi: launchLifecycleAbi, functionName: "launchAtomic", data: atomicReceipt });
|
|
97
98
|
assert.equal(decoded.planHash.toLowerCase(), planned.planHash.toLowerCase());
|
|
@@ -102,8 +103,9 @@ for (const row of exported.plans) for (const mode of ["atomic", "staged"]) {
|
|
|
102
103
|
const receipts = [];
|
|
103
104
|
let activationReceipt;
|
|
104
105
|
for (let guard = 0; guard < 64; ++guard) {
|
|
105
|
-
const next = await buildNextTransaction({ client, planned, receipts, limits, fork });
|
|
106
|
+
const next = await buildNextTransaction({ client, planned, receipts, limits, fork, submissionClient: client });
|
|
106
107
|
if (!next) break;
|
|
108
|
+
assert.equal(next.admission?.transportPreflight, "passed");
|
|
107
109
|
const submitted = await send(next);
|
|
108
110
|
receipts.push(submitted.reference);
|
|
109
111
|
if (["atomic", "activate"].includes(next.kind)) activationReceipt = submitted.receipt;
|
package/examples/launch.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { createPublicClient, http } from "viem";
|
|
4
4
|
import {
|
|
5
5
|
buildNextTransaction, createControlledLifecycleFork, parseLaunchPlan, planLaunch,
|
|
6
|
-
|
|
7
|
-
} from "
|
|
6
|
+
prepareAndPlanLifecycleLaunch, readLaunchProgress, readLifecycleProfiles, serializeLaunchPlan,
|
|
7
|
+
} from "@black-market/sdk";
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
const option = (name) => { const index = args.indexOf(name); return index === -1 ? undefined : args[index + 1]; };
|
|
10
10
|
const planFile = option("--plan"), rpcUrl = option("--rpc"), mode = option("--mode");
|
|
@@ -12,12 +12,10 @@ if (!planFile || !rpcUrl || !["atomic", "staged"].includes(mode)) {
|
|
|
12
12
|
throw new Error("Usage: node examples/launch.mjs --plan plan.json --rpc RPC_URL --mode atomic|staged [--limits current-limits.json] [--receipts receipts.json] [--confirmations N] [--fork-rpc LOOPBACK_RPC_URL] [--mine]");
|
|
13
13
|
}
|
|
14
14
|
const client = createPublicClient({ transport: http(rpcUrl) });
|
|
15
|
-
|
|
15
|
+
const plan = parseLaunchPlan(await readFile(planFile, "utf8"));
|
|
16
|
+
if (BigInt(await client.getChainId()) !== plan.chainId) throw new Error("RPC chain ID differs from the saved plan");
|
|
16
17
|
const profiles = await readLifecycleProfiles({ client, orchestrator: plan.orchestrator, profileIds: [...new Set(plan.markets.map((market) => market.profileId))] });
|
|
17
18
|
for (const profile of profiles) console.log(profile.id, profile.venueKind, profile.topology, profile.admitted ? "admitted" : profile.reason);
|
|
18
|
-
if (args.includes("--mine")) plan = (await preparePoolBoundLifecyclePlan({ client, plan,
|
|
19
|
-
onProgress: ({ marketIndex, attempts }) => console.error("salt mining", marketIndex, attempts.toString()),
|
|
20
|
-
})).plan;
|
|
21
19
|
const limitsFile = option("--limits");
|
|
22
20
|
const limits = limitsFile ? JSON.parse(await readFile(limitsFile, "utf8")) : undefined;
|
|
23
21
|
if (limits) for (const key of ["chainId", "observedBlockNumber", "chainGasLimit", "rpcGasLimit", "accountGasLimit", "maxSimulationGas"]) {
|
|
@@ -28,11 +26,14 @@ const receipts = receiptsFile ? JSON.parse(await readFile(receiptsFile, "utf8"),
|
|
|
28
26
|
const confirmations = Number(option("--confirmations") ?? "1");
|
|
29
27
|
const forkRpcUrl = option("--fork-rpc");
|
|
30
28
|
const fork = forkRpcUrl ? createControlledLifecycleFork({ sourceRpcUrl: rpcUrl, forkRpcUrl, allowTransactions: true, impersonation: "anvil" }) : undefined;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
const options = { client, account: plan.creator, plan, mode, limits, fork, receipts, confirmations };
|
|
30
|
+
const planned = args.includes("--mine")
|
|
31
|
+
? await prepareAndPlanLifecycleLaunch({ ...options, onProgress: ({ marketIndex, attempts }) => console.error("salt mining", marketIndex, attempts.toString()) })
|
|
32
|
+
: await planLaunch(options);
|
|
33
|
+
const progress = await readLaunchProgress({ client, planned, receipts, confirmations });
|
|
34
|
+
const next = planned.simulation.admitted ? await buildNextTransaction({ client, planned, limits, fork, receipts, confirmations }) : undefined;
|
|
35
|
+
console.log(JSON.stringify({ plan: JSON.parse(serializeLaunchPlan(planned.plan)), mode,
|
|
36
|
+
simulation: planned.simulation, progress, next },
|
|
36
37
|
(_key, value) => typeof value === "bigint" ? value.toString() : value, 2));
|
|
37
38
|
// Persist the returned plan/mode/confirmation policy and receipt references. An application
|
|
38
39
|
// must separately review, authenticate its wallet chain and sign exactly `next` if admitted.
|
package/examples/quickstart.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { zeroAddress } from "viem";
|
|
|
3
3
|
import {
|
|
4
4
|
createProtocolPublicClient, getAddresses, isSupportedChainId,
|
|
5
5
|
launchLifecycleAbi, readLifecycleProfiles,
|
|
6
|
-
} from "
|
|
6
|
+
} from "@black-market/sdk";
|
|
7
7
|
|
|
8
8
|
const chainId = Number(process.env.CHAIN_ID ?? "4663");
|
|
9
9
|
if (!isSupportedChainId(chainId)) throw new Error("CHAIN_ID must be 4663, 46631, or 31337");
|
package/package.json
CHANGED
package/src/addresses.ts
CHANGED
|
@@ -177,11 +177,11 @@ export const robinhoodAbyssInfrastructure: AbyssInfrastructureAddresses = {
|
|
|
177
177
|
abyssFeeRouter: "0x2c3B1b6fe0EDa8e10C0445567b47e66E825B34cd",
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
-
/** Mined pool-launch-v1 deployment,
|
|
180
|
+
/** Mined pool-launch-v1 deployment, 20261008T000141Z-e174ce6 (manifest.json). */
|
|
181
181
|
export const robinhoodLaunchApplication: LaunchApplicationAddresses = {
|
|
182
|
-
launchOrchestrator: "
|
|
183
|
-
launchImplementationRegistry: "
|
|
184
|
-
launchFeeOwnerRegistry: "
|
|
182
|
+
launchOrchestrator: "0x91560876033d568d25CDe98C78c33ff8FC43962c",
|
|
183
|
+
launchImplementationRegistry: "0xB2B0f9F36617810D67b8fC175153Aa10024C1358",
|
|
184
|
+
launchFeeOwnerRegistry: "0x64b5ca1f21B8E84305b0e4D847924dca39Eb1fcb",
|
|
185
185
|
};
|
|
186
186
|
|
|
187
187
|
|