@peddles/sdk 0.2.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 (124) hide show
  1. package/CLAUDE_PROMPT.md +159 -0
  2. package/LICENSE +21 -0
  3. package/README.md +186 -0
  4. package/dist/abis.d.ts +19 -0
  5. package/dist/abis.d.ts.map +1 -0
  6. package/dist/abis.generated.d.ts +2029 -0
  7. package/dist/abis.generated.d.ts.map +1 -0
  8. package/dist/abis.generated.js +2723 -0
  9. package/dist/abis.generated.js.map +1 -0
  10. package/dist/abis.js +19 -0
  11. package/dist/abis.js.map +1 -0
  12. package/dist/artDex.d.ts +937 -0
  13. package/dist/artDex.d.ts.map +1 -0
  14. package/dist/artDex.js +155 -0
  15. package/dist/artDex.js.map +1 -0
  16. package/dist/client.d.ts +10 -0
  17. package/dist/client.d.ts.map +1 -0
  18. package/dist/client.js +2 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/clog.d.ts +75 -0
  21. package/dist/clog.d.ts.map +1 -0
  22. package/dist/clog.js +91 -0
  23. package/dist/clog.js.map +1 -0
  24. package/dist/clogInflow.d.ts +136 -0
  25. package/dist/clogInflow.d.ts.map +1 -0
  26. package/dist/clogInflow.js +177 -0
  27. package/dist/clogInflow.js.map +1 -0
  28. package/dist/deployments.d.ts +30 -0
  29. package/dist/deployments.d.ts.map +1 -0
  30. package/dist/deployments.generated.d.ts +92 -0
  31. package/dist/deployments.generated.d.ts.map +1 -0
  32. package/dist/deployments.generated.js +93 -0
  33. package/dist/deployments.generated.js.map +1 -0
  34. package/dist/deployments.js +52 -0
  35. package/dist/deployments.js.map +1 -0
  36. package/dist/feeSplit.d.ts +54 -0
  37. package/dist/feeSplit.d.ts.map +1 -0
  38. package/dist/feeSplit.js +69 -0
  39. package/dist/feeSplit.js.map +1 -0
  40. package/dist/feeTerms.d.ts +46 -0
  41. package/dist/feeTerms.d.ts.map +1 -0
  42. package/dist/feeTerms.js +80 -0
  43. package/dist/feeTerms.js.map +1 -0
  44. package/dist/index.d.ts +102 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +108 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/launch/abi.generated.d.ts +2667 -0
  49. package/dist/launch/abi.generated.d.ts.map +1 -0
  50. package/dist/launch/abi.generated.js +2472 -0
  51. package/dist/launch/abi.generated.js.map +1 -0
  52. package/dist/launch/addresses.d.ts +24 -0
  53. package/dist/launch/addresses.d.ts.map +1 -0
  54. package/dist/launch/addresses.js +35 -0
  55. package/dist/launch/addresses.js.map +1 -0
  56. package/dist/launch/feeTerms.d.ts +52 -0
  57. package/dist/launch/feeTerms.d.ts.map +1 -0
  58. package/dist/launch/feeTerms.js +72 -0
  59. package/dist/launch/feeTerms.js.map +1 -0
  60. package/dist/launch/index.d.ts +33 -0
  61. package/dist/launch/index.d.ts.map +1 -0
  62. package/dist/launch/index.js +24 -0
  63. package/dist/launch/index.js.map +1 -0
  64. package/dist/launch/revert.d.ts +23 -0
  65. package/dist/launch/revert.d.ts.map +1 -0
  66. package/dist/launch/revert.js +169 -0
  67. package/dist/launch/revert.js.map +1 -0
  68. package/dist/launch/salt.d.ts +90 -0
  69. package/dist/launch/salt.d.ts.map +1 -0
  70. package/dist/launch/salt.js +153 -0
  71. package/dist/launch/salt.js.map +1 -0
  72. package/dist/launch/stockCall.d.ts +122 -0
  73. package/dist/launch/stockCall.d.ts.map +1 -0
  74. package/dist/launch/stockCall.js +144 -0
  75. package/dist/launch/stockCall.js.map +1 -0
  76. package/dist/launch/tickMath.d.ts +39 -0
  77. package/dist/launch/tickMath.d.ts.map +1 -0
  78. package/dist/launch/tickMath.js +109 -0
  79. package/dist/launch/tickMath.js.map +1 -0
  80. package/dist/launch/variants.d.ts +64 -0
  81. package/dist/launch/variants.d.ts.map +1 -0
  82. package/dist/launch/variants.js +131 -0
  83. package/dist/launch/variants.js.map +1 -0
  84. package/dist/launch/wethCall.d.ts +133 -0
  85. package/dist/launch/wethCall.d.ts.map +1 -0
  86. package/dist/launch/wethCall.js +173 -0
  87. package/dist/launch/wethCall.js.map +1 -0
  88. package/dist/launch/wethPlan.d.ts +71 -0
  89. package/dist/launch/wethPlan.d.ts.map +1 -0
  90. package/dist/launch/wethPlan.js +146 -0
  91. package/dist/launch/wethPlan.js.map +1 -0
  92. package/dist/perps/abi.generated.d.ts +794 -0
  93. package/dist/perps/abi.generated.d.ts.map +1 -0
  94. package/dist/perps/abi.generated.js +738 -0
  95. package/dist/perps/abi.generated.js.map +1 -0
  96. package/dist/perps/index.d.ts +160 -0
  97. package/dist/perps/index.d.ts.map +1 -0
  98. package/dist/perps/index.js +147 -0
  99. package/dist/perps/index.js.map +1 -0
  100. package/package.json +105 -0
  101. package/src/abis.generated.ts +2738 -0
  102. package/src/abis.ts +35 -0
  103. package/src/artDex.ts +254 -0
  104. package/src/client.ts +10 -0
  105. package/src/clog.ts +171 -0
  106. package/src/clogInflow.ts +217 -0
  107. package/src/deployments.generated.ts +98 -0
  108. package/src/deployments.ts +60 -0
  109. package/src/feeSplit.ts +76 -0
  110. package/src/feeTerms.ts +137 -0
  111. package/src/index.ts +233 -0
  112. package/src/launch/abi.generated.ts +2481 -0
  113. package/src/launch/addresses.ts +58 -0
  114. package/src/launch/feeTerms.ts +93 -0
  115. package/src/launch/index.ts +93 -0
  116. package/src/launch/revert.ts +188 -0
  117. package/src/launch/salt.ts +216 -0
  118. package/src/launch/stockCall.ts +223 -0
  119. package/src/launch/tickMath.ts +139 -0
  120. package/src/launch/variants.ts +183 -0
  121. package/src/launch/wethCall.ts +240 -0
  122. package/src/launch/wethPlan.ts +257 -0
  123. package/src/perps/abi.generated.ts +741 -0
  124. package/src/perps/index.ts +263 -0
@@ -0,0 +1,98 @@
1
+ // GENERATED FILE — DO NOT EDIT.
2
+ // Written by script/generate-deployments.mjs from contracts/deployments/<chainId>.json
3
+ // and <chainId>-perp.json.
4
+ // Re-run it after any deploy and commit the result.
5
+
6
+ /** Every Peddles contract address the SDK knows, by chain id. */
7
+ export const DEPLOYMENTS = {
8
+ 4663: {
9
+ PeddlesClogVaultFactory: '0x1d2142699F1D818203738226240C605243a39b9e',
10
+ PeddlesFactoryV20: '0x51336A94B55Ad82D1099Dc82b27b151eDFfc1978',
11
+ PeddlesFeeForwarder: '0xc572A003d0Ab529da670a42177Fc583b03D5b026',
12
+ PeddlesFeeHook: '0xfe055282E3cD471A8b2cd922d31E75ECd3bBc0CC',
13
+ PeddlesHandleLauncher: '0x3720ca29D87E692000Ca71C56a56a3BA05B00CD1',
14
+ PeddlesHandlePotFactory: '0x2b5881c79a83C9EB17C67354F6737f3c9A120DE2',
15
+ PeddlesHolderRewardsFactory: '0xB22d6D7B1eC296AA79cbFf4ABE4992A726BE1978',
16
+ PeddlesLaunchOrchestratorV20: '0x81213e2cE27f03714143152aE5cf5c94094f1978',
17
+ PeddlesLiquidityActivationManager: '0xBE8d8999b53c5E5249feBafe5f505221F3e41978',
18
+ PeddlesNFTBondingGraduationOrchestratorV20: '0x6C5032DeDCc78b92b370C86a2aA0EA3AD0D71978',
19
+ PeddlesNFTFactory: '0x2e0029BeDCBb2f211Fd17EeBA8f40657cF6A1978',
20
+ PeddlesNftFeeDistributorFactory: '0x3081d5E69b7D12A330C9594C175F88EB77f31978',
21
+ PeddlesNftLiquidityReserve: '0x367F626775E22818C369BB3a276E3e9Fbc201978',
22
+ PeddlesNFTMarketplace: '0xC0374a0776449fE60dD1653C6421bEB41e911978',
23
+ PeddlesNftStockGraduationOrchestrator: '0x3DAd8D2B78C8a71f01600FDecA0b17af5F961978',
24
+ PeddlesPerpFactory: '0xCFA648281759C406E921F736c86ece2a9c4D1Fca',
25
+ PeddlesPerpHookDeployer: '0x3ab1848069cE7d19CD62ffB645292146cDd0139F',
26
+ PeddlesPerpTreasury: '0x2C322f48B2dD97f6c5050eDF5F349C7f70126dB5',
27
+ PeddlesRouteSwapRouter: '0xA4858047F9c7359CC0d9071598BFA74187739d8D',
28
+ PeddlesStockLaunchpad: '0x4b7Aa977eA4B95D9859D73e6bc922ecF2F881978',
29
+ PeddlesStockSwapRouter: '0x5A934c5eE47daa3b4562f9Dc9224bB00e9191978',
30
+ PeddlesStockVaultFactory: '0xa565E57046B51C4C1144b837A1871FF89c141978',
31
+ PeddlesTransferValidator: '0x2ADA0EA86925b5EbA1CF7B75e0d606C89b1B1978',
32
+ PeddlesV4LiquidityExecutor: '0xAB23BD1234AB1BCa48a97aB8e2baaFAaE7A21978',
33
+ PeddlesV4SwapRouter: '0xDd1F65380C9f7283A61FC489DCAceA352ee31978',
34
+ PeddlesVaultFactoryV20: '0xe6EE48916F87DD8E8Cab9D100a5E9717aE731978',
35
+ PeddlesVaultRegistry: '0xd322A44c4600dfDcF399Ceb7a32869e83d561978',
36
+ },
37
+ 8453: {
38
+ PeddlesClogVaultFactory: '0x8563Aeec8C63d53BC8dfbE56FF9134a7b1e80503',
39
+ PeddlesFactoryV20: '0x90bD4d38F621529b4aD6480c221075B7317b1978',
40
+ PeddlesFeeForwarder: '0x77886951f19458B2FC27D4373553001A620264D3',
41
+ PeddlesFeeHook: '0xAB8E39207718f519865D6f0d52eFB31b231D40cc',
42
+ PeddlesFlywheel: '0x2E9366BF81d0D0ebA905927B55d3Fa3008891978',
43
+ PeddlesHandleLauncher: '0x265f0653aA62B30Fab11d0718D12CedE7965d7C4',
44
+ PeddlesHandlePotFactory: '0xC2575Cc3Eb3934C826Fa33FC9BA2e6DA8186D725',
45
+ PeddlesHolderRewardsFactory: '0xE4A89dCb02211409b50CEFd9B570E67dA2aD1978',
46
+ PeddlesLaunchOrchestratorV20: '0x7e7E62B41E447B6526E89D0AAAd7Cac9FB9f1978',
47
+ PeddlesLiquidityActivationManager: '0xa8bfd83d21eec43F9a984C6B13a31Ce1df191978',
48
+ PeddlesNFTBondingGraduationOrchestratorV20: '0xB7bB0f25B1AE59adcAa2787C68cd50296d2B1978',
49
+ PeddlesNFTFactory: '0xd111413B91b0E31865464658071099779e2C1978',
50
+ PeddlesNftFeeDistributorFactory: '0x9B0c155e7248706860c9707c2a8f41A0a2281978',
51
+ PeddlesNftLiquidityReserve: '0x444C34FF8503eA088a36792659D7c34D7F931978',
52
+ PeddlesNFTMarketplace: '0x5362a7DE96E8b3474Aef1a0fE6F6e2427Bd81978',
53
+ PeddlesNftStockGraduationOrchestrator: '0xD6d41a5029EC7F78886C135A71A9c85e96781978',
54
+ PeddlesPerpFactory: '0x71a8EF2288fFdB3b1d546C33a16F0d4104E11488',
55
+ PeddlesPerpHookDeployer: '0x9122dAd755B6F8c1DB5F2B7c494580169fB327F2',
56
+ PeddlesPerpTreasury: '0xDB77eA86006E530D25789F2cf2aeB7bd2C7c3745',
57
+ PeddlesRouteSwapRouter: '0x8f04A804Ba989d38A68D93eb8679d3a967f2b079',
58
+ PeddlesStockLaunchpad: '0x680805FBd0D6225d0bE4E12A2B73b2Ed83a31978',
59
+ PeddlesStockSwapRouter: '0xd150B1E102493e4F35A1e95DdA80948C0e581978',
60
+ PeddlesStockVaultFactory: '0x5EF74DFc6Dfd9e5dAb314Df5747AC4E117e41978',
61
+ PeddlesToken: '0xCF2Be52981b5BD563C268a0e2b17D50B53e21978',
62
+ PeddlesTransferValidator: '0xD1390f3E4741963A7eEd1E53163a1B09c97e1978',
63
+ PeddlesV4LiquidityExecutor: '0x4Ca94531420F0e4f5a612475c8c4E35E9c8d1978',
64
+ PeddlesV4SwapRouter: '0xbf7A709cBf71b303B856126D3aEb5051a07c1978',
65
+ PeddlesVaultFactoryV20: '0xD4fe62B8cd5a7e07fF09D6D1151fA5BC8A771978',
66
+ PeddlesVaultRegistry: '0xb390c4A83D8B308b378250a59F273A7c9aDF1978',
67
+ },
68
+ } as const;
69
+
70
+ /**
71
+ * The perp launchpad on each chain that has one (from contracts/deployments/<chainId>-perp.json).
72
+ * `baseCandidates` are the bases the deploy whitelisted — CANDIDATES only: whether a base is
73
+ * allowed, and on what curve, is read live from `PeddlesPerpFactory.bases(base)`.
74
+ */
75
+ export const PERP_DEPLOYMENTS = {
76
+ 4663: {
77
+ PeddlesPerpFactory: '0xCFA648281759C406E921F736c86ece2a9c4D1Fca',
78
+ PeddlesPerpHookDeployer: '0x3ab1848069cE7d19CD62ffB645292146cDd0139F',
79
+ PeddlesPerpTreasury: '0x2C322f48B2dD97f6c5050eDF5F349C7f70126dB5',
80
+ hookCodePointer: '0xf97907D6AA1Ae3D3Ec3247eEfA998C8D6BE78605',
81
+ baseCandidates: {
82
+ WETH: '0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73',
83
+ },
84
+ },
85
+ 8453: {
86
+ PeddlesPerpFactory: '0x71a8EF2288fFdB3b1d546C33a16F0d4104E11488',
87
+ PeddlesPerpHookDeployer: '0x9122dAd755B6F8c1DB5F2B7c494580169fB327F2',
88
+ PeddlesPerpTreasury: '0xDB77eA86006E530D25789F2cf2aeB7bd2C7c3745',
89
+ hookCodePointer: '0xDAE55090fF0ffb81E3da33C79950DF7Ef78379aA',
90
+ baseCandidates: {
91
+ WETH: '0x4200000000000000000000000000000000000006',
92
+ },
93
+ },
94
+ } as const;
95
+
96
+ export type KnownChainId = keyof typeof DEPLOYMENTS;
97
+ export type PerpChainId = keyof typeof PERP_DEPLOYMENTS;
98
+ export type ContractName<C extends KnownChainId = KnownChainId> = keyof (typeof DEPLOYMENTS)[C];
@@ -0,0 +1,60 @@
1
+ import { DEPLOYMENTS, type KnownChainId, type ContractName } from './deployments.generated.js';
2
+
3
+ export { DEPLOYMENTS };
4
+ export type { KnownChainId, ContractName };
5
+
6
+ /** Thrown when the SDK is asked about a chain it ships no address book for. */
7
+ export class UnknownChainError extends Error {
8
+ readonly chainId: number;
9
+ constructor(chainId: number) {
10
+ super(
11
+ `Peddles SDK has no deployment for chain ${chainId}. ` +
12
+ `Known chains: ${Object.keys(DEPLOYMENTS).join(', ')}. ` +
13
+ `Pass an explicit address map if you are pointing at a private deployment.`,
14
+ );
15
+ this.name = 'UnknownChainError';
16
+ this.chainId = chainId;
17
+ }
18
+ }
19
+
20
+ /** Thrown when a chain is known but does not carry the contract being asked for. */
21
+ export class UnknownContractError extends Error {
22
+ constructor(chainId: number, name: string) {
23
+ super(
24
+ `Peddles SDK has no address for ${name} on chain ${chainId}. ` +
25
+ `That deployment may predate the contract.`,
26
+ );
27
+ this.name = 'UnknownContractError';
28
+ }
29
+ }
30
+
31
+ export function isKnownChain(chainId: number): chainId is KnownChainId {
32
+ return Object.prototype.hasOwnProperty.call(DEPLOYMENTS, chainId);
33
+ }
34
+
35
+ /**
36
+ * The address book for one chain.
37
+ *
38
+ * FAILS CLOSED, ALWAYS. An unknown chain throws rather than returning a default
39
+ * or an empty map, and a missing contract throws rather than returning
40
+ * `undefined` or the zero address. Both of those alternatives look like a
41
+ * working call right up until a transaction is signed against nothing.
42
+ *
43
+ * This is not hypothetical in this codebase: every address changed in a single
44
+ * redeploy, and one stale copy of this list had the API quoting a launchpad that
45
+ * no longer existed — an error that surfaced as "neither leg is a launched coin"
46
+ * rather than as a missing address.
47
+ */
48
+ export function deploymentFor(chainId: number): Readonly<Record<string, `0x${string}`>> {
49
+ if (!isKnownChain(chainId)) throw new UnknownChainError(chainId);
50
+ return DEPLOYMENTS[chainId] as Readonly<Record<string, `0x${string}`>>;
51
+ }
52
+
53
+ /** One address, or a throw. Never a default, never the zero address. */
54
+ export function addressOf(chainId: number, name: string): `0x${string}` {
55
+ const book = deploymentFor(chainId);
56
+ // Own properties only: `book['constructor']` would otherwise return a function.
57
+ const found = Object.prototype.hasOwnProperty.call(book, name) ? book[name] : undefined;
58
+ if (!found || /^0x0{40}$/.test(found)) throw new UnknownContractError(chainId, name);
59
+ return found;
60
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * How one charged fee divides — the exact arithmetic `PeddlesFeeHook._book` runs.
3
+ *
4
+ * IMPORT-FREE on purpose, like `clogInflow.ts`: it is pure bigint maths with no
5
+ * runtime, so a UI, an indexer or a test can use it without pulling in viem.
6
+ *
7
+ * THE MODEL (fixed at launch, never changed afterwards):
8
+ *
9
+ * creatorTaxBps the ALL-IN rate a trader pays, 100..1000 (1%..10%)
10
+ * excessToCreatorBps the creator's share of everything ABOVE the two fixed
11
+ * 0.50% legs, 0..10000; the rest of that excess is holders'
12
+ *
13
+ * platform = fee × 50 / creatorTaxBps (floored)
14
+ * creatorBase = fee × 50 / creatorTaxBps (floored)
15
+ * excess = fee − platform − creatorBase
16
+ * creator = creatorBase + excess × excessToCreatorBps / 10000 (floored)
17
+ * holders = fee − platform − creator (the remainder)
18
+ *
19
+ * Every floor lands its dust with holders, so `platform + creator + holders`
20
+ * equals `fee` exactly; at `excessToCreatorBps == 10000` the holder leg is 0.
21
+ *
22
+ * `fee` is what the hook CHARGED, in the pool's quote asset. It is not the trade
23
+ * size: the hook charges `gross × creatorTaxBps / 10000` (floored) on an exact-in
24
+ * trade and grosses up an exact-out one. During a pool's opening window the hook
25
+ * uses a different, fixed split (½ liquidity / ¼ creator / ¼ platform) that this
26
+ * function does not model.
27
+ */
28
+
29
+ /** `PeddlesFeeHook.PLATFORM_FEE_BPS`. */
30
+ export const PLATFORM_FEE_BPS = 50;
31
+ /** `PeddlesFeeHook.CREATOR_FEE_BPS`. */
32
+ export const CREATOR_FEE_BPS = 50;
33
+ /** `PeddlesFeeHook.MIN_CREATOR_TAX_BPS` — the two fixed legs added together. */
34
+ export const MIN_CREATOR_TAX_BPS = PLATFORM_FEE_BPS + CREATOR_FEE_BPS;
35
+ /** `PeddlesFeeHook.MAX_CREATOR_TAX_BPS`. */
36
+ export const MAX_CREATOR_TAX_BPS = 1000;
37
+ /** Basis-point denominator. */
38
+ export const BPS = 10_000;
39
+ /** `PeddlesFeeHook.MAX_FEE_AMOUNT` — the largest fee one accrual can represent (int128 max). */
40
+ export const MAX_FEE_AMOUNT = (1n << 127n) - 1n;
41
+
42
+ /** One fee, divided. Base units of the pool's quote asset. */
43
+ export interface FeeSplit {
44
+ readonly platform: bigint;
45
+ readonly creator: bigint;
46
+ readonly holders: bigint;
47
+ }
48
+
49
+ function assertBps(label: string, value: number, min: number, max: number): void {
50
+ if (!Number.isInteger(value) || value < min || value > max) {
51
+ throw new RangeError(`${label} must be an integer in [${min}, ${max}], got ${String(value)}`);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Divide a charged fee between platform, creator and holders.
57
+ *
58
+ * Throws `RangeError` for terms `registerPool` would refuse, and for a fee the
59
+ * hook would not charge at all (negative, or above `MAX_FEE_AMOUNT`). Returning a
60
+ * split for either would describe a charge that cannot happen.
61
+ */
62
+ export function feeSplit(creatorTaxBps: number, excessToCreatorBps: number, fee: bigint): FeeSplit {
63
+ assertBps('creatorTaxBps', creatorTaxBps, MIN_CREATOR_TAX_BPS, MAX_CREATOR_TAX_BPS);
64
+ assertBps('excessToCreatorBps', excessToCreatorBps, 0, BPS);
65
+ if (typeof fee !== 'bigint') throw new TypeError('fee must be a bigint in base units');
66
+ if (fee < 0n) throw new RangeError(`fee must not be negative, got ${fee}`);
67
+ if (fee > MAX_FEE_AMOUNT) throw new RangeError(`fee ${fee} exceeds MAX_FEE_AMOUNT; the hook charges nothing`);
68
+
69
+ const total = BigInt(creatorTaxBps);
70
+ const platform = (fee * BigInt(PLATFORM_FEE_BPS)) / total;
71
+ const creatorBase = (fee * BigInt(CREATOR_FEE_BPS)) / total;
72
+ const excess = fee - platform - creatorBase;
73
+ const creator = creatorBase + (excess * BigInt(excessToCreatorBps)) / BigInt(BPS);
74
+ const holders = fee - platform - creator;
75
+ return { platform, creator, holders };
76
+ }
@@ -0,0 +1,137 @@
1
+ import type { ReadClient } from './client.js';
2
+ import type { Address, Hex } from 'viem';
3
+ import { feeHookAbi, liquidityExecutorAbi, stockLaunchpadAbi } from './abis.js';
4
+ import { addressOf } from './deployments.js';
5
+
6
+ /**
7
+ * A launch pool's permanent terms, read from `PeddlesFeeHook`.
8
+ *
9
+ * Every launch pool — stock-paired, WETH-paired, Clog, and both NFT graduations —
10
+ * is registered with the hook once, at launch, and nothing can change these
11
+ * afterwards. What a buyer pays is `creatorTaxBps`; `excessToCreatorBps` decides
12
+ * who receives the part above the fixed 1% (creator vs holders).
13
+ */
14
+ export interface PoolTerms {
15
+ readonly poolId: Hex;
16
+ readonly token: Address;
17
+ /** The asset the tax is charged in, and the asset holder rewards pay out in. */
18
+ readonly quote: Address;
19
+ readonly creator: Address;
20
+ /** All-in tax a trader pays, in bps (100..1000). Excludes any opening-window premium. */
21
+ readonly creatorTaxBps: number;
22
+ /** Creator's share of the tax above the fixed 1%, in bps (0..10000). The rest goes to holders. */
23
+ readonly excessToCreatorBps: number;
24
+ /** The pool's `PeddlesHolderRewards` distributor. */
25
+ readonly rewards: Address;
26
+ }
27
+
28
+ /** Why `getPoolTerms` could not answer. It never substitutes a default. */
29
+ export type PoolTermsFailure = 'legacy-fee-hook' | 'not-registered' | 'ambiguous';
30
+
31
+ export class PoolTermsUnavailableError extends Error {
32
+ readonly reason: PoolTermsFailure;
33
+ constructor(reason: PoolTermsFailure, message: string) {
34
+ super(message);
35
+ this.name = 'PoolTermsUnavailableError';
36
+ this.reason = reason;
37
+ }
38
+ }
39
+
40
+ const ZERO_ID = /^0x0{64}$/i;
41
+
42
+ interface RawPoolConfig {
43
+ quote: Address;
44
+ creatorTaxBps: number;
45
+ excessToCreatorBps: number;
46
+ registered: boolean;
47
+ token: Address;
48
+ creator: Address;
49
+ rewards: Address;
50
+ }
51
+
52
+ /**
53
+ * The launch terms of `token`'s pool on `chainId`.
54
+ *
55
+ * FINDING THE POOL. The hook is keyed by pool id, not by token, so the id comes
56
+ * from the contract that created the pool: `PeddlesStockLaunchpad.coins(token)`
57
+ * for stock-paired coins (including NFT stock graduations), and
58
+ * `PeddlesV4LiquidityExecutor.poolIdFor(token)` for WETH-paired ones. The hook's
59
+ * own record must then name this token and be registered — anything else throws.
60
+ *
61
+ * FAILS CLOSED, the same as the address book:
62
+ * - a deployment whose hook predates the fixed-terms model throws
63
+ * (`legacy-fee-hook`) — its third config field meant something else, so
64
+ * decoding it with this ABI would return a plausible, wrong number;
65
+ * - a token with no registered launch pool throws (`not-registered`).
66
+ */
67
+ export async function getPoolTerms(client: ReadClient, chainId: number, token: Address): Promise<PoolTerms> {
68
+ const hook = addressOf(chainId, 'PeddlesFeeHook');
69
+ const launchpad = addressOf(chainId, 'PeddlesStockLaunchpad');
70
+ const executor = addressOf(chainId, 'PeddlesV4LiquidityExecutor');
71
+
72
+ const [factory, coin, wethPoolId] = await client.multicall({
73
+ allowFailure: true,
74
+ contracts: [
75
+ { address: hook, abi: feeHookAbi, functionName: 'holderRewardsFactory' },
76
+ { address: launchpad, abi: stockLaunchpadAbi, functionName: 'coins', args: [token] },
77
+ { address: executor, abi: liquidityExecutorAbi, functionName: 'poolIdFor', args: [token] },
78
+ ],
79
+ });
80
+
81
+ // `holderRewardsFactory` exists only on the fixed-terms hook. A hook that does not
82
+ // answer it is the previous model, not a transient failure to be guessed around.
83
+ if (factory.status !== 'success' || /^0x0{40}$/i.test(factory.result as string)) {
84
+ throw new PoolTermsUnavailableError(
85
+ 'legacy-fee-hook',
86
+ `PeddlesFeeHook ${hook} on chain ${chainId} does not expose holderRewardsFactory(); ` +
87
+ `this deployment predates fixed launch terms, so they cannot be read from it.`,
88
+ );
89
+ }
90
+
91
+ const candidates: Hex[] = [];
92
+ if (coin.status === 'success') {
93
+ const poolId = (coin.result as readonly unknown[])[2] as Hex;
94
+ if (!ZERO_ID.test(poolId)) candidates.push(poolId);
95
+ }
96
+ if (wethPoolId.status === 'success' && !ZERO_ID.test(wethPoolId.result as Hex)) {
97
+ candidates.push(wethPoolId.result as Hex);
98
+ }
99
+
100
+ const configs =
101
+ candidates.length === 0
102
+ ? []
103
+ : await client.multicall({
104
+ allowFailure: false,
105
+ contracts: candidates.map(
106
+ (poolId) => ({ address: hook, abi: feeHookAbi, functionName: 'poolConfig', args: [poolId] }) as const,
107
+ ),
108
+ });
109
+
110
+ const matches = candidates
111
+ .map((poolId, i) => ({ poolId, config: configs[i] as unknown as RawPoolConfig }))
112
+ .filter(({ config }) => config.registered && config.token.toLowerCase() === token.toLowerCase());
113
+
114
+ if (matches.length === 0) {
115
+ throw new PoolTermsUnavailableError(
116
+ 'not-registered',
117
+ `No registered Peddles launch pool for ${token} on chain ${chainId}.`,
118
+ );
119
+ }
120
+ if (matches.length > 1) {
121
+ throw new PoolTermsUnavailableError(
122
+ 'ambiguous',
123
+ `${token} has more than one registered launch pool on chain ${chainId}; refusing to pick one.`,
124
+ );
125
+ }
126
+
127
+ const { poolId, config } = matches[0] as { poolId: Hex; config: RawPoolConfig };
128
+ return {
129
+ poolId,
130
+ token: config.token,
131
+ quote: config.quote,
132
+ creator: config.creator,
133
+ creatorTaxBps: Number(config.creatorTaxBps),
134
+ excessToCreatorBps: Number(config.excessToCreatorBps),
135
+ rewards: config.rewards,
136
+ };
137
+ }
package/src/index.ts ADDED
@@ -0,0 +1,233 @@
1
+ import type { ReadClient } from './client.js';
2
+ /**
3
+ * @peddles/sdk — read and build against Peddles launches.
4
+ *
5
+ * ONE PACKAGE, SIX ENTRY POINTS. Take only what you import:
6
+ *
7
+ * @peddles/sdk this file — chain resolution, launch reads, pool terms
8
+ * @peddles/sdk/abis ABI fragments alone, no runtime
9
+ * @peddles/sdk/clog the Clog launch type
10
+ * @peddles/sdk/deployments the address book
11
+ * @peddles/sdk/launch launch calldata builders, salts, revert decoding
12
+ * @peddles/sdk/perps the perp launchpad (PeddlesPerpFactory)
13
+ *
14
+ * `viem` is a PEER dependency, not a bundled one: an integrator already has a
15
+ * client, and two copies of viem in a bundle is both bloat and a source of
16
+ * "instance is not a PublicClient" errors that are miserable to diagnose.
17
+ *
18
+ * WHAT IS DELIBERATELY ABSENT. This package exposes reads and calldata, not
19
+ * infrastructure. `ContractService`, `WalletService` and the log-source used by
20
+ * the indexer stay internal to the monorepo: publishing them would freeze this
21
+ * project's internals as public API and commit it to supporting them.
22
+ */
23
+ import type { Address, Hex } from 'viem';
24
+ import { encodeAbiParameters, getContractAddress, keccak256 } from 'viem';
25
+ import { erc20Abi, factoryAbi } from './abis.js';
26
+ import { addressOf, deploymentFor, isKnownChain, UnknownChainError } from './deployments.js';
27
+
28
+ export {
29
+ DEPLOYMENTS,
30
+ addressOf,
31
+ deploymentFor,
32
+ isKnownChain,
33
+ UnknownChainError,
34
+ UnknownContractError,
35
+ } from './deployments.js';
36
+ export type { KnownChainId, ContractName } from './deployments.js';
37
+ export type { ReadClient } from './client.js';
38
+
39
+ export {
40
+ getVariantAllocation,
41
+ getClogVault,
42
+ getClogState,
43
+ inflowShareBps,
44
+ isReleaseReady,
45
+ CLOG_MODEL_FLOW_MULTIPLE,
46
+ CLOG_TABLE_SLICE_BPS,
47
+ clogShareOfInflows,
48
+ clogShareOfInflowsFromBps,
49
+ clogInflowShareBps,
50
+ } from './clog.js';
51
+ export type { VariantAllocation, ClogState, ShareOfInflows, ClogModelInput } from './clog.js';
52
+
53
+ export {
54
+ factoryAbi,
55
+ clogVaultAbi,
56
+ clogVaultFactoryAbi,
57
+ erc20Abi,
58
+ tokenV20Abi,
59
+ feeHookAbi,
60
+ stockLaunchpadAbi,
61
+ holderRewardsAbi,
62
+ nftFactoryAbi,
63
+ nftCollectionAbi,
64
+ nftFeeDistributorFactoryAbi,
65
+ nftFeeDistributorAbi,
66
+ nftBondingGraduationOrchestratorAbi,
67
+ nftStockGraduationOrchestratorAbi,
68
+ liquidityExecutorAbi,
69
+ } from './abis.js';
70
+
71
+ export {
72
+ relayParamsHash,
73
+ stockGraduationOptInRequest,
74
+ graduateToStockWithTermsRequest,
75
+ graduateAndBuyRequest,
76
+ predictDistributor,
77
+ getHolderCommitment,
78
+ getDistributorState,
79
+ holderClaimable,
80
+ } from './artDex.js';
81
+ export type { RelayParams, StockTermsLaunch, HolderCommitment, DistributorState, HolderClaimable } from './artDex.js';
82
+
83
+ export { getPoolTerms, PoolTermsUnavailableError } from './feeTerms.js';
84
+ export type { PoolTerms, PoolTermsFailure } from './feeTerms.js';
85
+
86
+ export {
87
+ feeSplit,
88
+ PLATFORM_FEE_BPS,
89
+ CREATOR_FEE_BPS,
90
+ MIN_CREATOR_TAX_BPS,
91
+ MAX_CREATOR_TAX_BPS,
92
+ MAX_FEE_AMOUNT,
93
+ } from './feeSplit.js';
94
+ export type { FeeSplit } from './feeSplit.js';
95
+
96
+ /** A launched token, as the chain describes it. */
97
+ export interface TokenInfo {
98
+ readonly address: Address;
99
+ readonly name: string;
100
+ readonly symbol: string;
101
+ readonly decimals: number;
102
+ readonly totalSupply: bigint;
103
+ }
104
+
105
+ /**
106
+ * Identity and supply, read from the token itself.
107
+ *
108
+ * `decimals` travels with `totalSupply` on purpose. Every amount this SDK
109
+ * returns is a raw `bigint` in base units and MUST be formatted against its own
110
+ * decimals — never assume 18, and never route a token amount through a JS
111
+ * `number`, which loses precision above 2^53 and will silently mis-price a
112
+ * large-supply token.
113
+ */
114
+ export async function getTokenInfo(client: ReadClient, token: Address): Promise<TokenInfo> {
115
+ const base = { address: token, abi: erc20Abi } as const;
116
+ const [name, symbol, decimals, totalSupply] = await client.multicall({
117
+ allowFailure: false,
118
+ contracts: [
119
+ { ...base, functionName: 'name' },
120
+ { ...base, functionName: 'symbol' },
121
+ { ...base, functionName: 'decimals' },
122
+ { ...base, functionName: 'totalSupply' },
123
+ ],
124
+ });
125
+ return {
126
+ address: token,
127
+ name: name as string,
128
+ symbol: symbol as string,
129
+ decimals: decimals as number,
130
+ totalSupply: totalSupply as bigint,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Whether a launch type can be launched on this chain right now.
136
+ *
137
+ * Ask before offering one. A variant with no code registered, or one an owner
138
+ * has retired, reports false — and a UI that offers it anyway sends a creator
139
+ * into a transaction that cannot succeed.
140
+ */
141
+ export async function isVariantSupported(
142
+ client: ReadClient,
143
+ chainId: number,
144
+ variant: number,
145
+ ): Promise<boolean> {
146
+ return (await client.readContract({
147
+ address: addressOf(chainId, 'PeddlesFactoryV20'),
148
+ abi: factoryAbi,
149
+ functionName: 'isVariantSupported',
150
+ args: [variant],
151
+ })) as boolean;
152
+ }
153
+
154
+ /** Everything `PeddlesFactoryV20` hashes into a token's CREATE2 address. */
155
+ export interface TokenAddressInput {
156
+ readonly factory: Address;
157
+ readonly variant: number;
158
+ /** The launcher that calls the factory (e.g. the orchestrator), not the end user. */
159
+ readonly sender: Address;
160
+ readonly salt: Hex;
161
+ /** keccak256 of the variant's creation code, as the factory reports it. */
162
+ readonly creationCodeHash: Hex;
163
+ }
164
+
165
+ /**
166
+ * The CREATE2 address, computed locally — the exact formula the factory uses:
167
+ *
168
+ * boundSalt = keccak256(abi.encode(uint8 variant, address sender, bytes32 salt))
169
+ * address = keccak256(0xff ‖ factory ‖ boundSalt ‖ creationCodeHash)[12:]
170
+ *
171
+ * `creationCodeHash` is deliberately an input, not a constant shipped here. For
172
+ * the plain token it is the factory's immutable `TOKEN_CREATION_CODE_HASH`
173
+ * (keccak256 of `type(PeddlesTokenV20).creationCode` in the build that
174
+ * deployed it), and for a registered variant the hash stored at registration.
175
+ * Any recompile moves it, so the deployed factory is the only reliable source —
176
+ * read it with `predictTokenAddress`, or pass one you read yourself.
177
+ */
178
+ export function computeTokenAddress(input: TokenAddressInput): Address {
179
+ const boundSalt = keccak256(
180
+ encodeAbiParameters(
181
+ [{ type: 'uint8' }, { type: 'address' }, { type: 'bytes32' }],
182
+ [input.variant, input.sender, input.salt],
183
+ ),
184
+ );
185
+ return getContractAddress({
186
+ opcode: 'CREATE2',
187
+ from: input.factory,
188
+ salt: boundSalt,
189
+ bytecodeHash: input.creationCodeHash,
190
+ });
191
+ }
192
+
193
+ /**
194
+ * The CREATE2 address a launch will deploy to, before it is launched.
195
+ *
196
+ * THE VARIANT IS PART OF THE ADDRESS: the factory binds
197
+ * `keccak256(abi.encode(variant, sender, salt))`, so the same salt under two
198
+ * launch types produces two different addresses. Passing the wrong variant here
199
+ * yields an address the factory will never deploy to.
200
+ *
201
+ * The creation-code hash is read from the deployed factory (`variantInfo`, which
202
+ * returns `TOKEN_CREATION_CODE_HASH` for the plain token), never assumed. An
203
+ * unknown variant throws, as `getPeddlesAddress` does; a retired one still
204
+ * resolves, so an address quoted before retirement stays recoverable.
205
+ */
206
+ export async function predictTokenAddress(
207
+ client: ReadClient,
208
+ chainId: number,
209
+ variant: number,
210
+ sender: Address,
211
+ salt: Hex,
212
+ ): Promise<Address> {
213
+ const factory = addressOf(chainId, 'PeddlesFactoryV20');
214
+ const [, creationCodeHash] = (await client.readContract({
215
+ address: factory,
216
+ abi: factoryAbi,
217
+ functionName: 'variantInfo',
218
+ args: [variant],
219
+ })) as readonly [Address, Hex, boolean];
220
+ if (/^0x0{64}$/i.test(creationCodeHash)) {
221
+ throw new Error(`PeddlesFactoryV20 on chain ${chainId} has no launch type ${variant}.`);
222
+ }
223
+ return computeTokenAddress({ factory, variant, sender, salt, creationCodeHash });
224
+ }
225
+
226
+ /** Chains this build ships an address book for. */
227
+ export function supportedChains(): number[] {
228
+ return Object.keys(DEPLOYMENTS_KEYS).map(Number);
229
+ }
230
+
231
+ // Imported lazily-shaped to keep the public surface honest: `supportedChains`
232
+ // reads the same generated map every other entry point does.
233
+ import { DEPLOYMENTS as DEPLOYMENTS_KEYS } from './deployments.generated.js';