@black-market/sdk 0.14.1 → 0.15.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 (68) hide show
  1. package/README.md +4 -3
  2. package/dist/lifecycle/authors.js +2 -2
  3. package/dist/lifecycle/authors.js.map +1 -1
  4. package/dist/lifecycle/gas-profile.d.ts +35 -0
  5. package/dist/lifecycle/gas-profile.d.ts.map +1 -0
  6. package/dist/lifecycle/gas-profile.js +183 -0
  7. package/dist/lifecycle/gas-profile.js.map +1 -0
  8. package/dist/lifecycle/index.d.ts +3 -1
  9. package/dist/lifecycle/index.d.ts.map +1 -1
  10. package/dist/lifecycle/index.js +3 -1
  11. package/dist/lifecycle/index.js.map +1 -1
  12. package/dist/lifecycle/opening-buys.d.ts +13 -0
  13. package/dist/lifecycle/opening-buys.d.ts.map +1 -0
  14. package/dist/lifecycle/opening-buys.js +171 -0
  15. package/dist/lifecycle/opening-buys.js.map +1 -0
  16. package/dist/lifecycle/planner.d.ts +2 -2
  17. package/dist/lifecycle/planner.d.ts.map +1 -1
  18. package/dist/lifecycle/planner.js +178 -163
  19. package/dist/lifecycle/planner.js.map +1 -1
  20. package/dist/lifecycle/presets.d.ts +3 -0
  21. package/dist/lifecycle/presets.d.ts.map +1 -1
  22. package/dist/lifecycle/presets.js +3 -0
  23. package/dist/lifecycle/presets.js.map +1 -1
  24. package/dist/lifecycle/progress.d.ts +1 -1
  25. package/dist/lifecycle/progress.d.ts.map +1 -1
  26. package/dist/lifecycle/progress.js +21 -20
  27. package/dist/lifecycle/progress.js.map +1 -1
  28. package/dist/lifecycle/rpc.d.ts +24 -19
  29. package/dist/lifecycle/rpc.d.ts.map +1 -1
  30. package/dist/lifecycle/rpc.js +58 -50
  31. package/dist/lifecycle/rpc.js.map +1 -1
  32. package/dist/lifecycle/simulation.d.ts +3 -4
  33. package/dist/lifecycle/simulation.d.ts.map +1 -1
  34. package/dist/lifecycle/simulation.js +98 -247
  35. package/dist/lifecycle/simulation.js.map +1 -1
  36. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  37. package/dist/lifecycle/snapshot.js +1 -2
  38. package/dist/lifecycle/snapshot.js.map +1 -1
  39. package/dist/lifecycle/transactions.d.ts +19 -3
  40. package/dist/lifecycle/transactions.d.ts.map +1 -1
  41. package/dist/lifecycle/transactions.js +25 -20
  42. package/dist/lifecycle/transactions.js.map +1 -1
  43. package/dist/lifecycle/types.d.ts +53 -45
  44. package/dist/lifecycle/types.d.ts.map +1 -1
  45. package/dist/lifecycle/types.js.map +1 -1
  46. package/dist/lifecycle/ufg.d.ts.map +1 -1
  47. package/dist/lifecycle/ufg.js +3 -7
  48. package/dist/lifecycle/ufg.js.map +1 -1
  49. package/docs/launch-lifecycle.md +67 -78
  50. package/docs/lifecycle-gas-profiles.md +90 -0
  51. package/docs/reference.md +43 -45
  52. package/examples/README.md +11 -8
  53. package/examples/launch-example.ts +35 -25
  54. package/examples/launch-lifecycle-smoke.ts +5 -3
  55. package/package.json +1 -1
  56. package/src/lifecycle/authors.ts +2 -2
  57. package/src/lifecycle/gas-profile.ts +175 -0
  58. package/src/lifecycle/index.ts +3 -1
  59. package/src/lifecycle/opening-buys.ts +157 -0
  60. package/src/lifecycle/planner.ts +164 -151
  61. package/src/lifecycle/presets.ts +5 -0
  62. package/src/lifecycle/progress.ts +19 -20
  63. package/src/lifecycle/rpc.ts +56 -44
  64. package/src/lifecycle/simulation.ts +85 -231
  65. package/src/lifecycle/snapshot.ts +1 -2
  66. package/src/lifecycle/transactions.ts +33 -21
  67. package/src/lifecycle/types.ts +52 -42
  68. package/src/lifecycle/ufg.ts +3 -7
@@ -1,4 +1,4 @@
1
- import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash, type Address, type Hash, type Hex } from "viem";
1
+ import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash, type Address, type Hex } from "viem";
2
2
  import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, poolFeeCollectorFactoryConfigV6Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, poolHookDeployerConfigV6Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, fixedFeePoolHookConfigV6Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
3
3
  import { buildPoolBoundHookParameters, decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, isPoolBoundV4ConfigVersion, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6, type PoolBoundHookParameters, type PoolBoundLifecycleConfigVersion } from "./markets.js";
4
4
  import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, lifecycleTokenComponents, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
@@ -507,7 +507,12 @@ type ReceiptObservation = {
507
507
 
508
508
  async function readReceipt(reference: LifecycleReceiptReference, options: ReadLaunchProgressOptions, block: LifecycleBlock, readIdentityBlock: (number: bigint) => Promise<LifecycleBlock>): Promise<ReceiptObservation> {
509
509
  const effectiveHash = reference.replacementHash ?? reference.transactionHash;
510
- const base = { transactionHash: reference.transactionHash, effectiveHash, replaced: reference.replacementHash !== undefined && reference.replacementHash.toLowerCase() !== reference.transactionHash.toLowerCase() };
510
+ const base: Pick<LifecycleReceiptStatus, "transactionHash" | "effectiveHash" | "replaced" | "nonce"> = {
511
+ transactionHash: reference.transactionHash, effectiveHash,
512
+ replaced: reference.replacementHash !== undefined && reference.replacementHash.toLowerCase() !== reference.transactionHash.toLowerCase(),
513
+ ...(reference.nonce === undefined ? {} : { nonce: reference.nonce }),
514
+ };
515
+ if (reference.nonce !== undefined && (typeof reference.nonce !== "bigint" || reference.nonce < 0n || reference.nonce >= 1n << 64n)) throw new LifecyclePlanningError("INVALID_RECEIPT_NONCE", "Observed submitted nonce must be a uint64 bigint");
511
516
  const pins: { number: bigint; hash: Hex }[] = [];
512
517
  const observedNumber = reference.observedBlockNumber, observedHash = reference.observedBlockHash;
513
518
  if (observedNumber !== undefined && observedHash !== undefined) pins.push({ number: observedNumber, hash: observedHash });
@@ -527,13 +532,20 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
527
532
  lifecycleRpc(options.client, "eth_getTransactionReceipt", [effectiveHash]),
528
533
  lifecycleRpc(options.client, "eth_getTransactionByHash", [effectiveHash]),
529
534
  ]);
535
+ const transaction = transactionValue === null ? undefined : rpcObject(transactionValue, "receipt transaction");
536
+ if (transaction !== undefined) {
537
+ if (rpcHex(transaction.from, "receipt transaction account").toLowerCase() !== options.planned.account.toLowerCase()) throw new LifecyclePlanningError("RECEIPT_ACCOUNT", "Submitted transaction belongs to a different account");
538
+ const nonce = rpcQuantity(transaction.nonce, "submitted transaction nonce");
539
+ if (reference.nonce !== undefined && nonce !== reference.nonce) throw new LifecyclePlanningError("RECEIPT_NONCE", "Submitted or replacement transaction differs from the retained nonce");
540
+ base.nonce = nonce;
541
+ }
530
542
  if (receiptValue === null) return finish({ ...base, status: "pending", blockNumber: observedNumber, blockHash: observedHash });
531
543
  if (transactionValue === null) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Receipt exists without its canonical transaction");
532
544
  const receipt = rpcObject(receiptValue, "receipt");
533
545
  const blockNumber = rpcQuantity(receipt.blockNumber, "receipt block number");
534
546
  const blockHash = rpcHex(receipt.blockHash, "receipt block hash");
535
547
  pins.push({ number: blockNumber, hash: blockHash });
536
- const matches = await transactionMatchesLaunch(rpcObject(transactionValue, "receipt transaction"), options,
548
+ const matches = await transactionMatchesLaunch(transaction!, options,
537
549
  () => readIdentityBlock(blockNumber));
538
550
  const confirmations = block.number >= blockNumber ? block.number - blockNumber + 1n : 0n;
539
551
  const required = reference.confirmations ?? 1;
@@ -595,7 +607,7 @@ export async function readLaunchProgress(options: ReadLaunchProgressOptions, pin
595
607
  return result;
596
608
  }
597
609
 
598
- /** Build-next needs the state machine, receipts and nonce, not display-only live markets. */
610
+ /** Build-next needs the state machine and receipts, not account nonce or display-only markets. */
599
611
  export async function readLifecycleExecutionProgress(options: ReadLaunchProgressOptions, block: LifecycleBlock): Promise<CanonicalLaunchProgress> {
600
612
  if (!assertLifecycleReadClientOpen(options.client)) throw new LifecyclePlanningError("INVALID_READ_CONTEXT", "Execution progress requires an invocation owner");
601
613
  return readLaunchProgressAtBlock(options, options.client, block, false);
@@ -622,13 +634,10 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
622
634
  if (hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config differs from its finalized commitment");
623
635
  } else if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
624
636
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
625
- const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
637
+ const [head, confirmedProgress, token] = await Promise.all([
626
638
  readLifecycleHeadProgress(client, planned.plan, planned.account, block),
627
639
  confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
628
640
  readLifecycleTokenAtBlock(client, planned.plan, block),
629
- lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
630
- confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
631
- lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
632
641
  ]);
633
642
  assertActive();
634
643
  const canonical = confirmedProgress ?? head;
@@ -638,18 +647,8 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
638
647
  if (progress.mode !== (planned.mode === "atomic" ? LifecycleMode.Atomic : LifecycleMode.Staged)) throw new LifecyclePlanningError("MODE_MISMATCH", "Execution mode differs from the already recorded launch");
639
648
  if (progress.preparedMarkets > progress.marketCount || progress.phase === LifecyclePhase.Activating || (progress.phase === LifecyclePhase.Ready && progress.preparedMarkets !== progress.marketCount)) throw new LifecyclePlanningError("INVALID_PROGRESS", "Canonical progress violates the committed ordered state machine");
640
649
  }
641
- if (planned.ufg !== undefined && head.phase !== LifecyclePhase.None) {
642
- const [principal, configHash] = await Promise.all([
643
- readLifecycleContract<Address>(client, UFG_LAUNCH_PROXY_ADDRESS, ufgLaunchProxyV1Abi, "principalOf", [planned.launchId], block),
644
- readLifecycleContract<Hash>(client, UFG_LAUNCH_PROXY_ADDRESS, ufgLaunchProxyV1Abi, "launchConfigHash", [planned.launchId], block),
645
- ]);
646
- if (principal.toLowerCase() !== planned.account.toLowerCase() || configHash.toLowerCase() !== planned.ufg.configHash.toLowerCase()) throw new LifecyclePlanningError("PLAN_REPLAY", "UFG proxy principal or launch config differs from the stored launch");
647
- }
648
- const confirmedAccountNonce = rpcQuantity(confirmedNonce, "confirmed account nonce");
649
- const headAccountNonce = headNonce === undefined ? confirmedAccountNonce : rpcQuantity(headNonce, "head account nonce");
650
- const pendingAccountNonce = rpcQuantity(pendingNonce, "pending account nonce");
651
650
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
652
- const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
651
+ const confirmationSafe = keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
653
652
  const receipts = await readReceipts({ ...options, client }, block, confirmationDepth);
654
653
  assertActive();
655
654
  let markets: LifecycleMarketProgress[] = [];
@@ -676,5 +675,5 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
676
675
  }
677
676
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
678
677
  assertActive();
679
- return { canonical, head, confirmationDepth, confirmedBlockNumber: confirmedBlock.number, confirmedBlockHash: confirmedBlock.hash, confirmedAccountNonce, headAccountNonce, pendingAccountNonce, confirmationSafe, token, planHash: planned.planHash, launchId: planned.launchId, chainId, blockNumber: block.number, blockHash: block.hash, markets, receipts };
678
+ return { canonical, head, confirmationDepth, confirmedBlockNumber: confirmedBlock.number, confirmedBlockHash: confirmedBlock.hash, confirmationSafe, token, planHash: planned.planHash, launchId: planned.launchId, chainId, blockNumber: block.number, blockHash: block.hash, markets, receipts };
680
679
  }
@@ -3,10 +3,19 @@ import { LifecyclePlanningError, type LifecycleBlock, type LifecycleDiagnosticEv
3
3
  import { lifecycleRegistryAbi } from "./abi.js";
4
4
  import type { ProfileTopologyV1 } from "./schema.js";
5
5
  import { robinhoodMainnet } from "../addresses.js";
6
+ import { ROBINHOOD_NATIVE_TRANSACTION_GAS_LIMIT } from "./presets.js";
6
7
 
7
8
  // Reviewed chain4663 deployment, independently observed at block 98,136,655.
8
9
  const multicall3CodeHash = "0xd5c15df687b16f2ff992fc8d767b4216323184a2bbc6ee2f9c398c318e770891";
9
10
  const sessionObservations = new WeakMap<object, Map<string, Promise<unknown>>>();
11
+ const clientChainIds = new WeakMap<LifecycleRpcClient, Promise<bigint>>();
12
+
13
+ function rememberClientChainId(source: LifecycleRpcClient, request: Promise<unknown>): Promise<bigint> {
14
+ const pending = request.then((value) => rpcQuantity(value, "chain ID"));
15
+ clientChainIds.set(source, pending);
16
+ void pending.catch(() => { if (clientChainIds.get(source) === pending) clientChainIds.delete(source); });
17
+ return pending;
18
+ }
10
19
  const canonicalWireValue = (_key: string, value: unknown): unknown => typeof value === "string" && value.startsWith("0x") ? value.toLowerCase() : value;
11
20
  type IndependentRead = { params: readonly unknown[]; resolve: (value: unknown) => void; reject: (failure: unknown) => void };
12
21
 
@@ -61,7 +70,7 @@ class SessionLifecycleCache implements LifecycleSessionCache {
61
70
  }
62
71
  }
63
72
 
64
- /** Opt in per caller/session; there is no global or transport-owned lifecycle cache. */
73
+ /** Opt-in pinned read reuse; client-bound identity/capability facts are separate. */
65
74
  export function createLifecycleSessionCache(): LifecycleSessionCache {
66
75
  return new SessionLifecycleCache();
67
76
  }
@@ -153,7 +162,17 @@ class InvocationLifecycleClient implements LifecycleRpcClient {
153
162
 
154
163
  observeChainId(): Promise<bigint> {
155
164
  this.assertOpen();
156
- return this.chainId ??= this.run("eth_chainId", []).then((value) => rpcQuantity(value, "chain ID"));
165
+ if (this.chainId === undefined) {
166
+ const observed = clientChainIds.get(this.source) ?? rememberClientChainId(this.source, this.run("eth_chainId", []));
167
+ this.chainId = this.session === undefined ? observed : observed.then((chainId) => {
168
+ if (!this.closed) {
169
+ this.observedChainId = chainId;
170
+ if (this.sessionGroup !== undefined) this.session!.observeChain(this.sessionGroup, chainId);
171
+ }
172
+ return chainId;
173
+ });
174
+ }
175
+ return this.chainId;
157
176
  }
158
177
 
159
178
  observeGasPrice(): Promise<unknown> {
@@ -373,16 +392,17 @@ export function lifecycleStaticRpc(client: LifecycleRpcClient, method: "eth_call
373
392
  export async function lifecycleRpc(client: LifecycleRpcClient, method: string, params: readonly unknown[] = []): Promise<unknown> {
374
393
  return client instanceof InvocationLifecycleClient ? client.run(method, params) : client.request({ method, params });
375
394
  }
376
- /** One invocation's observed identity; wallet submission owns its final chain check. */
395
+ /** Client-bound identity, observed once per underlying client, never a freshness guard. */
377
396
  export function readLifecycleChainId(client: LifecycleRpcClient): Promise<bigint> {
378
- return client instanceof InvocationLifecycleClient
379
- ? client.observeChainId()
380
- : lifecycleRpc(client, "eth_chainId").then((value) => rpcQuantity(value, "chain ID"));
397
+ if (client instanceof InvocationLifecycleClient) return client.observeChainId();
398
+ return clientChainIds.get(client) ?? rememberClientChainId(client, lifecycleRpc(client, "eth_chainId"));
381
399
  }
382
400
 
383
401
  /** A single operation-local recommendation, never a session or cross-head quote. */
384
- export function readLifecycleGasPrice(client: LifecycleRpcClient): Promise<unknown> {
385
- return client instanceof InvocationLifecycleClient ? client.observeGasPrice() : lifecycleRpc(client, "eth_gasPrice");
402
+ export function readLifecycleGasPrice(client: LifecycleRpcClient, block: LifecycleBlock, chainId: bigint): Promise<bigint> {
403
+ if (chainId === 4663n) return readNitroPrices(client, block).then((prices) => prices[5]);
404
+ return (client instanceof InvocationLifecycleClient ? client.observeGasPrice() : lifecycleRpc(client, "eth_gasPrice"))
405
+ .then((price) => rpcQuantity(price, "gas price"));
386
406
  }
387
407
 
388
408
  /** Only the reviewed immutable factory identity may use an invocation-wide key. */
@@ -472,18 +492,10 @@ export async function readLifecycleBlock(client: LifecycleRpcClient, tag: Hex |
472
492
  if (client instanceof InvocationLifecycleClient) client.observeBlock(block, tag === "latest");
473
493
  return block;
474
494
  }
475
- export async function assertLifecycleBlock(client: LifecycleRpcClient, block: Pick<LifecycleBlock, "number" | "hash">, expectedChainId?: bigint): Promise<void> {
495
+ export async function assertLifecycleBlock(client: LifecycleRpcClient, block: Pick<LifecycleBlock, "number" | "hash">): Promise<void> {
476
496
  if (client instanceof InvocationLifecycleClient) return;
477
- const [chainId, current] = await Promise.all([
478
- expectedChainId === undefined ? undefined : lifecycleRpc(client, "eth_chainId"),
479
- readLifecycleBlock(client, toHex(block.number)),
480
- ]);
481
- if (expectedChainId !== undefined && rpcQuantity(chainId, "chain ID") !== expectedChainId) {
482
- if (client instanceof InvocationLifecycleClient) client.invalidateSession();
483
- throw new LifecyclePlanningError("CHAIN_MISMATCH", "Source RPC chain changed during the pinned operation");
484
- }
497
+ const current = await readLifecycleBlock(client, toHex(block.number));
485
498
  if (current.hash.toLowerCase() !== block.hash.toLowerCase()) {
486
- if (client instanceof InvocationLifecycleClient) client.invalidateSession();
487
499
  throw new LifecyclePlanningError("STATE_REORGED", "Pinned launch state changed during the operation; read canonical progress again");
488
500
  }
489
501
  }
@@ -514,20 +526,29 @@ export async function readLifecycleProfileTopology(client: LifecycleRpcClient, r
514
526
  }
515
527
  export const nitroArbSys = "0x0000000000000000000000000000000000000064";
516
528
  export const nitroGasInfo = "0x000000000000000000000000000000000000006c";
517
- export const nitroNodeInterface = "0x00000000000000000000000000000000000000c8";
518
529
  export const nitroArbSysAbi = parseAbi(["function arbOSVersion() view returns (uint256)"]);
519
- export const nitroGasInfoAbi = parseAbi(["function getMaxTxGasLimit() view returns (uint256)", "function getMaxBlockGasLimit() view returns (uint64)"]);
520
- const nitroNodeInterfaceAbi = parseAbi(["function gasEstimateL1Component(address to, bool contractCreation, bytes data) payable returns (uint64 gasEstimateForL1, uint256 baseFee, uint256 l1BaseFeeEstimate)"]);
530
+ export const nitroGasInfoAbi = parseAbi([
531
+ // Transaction ceilings are release preset metadata, not guard-only getter reads.
532
+ "function getPricesInWei() view returns (uint256,uint256,uint256,uint256,uint256,uint256)",
533
+ ]);
534
+ export type LifecycleNitroPrices = readonly [bigint, bigint, bigint, bigint, bigint, bigint];
521
535
  const maxUint64 = (1n << 64n) - 1n;
522
536
 
523
- /** A poster quote is an envelope budget, never an execution/compute proof. */
524
- export async function readNitroPosterGas(transaction: LifecycleTransaction, context: LifecycleLimitContext): Promise<{ posterGas: bigint; baseFee: bigint; l1BaseFeeEstimate: bigint }> {
525
- const data = encodeFunctionData({ abi: nitroNodeInterfaceAbi, functionName: "gasEstimateL1Component", args: [transaction.to, false, transaction.data] });
526
- const result = rpcHex(await lifecycleStaticRpc(context.client, "eth_call", [{ from: transaction.from, to: nitroNodeInterface, data, value: toHex(transaction.value) }, toHex(context.block.number)], context.block), "Nitro poster estimate");
527
- if (result.length !== 194) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Nitro poster estimate must contain exactly three ABI words");
528
- const [posterGas, baseFee, l1BaseFeeEstimate] = decodeFunctionResult({ abi: nitroNodeInterfaceAbi, functionName: "gasEstimateL1Component", data: result });
529
- if (posterGas < 0n || posterGas > maxUint64 || baseFee <= 0n || l1BaseFeeEstimate < 0n) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Nitro poster estimate has invalid gas or fee values");
530
- return { posterGas, baseFee, l1BaseFeeEstimate };
537
+ /** Seeded by the planning aggregate; standalone simulation batches this with limits. */
538
+ export function readNitroPrices(client: LifecycleRpcClient, block: LifecycleBlock): Promise<LifecycleNitroPrices> {
539
+ return readLifecycleContract(client, nitroGasInfo, nitroGasInfoAbi, "getPricesInWei", [], block, undefined, true, true);
540
+ }
541
+
542
+ /** EOA legacy/1559 serialization and Nitro's Brotli allocation bound, not a
543
+ * per-transaction oracle quote. Wallet-added access/auth lists and AA wrappers
544
+ * need their own wallet envelope; they are outside the direct-EOA profile. */
545
+ export function nitroPosterGasBound(transaction: Pick<LifecycleTransaction, "data">, prices: LifecycleNitroPrices): { posterGas: bigint; feeBound: bigint } {
546
+ const serializedBytes = BigInt((transaction.data.length - 2) / 2) + 384n;
547
+ const compressedBytes = serializedBytes + 8n * (serializedBytes / 1024n) + 64n;
548
+ const feeBound = compressedBytes * prices[1];
549
+ if (feeBound === 0n) return { posterGas: 0n, feeBound };
550
+ if (prices[5] === 0n) throw new LifecyclePlanningError("NITRO_PRICES_UNAVAILABLE", "Positive poster fees cannot be converted at a zero L2 gas price");
551
+ return { posterGas: 2n * ((feeBound + prices[5] - 1n) / prices[5]), feeBound };
531
552
  }
532
553
 
533
554
  async function readNitroScalar(context: LifecycleLimitContext, to: Address, abi: Abi, functionName: string): Promise<bigint> {
@@ -583,33 +604,24 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
583
604
  throw new LifecyclePlanningError("LIMIT_CONTEXT_MISMATCH", "Supplied execution policy provenance differs from the canonical chain/account/core/block context");
584
605
  }
585
606
  const protocol = context.chainId === 4663n ? "nitro" : "evm";
586
- const maxSimulationGas = configuration.maxSimulationGas ?? (context.chainId === 4663n ? 50_000_000n : undefined);
587
- let arbOSVersion: bigint | undefined; let maxTxComputeGas: bigint | undefined; let maxBlockComputeGas: bigint | undefined;
607
+ const maxSimulationGas = configuration.maxSimulationGas;
608
+ let arbOSVersion: bigint | undefined;
588
609
  if (protocol === "nitro") {
589
610
  try {
590
- const [rawVersion, txComputeGas, blockComputeGas] = await Promise.all([
591
- readNitroScalar(context, nitroArbSys, nitroArbSysAbi, "arbOSVersion"),
592
- readNitroScalar(context, nitroGasInfo, nitroGasInfoAbi, "getMaxTxGasLimit"),
593
- readNitroScalar(context, nitroGasInfo, nitroGasInfoAbi, "getMaxBlockGasLimit"),
594
- ]);
611
+ const rawVersion = await readNitroScalar(context, nitroArbSys, nitroArbSysAbi, "arbOSVersion");
595
612
  // ArbSys alone adds 55 to the logical Nitro ArbOS version.
596
613
  if (typeof rawVersion !== "bigint" || rawVersion < 105n) throw new Error("ArbOS version 50 or newer is required (ArbSys raw version >= 105)");
597
614
  arbOSVersion = rawVersion - 55n;
598
- maxTxComputeGas = txComputeGas;
599
- maxBlockComputeGas = blockComputeGas;
600
- if ([maxTxComputeGas, maxBlockComputeGas].some((limit) => typeof limit !== "bigint" || limit <= 0n || limit > maxUint64)) throw new Error("Nitro compute getters must be positive uint64 values");
601
615
  } catch (failure) {
602
616
  if (failure instanceof Error && failure.name === "AbortError") throw failure;
603
- throw new LifecyclePlanningError("NITRO_LIMITS_UNAVAILABLE", `Pinned Nitro compute limits could not be established: ${lifecycleFailureReason(failure)}; header gasLimit is not a fallback`);
617
+ throw new LifecyclePlanningError("NITRO_LIMITS_UNAVAILABLE", `Pinned Nitro version could not be established: ${lifecycleFailureReason(failure)}`);
604
618
  }
605
619
  if (configuration.estimateDataFee !== undefined) throw new LifecyclePlanningError("INVALID_LIMITS", "Nitro poster fees are already included in gas; an external data-fee estimator would double-charge them");
606
620
  }
607
621
  const envelopeCaps = [configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit].filter((limit): limit is bigint => limit !== undefined);
608
622
  if (protocol === "evm") envelopeCaps.push(context.block.gasLimit);
609
623
  const transactionGasCeiling = envelopeCaps.length === 0 ? undefined : envelopeCaps.reduce((minimum, limit) => limit < minimum ? limit : minimum);
610
- const executionGasCeiling = maxTxComputeGas !== undefined && maxBlockComputeGas !== undefined
611
- ? (maxTxComputeGas < maxBlockComputeGas ? maxTxComputeGas : maxBlockComputeGas)
612
- : transactionGasCeiling ?? context.block.gasLimit;
624
+ const executionGasCeiling = protocol === "nitro" ? ROBINHOOD_NATIVE_TRANSACTION_GAS_LIMIT : transactionGasCeiling ?? context.block.gasLimit;
613
625
  const unknownExecutionConstraints = [
614
626
  ...(protocol === "evm" && configuration.chainGasLimit === undefined ? ["Independent chain per-transaction execution cap is unknown; proof is bounded by the pinned header and validated backend"] : []),
615
627
  ...(configuration.rpcGasLimit === undefined ? ["RPC transaction gas-envelope cap is unknown"] : []),
@@ -621,7 +633,7 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
621
633
  return {
622
634
  configuration, assertCurrent,
623
635
  limits: Object.freeze({
624
- protocol, executionGasCeiling, transactionGasCeiling, arbOSVersion, maxTxComputeGas, maxBlockComputeGas, headroomBps,
636
+ protocol, executionGasCeiling, transactionGasCeiling, arbOSVersion, headroomBps,
625
637
  maxCalldataBytes: configuration.maxCalldataBytes, maxSimulationGas,
626
638
  blockGasLimit: context.block.gasLimit, chainGasLimit: configuration.chainGasLimit,
627
639
  rpcGasLimit: configuration.rpcGasLimit, accountGasLimit: configuration.accountGasLimit,