hookr-sdk 0.2.1 → 1.1.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/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATING.md +89 -0
  4. package/README.md +308 -105
  5. package/dist/core/abi.d.ts +10930 -0
  6. package/dist/core/abi.js +455 -0
  7. package/dist/core/advisories.d.ts +184 -0
  8. package/dist/core/advisories.js +291 -0
  9. package/dist/core/client.d.ts +55 -0
  10. package/dist/core/client.js +107 -0
  11. package/dist/core/generated.d.ts +14295 -0
  12. package/dist/core/generated.js +1058 -0
  13. package/dist/core/index.d.ts +17 -0
  14. package/dist/core/index.js +17 -0
  15. package/dist/core/launch.d.ts +200 -0
  16. package/dist/core/launch.js +427 -0
  17. package/dist/core/pair.d.ts +64 -0
  18. package/dist/core/pair.js +136 -0
  19. package/dist/core/params.d.ts +415 -0
  20. package/dist/core/params.js +1235 -0
  21. package/dist/core/policies.d.ts +136 -0
  22. package/dist/core/policies.js +125 -0
  23. package/dist/core/proofs.d.ts +25 -0
  24. package/dist/core/proofs.js +118 -0
  25. package/dist/core/quotes.d.ts +185 -0
  26. package/dist/core/quotes.js +550 -0
  27. package/dist/core/referrals.d.ts +32 -0
  28. package/dist/core/referrals.js +36 -0
  29. package/dist/core/registry.d.ts +357 -0
  30. package/dist/core/registry.js +378 -0
  31. package/dist/core/rules.d.ts +675 -0
  32. package/dist/core/rules.js +1049 -0
  33. package/dist/core/schema.d.ts +358 -0
  34. package/dist/core/schema.js +105 -0
  35. package/dist/core/stocks.generated.d.ts +11 -0
  36. package/dist/core/stocks.generated.js +1228 -0
  37. package/dist/core/vault.d.ts +59 -0
  38. package/dist/core/vault.js +112 -0
  39. package/dist/core/wallet.d.ts +142 -0
  40. package/dist/core/wallet.js +304 -0
  41. package/dist/deployments.d.ts +8 -0
  42. package/dist/deployments.js +26 -0
  43. package/dist/index.d.ts +42 -15
  44. package/dist/index.js +40 -15
  45. package/dist/releases.d.ts +219 -0
  46. package/dist/releases.js +365 -0
  47. package/dist/wallet-cap.d.ts +159 -0
  48. package/dist/wallet-cap.js +234 -0
  49. package/package.json +46 -10
  50. package/provenance.json +31 -0
  51. package/dist/abi.d.ts +0 -6404
  52. package/dist/abi.js +0 -8300
  53. package/dist/chain.d.ts +0 -87
  54. package/dist/chain.js +0 -86
  55. package/dist/claims.d.ts +0 -32
  56. package/dist/claims.js +0 -31
  57. package/dist/fees.d.ts +0 -59
  58. package/dist/fees.js +0 -142
  59. package/dist/links.d.ts +0 -48
  60. package/dist/links.js +0 -93
  61. package/dist/market.d.ts +0 -264
  62. package/dist/market.js +0 -310
  63. package/dist/reads.d.ts +0 -136
  64. package/dist/reads.js +0 -219
  65. package/dist/rules.d.ts +0 -84
  66. package/dist/rules.js +0 -296
  67. package/dist/swap.d.ts +0 -108
  68. package/dist/swap.js +0 -144
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Hookr on Robinhood Chain: the production release `hookr-1`, its canary `canary-hookr-1-a`, each release's
3
+ * deployment, the Rules modules its root admits beside the release's own, and the deployment manifest the builders take.
4
+ *
5
+ * Every address is fixed by CREATE3 from the release deployer, the release id and the contract's name, so it is known
6
+ * before the contract is deployed and is the same on every machine. `test/releases.test.ts` derives each one again
7
+ * from those inputs. The canary runs the same code at its own addresses and exists to prove a release before
8
+ * production; build on `HOOKR_1`.
9
+ */
10
+ import { defineChain, keccak256 } from "viem";
11
+ import { deployment } from "./core/index.js";
12
+ import { DEPLOYMENTS } from "./deployments.js";
13
+ /** Robinhood Chain's id. */
14
+ export const CHAIN_ID = 4663;
15
+ /**
16
+ * The chain's public, keyless RPC endpoint. Fine for a few reads. Under load it rate limits, and a wide `eth_getLogs`
17
+ * can come back as an empty array instead of an error, so anything that must not miss data should use a provider
18
+ * endpoint of its own.
19
+ */
20
+ export const RPC_URL = "https://rpc.mainnet.chain.robinhood.com";
21
+ /** The block explorer people open: RobinScan. No trailing slash. */
22
+ export const EXPLORER_URL = "https://robin.etherscan.io";
23
+ /**
24
+ * Robinhood Chain for viem, on any viem 2, with RobinScan as its explorer. viem 2.55 and later also ship it as
25
+ * `robinhood` from `viem/chains`, with the same id and RPC endpoint and Blockscout as its explorer.
26
+ */
27
+ export const ROBINHOOD_CHAIN = defineChain({
28
+ id: CHAIN_ID,
29
+ name: "Robinhood Chain",
30
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
31
+ rpcUrls: { default: { http: [RPC_URL] } },
32
+ blockExplorers: { default: { name: "RobinScan", url: EXPLORER_URL } },
33
+ contracts: { multicall3: { address: "0xca11bde05977b3631167028862be2a173976ca11" } },
34
+ });
35
+ /** An address's page on the explorer. */
36
+ export function explorerAddressUrl(address) {
37
+ return `${EXPLORER_URL}/address/${address}`;
38
+ }
39
+ /** A transaction's page on the explorer. */
40
+ export function explorerTxUrl(hash) {
41
+ return `${EXPLORER_URL}/tx/${hash}`;
42
+ }
43
+ /** Uniswap v4's dynamic-fee flag. Every Hookr pool's `PoolKey.fee` is this: the hook sets the LP fee on each swap. */
44
+ export const DYNAMIC_FEE = 0x800000;
45
+ /** The price limit for a `zeroForOne` swap that may fill in full: TickMath's MIN_SQRT_PRICE + 1. */
46
+ export const MIN_SQRT_PRICE_LIMIT = 4295128740n;
47
+ /** The price limit for a `oneForZero` swap that may fill in full: TickMath's MAX_SQRT_PRICE - 1. */
48
+ export const MAX_SQRT_PRICE_LIMIT = 1461446703485210103287273052203988822378723970341n;
49
+ /** Uniswap v4's PoolManager on Robinhood Chain. Every Hookr pool lives in it. */
50
+ export const POOL_MANAGER = "0x8366a39CC670B4001A1121B8F6A443A643e40951";
51
+ /** The account that deploys both releases. CREATE3 addresses depend on it, never on a nonce. */
52
+ export const RELEASE_DEPLOYER = "0x0c0967485A25F18E70262a2Ee80c7595CC64a5fC";
53
+ /** The CREATE3 factory both releases deploy through. */
54
+ export const CREATE3_FACTORY = "0xc7c662Fc760FE1d5cB97fd8A68cb43A046da3F7d";
55
+ /** The source commit both releases are built from. */
56
+ export const RELEASE_SOURCE_COMMIT = "412960b37a631ce145d49fb4a1887f04a4d1fa74";
57
+ /** The release digest: the hash of the build both releases deploy from. */
58
+ export const RELEASE_PACKET_DIGEST = "83962c12ff55df431612ffae8bfc66a30041eb310035cfc30eab1bb9bdc0e031";
59
+ /** A deployment, frozen, so no caller can change the facts a release carries. */
60
+ function frozen(d) {
61
+ return d && Object.freeze({ ...d, runtimeHashes: Object.freeze({ ...d.runtimeHashes }) });
62
+ }
63
+ /** The production release. Build on this one. */
64
+ export const HOOKR_1 = Object.freeze({
65
+ releaseId: "hookr-1",
66
+ canary: false,
67
+ chainId: CHAIN_ID,
68
+ deployment: frozen(DEPLOYMENTS["hookr-1"]),
69
+ rulesModules: Object.freeze({
70
+ standard: Object.freeze({
71
+ name: "standard",
72
+ rules: "0x4a71c270b1520F94064268102AebD744b0DE7942",
73
+ rulesRuntimeHash: "0x4b6d50b40981b8082f3ee5a7ebf57e48489f71c94c2a3b94032a1d1a382b515b",
74
+ recapture: "0xF83569E564d70daebc35950f66a73f2e2B915583",
75
+ recaptureRuntimeHash: "0xb78c68d64388c9c0c4d20faa3d429309a6a025024c3eefc579831bb10de145ed",
76
+ }),
77
+ /**
78
+ * Deployed 2026-10-07 beside the release (CREATE3 from the release deployer, release id `hookr-1-whole-pot`), with
79
+ * the release's constructor arguments. Its HookrRecapture pays a round's winner the whole pot and lets a pool put
80
+ * up to all of its arb recapture share in the pot. Same ABI as the release's Rules.
81
+ */
82
+ "whole-pot": Object.freeze({
83
+ name: "whole-pot",
84
+ rules: "0x10f684361b35C8411a2c07F68A6fB1F4B7e555F7",
85
+ rulesRuntimeHash: "0x3ffac05e248f81ad3843949052af1c0c6343179f0ec591bae3e271e249c7d879",
86
+ recapture: "0x60Efc4a41498e9433be2155b6cc1677AEC31D649",
87
+ recaptureRuntimeHash: "0xdc89f3eb02ff5bfbc07ac078e4f1d1de215ec253bc7ba843962e0b6cd11445c4",
88
+ }),
89
+ /**
90
+ * HookrRulesWalletCap v2, deployed 2026-10-12 beside the release (CREATE3 from the release deployer, release id
91
+ * `hookr-1-wallet-cap-v2`) with the release's constructor arguments, and admitted on HookrRoot with the release
92
+ * Rules' terms. The whole-pot Rules plus a per-wallet buy cap while a pool's launch guard lasts: it binds only a
93
+ * pool with a cap and a guard, and answers `extsload` (`wallet-cap.ts`). Its HookrRecapture is the whole-pot one's
94
+ * source, so its runtime, which keeps its own address, is its own. Same ABI as the release's Rules otherwise.
95
+ */
96
+ "wallet-cap": Object.freeze({
97
+ name: "wallet-cap",
98
+ rules: "0xb67b73E28C522a3AAC438bE970bf94976Deee52a",
99
+ rulesRuntimeHash: "0x0fed58ec91368b16ba4166acc65ddd9252529fb860834f81fdc5dab3179a6247",
100
+ recapture: "0xbbC43DC2f01F5E7882caCd78EF094A44eF0E4d23",
101
+ recaptureRuntimeHash: "0x04001c31836049074c926cf6519ecf999c18267229fe6a05df26685caadb0984",
102
+ }),
103
+ /**
104
+ * HookrRulesRoundTrip, deployed beside the release (CREATE3 from the release deployer, release id `hookr-1`) and
105
+ * admitted on HookrRoot with the release Rules' terms at block 82,372,631. Rules that keep a record of who traded
106
+ * in each block, which Buy/Sell Block's round-trip check reads. Same ABI as the release's Rules otherwise.
107
+ */
108
+ "round-trip": Object.freeze({
109
+ name: "round-trip",
110
+ rules: "0x3d7e31746127FD9a14d3C7fC12E73611D2e8cb34",
111
+ rulesRuntimeHash: "0x925797125cde5723bfa57ac04238e847ac0098a2821e6703fd4d5e20881a322b",
112
+ recapture: "0xddFF6326E5CB9AA98b88e9064d9A9570f777f8A3",
113
+ recaptureRuntimeHash: "0x7e8e752507e29441d26bec6646cd57598e63825ab888cf166da27cf9a5a31e5e",
114
+ }),
115
+ }),
116
+ contracts: Object.freeze({
117
+ HookrRegistryChecks: "0xB9F5c54E5Cc7ac6c7a96335E5A334Cf4eA496E98",
118
+ HookrRegistryAdmin: "0x675ac2102E8c69E2467F64656E3c235AF3901Ac1",
119
+ HookrRegistry: "0x463c82beA1a52F4Cc05ca0741a24F0AD24bA4602",
120
+ HookrTreasury: "0x2242F7FC75606500503075f29ea8cDA684EfB4a9",
121
+ HookrForwarder: "0xe7863CC0E2Fb783b44c279aB159bcD812c2ceA1c",
122
+ HookrRouter: "0xC2Bc9a3fe6c4Ce8C2F7EB4655b0869b01D8c93C9",
123
+ HookrQuoter: "0x445D7891381Aa7F91F5048EdBC6a611ccBDe7865",
124
+ HookrLaunchChecks: "0x8567369a70100828611BB6F44ec051B48F10fb59",
125
+ HookrTokenDeployer: "0x1B23D6d7beBcB61E39317E9F0Bc02AEB91aFCcAe",
126
+ HookrLauncher: "0x1cf2748A6670572425eAbf9Ab9e5BC4eb2eB8Ef4",
127
+ HookrReferralRegistry: "0xefD1DfBA80c06E72F793601706c3a1c71aab3057",
128
+ HookrProgramsAdmin: "0x40B40D65543E70a9C757eda1A2208517a8BC7292",
129
+ HookrPrograms: "0x9fA204AC99176782Fd20615b2b2A516D1F8C328F",
130
+ HookrMilestoneNFTFactory: "0xa340F964F1E685c681dC3ABB5B0405E608914d00",
131
+ HookrProgramValidator: "0x33e0249083Be7Cd8bD2fe175E0671AcfD0516850",
132
+ HookrReferralDistributor: "0x690539c53212899A9c6AEEE3b4B6737A5c4851d9",
133
+ HookrRoot: "0x89c9FB50d1f03192230FFB1c279BB6E7d3Fe6aCc",
134
+ HookrLane: "0x9A8a1006469BA196f8FEfc017a53505C83A060BB",
135
+ HookrRules: "0x4a71c270b1520F94064268102AebD744b0DE7942",
136
+ HookrRecapture: "0xF83569E564d70daebc35950f66a73f2e2B915583",
137
+ HookrFamilyRouter: "0x854f3Fa7AC342B244DF0d9A8c8F597b051aAE559",
138
+ HookrFamilyLock: "0xfAFEcE64099C1E791C4B46189c277f00c0c7e00b",
139
+ HookrCompliance: "0x9D205e8a68464761A9d84bdeCcEa0C3CB1e7B1EC",
140
+ HookrComplianceGuard: "0x6CD70db1ac99344416a6fD7184e23FA8f09f313A",
141
+ HookrAccountCalls: "0x0579a95A7760070E2d6D34e5beCe571e5F1c4818",
142
+ HookrPaymasterAdmin: "0x1d81F4B131832f741392edf7B91b4CF099FbB008",
143
+ HookrPaymaster: "0x1c4682915092E742231ea038C4fcA61e96a83c99",
144
+ HookrRootFactory: "0x21E438FaBE10770afb4407cF6b09d32369CBdCE4",
145
+ HookrSessionAdvisory: "0x78Bc6b7302Dda8632e661c8d74966bdBea1c66b1",
146
+ HookrFeeAdvisory: "0x3B6b3f00684987B831fcF9EB5f0d5B806afF7306",
147
+ HookrLiquidityVault: "0xA408923EF811945Ef119Bc700B1950Dab65467Fc",
148
+ HookrOwnedProfiles: "0xb46c318B6AEA2d6274f5566da414b09fDa6CC325",
149
+ HookrOwnedRootsLens: "0x6Bb5c9d21dD095F788cb8373d0143429ad277366",
150
+ HookrOwnedRootFactory: "0xA6c2a401597301dFf57A59bC8F70a9622499f3ec",
151
+ HookrLpBoost: "0x52536F5F4E5BC7cEA2197c8F1BB49bf2B133E7f1",
152
+ }),
153
+ });
154
+ /**
155
+ * The canary: the same contracts at their own addresses, deployed and proven before production. Not for users.
156
+ * @internal
157
+ */
158
+ export const CANARY_HOOKR_1_A = Object.freeze({
159
+ releaseId: "canary-hookr-1-a",
160
+ canary: true,
161
+ chainId: CHAIN_ID,
162
+ deployment: frozen(DEPLOYMENTS["canary-hookr-1-a"]),
163
+ rulesModules: Object.freeze({
164
+ standard: Object.freeze({
165
+ name: "standard",
166
+ rules: "0x8Ef8869CD47d9928d563001F1d38D65c3c3e01Ab",
167
+ rulesRuntimeHash: "0x7e51ac228eafdaa7f2b3c45f2fec6ec9c872f81ce0258c1a2da95090883b30a5",
168
+ recapture: "0xe7035c0d8523a5eE9Fc5Ba5Ec97a4f1D771a6F87",
169
+ recaptureRuntimeHash: "0x54aa061f121e798029af86b9b700433fec566cdb570578e4e57a69865d6095a6",
170
+ }),
171
+ }),
172
+ contracts: Object.freeze({
173
+ HookrRegistryChecks: "0x4598d64B6c5730813a469e4B36cf379DbfB24118",
174
+ HookrRegistryAdmin: "0x57252DB7C9ed51213a324A2160934Ee2f840d6E8",
175
+ HookrRegistry: "0x91eDc45319B66dc4f168BcF7B957e8f753771834",
176
+ HookrTreasury: "0x3cA2926E5971Cda3E3667e9A6835b2cDfF8DD836",
177
+ HookrForwarder: "0xDd3407eB6DbFF4a3Cb6258923C35F3e306C86320",
178
+ HookrRouter: "0x3b6848A557F5Aedc18EbE5f3E5b6EafB4F46f6F9",
179
+ HookrQuoter: "0xc324D74339763E8b12709092a2c9d8f04CD02c88",
180
+ HookrLaunchChecks: "0xa22BF1F6a8709441Fc7af410891204454Ed24026",
181
+ HookrTokenDeployer: "0x85b6B07688Da8089F4191439dbFC315a4757DB0F",
182
+ HookrLauncher: "0x064a2b59f7c3d70ECdC0009be30Eb464110Ae787",
183
+ HookrReferralRegistry: "0x8EC0AE84b21BF2dD187ED390dbDA7A967ECfC5dD",
184
+ HookrProgramsAdmin: "0x2Bec6C3bEB51c062C0Ef59ddC67237354E86BeF1",
185
+ HookrPrograms: "0x3A11ea03e1B6c6750dEF8A5fBb41bd4c3d0C3d97",
186
+ HookrMilestoneNFTFactory: "0xFeE0AbA75f9C0A23bB5AB686314a5112dfe3f0b2",
187
+ HookrProgramValidator: "0xA6883F0424FA4Ec11E0591Aa14A26be957EE863A",
188
+ HookrReferralDistributor: "0x9ab480d01e89F9d9A1D1e3cd8433DCbC825BA4cC",
189
+ HookrRoot: "0x4C0904bfbdaEC6AA847C36BBf395E5f61325eacc",
190
+ HookrLane: "0x3ba186E41b83ABd4E91a2a72c2fF3BAF8f88003C",
191
+ HookrRules: "0x8Ef8869CD47d9928d563001F1d38D65c3c3e01Ab",
192
+ HookrRecapture: "0xe7035c0d8523a5eE9Fc5Ba5Ec97a4f1D771a6F87",
193
+ HookrFamilyRouter: "0xD6F31D46D06d4Aa91e937407369a369D1DB9C684",
194
+ HookrFamilyLock: "0xC39144589aC7336eFC9baB9d7608c7B014Fe757e",
195
+ HookrCompliance: "0xed326114d79b8bf64f00585fF3Ca1D5C01693649",
196
+ HookrComplianceGuard: "0xB6a3d34513020458fBC80D71F5753fC41a863CE5",
197
+ HookrAccountCalls: "0xdd706C93179730cFE73b8c1627bEc6103e51E3a3",
198
+ HookrPaymasterAdmin: "0x7a44B8E11097c4B801bcf195EF09295882538bF3",
199
+ HookrPaymaster: "0x1Bf7C2e0755b8776C04f21208Ea4CF29A0942866",
200
+ HookrRootFactory: "0x2B2fF011b4A3854b7796e86307736AEB558C80C7",
201
+ HookrSessionAdvisory: "0x6fE7e2244257664e4658FEcB308Cf374e6c83Af1",
202
+ HookrFeeAdvisory: "0x02B13627a634dfebBb864389cDb3E4D6945e80Ac",
203
+ HookrLiquidityVault: "0x67E93f59E3fD78e7976960aBce835E27c63Aa8c9",
204
+ HookrOwnedProfiles: "0xeCeD7a6D923Dc5C0c49B0a8c5089f372C6c40244",
205
+ HookrOwnedRootsLens: "0x76f0F2a48C154c8261110a4c13cdc5d4437A0631",
206
+ HookrOwnedRootFactory: "0xEFD7bE480bbA7e3b14dbA0878F34AB436bC08ff8",
207
+ HookrLpBoost: "0xe06dac9dea02424fAd01CEE10feA0ce5517765A9",
208
+ }),
209
+ });
210
+ /** Both releases by release id. */
211
+ export const RELEASES = Object.freeze({
212
+ "hookr-1": HOOKR_1,
213
+ "canary-hookr-1-a": CANARY_HOOKR_1_A,
214
+ });
215
+ /** The contract each manifest role pins, in the order the manifest lists them. */
216
+ export const MANIFEST_ROLES = Object.freeze({
217
+ poolManager: "PoolManager",
218
+ registry: "HookrRegistry",
219
+ root: "HookrRoot",
220
+ rules: "HookrRules",
221
+ router: "HookrRouter",
222
+ quoter: "HookrQuoter",
223
+ launcher: "HookrLauncher",
224
+ treasury: "HookrTreasury",
225
+ programs: "HookrPrograms",
226
+ referrals: "HookrReferralRegistry",
227
+ commissions: "HookrReferralDistributor",
228
+ forwarder: "HookrForwarder",
229
+ rootFactory: "HookrRootFactory",
230
+ feeAdvisory: "HookrFeeAdvisory",
231
+ sessionAdvisory: "HookrSessionAdvisory",
232
+ liquidityVault: "HookrLiquidityVault",
233
+ });
234
+ /** The address a manifest role pins in a release. */
235
+ export function releaseAddress(release, role) {
236
+ const name = MANIFEST_ROLES[role];
237
+ if (!name)
238
+ throw new Error(`No ${role} in ${release.releaseId}`);
239
+ return name === "PoolManager" ? POOL_MANAGER : release.contracts[name];
240
+ }
241
+ /** What a version of this package made before the release was verified tells a caller. */
242
+ function noDeployment(release) {
243
+ return new Error(`This version of hookr-sdk has no deployment for ${release.releaseId}: pass its startBlock and evidenceHash`);
244
+ }
245
+ /**
246
+ * The DeploymentManifest the builders take for a release: every role at its fixed address with its runtime hash, from
247
+ * the release's own deployment unless one is given, then each further Rules module (`otherRulesModules`) as one more
248
+ * `rules` pin with its own runtime hash, so the builders that name a Rules accept it. It makes no reads. Refuses a role
249
+ * without a runtime hash, and anything `deployment()` refuses.
250
+ *
251
+ * It is the builders' manifest. Its release id is the release's, `hookr-1`, so it is not the manifest a Hookr service
252
+ * publishes: `HookrClient` takes the release id and manifest hash the service names.
253
+ */
254
+ export function releaseManifest(release, d = release.deployment) {
255
+ if (!d)
256
+ throw noDeployment(release);
257
+ const contracts = Object.keys(MANIFEST_ROLES).map((role) => {
258
+ const runtimeHash = d.runtimeHashes[role];
259
+ if (!runtimeHash)
260
+ throw new Error(`No runtime hash for ${role}`);
261
+ return {
262
+ role,
263
+ address: releaseAddress(release, role).toLowerCase(),
264
+ runtimeHash,
265
+ startBlock: d.startBlock,
266
+ };
267
+ });
268
+ for (const m of otherRulesModules(release))
269
+ contracts.push({ role: "rules", address: m.rules.toLowerCase(), runtimeHash: m.rulesRuntimeHash, startBlock: d.startBlock });
270
+ return deployment({
271
+ schema: "hookr.engagement.sources.v1",
272
+ releaseId: release.releaseId,
273
+ chainId: String(release.chainId),
274
+ startBlock: d.startBlock,
275
+ parentHash: d.parentHash,
276
+ contracts,
277
+ qualification: { status: "verified", evidenceHash: d.evidenceHash, sourceCommit: RELEASE_SOURCE_COMMIT },
278
+ });
279
+ }
280
+ /** The release's Rules modules other than its own HookrRules: on `hookr-1`, the whole-pot, Wallet Cap and round-trip Rules. */
281
+ export function otherRulesModules(release) {
282
+ return Object.values(release.rulesModules).filter((m) => m !== undefined && m.rules.toLowerCase() !== release.contracts.HookrRules.toLowerCase());
283
+ }
284
+ /** The release's Rules module `name`, or an error for a release without it (the canary has only `standard`). */
285
+ export function rulesModule(release, name) {
286
+ const m = release.rulesModules[name];
287
+ if (!m)
288
+ throw new Error(`${release.releaseId} has no ${name} Rules`);
289
+ return m;
290
+ }
291
+ /**
292
+ * `releaseManifest` with each role's runtime hash and the start block's hash read from chain. Refuses a role with no
293
+ * code: the release is not deployed, or not on this chain. Where the release carries its deployment, it also refuses
294
+ * a role whose code is not the release's, and a start block whose hash is not the release's. It always refuses a
295
+ * further Rules module whose code is not the one the release pins for it.
296
+ */
297
+ export async function readReleaseManifest(client, release, options = {}) {
298
+ const pinned = release.deployment;
299
+ const startBlock = options.startBlock ?? (pinned ? BigInt(pinned.startBlock) : undefined);
300
+ const evidenceHash = options.evidenceHash ?? pinned?.evidenceHash;
301
+ if (startBlock === undefined || evidenceHash === undefined)
302
+ throw noDeployment(release);
303
+ const roles = Object.keys(MANIFEST_ROLES);
304
+ const modules = otherRulesModules(release);
305
+ const [block, codes, moduleCodes] = await Promise.all([
306
+ client.getBlock({ blockNumber: startBlock }),
307
+ Promise.all(roles.map((role) => client.getCode({ address: releaseAddress(release, role), blockNumber: options.blockNumber }))),
308
+ Promise.all(modules.map((m) => client.getCode({ address: m.rules, blockNumber: options.blockNumber }))),
309
+ ]);
310
+ if (!block.hash)
311
+ throw new Error(`Block ${startBlock} has no hash yet`);
312
+ const pinnedStart = pinned && startBlock === BigInt(pinned.startBlock);
313
+ if (pinnedStart && block.hash.toLowerCase() !== pinned.parentHash.toLowerCase())
314
+ throw new Error(`Block ${startBlock} is not ${release.releaseId}'s start block on this chain`);
315
+ const runtimeHashes = {};
316
+ roles.forEach((role, i) => {
317
+ const code = codes[i];
318
+ if (!code || code === "0x")
319
+ throw new Error(`No code at ${role} ${releaseAddress(release, role)}`);
320
+ const runtimeHash = keccak256(code);
321
+ const expected = pinned?.runtimeHashes[role];
322
+ if (expected && runtimeHash !== expected.toLowerCase())
323
+ throw new Error(`${role} ${releaseAddress(release, role)} does not run ${release.releaseId}'s code`);
324
+ runtimeHashes[role] = runtimeHash;
325
+ });
326
+ modules.forEach((m, i) => {
327
+ const code = moduleCodes[i];
328
+ if (!code || code === "0x")
329
+ throw new Error(`No code at the ${m.name} Rules ${m.rules}`);
330
+ if (keccak256(code) !== m.rulesRuntimeHash.toLowerCase())
331
+ throw new Error(`The ${m.name} Rules ${m.rules} do not run the code ${release.releaseId} pins for them`);
332
+ });
333
+ return releaseManifest(release, {
334
+ startBlock: startBlock.toString(),
335
+ parentHash: block.hash,
336
+ runtimeHashes,
337
+ evidenceHash,
338
+ });
339
+ }
340
+ /**
341
+ * The gas floor a swap on an arb recapture pool must carry while its lane is on: `2 × gasCap + gasCap / 63 + 755,000`
342
+ * (HookrLane), the `gasFloor` `laneOf(id)` returns. At the 3,000,000 cap the lane opens with it is 6,802,619. A swap
343
+ * sent with less reverts `InsufficientLaneGas`. The swap's own gas comes on top: a buy on a live lane pool estimates at
344
+ * about 7.5 million, so a fixed limit on such a pool should be at least 8,800,000, the estimate plus a sixth.
345
+ */
346
+ export function laneGasFloor(gasCap) {
347
+ const cap = BigInt(gasCap);
348
+ if (cap <= 0n)
349
+ return 0n;
350
+ return 2n * cap + cap / 63n + 755000n;
351
+ }
352
+ /**
353
+ * The block number the contracts read as `block.number`: on Robinhood Chain, an Arbitrum Orbit chain, it is the parent
354
+ * chain's (Ethereum's), which each block carries as `l1BlockNumber`, about one every 12 seconds. Anti-Snipe's launch
355
+ * guard (`guardEndBlock`), and Wallet Cap with it, counts these blocks, so pass this as a launch's `blockNumber`.
356
+ * `getBlockNumber()` is the chain's own, far larger number: a guard counted from it is one the Rules refuse at bind.
357
+ */
358
+ export async function readParentBlockNumber(client) {
359
+ const { l1BlockNumber } = ((await client.getBlock({ blockTag: "latest" })) ?? {});
360
+ if (typeof l1BlockNumber === "bigint" && l1BlockNumber > 0n)
361
+ return l1BlockNumber;
362
+ if (typeof l1BlockNumber === "string" && /^0x0*[1-9a-fA-F][0-9a-fA-F]*$/.test(l1BlockNumber))
363
+ return BigInt(l1BlockNumber);
364
+ throw new Error("The latest block carries no l1BlockNumber: read it from Robinhood Chain's own RPC");
365
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Wallet Cap: HookrRulesWalletCap (`rulesModule(HOOKR_1, "wallet-cap")`), the whole-pot Rules with a per-wallet buy
3
+ * cap on each launch while its pools' launch guards last.
4
+ *
5
+ * - The cap rides in `RulesConfig.potMinBuyQuote`, in the token's raw units: the most of it one wallet may buy while the
6
+ * pool's guard lasts. These Rules bind only a pool with a cap and a launch guard (`guardEndBlock` within the next
7
+ * 100,000 parent blocks), and `potEveryNBuys` stays zero. Every other Rules refuses a nonzero `potMinBuyQuote`, as the
8
+ * core's `checkRulesConfig` does.
9
+ * - Each guarded buy counts what it delivers (the pool's output less Auto Burn) against its wallet: the payer
10
+ * HookrRouter, the forwarder, the family router or the curated Uniswap router names, or for a swap through a router
11
+ * that names none, the transaction's signer. Counts are per token and per launch: the pools of one launch share
12
+ * `Bound.launchedAt`, the second they bound, and a later launch of the token counts from zero, so every pool of a
13
+ * launch takes the same cap, and arb recapture on every pool or none (`checkWalletCapLaunch`). A sell frees no room.
14
+ * The creator's dev buy and the pool's own arb recapture executor are not counted. A buy past the cap reverts
15
+ * `GuardWalletLimit(wallet, actual, limit)`. After the guard nothing is counted or capped.
16
+ * - The Rules answer `extsload(bytes32)`, one word of their storage, and `readWalletCap` reads through it.
17
+ */
18
+ import { type Hex } from "viem";
19
+ import { type Address, type Admission, type Caps, type Hash, type ParamContext, type PoolTerms, type RootAdvisoryKind, type RulesConfig, type RulesContext, type RulesLaunch } from "./core/index.js";
20
+ import { type HookrRelease } from "./releases.js";
21
+ /**
22
+ * What HookrRulesWalletCap adds to the release Rules' ABI (`rulesAbi`): `extsload`, one word of its storage as Uniswap
23
+ * v4's Extsload reads it, and the refusal of a buy that would take a wallet past its pool's cap.
24
+ */
25
+ export declare const WALLET_CAP_ABI: readonly [{
26
+ readonly type: "function";
27
+ readonly name: "extsload";
28
+ readonly stateMutability: "view";
29
+ readonly inputs: readonly [{
30
+ readonly name: "slot";
31
+ readonly type: "bytes32";
32
+ readonly internalType: "bytes32";
33
+ }];
34
+ readonly outputs: readonly [{
35
+ readonly name: "value";
36
+ readonly type: "bytes32";
37
+ readonly internalType: "bytes32";
38
+ }];
39
+ }, {
40
+ readonly type: "error";
41
+ readonly name: "GuardWalletLimit";
42
+ readonly inputs: readonly [{
43
+ readonly name: "wallet";
44
+ readonly type: "address";
45
+ readonly internalType: "address";
46
+ }, {
47
+ readonly name: "actual";
48
+ readonly type: "uint256";
49
+ readonly internalType: "uint256";
50
+ }, {
51
+ readonly name: "limit";
52
+ readonly type: "uint256";
53
+ readonly internalType: "uint256";
54
+ }];
55
+ }];
56
+ /** erc7201:hookr.rules, every HookrRules' State: keccak256(abi.encode(uint256(keccak256("hookr.rules")) - 1)) & ~0xff. */
57
+ export declare const RULES_SLOT = 53445494162334802764660739626191472437579742535980357254313114366502610401536n;
58
+ /**
59
+ * The slot of a pool's first Bound word, `State.pools[poolId]`: keccak256(abi.encode(poolId, RULES_SLOT)). Its launch
60
+ * guard's end is in the next slot (`uint40` of the word) and its cap two slots on (the top 128 bits).
61
+ */
62
+ export declare function walletCapPoolSlot(poolId: Hash): Hash;
63
+ /** The slot of `wallet`'s count of `subject` on launch `launch`, `State.bought[subject][launch][wallet]`. */
64
+ export declare function walletCapBoughtSlot(subject: Address, launch: number | bigint, wallet: Address): Hash;
65
+ /**
66
+ * The launch in a pool's first Bound word: its top 32 bits, `Bound.launchedAt`. The word packs, low to high,
67
+ * baseFeePips (24 bits), tickSpacing (16), bound (8), quoteIsCurrency0 (8), terms (8), liquidityOwner (160) and
68
+ * launchedAt (32). Null for anything but 32 bytes of hex with the bound flag set: a pool these Rules never bound.
69
+ */
70
+ export declare function walletCapLaunch(value: Hex): number | null;
71
+ /**
72
+ * A pool's cap, in the token's raw units, for a share of `supply` (raw units) in basis points: the floor of
73
+ * supply × bps / 10,000, so 200 bps of a supply of 10^27 is 2 × 10^25. It goes in the pool's `rules.potMinBuyQuote`.
74
+ * Refuses a share outside 1 to 10,000 bps and a cap that rounds to zero. hookr.fun offers 0.1% to 10%, 2% by default.
75
+ */
76
+ export declare function walletCapAmount(supply: bigint, bps: number | bigint): bigint;
77
+ /** What a wallet may still buy while the guard lasts: the cap less what it bought, never below zero. */
78
+ export declare function walletCapRoom(cap: bigint, bought: bigint): bigint;
79
+ /**
80
+ * Refuses a RulesConfig HookrRulesWalletCap's bind would refuse and returns its fee ceilings: `checkRulesConfig` with
81
+ * the cap set aside, plus the Wallet Cap Rules' own terms: a cap above zero in `potMinBuyQuote` (below 2^128), and a
82
+ * launch guard, with its liquidity owner, ending within 100,000 parent blocks after `ctx.blockNumber`, which is required
83
+ * here. `potEveryNBuys` stays zero. Every other Rules refuses a cap: check those with `checkRulesConfig`.
84
+ */
85
+ export declare function checkWalletCapRulesConfig(c: RulesConfig, ctx: RulesContext): Caps;
86
+ /**
87
+ * `rulesPoolTerms` for a launch member on the Wallet Cap Rules: every check `checkWalletCapRulesConfig` makes, the
88
+ * pool's caps and gas limits from the Rules' admission (`admission(HookrRoot, rulesModule(HOOKR_1, "wallet-cap").rules)`
89
+ * on the registry), and its `rulesData` with the cap. Refuses the admission of any other Rules. The member binds these
90
+ * Rules as `config.rules` and `l.rules`, cap included, as `rules`; every pool of the launch takes the same cap.
91
+ */
92
+ export declare function walletCapPoolTerms(l: RulesLaunch, ctx: ParamContext & {
93
+ liquidityOwner?: Address;
94
+ }, rulesAdmission: Admission, advisory?: {
95
+ admission: Admission;
96
+ kind: RootAdvisoryKind;
97
+ phases?: number;
98
+ }, release?: HookrRelease): PoolTerms;
99
+ /**
100
+ * Refuses a launch whose members (`buildLaunch`'s) Wallet Cap would leave a way round, or turn against traders, though
101
+ * the Rules bind each pool alone: some pools on the Wallet Cap Rules and some not, capped pools with different caps (a
102
+ * wallet could then buy the largest), or capped pools some with arb recapture and some without (on a capped pool
103
+ * without the lane, the executor's legs count against the trader whose swap set them off). Passes a launch without it.
104
+ */
105
+ export declare function checkWalletCapLaunch(members: readonly Record<string, unknown>[], release?: HookrRelease): void;
106
+ /** The read `readWalletCap` makes: an `eth_call`. A viem PublicClient has it. */
107
+ export interface WalletCapReader {
108
+ call(args: {
109
+ to: Address;
110
+ data: Hex;
111
+ blockNumber?: bigint;
112
+ }): Promise<{
113
+ data?: Hex;
114
+ }>;
115
+ }
116
+ /** Which pool, and optionally which wallet, `readWalletCap` reads. */
117
+ export interface WalletCapQuery {
118
+ /** The pool's PoolId (`poolId(key)`). */
119
+ poolId: Hash;
120
+ /** The wallet whose count and room to read. */
121
+ wallet?: Address;
122
+ /** The pool's token, with `wallet`: counts are per token. */
123
+ subject?: Address;
124
+ /** The block to read at. Defaults to the latest. */
125
+ blockNumber?: bigint;
126
+ }
127
+ /** A pool's wallet cap as its Rules hold it, and a wallet's count against it. */
128
+ export interface WalletCap {
129
+ /** The pool's launch: `Bound.launchedAt`, the second its launch bound it, which the launch's pools share. */
130
+ launch: number;
131
+ /** The parent block the pool's launch guard, and the cap with it, ends at (`readParentBlockNumber` reads the clock). */
132
+ guardEndBlock: bigint;
133
+ /** The most of the token, in raw units, one wallet may buy across the launch's pools while the guard lasts. */
134
+ cap: bigint;
135
+ /** What the wallet bought across the launch's pools while their guards lasted, or null without a wallet. */
136
+ bought: bigint | null;
137
+ /** What the wallet may still buy before the guard ends (`walletCapRoom`), or null without a wallet. */
138
+ room: bigint | null;
139
+ }
140
+ /**
141
+ * Reads a pool on the Wallet Cap Rules through their `extsload`: its launch, guard end and cap from its Bound
142
+ * (`walletCapPoolSlot` and the two slots after it), then, with a wallet, that wallet's count on the launch
143
+ * (`walletCapBoughtSlot`) and its room. Null for a pool these Rules have not bound: a pool on any other Rules has no
144
+ * wallet cap. A count and its room hold while the guard lasts; after it nothing is counted or capped.
145
+ */
146
+ export declare function readWalletCap(client: WalletCapReader, query: WalletCapQuery, release?: HookrRelease): Promise<WalletCap | null>;
147
+ /** A buy Wallet Cap refused: the wallet it counted, the total the buy would have brought it to, and the cap. */
148
+ export interface WalletCapRefusal {
149
+ wallet: Address;
150
+ actual: bigint;
151
+ limit: bigint;
152
+ }
153
+ /**
154
+ * The GuardWalletLimit inside a failed quote, swap or launch, however the root, the lane, the PoolManager and the quoter
155
+ * wrapped it, or null. Takes the revert data, or the viem error that carries it.
156
+ */
157
+ export declare function walletCapRefusal(error: unknown): WalletCapRefusal | null;
158
+ /** Names Wallet Cap's refusal in `error` (revert data, or the viem error that carries it), or null. */
159
+ export declare function walletCapRevert(error: unknown): string | null;