@black-market/sdk 0.16.1 → 0.17.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 (62) hide show
  1. package/README.md +5 -4
  2. package/dist/lifecycle/deployments/rwa-dynamic-pool-v1-20261113.d.ts +71 -0
  3. package/dist/lifecycle/deployments/rwa-dynamic-pool-v1-20261113.d.ts.map +1 -0
  4. package/dist/lifecycle/deployments/rwa-dynamic-pool-v1-20261113.js +73 -0
  5. package/dist/lifecycle/deployments/rwa-dynamic-pool-v1-20261113.js.map +1 -0
  6. package/dist/lifecycle/deployments/rwa-fixed-pool-v1-20261113.d.ts +71 -0
  7. package/dist/lifecycle/deployments/rwa-fixed-pool-v1-20261113.d.ts.map +1 -0
  8. package/dist/lifecycle/deployments/rwa-fixed-pool-v1-20261113.js +73 -0
  9. package/dist/lifecycle/deployments/rwa-fixed-pool-v1-20261113.js.map +1 -0
  10. package/dist/lifecycle/gas-profile.d.ts +1 -1
  11. package/dist/lifecycle/gas-profile.d.ts.map +1 -1
  12. package/dist/lifecycle/gas-profile.js +17 -6
  13. package/dist/lifecycle/gas-profile.js.map +1 -1
  14. package/dist/lifecycle/index.d.ts +2 -0
  15. package/dist/lifecycle/index.d.ts.map +1 -1
  16. package/dist/lifecycle/index.js +2 -0
  17. package/dist/lifecycle/index.js.map +1 -1
  18. package/dist/lifecycle/markets.d.ts +2 -0
  19. package/dist/lifecycle/markets.d.ts.map +1 -1
  20. package/dist/lifecycle/markets.js +32 -4
  21. package/dist/lifecycle/markets.js.map +1 -1
  22. package/dist/lifecycle/opening-buys.js +1 -1
  23. package/dist/lifecycle/opening-buys.js.map +1 -1
  24. package/dist/lifecycle/planner.d.ts.map +1 -1
  25. package/dist/lifecycle/planner.js +5 -1
  26. package/dist/lifecycle/planner.js.map +1 -1
  27. package/dist/lifecycle/presets.d.ts +4 -2
  28. package/dist/lifecycle/presets.d.ts.map +1 -1
  29. package/dist/lifecycle/presets.js +8 -1
  30. package/dist/lifecycle/presets.js.map +1 -1
  31. package/dist/lifecycle/raeto-abi.d.ts +29 -0
  32. package/dist/lifecycle/raeto-abi.d.ts.map +1 -1
  33. package/dist/lifecycle/raeto-abi.js +40 -1
  34. package/dist/lifecycle/raeto-abi.js.map +1 -1
  35. package/dist/lifecycle/rwa-abi.d.ts +752 -0
  36. package/dist/lifecycle/rwa-abi.d.ts.map +1 -0
  37. package/dist/lifecycle/rwa-abi.js +968 -0
  38. package/dist/lifecycle/rwa-abi.js.map +1 -0
  39. package/dist/lifecycle/rwa.d.ts +42 -0
  40. package/dist/lifecycle/rwa.d.ts.map +1 -0
  41. package/dist/lifecycle/rwa.js +113 -0
  42. package/dist/lifecycle/rwa.js.map +1 -0
  43. package/dist/lifecycle/transactions.d.ts.map +1 -1
  44. package/dist/lifecycle/transactions.js +11 -3
  45. package/dist/lifecycle/transactions.js.map +1 -1
  46. package/docs/launch-lifecycle.md +81 -1
  47. package/docs/lifecycle-gas-profiles.md +60 -3
  48. package/docs/reference.md +61 -0
  49. package/examples/README.md +2 -2
  50. package/package.json +1 -1
  51. package/src/lifecycle/deployments/rwa-dynamic-pool-v1-20261113.ts +74 -0
  52. package/src/lifecycle/deployments/rwa-fixed-pool-v1-20261113.ts +74 -0
  53. package/src/lifecycle/gas-profile.ts +17 -7
  54. package/src/lifecycle/index.ts +2 -0
  55. package/src/lifecycle/markets.ts +24 -2
  56. package/src/lifecycle/opening-buys.ts +1 -1
  57. package/src/lifecycle/planner.ts +6 -1
  58. package/src/lifecycle/presets.ts +8 -1
  59. package/src/lifecycle/raeto-abi.ts +41 -1
  60. package/src/lifecycle/rwa-abi.ts +968 -0
  61. package/src/lifecycle/rwa.ts +119 -0
  62. package/src/lifecycle/transactions.ts +9 -2
@@ -0,0 +1,119 @@
1
+ import { decodeAbiParameters, encodeAbiParameters, getAddress, isAddress, type Address, type Hex } from "viem";
2
+ import { decodePoolBoundV4LifecycleMarketConfig } from "./markets.js";
3
+ import { LifecycleRewardMode, type LaunchPlanV1, type MarketConfigV1 } from "./schema.js";
4
+ import { LifecyclePlanningError } from "./types.js";
5
+
6
+ export const RWA_FIXED_PROFILE_ID: Hex = "0x3b170b03f1ff1c808970f43827df234cde866d73608b6e3e3937601ae7c8676b";
7
+ export const RWA_DYNAMIC_PROFILE_ID: Hex = "0xab6c24038ff6ec16a300aa268dc4116f1649ae07c272dfc7e814c6f198515043";
8
+ export const RWA_FIXED_ADAPTER_ID: Hex = "0x0f13f4a8553a4dece69888deb425b04ff169d41132f3e2097034aff00f584dd6";
9
+ export const RWA_DYNAMIC_ADAPTER_ID: Hex = "0x46a5f7c611f6584cc99cffcdeb55183d6c3dcafe5fc6fcce85995be82639bbe1";
10
+ /** Upgradeable stock registry (ERC1967 proxy) the deployed RWA collectors read. */
11
+ export const RWA_ASSET_REGISTRY_ADDRESS: Address = "0x40356d61871C7Af760903FDD6ceb9344D7c3F328";
12
+ /** USDG is the only quote the deployed RWA collector factories accept. */
13
+ export const RWA_QUOTE_ASSET: Address = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
14
+ export const RWA_MAX_BASKET = 6;
15
+ export const RWA_MAX_PREMIUM_BPS = 100;
16
+ /** `maxPriceAge` value that disables the stock-feed age check (the USDG feed keeps its window). */
17
+ export const RWA_ANY_PRICE_AGE = 0xffffffff;
18
+
19
+ /**
20
+ * Exactly `RwaLaunchConfigV1`. `basket` is 1-6 strictly ascending listed stock tokens with
21
+ * `weightsBps[i]` for `basket[i]` summing to 10,000. `shareBps` 0-10,000 (0 = off),
22
+ * `premiumBps` 0-100, `maxPriceAge` 0 = registry defaults or `RWA_ANY_PRICE_AGE`.
23
+ */
24
+ export type RwaLaunchConfigV1 = {
25
+ shareBps: number;
26
+ premiumBps: number;
27
+ maxPriceAge: number;
28
+ basket: readonly Address[];
29
+ weightsBps: readonly number[];
30
+ };
31
+
32
+ const rwaLaunchConfigComponents = [
33
+ { name: "shareBps", type: "uint16" }, { name: "premiumBps", type: "uint16" }, { name: "maxPriceAge", type: "uint32" },
34
+ { name: "basket", type: "address[]" }, { name: "weightsBps", type: "uint16[]" },
35
+ ] as const;
36
+
37
+ const invalid = (reason: string) => new LifecyclePlanningError("INVALID_RWA_CONFIG", reason);
38
+ const uint = (value: number, max: number) => Number.isInteger(value) && value >= 0 && value <= max;
39
+
40
+ export function isRwaProfile(profileId: Hex): boolean {
41
+ const id = profileId.toLowerCase();
42
+ return id === RWA_FIXED_PROFILE_ID || id === RWA_DYNAMIC_PROFILE_ID;
43
+ }
44
+
45
+ /** Static terms only; registry listing is checked on-chain when the launch begins. */
46
+ export function validateRwaLaunchConfig(config: RwaLaunchConfigV1): void {
47
+ if (!uint(config.shareBps, 10000)) throw invalid("RWA shareBps must be an integer from 0 to 10000");
48
+ if (!uint(config.premiumBps, RWA_MAX_PREMIUM_BPS)) throw invalid("RWA premiumBps must be an integer from 0 to 100");
49
+ if (!uint(config.maxPriceAge, RWA_ANY_PRICE_AGE)) throw invalid("RWA maxPriceAge must be a uint32");
50
+ const count = config.basket.length;
51
+ if (count === 0 || count > RWA_MAX_BASKET) throw invalid("An RWA basket holds 1 to 6 stock tokens");
52
+ if (config.weightsBps.length !== count) throw invalid("RWA weightsBps needs exactly one weight per basket stock");
53
+ let previous = 0n, total = 0;
54
+ for (const [index, asset] of config.basket.entries()) {
55
+ if (!isAddress(asset, { strict: false }) || BigInt(asset) <= previous) throw invalid("RWA basket stocks must be unique ascending addresses");
56
+ if (asset.toLowerCase() === RWA_QUOTE_ASSET.toLowerCase()) throw invalid("USDG cannot be an RWA basket stock");
57
+ const weight = config.weightsBps[index]!;
58
+ if (!uint(weight, 10000)) throw invalid("RWA weights must be integer basis points");
59
+ previous = BigInt(asset);
60
+ total += weight;
61
+ }
62
+ if (total !== 10000) throw invalid("RWA weightsBps must sum to exactly 10000");
63
+ }
64
+
65
+ /** Exactly abi.encode(RwaLaunchConfigV1), including the leading tuple offset word. */
66
+ export function encodeRwaLaunchTail(config: RwaLaunchConfigV1): Hex {
67
+ validateRwaLaunchConfig(config);
68
+ return encodeAbiParameters([{ type: "tuple", components: rwaLaunchConfigComponents }], [{
69
+ shareBps: config.shareBps, premiumBps: config.premiumBps, maxPriceAge: config.maxPriceAge,
70
+ basket: config.basket.map((asset) => getAddress(asset)), weightsBps: [...config.weightsBps],
71
+ }]);
72
+ }
73
+
74
+ export function decodeRwaLaunchTail(encoded: Hex): RwaLaunchConfigV1 {
75
+ let decoded;
76
+ try {
77
+ decoded = decodeAbiParameters([{ type: "tuple", components: rwaLaunchConfigComponents }], encoded)[0];
78
+ } catch {
79
+ throw invalid("RWA tail must be abi.encode(RwaLaunchConfigV1)");
80
+ }
81
+ const config: RwaLaunchConfigV1 = {
82
+ shareBps: decoded.shareBps, premiumBps: decoded.premiumBps, maxPriceAge: decoded.maxPriceAge,
83
+ basket: [...decoded.basket], weightsBps: [...decoded.weightsBps],
84
+ };
85
+ if (encodeRwaLaunchTail(config).toLowerCase() !== encoded.toLowerCase()) throw invalid("RWA tail is not canonical");
86
+ return config;
87
+ }
88
+
89
+ /** Outer market binding: config6, the variant's adapter and the USDG quote. */
90
+ export function validateRwaMarketContext(market: MarketConfigV1): void {
91
+ if (!isRwaProfile(market.profileId)) return;
92
+ const adapterId = market.profileId.toLowerCase() === RWA_FIXED_PROFILE_ID ? RWA_FIXED_ADAPTER_ID : RWA_DYNAMIC_ADAPTER_ID;
93
+ if (market.configVersion !== 6 || market.adapterId.toLowerCase() !== adapterId || market.quoteAsset.toLowerCase() !== RWA_QUOTE_ASSET.toLowerCase()) {
94
+ throw invalid("RWA markets require config6, their RWA adapter and a USDG quote");
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Plan checks for the collector-construction invariants that would otherwise fail at market
100
+ * preparation, after begin: each RWA market's outer binding, a Dividends launch token, and
101
+ * every basket stock committed as a hub fee asset with a 0/10000/0 owner/rewards/burn policy.
102
+ * No-op for plans without RWA markets.
103
+ */
104
+ export function validateRwaLaunchPlan(plan: LaunchPlanV1): void {
105
+ const policies = new Map(plan.feeAssets.map((policy) => [policy.asset.toLowerCase(), policy]));
106
+ for (const market of plan.markets) {
107
+ if (!isRwaProfile(market.profileId)) continue;
108
+ validateRwaMarketContext(market);
109
+ const config = decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
110
+ if (config.version !== 6 || config.rwaLaunchConfig === undefined) throw invalid("RWA markets require an RWA tail");
111
+ if (plan.token.rewardMode !== LifecycleRewardMode.Dividends) throw invalid("RWA launches require a Dividends launch token");
112
+ for (const asset of config.rwaLaunchConfig.basket) {
113
+ const policy = policies.get(asset.toLowerCase());
114
+ if (policy === undefined || policy.ownerBps !== 0 || policy.rewardsBps !== 10000 || policy.burnBps !== 0) {
115
+ throw invalid("Every RWA basket stock must be a fee asset with a 0/10000/0 owner/rewards/burn policy");
116
+ }
117
+ }
118
+ }
119
+ }
@@ -8,6 +8,7 @@ import { UFG_LAUNCH_PROXY_ADDRESS, isUfgProfile } from "./ufg-config.js";
8
8
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
9
9
  import { isHmmProfile } from "./hmm.js";
10
10
  import { isRaetoProfile } from "./raeto.js";
11
+ import { isRwaProfile } from "./rwa.js";
11
12
  import { isAutoCompoundProfile } from "./auto-compound.js";
12
13
  import type { UfgLaunchConfig } from "./ufg-config.js";
13
14
 
@@ -22,7 +23,11 @@ const preparationCosts = {
22
23
  raeto: { market: 9_300_000n, position: 70_000n },
23
24
  // Same matrix: auto-compound1 <=8.71M,32 <=10.76M.
24
25
  autoCompound: { market: 8_900_000n, position: 70_000n },
26
+ // template-gas-rwa-matrix.json: RWA one-stock1 9.85M, six-stock1 10.24M, six-stock32 12.29M;
27
+ // the collector checks each basket stock against the hub policy and registry.
28
+ rwa: { market: 9_900_000n, position: 70_000n },
25
29
  } as const;
30
+ const RWA_PREPARATION_GAS_PER_STOCK = 80_000n;
26
31
  export const LIFECYCLE_CAPACITY_PADDING_BPS = 2500;
27
32
  /** Full single-market observations include call overhead. Add conservative full
28
33
  * calldata/hash processing (16 intrinsic gas per nonzero byte plus8 for copying). */
@@ -32,13 +37,15 @@ export function preparationGasEstimate(plan: LaunchPlanV1, first: number, count:
32
37
  let gas = 0n;
33
38
  for (let index = first; index < first + count; index += 1) {
34
39
  const market = plan.markets[index]!;
40
+ const bound = isPoolBoundV4ConfigVersion(market.configVersion) ? decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion) : undefined;
35
41
  const positions = market.configVersion === 1 ? decodeAbyssLifecycleMarketConfig(market.config).positions.length
36
- : isPoolBoundV4ConfigVersion(market.configVersion) ? decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion).positions.length
37
- : decodeV4LifecycleMarketConfig(market.config).positions.length;
42
+ : bound !== undefined ? bound.positions.length : decodeV4LifecycleMarketConfig(market.config).positions.length;
38
43
  const cost = market.configVersion === 1 ? preparationCosts.abyss : isHmmProfile(market.profileId) ? preparationCosts.hmm
39
44
  : isUfgProfile(market.profileId) ? preparationCosts.ufg : isRaetoProfile(market.profileId) ? preparationCosts.raeto
45
+ : isRwaProfile(market.profileId) ? preparationCosts.rwa
40
46
  : isAutoCompoundProfile(market.profileId) ? preparationCosts.autoCompound : preparationCosts.v4;
41
47
  gas += cost.market + cost.position * BigInt(positions - 1);
48
+ if (bound?.version === 6 && bound.rwaLaunchConfig !== undefined) gas += RWA_PREPARATION_GAS_PER_STOCK * BigInt(bound.rwaLaunchConfig.basket.length);
42
49
  }
43
50
  return gas;
44
51
  }