@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.
- package/README.md +4 -3
- package/dist/lifecycle/authors.js +2 -2
- package/dist/lifecycle/authors.js.map +1 -1
- package/dist/lifecycle/gas-profile.d.ts +35 -0
- package/dist/lifecycle/gas-profile.d.ts.map +1 -0
- package/dist/lifecycle/gas-profile.js +183 -0
- package/dist/lifecycle/gas-profile.js.map +1 -0
- package/dist/lifecycle/index.d.ts +3 -1
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +3 -1
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/opening-buys.d.ts +13 -0
- package/dist/lifecycle/opening-buys.d.ts.map +1 -0
- package/dist/lifecycle/opening-buys.js +171 -0
- package/dist/lifecycle/opening-buys.js.map +1 -0
- package/dist/lifecycle/planner.d.ts +2 -2
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +178 -163
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +3 -0
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +3 -0
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +21 -20
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +24 -19
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +58 -50
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +3 -4
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +98 -247
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +1 -2
- package/dist/lifecycle/snapshot.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +19 -3
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +25 -20
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +53 -45
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg.d.ts.map +1 -1
- package/dist/lifecycle/ufg.js +3 -7
- package/dist/lifecycle/ufg.js.map +1 -1
- package/docs/launch-lifecycle.md +67 -78
- package/docs/lifecycle-gas-profiles.md +90 -0
- package/docs/reference.md +43 -45
- package/examples/README.md +11 -8
- package/examples/launch-example.ts +35 -25
- package/examples/launch-lifecycle-smoke.ts +5 -3
- package/package.json +1 -1
- package/src/lifecycle/authors.ts +2 -2
- package/src/lifecycle/gas-profile.ts +175 -0
- package/src/lifecycle/index.ts +3 -1
- package/src/lifecycle/opening-buys.ts +157 -0
- package/src/lifecycle/planner.ts +164 -151
- package/src/lifecycle/presets.ts +5 -0
- package/src/lifecycle/progress.ts +19 -20
- package/src/lifecycle/rpc.ts +56 -44
- package/src/lifecycle/simulation.ts +85 -231
- package/src/lifecycle/snapshot.ts +1 -2
- package/src/lifecycle/transactions.ts +33 -21
- package/src/lifecycle/types.ts +52 -42
- 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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
}
|
package/src/lifecycle/rpc.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
/**
|
|
395
|
+
/** Client-bound identity, observed once per underlying client, never a freshness guard. */
|
|
377
396
|
export function readLifecycleChainId(client: LifecycleRpcClient): Promise<bigint> {
|
|
378
|
-
|
|
379
|
-
|
|
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<
|
|
385
|
-
|
|
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"
|
|
495
|
+
export async function assertLifecycleBlock(client: LifecycleRpcClient, block: Pick<LifecycleBlock, "number" | "hash">): Promise<void> {
|
|
476
496
|
if (client instanceof InvocationLifecycleClient) return;
|
|
477
|
-
const
|
|
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([
|
|
520
|
-
|
|
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
|
-
/**
|
|
524
|
-
export
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
|
587
|
-
let arbOSVersion: bigint | undefined;
|
|
607
|
+
const maxSimulationGas = configuration.maxSimulationGas;
|
|
608
|
+
let arbOSVersion: bigint | undefined;
|
|
588
609
|
if (protocol === "nitro") {
|
|
589
610
|
try {
|
|
590
|
-
const
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|