@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.
Files changed (82) hide show
  1. package/README.md +28 -4
  2. package/dist/addresses.d.ts +1 -1
  3. package/dist/addresses.js +4 -4
  4. package/dist/launch.d.ts +61 -0
  5. package/dist/launch.d.ts.map +1 -1
  6. package/dist/launch.js +171 -6
  7. package/dist/launch.js.map +1 -1
  8. package/dist/lifecycle/abi.d.ts +1999 -253
  9. package/dist/lifecycle/abi.d.ts.map +1 -1
  10. package/dist/lifecycle/abi.js +43 -14
  11. package/dist/lifecycle/abi.js.map +1 -1
  12. package/dist/lifecycle/allocations.d.ts +11 -0
  13. package/dist/lifecycle/allocations.d.ts.map +1 -0
  14. package/dist/lifecycle/allocations.js +36 -0
  15. package/dist/lifecycle/allocations.js.map +1 -0
  16. package/dist/lifecycle/calibration.d.ts +19 -0
  17. package/dist/lifecycle/calibration.d.ts.map +1 -0
  18. package/dist/lifecycle/calibration.js +65 -0
  19. package/dist/lifecycle/calibration.js.map +1 -0
  20. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts +11 -0
  21. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts.map +1 -0
  22. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js +10 -0
  23. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js.map +1 -0
  24. package/dist/lifecycle/index.d.ts +4 -2
  25. package/dist/lifecycle/index.d.ts.map +1 -1
  26. package/dist/lifecycle/index.js +4 -2
  27. package/dist/lifecycle/index.js.map +1 -1
  28. package/dist/lifecycle/markets.d.ts +170 -6
  29. package/dist/lifecycle/markets.d.ts.map +1 -1
  30. package/dist/lifecycle/markets.js +164 -37
  31. package/dist/lifecycle/markets.js.map +1 -1
  32. package/dist/lifecycle/planner.d.ts +7 -14
  33. package/dist/lifecycle/planner.d.ts.map +1 -1
  34. package/dist/lifecycle/planner.js +355 -392
  35. package/dist/lifecycle/planner.js.map +1 -1
  36. package/dist/lifecycle/presets.d.ts +62 -0
  37. package/dist/lifecycle/presets.d.ts.map +1 -0
  38. package/dist/lifecycle/presets.js +195 -0
  39. package/dist/lifecycle/presets.js.map +1 -0
  40. package/dist/lifecycle/progress.d.ts +5 -1
  41. package/dist/lifecycle/progress.d.ts.map +1 -1
  42. package/dist/lifecycle/progress.js +286 -118
  43. package/dist/lifecycle/progress.js.map +1 -1
  44. package/dist/lifecycle/rpc.d.ts +13 -2
  45. package/dist/lifecycle/rpc.d.ts.map +1 -1
  46. package/dist/lifecycle/rpc.js +92 -8
  47. package/dist/lifecycle/rpc.js.map +1 -1
  48. package/dist/lifecycle/simulation.d.ts +14 -1
  49. package/dist/lifecycle/simulation.d.ts.map +1 -1
  50. package/dist/lifecycle/simulation.js +400 -100
  51. package/dist/lifecycle/simulation.js.map +1 -1
  52. package/dist/lifecycle/transactions.d.ts +6 -0
  53. package/dist/lifecycle/transactions.d.ts.map +1 -0
  54. package/dist/lifecycle/transactions.js +40 -0
  55. package/dist/lifecycle/transactions.js.map +1 -0
  56. package/dist/lifecycle/types.d.ts +39 -2
  57. package/dist/lifecycle/types.d.ts.map +1 -1
  58. package/dist/lifecycle/types.js.map +1 -1
  59. package/docs/launch-lifecycle.md +236 -39
  60. package/examples/README.md +108 -18
  61. package/examples/generate-lifecycle-commitment-fixture.mjs +3 -1
  62. package/examples/launch-example-support.mjs +1 -1
  63. package/examples/launch-example.mjs +73 -26
  64. package/examples/launch-lifecycle-smoke.mjs +13 -11
  65. package/examples/launch.mjs +12 -11
  66. package/examples/quickstart.mjs +1 -1
  67. package/package.json +1 -1
  68. package/src/addresses.ts +4 -4
  69. package/src/launch.ts +213 -6
  70. package/src/lifecycle/abi.ts +43 -14
  71. package/src/lifecycle/allocations.ts +35 -0
  72. package/src/lifecycle/calibration.ts +59 -0
  73. package/src/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.ts +11 -0
  74. package/src/lifecycle/index.ts +4 -2
  75. package/src/lifecycle/markets.ts +169 -35
  76. package/src/lifecycle/planner.ts +326 -321
  77. package/src/lifecycle/presets.ts +243 -0
  78. package/src/lifecycle/progress.ts +254 -102
  79. package/src/lifecycle/rpc.ts +97 -9
  80. package/src/lifecycle/simulation.ts +391 -94
  81. package/src/lifecycle/transactions.ts +34 -0
  82. 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
- // Independent wire-format reference: does not import the SDK encoder under test.
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 > 64) throw exampleError("UNSUPPORTED_CASE", "Case exceeds 64 permanent 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" && same(row.registration.configSchema, sdk.V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA) && row.topology.hookTopology === 2 && row.topology.configVersion === 5);
97
- if (bound.length !== 1 || !bound[0].admitted || !bound[0].envelope || !bound[0].developerTerms) throw exampleError("PROFILE_NOT_ADMITTED", "An unambiguous admitted certified pool-bound V4/schema5 profile with frozen terms is required", { data: bound });
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: 5, lpFeePips: 3000, hookFeePips: 10000, tickSpacing: 60, sqrtPriceX96: OPENING_PRICE,
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: 1100n * UNIT })),
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: 1100n * UNIT })),
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: 1100n * UNIT, configVersion: spec.venue === "v4" ? 5 : 1, config };
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("salt-finalization", { predictedToken, selectedProfiles: Object.fromEntries(Object.entries(selected).map(([key, profile]) => [key, profile.id])) });
177
- const prepared = await sdk.preparePoolBoundLifecyclePlan({ client, plan, signal,
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(prepared.plan)));
181
- artifacts.save("hook-deployments.json", prepared.deployments);
182
- const actualPrediction = await sdk.predictLifecycleToken({ client, plan: prepared.plan });
183
- addressEqual(actualPrediction, predictedToken, "Finalized token prediction");
184
- artifacts.result.chain.planHash = sdk.hashLaunchPlan(prepared.plan);
185
- artifacts.result.chain.launchId = sdk.hashLaunchIdentity(prepared.plan);
186
- artifacts.event("plan-finalized", { predictedToken, planHash: artifacts.result.chain.planHash, launchId: artifacts.result.chain.launchId, mode: scenario.mode, markets: markets.length, positions: scenario.markets.reduce((sum, market) => sum + market.positions, 0), buys: buys.length, deployments: prepared.deployments });
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 next = await sdk.buildNextTransaction({ client, planned, receipts: references, limits, submissionClient: client });
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); assert.equal(tokenState.totalSupply, 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 && error.session.token != null) addressEqual(error.session.token, planned.predictedToken, "Pending API token");
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 sdk.planLaunch({ client, account: fixture.creator, plan, mode: scenario.mode, limits, confirmations: 1 });
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, atomicAttempt: planned.atomicAttempt, prerequisites: planned.prerequisites, transactions: planned.transactions });
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, preparePoolBoundLifecyclePlan,
10
+ LifecyclePhase, parseLaunchPlan, planLaunch, predictLifecycleToken, prepareAndPlanLifecycleLaunch,
11
11
  readAuthorHubs, readDeveloperFees, readLaunchProgress, readLifecycleProfiles, readPoolBoundHookDeployment,
12
- simulateLaunchPlan,
13
- } from "../dist/lifecycle/index.js";
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 === 5 ? {
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
- const prepared = await preparePoolBoundLifecyclePlan({ client, plan: draft });
84
- plan = prepared.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 prepared.deployments) {
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
- const planned = await planLaunch({ client, account: plan.creator, plan, mode, limits, fork });
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.atomicAttempt.steps.find((step) => step.transactionId === "atomic")?.returnData;
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;
@@ -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
- preparePoolBoundLifecyclePlan, readLaunchProgress, readLifecycleProfiles, serializeLaunchPlan,
7
- } from "../dist/index.js";
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
- let plan = parseLaunchPlan(await readFile(planFile, "utf8"));
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 planned = await planLaunch({ client, account: plan.creator, plan, mode, limits, fork, receipts, confirmations });
32
- const progress = await readLaunchProgress({ client, planned, receipts });
33
- const next = planned.simulation.admitted ? await buildNextTransaction({ client, planned, limits, fork, receipts }) : undefined;
34
- console.log(JSON.stringify({ plan: JSON.parse(serializeLaunchPlan(plan)), mode,
35
- simulation: planned.simulation, atomicAttempt: planned.atomicAttempt, progress, next },
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.
@@ -3,7 +3,7 @@ import { zeroAddress } from "viem";
3
3
  import {
4
4
  createProtocolPublicClient, getAddresses, isSupportedChainId,
5
5
  launchLifecycleAbi, readLifecycleProfiles,
6
- } from "../dist/index.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@black-market/sdk",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "TypeScript SDK for Abyss token launches, swaps, and lending.",
5
5
  "license": "MIT",
6
6
  "repository": {
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, 20261005T053747Z-b5a7b88 (manifest.json). */
180
+ /** Mined pool-launch-v1 deployment, 20261008T000141Z-e174ce6 (manifest.json). */
181
181
  export const robinhoodLaunchApplication: LaunchApplicationAddresses = {
182
- launchOrchestrator: "0xb75CBD17b9aecb7305B4DFcDa69595F783341c0E",
183
- launchImplementationRegistry: "0xaa8a410709B79cBA6F118F1be1FF568877A3B8Ee",
184
- launchFeeOwnerRegistry: "0x15778Aad08e12D458B2848F035860e2a8c2a0725",
182
+ launchOrchestrator: "0x91560876033d568d25CDe98C78c33ff8FC43962c",
183
+ launchImplementationRegistry: "0xB2B0f9F36617810D67b8fC175153Aa10024C1358",
184
+ launchFeeOwnerRegistry: "0x64b5ca1f21B8E84305b0e4D847924dca39Eb1fcb",
185
185
  };
186
186
 
187
187